Index: branches/new-upload/phase3/includes/UploadFromUrl.php |
— | — | @@ -1,80 +0,0 @@ |
2 | | -<?php |
3 | | -class UploadFromUrl extends UploadBase { |
4 | | - protected $mTempDownloadPath; |
5 | | - |
6 | | - //by default do a SYNC_DOWNLOAD |
7 | | - protected $dl_mode = null; |
8 | | - |
9 | | - static function isAllowed( $user ) { |
10 | | - if( !$user->isAllowed( 'upload_by_url' ) ) |
11 | | - return 'upload_by_url'; |
12 | | - return parent::isAllowed( $user ); |
13 | | - } |
14 | | - static function isEnabled() { |
15 | | - global $wgAllowCopyUploads; |
16 | | - return $wgAllowCopyUploads && parent::isEnabled(); |
17 | | - } |
18 | | - /*entry point for Api upload:: ASYNC_DOWNLOAD (if possible) */ |
19 | | - function initialize( $name, $url, $asyncdownload = false) { |
20 | | - global $wgTmpDirectory, $wgPhpCliPath; |
21 | | - |
22 | | - //check for $asyncdownload request: |
23 | | - if($asyncdownload !== false){ |
24 | | - if($wgPhpCliPath && wfShellExecEnabled() ){ |
25 | | - $this->dl_mode = Http::ASYNC_DOWNLOAD; |
26 | | - }else{ |
27 | | - $this->dl_mode = Http::SYNC_DOWNLOAD; |
28 | | - } |
29 | | - } |
30 | | - |
31 | | - $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); |
32 | | - parent::initialize( $name, $local_file, 0, true ); |
33 | | - |
34 | | - $this->mUrl = trim( $url ); |
35 | | - } |
36 | | - public function isAsync(){ |
37 | | - return $this->dl_mode == Http::ASYNC_DOWNLOAD; |
38 | | - } |
39 | | - /*entry point for SpecialUpload no ASYNC_DOWNLOAD possible: */ |
40 | | - function initializeFromRequest( &$request ) { |
41 | | - |
42 | | - //set dl mode if not set: |
43 | | - if(!$this->dl_mode) |
44 | | - $this->dl_mode = Http::SYNC_DOWNLOAD; |
45 | | - |
46 | | - $desiredDestName = $request->getText( 'wpDestFile' ); |
47 | | - if( !$desiredDestName ) |
48 | | - $desiredDestName = $request->getText( 'wpUploadFile' ); |
49 | | - return $this->initialize( |
50 | | - $desiredDestName, |
51 | | - $request->getVal('wpUploadFileURL') |
52 | | - ); |
53 | | - } |
54 | | - /** |
55 | | - * Do the real fetching stuff |
56 | | - */ |
57 | | - function fetchFile( ) { |
58 | | - //entry point for SpecialUplaod |
59 | | - if( self::isValidURI($this->mUrl) === false) { |
60 | | - return Status::newFatal('upload-proto-error'); |
61 | | - } |
62 | | - //now do the actual download to the target file: |
63 | | - $status = Http::doDownload ( $this->mUrl, $this->mTempPath, $this->dl_mode ); |
64 | | - |
65 | | - //update the local filesize var: |
66 | | - $this->mFileSize = filesize( $this->mTempPath ); |
67 | | - |
68 | | - return $status; |
69 | | - } |
70 | | - |
71 | | - static function isValidRequest( $request ){ |
72 | | - if( !$request->getVal('wpUploadFileURL') ) |
73 | | - return false; |
74 | | - //check that is a valid url: |
75 | | - return self::isValidURI( $request->getVal('wpUploadFileURL') ); |
76 | | - } |
77 | | - static function isValidURI( $uri ){ |
78 | | - return preg_match('/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/', |
79 | | - $uri, $matches); |
80 | | - } |
81 | | -} |
\ No newline at end of file |
Index: branches/new-upload/phase3/includes/UploadFromStash.php |
— | — | @@ -1,56 +0,0 @@ |
2 | | -<?php |
3 | | -class UploadFromStash extends UploadBase { |
4 | | - static function isValidSessionKey( $key, $sessionData ) { |
5 | | - return !empty( $key ) && |
6 | | - is_array( $sessionData ) && |
7 | | - isset( $sessionData[$key] ) && |
8 | | - isset( $sessionData[$key]['version'] ) && |
9 | | - $sessionData[$key]['version'] == self::SESSION_VERSION |
10 | | - ; |
11 | | - } |
12 | | - static function isValidRequest(& $request ) { |
13 | | - $sessionData = $request->getSessionData('wsUploadData'); |
14 | | - return self::isValidSessionKey( |
15 | | - $request->getInt( 'wpSessionKey' ), |
16 | | - $sessionData |
17 | | - ); |
18 | | - } |
19 | | - function initialize( $name, $sessionData ) { |
20 | | - /** |
21 | | - * Confirming a temporarily stashed upload. |
22 | | - * We don't want path names to be forged, so we keep |
23 | | - * them in the session on the server and just give |
24 | | - * an opaque key to the user agent. |
25 | | - */ |
26 | | - parent::initialize( $name, |
27 | | - $sessionData['mTempPath'], |
28 | | - $sessionData['mFileSize'], |
29 | | - false |
30 | | - ); |
31 | | - |
32 | | - $this->mFileProps = $sessionData['mFileProps']; |
33 | | - } |
34 | | - function initializeFromRequest( &$request ) { |
35 | | - $sessionKey = $request->getInt( 'wpSessionKey' ); |
36 | | - $sessionData = $request->getSessionData('wsUploadData'); |
37 | | - |
38 | | - $desiredDestName = $request->getText( 'wpDestFile' ); |
39 | | - if( !$desiredDestName ) |
40 | | - $desiredDestName = $request->getText( 'wpUploadFile' ); |
41 | | - return $this->initialize( $desiredDestName, $sessionData[$sessionKey] ); |
42 | | - } |
43 | | - |
44 | | - /** |
45 | | - * File has been previously verified so no need to do so again. |
46 | | - */ |
47 | | - protected function verifyFile( $tmpfile ) { |
48 | | - return true; |
49 | | - } |
50 | | - /** |
51 | | - * We're here from "ignore warnings anyway" so return just OK |
52 | | - */ |
53 | | - function checkWarnings() { |
54 | | - return array(); |
55 | | - } |
56 | | -} |
57 | | -?> |
\ No newline at end of file |
Index: branches/new-upload/phase3/includes/UploadFromChunks.php |
— | — | @@ -1,231 +0,0 @@ |
2 | | -<?php |
3 | | -/* |
4 | | -* first destination checks are made (if ignorewarnings is not checked) errors / warning is returned. |
5 | | -* |
6 | | -* we return the uploadUrl |
7 | | -* we then accept chunk uploads from the client. |
8 | | -* return chunk id on each POSTED chunk |
9 | | -* once the client posts done=1 concatenated the files together. |
10 | | -* more info at: http://firefogg.org/dev/chunk_post.html |
11 | | -*/ |
12 | | -class UploadFromChunks extends UploadBase { |
13 | | - |
14 | | - var $chunk_mode; //init, chunk, done |
15 | | - var $mSessionKey = false; |
16 | | - var $status = array(); |
17 | | - |
18 | | - const INIT = 1; |
19 | | - const CHUNK = 2; |
20 | | - const DONE = 3; |
21 | | - |
22 | | - function initializeFromParams( $param , &$request) { |
23 | | - $this->initFromSessionKey( $param['chunksessionkey'], $request ); |
24 | | - //set the chunk mode: |
25 | | - if( !$this->mSessionKey && !$param['done'] ){ |
26 | | - //session key not set init the chunk upload system: |
27 | | - $this->chunk_mode = UploadFromChunks::INIT; |
28 | | - $this->mDesiredDestName = $param['filename']; |
29 | | - |
30 | | - }else if( $this->mSessionKey && !$param['done']){ |
31 | | - //this is a chunk piece |
32 | | - $this->chunk_mode = UploadFromChunks::CHUNK; |
33 | | - }else if( $this->mSessionKey && $param['done']){ |
34 | | - //this is the last chunk |
35 | | - $this->chunk_mode = UploadFromChunks::DONE; |
36 | | - } |
37 | | - if( $this->chunk_mode == UploadFromChunks::CHUNK || |
38 | | - $this->chunk_mode == UploadFromChunks::DONE ){ |
39 | | - //set chunk related vars: |
40 | | - $this->mTempPath = $request->getFileTempName( 'chunk' ); |
41 | | - $this->mFileSize = $request->getFileSize( 'chunk' ); |
42 | | - } |
43 | | - |
44 | | - return $this->status; |
45 | | - } |
46 | | - |
47 | | - static function isValidRequest( $request ) { |
48 | | - $sessionData = $request->getSessionData('wsUploadData'); |
49 | | - if(! self::isValidSessionKey( |
50 | | - $request->getInt( 'wpSessionKey' ), |
51 | | - $sessionData) ) |
52 | | - return false; |
53 | | - //check for the file: |
54 | | - return (bool)$request->getFileTempName( 'file' ); |
55 | | - } |
56 | | - |
57 | | - /* check warnings depending on chunk_mode*/ |
58 | | - function checkWarnings(){ |
59 | | - $warning = array(); |
60 | | - return $warning; |
61 | | - } |
62 | | - |
63 | | - function isEmptyFile(){ |
64 | | - //does not apply to chunk init |
65 | | - if( $this->chunk_mode == UploadFromChunks::INIT ){ |
66 | | - return false; |
67 | | - }else{ |
68 | | - return parent::isEmptyFile(); |
69 | | - } |
70 | | - } |
71 | | - /* Verify whether the upload is sane. |
72 | | - * Returns self::OK or else an array with error information |
73 | | - */ |
74 | | - function verifyUpload( $resultDetails ) { |
75 | | - //no checks on chunk upload mode: |
76 | | - if( $this->chunk_mode == UploadFromChunks::INIT ) |
77 | | - return self::OK; |
78 | | - |
79 | | - //verify on init and last chunk request |
80 | | - if( $this->chunk_mode == UploadFromChunks::CHUNK || |
81 | | - $this->chunk_mode == UploadFromChunks::DONE ) |
82 | | - return parent::verifyUpload( $resultDetails ); |
83 | | - } |
84 | | - //only run verifyFile on completed uploaded chunks |
85 | | - function verifyFile( $tmpFile ){ |
86 | | - if( $this->chunk_mode == UploadFromChunks::DONE ){ |
87 | | - //first append last chunk (so we can do a real verifyFile check... (check file type etc) |
88 | | - $status = $this->doChunkAppend(); |
89 | | - if( $status->isOK() ){ |
90 | | - $this->mTempPath = $this->getRealPath( $this->mTempAppendPath ); |
91 | | - //verify the completed merged chunks as if it was the file that got uploaded: |
92 | | - return parent::verifyFile( $this->mTempPath ) ; |
93 | | - }else{ |
94 | | - //conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me.. |
95 | | - return $status->getErrorsArray(); |
96 | | - } |
97 | | - }else{ |
98 | | - return true; |
99 | | - } |
100 | | - } |
101 | | - function getRealPath($srcPath){ |
102 | | - $repo = RepoGroup::singleton()->getLocalRepo(); |
103 | | - if ( $repo->isVirtualUrl( $srcPath) ) { |
104 | | - return $repo->resolveVirtualUrl( $srcPath ); |
105 | | - } |
106 | | - } |
107 | | - //pretty ugly inter-mixing of mParam and local vars |
108 | | - function setupChunkSession( $summary, $comment, $watch ) { |
109 | | - $this->mSessionKey = $this->getSessionKey(); |
110 | | - $_SESSION['wsUploadData'][ $this->mSessionKey ] = array( |
111 | | - 'mComment' => $comment, |
112 | | - 'mSummary' => $summary, |
113 | | - 'mWatch' => $watch, |
114 | | - 'mFilteredName' => $this->mFilteredName, |
115 | | - 'mTempAppendPath' => null, //the repo append path (not temporary local node mTempPath) |
116 | | - 'mDesiredDestName' => $this->mDesiredDestName, |
117 | | - 'version' => self::SESSION_VERSION, |
118 | | - ); |
119 | | - return $this->mSessionKey; |
120 | | - } |
121 | | - function initFromSessionKey( $sessionKey, $request ){ |
122 | | - if( !$sessionKey || empty( $sessionKey ) ){ |
123 | | - return false; |
124 | | - } |
125 | | - $this->mSessionKey = $sessionKey; |
126 | | - //load the sessionData array: |
127 | | - $sessionData = $request->getSessionData('wsUploadData'); |
128 | | - |
129 | | - if( isset( $sessionData[$this->mSessionKey]['version'] ) && |
130 | | - $sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) { |
131 | | - //update the local object from the session // |
132 | | - $this->mComment = $sessionData[ $this->mSessionKey ][ 'mComment' ]; |
133 | | - $this->mSummary = $sessionData[ $this->mSessionKey ][ 'mSummary' ]; |
134 | | - $this->mWatch = $sessionData[ $this->mSessionKey ][ 'mWatch' ]; |
135 | | - $this->mIgnorewarnings = $sessionData[ $this->mSessionKey ][ 'mIgnorewarnings' ]; |
136 | | - $this->mFilteredName = $sessionData[ $this->mSessionKey ][ 'mFilteredName' ]; |
137 | | - $this->mTempAppendPath = $sessionData[ $this->mSessionKey ][ 'mTempAppendPath' ]; |
138 | | - $this->mDesiredDestName = $sessionData[ $this->mSessionKey ][ 'mDesiredDestName' ]; |
139 | | - }else{ |
140 | | - $this->status = Array( 'error'=> 'missing session data'); |
141 | | - return false; |
142 | | - } |
143 | | - } |
144 | | - //lets us return an api result (as flow for chunk uploads is kind of different than others. |
145 | | - function performUpload($summary='', $comment='', $watch='', $user){ |
146 | | - global $wgServer, $wgScriptPath, $wgUser; |
147 | | - if( $this->chunk_mode == UploadFromChunks::INIT ){ |
148 | | - //firefogg expects a specific result per: |
149 | | - //http://www.firefogg.org/dev/chunk_post.html |
150 | | - |
151 | | - //its oky to return the token here because |
152 | | - //a) the user must have requested the token to get here and |
153 | | - //b) should only happen over POST |
154 | | - //c) (we need the token to validate chunks are coming from a non-xss request) |
155 | | - $token = urlencode( $wgUser->editToken() ); |
156 | | - ob_clean(); |
157 | | - echo ApiFormatJson::getJsonEncode( array( |
158 | | - "uploadUrl" => "{$wgServer}{$wgScriptPath}/api.php?action=upload&". |
159 | | - "token={$token}&format=json&enablechunks=true&chunksessionkey=". |
160 | | - $this->setupChunkSession($summary, $comment, $watch ) ) ); |
161 | | - exit(0); |
162 | | - }else if( $this->chunk_mode == UploadFromChunks::CHUNK ){ |
163 | | - $status = $this->doChunkAppend(); |
164 | | - if( $status->isOK() ){ |
165 | | - //return success: |
166 | | - //firefogg expects a specific result per: |
167 | | - //http://www.firefogg.org/dev/chunk_post.html |
168 | | - ob_clean(); |
169 | | - echo ApiFormatJson::getJsonEncode( array( |
170 | | - "result"=>1, |
171 | | - "filesize"=> filesize( $this->getRealPath( $this->mTempAppendPath ) ) |
172 | | - ) |
173 | | - ); |
174 | | - exit(0); |
175 | | - /*return array( |
176 | | - 'result' => 1 |
177 | | - );*/ |
178 | | - }else{ |
179 | | - return $status; |
180 | | - } |
181 | | - }else if( $this->chunk_mode == UploadFromChunks::DONE ){ |
182 | | - //update the values from the local (session init) if not paseed again) |
183 | | - if($summary == '') |
184 | | - $summary = $this->mSummary; |
185 | | - |
186 | | - if($comment == '') |
187 | | - $comment = $this->mComment; |
188 | | - |
189 | | - if($watch == '') |
190 | | - $watch = $this->mWatch; |
191 | | - $status = parent::performUpload($summary, $comment, $watch, $user ); |
192 | | - if( !$status->isGood() ) { |
193 | | - return $status; |
194 | | - } |
195 | | - $file = $this->getLocalFile(); |
196 | | - //firefogg expects a specific result per: |
197 | | - //http://www.firefogg.org/dev/chunk_post.html |
198 | | - ob_clean(); |
199 | | - echo ApiFormatJson::getJsonEncode( array( |
200 | | - "result"=>1, |
201 | | - "done"=>1, |
202 | | - "resultUrl"=> $file->getDescriptionUrl() |
203 | | - ) |
204 | | - ); |
205 | | - exit(0); |
206 | | - |
207 | | - } |
208 | | - } |
209 | | - //append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it. |
210 | | - function doChunkAppend(){ |
211 | | - //if we don't have a mTempAppendPath to generate a file from the chunk packaged var: |
212 | | - if( ! $this->mTempAppendPath ){ |
213 | | - //die(); |
214 | | - //get temp name: |
215 | | - //make a chunk store path. (append tmp file to chunk) |
216 | | - $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
217 | | - |
218 | | - if( $status->isOK() ) { |
219 | | - $this->mTempAppendPath = $status->value; |
220 | | - $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mTempAppendPath' ] = $this->mTempAppendPath; |
221 | | - } |
222 | | - return $status; |
223 | | - }else{ |
224 | | - if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){ |
225 | | - $status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath ); |
226 | | - }else{ |
227 | | - $status->fatal( 'filenotfound', $this->mTempAppendPath ); |
228 | | - } |
229 | | - return $status; |
230 | | - } |
231 | | - } |
232 | | -} |
Index: branches/new-upload/phase3/includes/UploadFromUpload.php |
— | — | @@ -1,19 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class UploadFromUpload extends UploadBase { |
5 | | - |
6 | | - function initializeFromRequest( &$request ) { |
7 | | - $desiredDestName = $request->getText( 'wpDestFile' ); |
8 | | - if( !$desiredDestName ) |
9 | | - $desiredDestName = $request->getText( 'wpUploadFile' ); |
10 | | - return $this->initialize( |
11 | | - $desiredDestName, |
12 | | - $request->getFileTempName( 'wpUploadFile' ), |
13 | | - $request->getFileSize( 'wpUploadFile' ) |
14 | | - ); |
15 | | - } |
16 | | - |
17 | | - static function isValidRequest( $request ) { |
18 | | - return (bool)$request->getFileTempName( 'wpUploadFile' ); |
19 | | - } |
20 | | -} |
Index: branches/new-upload/phase3/includes/UploadBase.php |
— | — | @@ -1,972 +0,0 @@ |
2 | | -<?php |
3 | | - |
4 | | -class UploadBase { |
5 | | - var $mTempPath; |
6 | | - var $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType; |
7 | | - var $mTitle = false, $mTitleError = 0; |
8 | | - var $mFilteredName, $mFinalExtension; |
9 | | - |
10 | | - const SUCCESS = 0; |
11 | | - const OK = 0; |
12 | | - const BEFORE_PROCESSING = 1; |
13 | | - const LARGE_FILE_SERVER = 2; |
14 | | - const EMPTY_FILE = 3; |
15 | | - const MIN_LENGTH_PARTNAME = 4; |
16 | | - const ILLEGAL_FILENAME = 5; |
17 | | - const PROTECTED_PAGE = 6; |
18 | | - const OVERWRITE_EXISTING_FILE = 7; |
19 | | - const FILETYPE_MISSING = 8; |
20 | | - const FILETYPE_BADTYPE = 9; |
21 | | - const VERIFICATION_ERROR = 10; |
22 | | - const UPLOAD_VERIFICATION_ERROR = 11; |
23 | | - const UPLOAD_WARNING = 12; |
24 | | - const INTERNAL_ERROR = 13; |
25 | | - const MIN_LENGHT_PARTNAME = 14; |
26 | | - |
27 | | - const SESSION_VERSION = 2; |
28 | | - |
29 | | - /** |
30 | | - * Returns true if uploads are enabled. |
31 | | - * Can be override by subclasses. |
32 | | - */ |
33 | | - static function isEnabled() { |
34 | | - global $wgEnableUploads; |
35 | | - if ( !$wgEnableUploads ) |
36 | | - return false; |
37 | | - |
38 | | - # Check php's file_uploads setting |
39 | | - if( !wfIniGetBool( 'file_uploads' ) ) { |
40 | | - return false; |
41 | | - } |
42 | | - return true; |
43 | | - } |
44 | | - /** |
45 | | - * Returns true if the user can use this upload module or else a string |
46 | | - * identifying the missing permission. |
47 | | - * Can be overriden by subclasses. |
48 | | - */ |
49 | | - static function isAllowed( $user ) { |
50 | | - if( !$user->isAllowed( 'upload' ) ) |
51 | | - return 'upload'; |
52 | | - return true; |
53 | | - } |
54 | | - |
55 | | - // Upload handlers. Should probably just be a global |
56 | | - static $uploadHandlers = array( 'Stash', 'Upload', 'Url' ); |
57 | | - /** |
58 | | - * Create a form of UploadBase depending on wpSourceType and initializes it |
59 | | - */ |
60 | | - static function createFromRequest( &$request, $type = null ) { |
61 | | - $type = $type ? $type : $request->getVal( 'wpSourceType' ); |
62 | | - |
63 | | - if( !$type ) |
64 | | - return null; |
65 | | - |
66 | | - $type = ucfirst($type); |
67 | | - $className = 'UploadFrom'.$type; |
68 | | - if( !in_array( $type, self::$uploadHandlers ) ) |
69 | | - return null; |
70 | | - |
71 | | - if( !call_user_func( array( $className, 'isEnabled' ) ) ) |
72 | | - return null; |
73 | | - |
74 | | - |
75 | | - if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) |
76 | | - return null; |
77 | | - |
78 | | - $handler = new $className; |
79 | | - |
80 | | - $handler->initializeFromRequest( $request ); |
81 | | - return $handler; |
82 | | - } |
83 | | - /** |
84 | | - * Check whether a request if valid for this handler |
85 | | - */ |
86 | | - static function isValidRequest( $request ) { |
87 | | - return false; |
88 | | - } |
89 | | - |
90 | | - function __construct() {} |
91 | | - |
92 | | - /** |
93 | | - * Do the real variable initialization |
94 | | - */ |
95 | | - function initialize( $name, $tempPath, $fileSize, $removeTempFile = false ) { |
96 | | - $this->mDesiredDestName = $name; |
97 | | - $this->mTempPath = $tempPath; |
98 | | - $this->mFileSize = $fileSize; |
99 | | - $this->mRemoveTempFile = $removeTempFile; |
100 | | - } |
101 | | - |
102 | | - /** |
103 | | - * Fetch the file. Usually a no-op |
104 | | - */ |
105 | | - function fetchFile() { |
106 | | - return Status::newGood(); |
107 | | - } |
108 | | - //return the file size |
109 | | - function isEmptyFile(){ |
110 | | - return empty( $this->mFileSize); |
111 | | - } |
112 | | - /** |
113 | | - * Verify whether the upload is sane. |
114 | | - * Returns self::OK or else an array with error information |
115 | | - */ |
116 | | - function verifyUpload() { |
117 | | - /** |
118 | | - * If there was no filename or a zero size given, give up quick. |
119 | | - */ |
120 | | - |
121 | | - if( $this->isEmptyFile() ) |
122 | | - return array( 'status' => self::EMPTY_FILE ); |
123 | | - |
124 | | - $nt = $this->getTitle(); |
125 | | - if( is_null( $nt ) ) { |
126 | | - $result = array( 'status' => $this->mTitleError ); |
127 | | - if( $this->mTitleError == self::ILLEGAL_FILENAME ) |
128 | | - $result['filtered'] = $this->mFilteredName; |
129 | | - if ( $this->mTitleError == self::FILETYPE_BADTYPE ) |
130 | | - $result['finalExt'] = $this->mFinalExtension; |
131 | | - return $result; |
132 | | - } |
133 | | - $this->mLocalFile = wfLocalFile( $nt ); |
134 | | - $this->mDestName = $this->mLocalFile->getName(); |
135 | | - |
136 | | - /** |
137 | | - * In some cases we may forbid overwriting of existing files. |
138 | | - */ |
139 | | - $overwrite = $this->checkOverwrite(); |
140 | | - if( $overwrite !== true ) |
141 | | - return array( 'status' => self::OVERWRITE_EXISTING_FILE, 'overwrite' => $overwrite ); |
142 | | - |
143 | | - /** |
144 | | - * Look at the contents of the file; if we can recognize the |
145 | | - * type but it's corrupt or data of the wrong type, we should |
146 | | - * probably not accept it. |
147 | | - */ |
148 | | - $verification = $this->verifyFile( $this->mTempPath ); |
149 | | - |
150 | | - if( $verification !== true ) { |
151 | | - if( !is_array( $verification ) ) |
152 | | - $verification = array( $verification ); |
153 | | - $verification['status'] = self::VERIFICATION_ERROR; |
154 | | - return $verification; |
155 | | - } |
156 | | - |
157 | | - $error = ''; |
158 | | - if( !wfRunHooks( 'UploadVerification', |
159 | | - array( $this->mDestName, $this->mTempPath, &$error ) ) ) { |
160 | | - return array( 'status' => self::UPLOAD_VERIFICATION_ERROR, 'error' => $error ); |
161 | | - } |
162 | | - |
163 | | - return self::OK; |
164 | | - } |
165 | | - |
166 | | - /** |
167 | | - * Verifies that it's ok to include the uploaded file |
168 | | - * |
169 | | - * this function seems to intermixes tmpfile and $this->mTempPath .. no idea why this is |
170 | | - * |
171 | | - * @param string $tmpfile the full path of the temporary file to verify |
172 | | - * @return mixed true of the file is verified, a string or array otherwise. |
173 | | - */ |
174 | | - protected function verifyFile( $tmpfile ) { |
175 | | - $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension ); |
176 | | - $this->checkMacBinary( ); |
177 | | - |
178 | | - #magically determine mime type |
179 | | - $magic = MimeMagic::singleton(); |
180 | | - $mime = $magic->guessMimeType( $tmpfile, false ); |
181 | | - |
182 | | - #check mime type, if desired |
183 | | - global $wgVerifyMimeType; |
184 | | - if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) { |
185 | | - if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) |
186 | | - return array( 'filetype-badmime', $mime ); |
187 | | - |
188 | | - # Check IE type |
189 | | - $fp = fopen( $tmpfile, 'rb' ); |
190 | | - $chunk = fread( $fp, 256 ); |
191 | | - fclose( $fp ); |
192 | | - $extMime = $magic->guessTypesForExtension( $this->mFinalExtension ); |
193 | | - $ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime ); |
194 | | - foreach ( $ieTypes as $ieType ) { |
195 | | - if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) { |
196 | | - return array( 'filetype-bad-ie-mime', $ieType ); |
197 | | - } |
198 | | - } |
199 | | - } |
200 | | - |
201 | | - |
202 | | - #check for htmlish code and javascript |
203 | | - if( $this->detectScript ( $tmpfile, $mime, $this->mFinalExtension ) ) { |
204 | | - return 'uploadscripted'; |
205 | | - } |
206 | | - if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { |
207 | | - if( $this->detectScriptInSvg( $tmpfile ) ) { |
208 | | - return 'uploadscripted'; |
209 | | - } |
210 | | - } |
211 | | - |
212 | | - /** |
213 | | - * Scan the uploaded file for viruses |
214 | | - */ |
215 | | - $virus = $this->detectVirus($tmpfile); |
216 | | - if ( $virus ) { |
217 | | - return array( 'uploadvirus', $virus ); |
218 | | - } |
219 | | - wfDebug( __METHOD__.": all clear; passing.\n" ); |
220 | | - return true; |
221 | | - } |
222 | | - |
223 | | - /** |
224 | | - * Check whether the user can edit, upload and create the image |
225 | | - */ |
226 | | - function verifyPermissions( $user ) { |
227 | | - /** |
228 | | - * If the image is protected, non-sysop users won't be able |
229 | | - * to modify it by uploading a new revision. |
230 | | - */ |
231 | | - $nt = $this->getTitle(); |
232 | | - if( is_null( $nt ) ) |
233 | | - return true; |
234 | | - $permErrors = $nt->getUserPermissionsErrors( 'edit', $user ); |
235 | | - $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user ); |
236 | | - $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $user ) ); |
237 | | - if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { |
238 | | - $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); |
239 | | - $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) ); |
240 | | - return $permErrors; |
241 | | - } |
242 | | - return true; |
243 | | - } |
244 | | - |
245 | | - /** |
246 | | - * Check for non fatal problems with the file |
247 | | - */ |
248 | | - function checkWarnings() { |
249 | | - $warning = array(); |
250 | | - |
251 | | - $filename = $this->mLocalFile->getName(); |
252 | | - $n = strrpos( $filename, '.' ); |
253 | | - $partname = $n ? substr( $filename, 0, $n ) : $filename; |
254 | | - |
255 | | - /* |
256 | | - * Check whether the resulting filename is different from the desired one, |
257 | | - * but ignore things like ucfirst() and spaces/underscore things |
258 | | - **/ |
259 | | - $comparableName = str_replace( ' ', '_', $this->mDesiredDestName ); |
260 | | - global $wgCapitalLinks, $wgContLang; |
261 | | - if ( $wgCapitalLinks ) { |
262 | | - $comparableName = $wgContLang->ucfirst( $comparableName ); |
263 | | - } |
264 | | - if( $this->mDesiredDestName != $filename && $comparableName != $filename ) |
265 | | - $warning['badfilename'] = $filename; |
266 | | - |
267 | | - // Check whether the file extension is on the unwanted list |
268 | | - global $wgCheckFileExtensions, $wgFileExtensions; |
269 | | - if ( $wgCheckFileExtensions ) { |
270 | | - if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) |
271 | | - $warning['filetype-unwanted-type'] = $this->mFinalExtension; |
272 | | - } |
273 | | - |
274 | | - global $wgUploadSizeWarning; |
275 | | - if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) |
276 | | - $warning['large-file'] = $wgUploadSizeWarning; |
277 | | - |
278 | | - if ( $this->mFileSize == 0 ) |
279 | | - $warning['emptyfile'] = true; |
280 | | - |
281 | | - |
282 | | - $exists = self::getExistsWarning( $this->mLocalFile ); |
283 | | - if( $exists !== false ) |
284 | | - $warning['exists'] = $exists; |
285 | | - |
286 | | - // Check whether this may be a thumbnail |
287 | | - if( $exists !== false && $exists[0] != 'thumb' |
288 | | - && self::isThumbName( $this->mLocalFile->getName() ) ){ |
289 | | - //make the title: |
290 | | - $nt = $this->getTitle(); |
291 | | - $warning['file-thumbnail-no'] = substr( $filename , 0, |
292 | | - strpos( $nt->getText() , '-' ) +1 ); |
293 | | - } |
294 | | - |
295 | | - // Check dupes against existing files |
296 | | - $hash = File::sha1Base36( $this->mTempPath ); |
297 | | - $dupes = RepoGroup::singleton()->findBySha1( $hash ); |
298 | | - $title = $this->getTitle(); |
299 | | - // Remove all matches against self |
300 | | - foreach ( $dupes as $key => $dupe ) { |
301 | | - if( $title->equals( $dupe->getTitle() ) ) |
302 | | - unset( $dupes[$key] ); |
303 | | - } |
304 | | - if( $dupes ) |
305 | | - $warning['duplicate'] = $dupes; |
306 | | - |
307 | | - // Check dupes against archives |
308 | | - $archivedImage = new ArchivedFile( null, 0, "{$hash}.{$this->mFinalExtension}" ); |
309 | | - if ( $archivedImage->getID() > 0 ) |
310 | | - $warning['duplicate-archive'] = $archivedImage->getName(); |
311 | | - |
312 | | - $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist(); |
313 | | - foreach( $filenamePrefixBlacklist as $prefix ) { |
314 | | - if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { |
315 | | - $warning['filename-bad-prefix'] = $prefix; |
316 | | - break; |
317 | | - } |
318 | | - } |
319 | | - |
320 | | - # If the file existed before and was deleted, warn the user of this |
321 | | - # Don't bother doing so if the file exists now, however |
322 | | - if( $this->mLocalFile->wasDeleted() && !$this->mLocalFile->exists() ) |
323 | | - $warning['filewasdeleted'] = $this->mLocalFile->getTitle(); |
324 | | - |
325 | | - return $warning; |
326 | | - } |
327 | | - |
328 | | - /** |
329 | | - * Really perform the upload. |
330 | | - */ |
331 | | - function performUpload( $comment, $pageText, $watch, $user ) { |
332 | | - wfDebug("\n\n\performUpload: sum:" . $comment . ' c: ' . $pageText . ' w:' .$watch); |
333 | | - $status = $this->mLocalFile->upload( $this->mTempPath, $comment, $pageText, |
334 | | - File::DELETE_SOURCE, $this->mFileProps, false, $user ); |
335 | | - |
336 | | - if( $status->isGood() && $watch ) |
337 | | - $user->addWatch( $this->mLocalFile->getTitle() ); |
338 | | - |
339 | | - if( $status->isGood() ) |
340 | | - wfRunHooks( 'UploadComplete', array( &$this ) ); |
341 | | - |
342 | | - return $status; |
343 | | - } |
344 | | - |
345 | | - /** |
346 | | - * Returns a title or null |
347 | | - */ |
348 | | - function getTitle() { |
349 | | - if ( $this->mTitle !== false ) |
350 | | - return $this->mTitle; |
351 | | - |
352 | | - /** |
353 | | - * Chop off any directories in the given filename. Then |
354 | | - * filter out illegal characters, and try to make a legible name |
355 | | - * out of it. We'll strip some silently that Title would die on. |
356 | | - */ |
357 | | - |
358 | | - $basename = $this->mDesiredDestName; |
359 | | - |
360 | | - $this->mFilteredName = wfStripIllegalFilenameChars( $basename ); |
361 | | - /* Normalize to title form before we do any further processing */ |
362 | | - $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName ); |
363 | | - if( is_null( $nt ) ) { |
364 | | - $this->mTitleError = self::ILLEGAL_FILENAME; |
365 | | - return $this->mTitle = null; |
366 | | - } |
367 | | - $this->mFilteredName = $nt->getDBkey(); |
368 | | - |
369 | | - /** |
370 | | - * We'll want to blacklist against *any* 'extension', and use |
371 | | - * only the final one for the whitelist. |
372 | | - */ |
373 | | - list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName ); |
374 | | - |
375 | | - if( count( $ext ) ) { |
376 | | - $this->mFinalExtension = trim( $ext[count( $ext ) - 1] ); |
377 | | - } else { |
378 | | - $this->mFinalExtension = ''; |
379 | | - } |
380 | | - |
381 | | - /* Don't allow users to override the blacklist (check file extension) */ |
382 | | - global $wgCheckFileExtensions, $wgStrictFileExtensions; |
383 | | - global $wgFileExtensions, $wgFileBlacklist; |
384 | | - if ( $this->mFinalExtension == '' ) { |
385 | | - $this->mTitleError = self::FILETYPE_MISSING; |
386 | | - return $this->mTitle = null; |
387 | | - } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || |
388 | | - ( $wgCheckFileExtensions && $wgStrictFileExtensions && |
389 | | - !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) { |
390 | | - $this->mTitleError = self::FILETYPE_BADTYPE; |
391 | | - return $this->mTitle = null; |
392 | | - } |
393 | | - |
394 | | - # If there was more than one "extension", reassemble the base |
395 | | - # filename to prevent bogus complaints about length |
396 | | - if( count( $ext ) > 1 ) { |
397 | | - for( $i = 0; $i < count( $ext ) - 1; $i++ ) |
398 | | - $partname .= '.' . $ext[$i]; |
399 | | - } |
400 | | - |
401 | | - if( strlen( $partname ) < 1 ) { |
402 | | - $this->mTitleError = self::MIN_LENGTH_PARTNAME; |
403 | | - return $this->mTitle = null; |
404 | | - } |
405 | | - |
406 | | - $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName ); |
407 | | - if( is_null( $nt ) ) { |
408 | | - $this->mTitleError = self::ILLEGAL_FILENAME; |
409 | | - return $this->mTitle = null; |
410 | | - } |
411 | | - return $this->mTitle = $nt; |
412 | | - } |
413 | | - |
414 | | - function getLocalFile() { |
415 | | - if( is_null( $this->mLocalFile ) ) { |
416 | | - $nt = $this->getTitle(); |
417 | | - $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt ); |
418 | | - } |
419 | | - return $this->mLocalFile; |
420 | | - } |
421 | | - |
422 | | - /** |
423 | | - * Stash a file in a temporary directory for later processing |
424 | | - * after the user has confirmed it. |
425 | | - * |
426 | | - * If the user doesn't explicitly cancel or accept, these files |
427 | | - * can accumulate in the temp directory. |
428 | | - * |
429 | | - * @param string $saveName - the destination filename |
430 | | - * @param string $tempName - the source temporary file to save |
431 | | - * @return string - full path the stashed file, or false on failure |
432 | | - * @access private |
433 | | - */ |
434 | | - function saveTempUploadedFile( $saveName, $tempName ) { |
435 | | - $repo = RepoGroup::singleton()->getLocalRepo(); |
436 | | - $status = $repo->storeTemp( $saveName, $tempName ); |
437 | | - return $status; |
438 | | - } |
439 | | - /* append to a stashed file */ |
440 | | - function appendToUploadFile($srcPath, $toAppendPath ){ |
441 | | - $repo = RepoGroup::singleton()->getLocalRepo(); |
442 | | - $status = $repo->append($srcPath, $toAppendPath); |
443 | | - return $status; |
444 | | - } |
445 | | - |
446 | | - /** |
447 | | - * Stash a file in a temporary directory for later processing, |
448 | | - * and save the necessary descriptive info into the session. |
449 | | - * Returns a key value which will be passed through a form |
450 | | - * to pick up the path info on a later invocation. |
451 | | - * |
452 | | - * @return int |
453 | | - * @access private |
454 | | - */ |
455 | | - function stashSession() { |
456 | | - $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
457 | | - if( !$status->isOK() ) { |
458 | | - # Couldn't save the file. |
459 | | - return false; |
460 | | - } |
461 | | - $mTempPath = $status->value; |
462 | | - session_start();//start up the session (might have been previously closed to prevent php session locking) |
463 | | - $key = $this->getSessionKey (); |
464 | | - $_SESSION['wsUploadData'][$key] = array( |
465 | | - 'mTempPath' => $mTempPath, |
466 | | - 'mFileSize' => $this->mFileSize, |
467 | | - 'mSrcName' => $this->mSrcName, |
468 | | - 'mFileProps' => $this->mFileProps, |
469 | | - 'version' => self::SESSION_VERSION, |
470 | | - ); |
471 | | - session_write_close(); |
472 | | - return $key; |
473 | | - } |
474 | | - //pull session Key gen from stash in cases where we want to start an upload without much information |
475 | | - function getSessionKey(){ |
476 | | - $key = mt_rand( 0, 0x7fffffff ); |
477 | | - $_SESSION['wsUploadData'][$key] = array(); |
478 | | - return $key; |
479 | | - } |
480 | | - |
481 | | - /** |
482 | | - * Remove a temporarily kept file stashed by saveTempUploadedFile(). |
483 | | - * @return success |
484 | | - */ |
485 | | - function unsaveUploadedFile() { |
486 | | - $repo = RepoGroup::singleton()->getLocalRepo(); |
487 | | - $success = $repo->freeTemp( $this->mTempPath ); |
488 | | - return $success; |
489 | | - } |
490 | | - |
491 | | - /** |
492 | | - * If we've modified the upload file we need to manually remove it |
493 | | - * on exit to clean up. |
494 | | - * @access private |
495 | | - */ |
496 | | - function cleanupTempFile() { |
497 | | - if ( $this->mRemoveTempFile && $this->mTempPath && file_exists( $this->mTempPath ) ) { |
498 | | - wfDebug( __METHOD__.": Removing temporary file {$this->mTempPath}\n" ); |
499 | | - unlink( $this->mTempPath ); |
500 | | - } |
501 | | - } |
502 | | - |
503 | | - function getTempPath() { |
504 | | - return $this->mTempPath; |
505 | | - } |
506 | | - |
507 | | - |
508 | | - /** |
509 | | - * Split a file into a base name and all dot-delimited 'extensions' |
510 | | - * on the end. Some web server configurations will fall back to |
511 | | - * earlier pseudo-'extensions' to determine type and execute |
512 | | - * scripts, so the blacklist needs to check them all. |
513 | | - * |
514 | | - * @return array |
515 | | - */ |
516 | | - public static function splitExtensions( $filename ) { |
517 | | - $bits = explode( '.', $filename ); |
518 | | - $basename = array_shift( $bits ); |
519 | | - return array( $basename, $bits ); |
520 | | - } |
521 | | - |
522 | | - /** |
523 | | - * Perform case-insensitive match against a list of file extensions. |
524 | | - * Returns true if the extension is in the list. |
525 | | - * |
526 | | - * @param string $ext |
527 | | - * @param array $list |
528 | | - * @return bool |
529 | | - */ |
530 | | - public static function checkFileExtension( $ext, $list ) { |
531 | | - return in_array( strtolower( $ext ), $list ); |
532 | | - } |
533 | | - |
534 | | - /** |
535 | | - * Perform case-insensitive match against a list of file extensions. |
536 | | - * Returns true if any of the extensions are in the list. |
537 | | - * |
538 | | - * @param array $ext |
539 | | - * @param array $list |
540 | | - * @return bool |
541 | | - */ |
542 | | - public static function checkFileExtensionList( $ext, $list ) { |
543 | | - foreach( $ext as $e ) { |
544 | | - if( in_array( strtolower( $e ), $list ) ) { |
545 | | - return true; |
546 | | - } |
547 | | - } |
548 | | - return false; |
549 | | - } |
550 | | - |
551 | | - |
552 | | - /** |
553 | | - * Checks if the mime type of the uploaded file matches the file extension. |
554 | | - * |
555 | | - * @param string $mime the mime type of the uploaded file |
556 | | - * @param string $extension The filename extension that the file is to be served with |
557 | | - * @return bool |
558 | | - */ |
559 | | - public static function verifyExtension( $mime, $extension ) { |
560 | | - $magic = MimeMagic::singleton(); |
561 | | - |
562 | | - if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) |
563 | | - if ( ! $magic->isRecognizableExtension( $extension ) ) { |
564 | | - wfDebug( __METHOD__.": passing file with unknown detected mime type; " . |
565 | | - "unrecognized extension '$extension', can't verify\n" ); |
566 | | - return true; |
567 | | - } else { |
568 | | - wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ". |
569 | | - "recognized extension '$extension', so probably invalid file\n" ); |
570 | | - return false; |
571 | | - } |
572 | | - |
573 | | - $match= $magic->isMatchingExtension($extension,$mime); |
574 | | - |
575 | | - if ($match===NULL) { |
576 | | - wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" ); |
577 | | - return true; |
578 | | - } elseif ($match===true) { |
579 | | - wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" ); |
580 | | - |
581 | | - #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! |
582 | | - return true; |
583 | | - |
584 | | - } else { |
585 | | - wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" ); |
586 | | - return false; |
587 | | - } |
588 | | - } |
589 | | - |
590 | | - /** |
591 | | - * Heuristic for detecting files that *could* contain JavaScript instructions or |
592 | | - * things that may look like HTML to a browser and are thus |
593 | | - * potentially harmful. The present implementation will produce false positives in some situations. |
594 | | - * |
595 | | - * @param string $file Pathname to the temporary upload file |
596 | | - * @param string $mime The mime type of the file |
597 | | - * @param string $extension The extension of the file |
598 | | - * @return bool true if the file contains something looking like embedded scripts |
599 | | - */ |
600 | | - function detectScript($file, $mime, $extension) { |
601 | | - global $wgAllowTitlesInSVG; |
602 | | - |
603 | | - #ugly hack: for text files, always look at the entire file. |
604 | | - #For binary field, just check the first K. |
605 | | - |
606 | | - if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file ); |
607 | | - else { |
608 | | - $fp = fopen( $file, 'rb' ); |
609 | | - $chunk = fread( $fp, 1024 ); |
610 | | - fclose( $fp ); |
611 | | - } |
612 | | - |
613 | | - $chunk= strtolower( $chunk ); |
614 | | - |
615 | | - if (!$chunk) return false; |
616 | | - |
617 | | - #decode from UTF-16 if needed (could be used for obfuscation). |
618 | | - if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE"; |
619 | | - elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE"; |
620 | | - else $enc= NULL; |
621 | | - |
622 | | - if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk); |
623 | | - |
624 | | - $chunk= trim($chunk); |
625 | | - |
626 | | - #FIXME: convert from UTF-16 if necessarry! |
627 | | - |
628 | | - wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n"); |
629 | | - |
630 | | - #check for HTML doctype |
631 | | - if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true; |
632 | | - |
633 | | - /** |
634 | | - * Internet Explorer for Windows performs some really stupid file type |
635 | | - * autodetection which can cause it to interpret valid image files as HTML |
636 | | - * and potentially execute JavaScript, creating a cross-site scripting |
637 | | - * attack vectors. |
638 | | - * |
639 | | - * Apple's Safari browser also performs some unsafe file type autodetection |
640 | | - * which can cause legitimate files to be interpreted as HTML if the |
641 | | - * web server is not correctly configured to send the right content-type |
642 | | - * (or if you're really uploading plain text and octet streams!) |
643 | | - * |
644 | | - * Returns true if IE is likely to mistake the given file for HTML. |
645 | | - * Also returns true if Safari would mistake the given file for HTML |
646 | | - * when served with a generic content-type. |
647 | | - */ |
648 | | - |
649 | | - $tags = array( |
650 | | - '<a', |
651 | | - '<body', |
652 | | - '<head', |
653 | | - '<html', #also in safari |
654 | | - '<img', |
655 | | - '<pre', |
656 | | - '<script', #also in safari |
657 | | - '<table' |
658 | | - ); |
659 | | - if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) { |
660 | | - $tags[] = '<title'; |
661 | | - } |
662 | | - |
663 | | - foreach( $tags as $tag ) { |
664 | | - if( false !== strpos( $chunk, $tag ) ) { |
665 | | - return true; |
666 | | - } |
667 | | - } |
668 | | - |
669 | | - /* |
670 | | - * look for javascript |
671 | | - */ |
672 | | - |
673 | | - #resolve entity-refs to look at attributes. may be harsh on big files... cache result? |
674 | | - $chunk = Sanitizer::decodeCharReferences( $chunk ); |
675 | | - |
676 | | - #look for script-types |
677 | | - if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true; |
678 | | - |
679 | | - #look for html-style script-urls |
680 | | - if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; |
681 | | - |
682 | | - #look for css-style script-urls |
683 | | - if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; |
684 | | - |
685 | | - wfDebug("SpecialUpload::detectScript: no scripts found\n"); |
686 | | - return false; |
687 | | - } |
688 | | - |
689 | | - function detectScriptInSvg( $filename ) { |
690 | | - $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) ); |
691 | | - return $check->filterMatch; |
692 | | - } |
693 | | - |
694 | | - /** |
695 | | - * @todo Replace this with a whitelist filter! |
696 | | - */ |
697 | | - function checkSvgScriptCallback( $element, $attribs ) { |
698 | | - $stripped = $this->stripXmlNamespace( $element ); |
699 | | - |
700 | | - if( $stripped == 'script' ) { |
701 | | - wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" ); |
702 | | - return true; |
703 | | - } |
704 | | - |
705 | | - foreach( $attribs as $attrib => $value ) { |
706 | | - $stripped = $this->stripXmlNamespace( $attrib ); |
707 | | - if( substr( $stripped, 0, 2 ) == 'on' ) { |
708 | | - wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" ); |
709 | | - return true; |
710 | | - } |
711 | | - if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) { |
712 | | - wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" ); |
713 | | - return true; |
714 | | - } |
715 | | - } |
716 | | - } |
717 | | - |
718 | | - private function stripXmlNamespace( $name ) { |
719 | | - // 'http://www.w3.org/2000/svg:script' -> 'script' |
720 | | - $parts = explode( ':', strtolower( $name ) ); |
721 | | - return array_pop( $parts ); |
722 | | - } |
723 | | - |
724 | | - |
725 | | - |
726 | | - /** |
727 | | - * Generic wrapper function for a virus scanner program. |
728 | | - * This relies on the $wgAntivirus and $wgAntivirusSetup variables. |
729 | | - * $wgAntivirusRequired may be used to deny upload if the scan fails. |
730 | | - * |
731 | | - * @param string $file Pathname to the temporary upload file |
732 | | - * @return mixed false if not virus is found, NULL if the scan fails or is disabled, |
733 | | - * or a string containing feedback from the virus scanner if a virus was found. |
734 | | - * If textual feedback is missing but a virus was found, this function returns true. |
735 | | - */ |
736 | | - function detectVirus($file) { |
737 | | - global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut; |
738 | | - |
739 | | - if ( !$wgAntivirus ) { |
740 | | - wfDebug( __METHOD__.": virus scanner disabled\n"); |
741 | | - return NULL; |
742 | | - } |
743 | | - |
744 | | - if ( !$wgAntivirusSetup[$wgAntivirus] ) { |
745 | | - wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" ); |
746 | | - $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) ); |
747 | | - return wfMsg('virus-unknownscanner') . " $wgAntivirus"; |
748 | | - } |
749 | | - |
750 | | - # look up scanner configuration |
751 | | - $command = $wgAntivirusSetup[$wgAntivirus]["command"]; |
752 | | - $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"]; |
753 | | - $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ? |
754 | | - $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null; |
755 | | - |
756 | | - if ( strpos( $command,"%f" ) === false ) { |
757 | | - # simple pattern: append file to scan |
758 | | - $command .= " " . wfEscapeShellArg( $file ); |
759 | | - } else { |
760 | | - # complex pattern: replace "%f" with file to scan |
761 | | - $command = str_replace( "%f", wfEscapeShellArg( $file ), $command ); |
762 | | - } |
763 | | - |
764 | | - wfDebug( __METHOD__.": running virus scan: $command \n" ); |
765 | | - |
766 | | - # execute virus scanner |
767 | | - $exitCode = false; |
768 | | - |
769 | | - #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. |
770 | | - # that does not seem to be worth the pain. |
771 | | - # Ask me (Duesentrieb) about it if it's ever needed. |
772 | | - $output = array(); |
773 | | - if ( wfIsWindows() ) { |
774 | | - exec( "$command", $output, $exitCode ); |
775 | | - } else { |
776 | | - exec( "$command 2>&1", $output, $exitCode ); |
777 | | - } |
778 | | - |
779 | | - # map exit code to AV_xxx constants. |
780 | | - $mappedCode = $exitCode; |
781 | | - if ( $exitCodeMap ) { |
782 | | - if ( isset( $exitCodeMap[$exitCode] ) ) { |
783 | | - $mappedCode = $exitCodeMap[$exitCode]; |
784 | | - } elseif ( isset( $exitCodeMap["*"] ) ) { |
785 | | - $mappedCode = $exitCodeMap["*"]; |
786 | | - } |
787 | | - } |
788 | | - |
789 | | - if ( $mappedCode === AV_SCAN_FAILED ) { |
790 | | - # scan failed (code was mapped to false by $exitCodeMap) |
791 | | - wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" ); |
792 | | - |
793 | | - if ( $wgAntivirusRequired ) { |
794 | | - return wfMsg('virus-scanfailed', array( $exitCode ) ); |
795 | | - } else { |
796 | | - return NULL; |
797 | | - } |
798 | | - } else if ( $mappedCode === AV_SCAN_ABORTED ) { |
799 | | - # scan failed because filetype is unknown (probably imune) |
800 | | - wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" ); |
801 | | - return NULL; |
802 | | - } else if ( $mappedCode === AV_NO_VIRUS ) { |
803 | | - # no virus found |
804 | | - wfDebug( __METHOD__.": file passed virus scan.\n" ); |
805 | | - return false; |
806 | | - } else { |
807 | | - $output = join( "\n", $output ); |
808 | | - $output = trim( $output ); |
809 | | - |
810 | | - if ( !$output ) { |
811 | | - $output = true; #if there's no output, return true |
812 | | - } elseif ( $msgPattern ) { |
813 | | - $groups = array(); |
814 | | - if ( preg_match( $msgPattern, $output, $groups ) ) { |
815 | | - if ( $groups[1] ) { |
816 | | - $output = $groups[1]; |
817 | | - } |
818 | | - } |
819 | | - } |
820 | | - |
821 | | - wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output \n" ); |
822 | | - return $output; |
823 | | - } |
824 | | - } |
825 | | - |
826 | | - /** |
827 | | - * Check if the temporary file is MacBinary-encoded, as some uploads |
828 | | - * from Internet Explorer on Mac OS Classic and Mac OS X will be. |
829 | | - * If so, the data fork will be extracted to a second temporary file, |
830 | | - * which will then be checked for validity and either kept or discarded. |
831 | | - * |
832 | | - * @access private |
833 | | - */ |
834 | | - function checkMacBinary() { |
835 | | - $macbin = new MacBinary( $this->mTempPath ); |
836 | | - if( $macbin->isValid() ) { |
837 | | - $dataFile = tempnam( wfTempDir(), "WikiMacBinary" ); |
838 | | - $dataHandle = fopen( $dataFile, 'wb' ); |
839 | | - |
840 | | - wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" ); |
841 | | - $macbin->extractData( $dataHandle ); |
842 | | - |
843 | | - $this->mTempPath = $dataFile; |
844 | | - $this->mFileSize = $macbin->dataForkLength(); |
845 | | - |
846 | | - // We'll have to manually remove the new file if it's not kept. |
847 | | - $this->mRemoveTempFile = true; |
848 | | - } |
849 | | - $macbin->close(); |
850 | | - } |
851 | | - |
852 | | - /** |
853 | | - * Check if there's an overwrite conflict and, if so, if restrictions |
854 | | - * forbid this user from performing the upload. |
855 | | - * |
856 | | - * @return mixed true on success, WikiError on failure |
857 | | - * @access private |
858 | | - */ |
859 | | - function checkOverwrite() { |
860 | | - global $wgUser; |
861 | | - // First check whether the local file can be overwritten |
862 | | - if( $this->mLocalFile->exists() ) |
863 | | - if( !self::userCanReUpload( $wgUser, $this->mLocalFile ) ) |
864 | | - return 'fileexists-forbidden'; |
865 | | - |
866 | | - // Check shared conflicts |
867 | | - $file = wfFindFile( $this->mLocalFile->getName() ); |
868 | | - if ( $file && ( !$wgUser->isAllowed( 'reupload' ) || |
869 | | - !$wgUser->isAllowed( 'reupload-shared' ) ) ) |
870 | | - return 'fileexists-shared-forbidden'; |
871 | | - |
872 | | - return true; |
873 | | - |
874 | | - } |
875 | | - /** |
876 | | - * Check if a user is the last uploader |
877 | | - * |
878 | | - * @param User $user |
879 | | - * @param string $img, image name |
880 | | - * @return bool |
881 | | - */ |
882 | | - public static function userCanReUpload( User $user, $img ) { |
883 | | - if( $user->isAllowed( 'reupload' ) ) |
884 | | - return true; // non-conditional |
885 | | - if( !$user->isAllowed( 'reupload-own' ) ) |
886 | | - return false; |
887 | | - if( is_string( $img ) ) |
888 | | - $img = wfLocalFile( $img ); |
889 | | - if ( !( $img instanceof LocalFile ) ) |
890 | | - return false; |
891 | | - |
892 | | - return $user->getId() == $img->getUser( 'id' ); |
893 | | - } |
894 | | - |
895 | | - public static function getExistsWarning( $file ) { |
896 | | - if( $file->exists() ) |
897 | | - return array( 'exists', $file ); |
898 | | - |
899 | | - if( $file->getTitle()->getArticleID() ) |
900 | | - return array( 'page-exists', $file ); |
901 | | - |
902 | | - if( strpos( $file->getName(), '.' ) == false ) { |
903 | | - $partname = $file->getName(); |
904 | | - $rawExtension = ''; |
905 | | - } else { |
906 | | - $n = strrpos( $file->getName(), '.' ); |
907 | | - $rawExtension = substr( $file->getName(), $n + 1 ); |
908 | | - $partname = substr( $file->getName(), 0, $n ); |
909 | | - } |
910 | | - |
911 | | - if ( $rawExtension != $file->getExtension() ) { |
912 | | - // We're not using the normalized form of the extension. |
913 | | - // Normal form is lowercase, using most common of alternate |
914 | | - // extensions (eg 'jpg' rather than 'JPEG'). |
915 | | - // |
916 | | - // Check for another file using the normalized form... |
917 | | - $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() ); |
918 | | - $file_lc = wfLocalFile( $nt_lc ); |
919 | | - |
920 | | - if( $file_lc->exists() ) |
921 | | - return array( 'exists-normalized', $file_lc ); |
922 | | - } |
923 | | - |
924 | | - if ( self::isThumbName( $file->getName() ) ) { |
925 | | - # Check for filenames like 50px- or 180px-, these are mostly thumbnails |
926 | | - $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); |
927 | | - $file_thb = wfLocalFile( $nt_thb ); |
928 | | - if( $file_thb->exists() ) |
929 | | - return array( 'thumb', $file_thb ); |
930 | | - } |
931 | | - |
932 | | - return false; |
933 | | - } |
934 | | - |
935 | | - public static function isThumbName( $filename ) { |
936 | | - $n = strrpos( $filename, '.' ); |
937 | | - $partname = $n ? substr( $filename, 0, $n ) : $filename; |
938 | | - return ( |
939 | | - substr( $partname , 3, 3 ) == 'px-' || |
940 | | - substr( $partname , 2, 3 ) == 'px-' |
941 | | - ) && |
942 | | - ereg( "[0-9]{2}" , substr( $partname , 0, 2) ); |
943 | | - } |
944 | | - |
945 | | - /** |
946 | | - * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]] |
947 | | - * |
948 | | - * @return array list of prefixes |
949 | | - */ |
950 | | - public static function getFilenamePrefixBlacklist() { |
951 | | - $blacklist = array(); |
952 | | - $message = wfMsgForContent( 'filename-prefix-blacklist' ); |
953 | | - if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) { |
954 | | - $lines = explode( "\n", $message ); |
955 | | - foreach( $lines as $line ) { |
956 | | - // Remove comment lines |
957 | | - $comment = substr( trim( $line ), 0, 1 ); |
958 | | - if ( $comment == '#' || $comment == '' ) { |
959 | | - continue; |
960 | | - } |
961 | | - // Remove additional comments after a prefix |
962 | | - $comment = strpos( $line, '#' ); |
963 | | - if ( $comment > 0 ) { |
964 | | - $line = substr( $line, 0, $comment-1 ); |
965 | | - } |
966 | | - $blacklist[] = trim( $line ); |
967 | | - } |
968 | | - } |
969 | | - return $blacklist; |
970 | | - } |
971 | | - |
972 | | - |
973 | | -} |
Index: branches/new-upload/phase3/includes/upload/UploadFromStash.php |
— | — | @@ -0,0 +1,56 @@ |
| 2 | +<?php |
| 3 | +class UploadFromStash extends UploadBase { |
| 4 | + static function isValidSessionKey( $key, $sessionData ) { |
| 5 | + return !empty( $key ) && |
| 6 | + is_array( $sessionData ) && |
| 7 | + isset( $sessionData[$key] ) && |
| 8 | + isset( $sessionData[$key]['version'] ) && |
| 9 | + $sessionData[$key]['version'] == self::SESSION_VERSION |
| 10 | + ; |
| 11 | + } |
| 12 | + static function isValidRequest(& $request ) { |
| 13 | + $sessionData = $request->getSessionData('wsUploadData'); |
| 14 | + return self::isValidSessionKey( |
| 15 | + $request->getInt( 'wpSessionKey' ), |
| 16 | + $sessionData |
| 17 | + ); |
| 18 | + } |
| 19 | + function initialize( $name, $sessionData ) { |
| 20 | + /** |
| 21 | + * Confirming a temporarily stashed upload. |
| 22 | + * We don't want path names to be forged, so we keep |
| 23 | + * them in the session on the server and just give |
| 24 | + * an opaque key to the user agent. |
| 25 | + */ |
| 26 | + parent::initialize( $name, |
| 27 | + $sessionData['mTempPath'], |
| 28 | + $sessionData['mFileSize'], |
| 29 | + false |
| 30 | + ); |
| 31 | + |
| 32 | + $this->mFileProps = $sessionData['mFileProps']; |
| 33 | + } |
| 34 | + function initializeFromRequest( &$request ) { |
| 35 | + $sessionKey = $request->getInt( 'wpSessionKey' ); |
| 36 | + $sessionData = $request->getSessionData('wsUploadData'); |
| 37 | + |
| 38 | + $desiredDestName = $request->getText( 'wpDestFile' ); |
| 39 | + if( !$desiredDestName ) |
| 40 | + $desiredDestName = $request->getText( 'wpUploadFile' ); |
| 41 | + return $this->initialize( $desiredDestName, $sessionData[$sessionKey] ); |
| 42 | + } |
| 43 | + |
| 44 | + /** |
| 45 | + * File has been previously verified so no need to do so again. |
| 46 | + */ |
| 47 | + protected function verifyFile( $tmpfile ) { |
| 48 | + return true; |
| 49 | + } |
| 50 | + /** |
| 51 | + * We're here from "ignore warnings anyway" so return just OK |
| 52 | + */ |
| 53 | + function checkWarnings() { |
| 54 | + return array(); |
| 55 | + } |
| 56 | +} |
| 57 | +?> |
\ No newline at end of file |
Property changes on: branches/new-upload/phase3/includes/upload/UploadFromStash.php |
___________________________________________________________________ |
Name: svn:mergeinfo |
1 | 58 | + /branches/REL1_15/phase3/includes/UploadFromStash.php:51646 |
/trunk/phase3/includes/UploadFromStash.php:46619-47809 |
Name: svn:eol-style |
2 | 59 | + native |
Index: branches/new-upload/phase3/includes/upload/UploadFromUrl.php |
— | — | @@ -0,0 +1,80 @@ |
| 2 | +<?php |
| 3 | +class UploadFromUrl extends UploadBase { |
| 4 | + protected $mTempDownloadPath; |
| 5 | + |
| 6 | + //by default do a SYNC_DOWNLOAD |
| 7 | + protected $dl_mode = null; |
| 8 | + |
| 9 | + static function isAllowed( $user ) { |
| 10 | + if( !$user->isAllowed( 'upload_by_url' ) ) |
| 11 | + return 'upload_by_url'; |
| 12 | + return parent::isAllowed( $user ); |
| 13 | + } |
| 14 | + static function isEnabled() { |
| 15 | + global $wgAllowCopyUploads; |
| 16 | + return $wgAllowCopyUploads && parent::isEnabled(); |
| 17 | + } |
| 18 | + /*entry point for Api upload:: ASYNC_DOWNLOAD (if possible) */ |
| 19 | + function initialize( $name, $url, $asyncdownload = false) { |
| 20 | + global $wgTmpDirectory, $wgPhpCliPath; |
| 21 | + |
| 22 | + //check for $asyncdownload request: |
| 23 | + if($asyncdownload !== false){ |
| 24 | + if($wgPhpCliPath && wfShellExecEnabled() ){ |
| 25 | + $this->dl_mode = Http::ASYNC_DOWNLOAD; |
| 26 | + }else{ |
| 27 | + $this->dl_mode = Http::SYNC_DOWNLOAD; |
| 28 | + } |
| 29 | + } |
| 30 | + |
| 31 | + $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); |
| 32 | + parent::initialize( $name, $local_file, 0, true ); |
| 33 | + |
| 34 | + $this->mUrl = trim( $url ); |
| 35 | + } |
| 36 | + public function isAsync(){ |
| 37 | + return $this->dl_mode == Http::ASYNC_DOWNLOAD; |
| 38 | + } |
| 39 | + /*entry point for SpecialUpload no ASYNC_DOWNLOAD possible: */ |
| 40 | + function initializeFromRequest( &$request ) { |
| 41 | + |
| 42 | + //set dl mode if not set: |
| 43 | + if(!$this->dl_mode) |
| 44 | + $this->dl_mode = Http::SYNC_DOWNLOAD; |
| 45 | + |
| 46 | + $desiredDestName = $request->getText( 'wpDestFile' ); |
| 47 | + if( !$desiredDestName ) |
| 48 | + $desiredDestName = $request->getText( 'wpUploadFile' ); |
| 49 | + return $this->initialize( |
| 50 | + $desiredDestName, |
| 51 | + $request->getVal('wpUploadFileURL') |
| 52 | + ); |
| 53 | + } |
| 54 | + /** |
| 55 | + * Do the real fetching stuff |
| 56 | + */ |
| 57 | + function fetchFile( ) { |
| 58 | + //entry point for SpecialUplaod |
| 59 | + if( self::isValidURI($this->mUrl) === false) { |
| 60 | + return Status::newFatal('upload-proto-error'); |
| 61 | + } |
| 62 | + //now do the actual download to the target file: |
| 63 | + $status = Http::doDownload ( $this->mUrl, $this->mTempPath, $this->dl_mode ); |
| 64 | + |
| 65 | + //update the local filesize var: |
| 66 | + $this->mFileSize = filesize( $this->mTempPath ); |
| 67 | + |
| 68 | + return $status; |
| 69 | + } |
| 70 | + |
| 71 | + static function isValidRequest( $request ){ |
| 72 | + if( !$request->getVal('wpUploadFileURL') ) |
| 73 | + return false; |
| 74 | + //check that is a valid url: |
| 75 | + return self::isValidURI( $request->getVal('wpUploadFileURL') ); |
| 76 | + } |
| 77 | + static function isValidURI( $uri ){ |
| 78 | + return preg_match('/(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/', |
| 79 | + $uri, $matches); |
| 80 | + } |
| 81 | +} |
\ No newline at end of file |
Property changes on: branches/new-upload/phase3/includes/upload/UploadFromUrl.php |
___________________________________________________________________ |
Name: svn:mergeinfo |
1 | 82 | + /branches/REL1_15/phase3/includes/UploadFromUrl.php:51646 |
/trunk/phase3/includes/UploadFromUrl.php:46619-47809 |
Name: svn:eol-style |
2 | 83 | + native |
Index: branches/new-upload/phase3/includes/upload/UploadBase.php |
— | — | @@ -0,0 +1,973 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class UploadBase { |
| 5 | + var $mTempPath; |
| 6 | + var $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType; |
| 7 | + var $mTitle = false, $mTitleError = 0; |
| 8 | + var $mFilteredName, $mFinalExtension; |
| 9 | + |
| 10 | + const SUCCESS = 0; |
| 11 | + const OK = 0; |
| 12 | + const BEFORE_PROCESSING = 1; |
| 13 | + const LARGE_FILE_SERVER = 2; |
| 14 | + const EMPTY_FILE = 3; |
| 15 | + const MIN_LENGTH_PARTNAME = 4; |
| 16 | + const ILLEGAL_FILENAME = 5; |
| 17 | + const PROTECTED_PAGE = 6; |
| 18 | + const OVERWRITE_EXISTING_FILE = 7; |
| 19 | + const FILETYPE_MISSING = 8; |
| 20 | + const FILETYPE_BADTYPE = 9; |
| 21 | + const VERIFICATION_ERROR = 10; |
| 22 | + const UPLOAD_VERIFICATION_ERROR = 11; |
| 23 | + const UPLOAD_WARNING = 12; |
| 24 | + const INTERNAL_ERROR = 13; |
| 25 | + const MIN_LENGHT_PARTNAME = 14; |
| 26 | + |
| 27 | + const SESSION_VERSION = 2; |
| 28 | + |
| 29 | + /** |
| 30 | + * Returns true if uploads are enabled. |
| 31 | + * Can be override by subclasses. |
| 32 | + */ |
| 33 | + static function isEnabled() { |
| 34 | + global $wgEnableUploads; |
| 35 | + if ( !$wgEnableUploads ) |
| 36 | + return false; |
| 37 | + |
| 38 | + # Check php's file_uploads setting |
| 39 | + if( !wfIniGetBool( 'file_uploads' ) ) { |
| 40 | + return false; |
| 41 | + } |
| 42 | + return true; |
| 43 | + } |
| 44 | + /** |
| 45 | + * Returns true if the user can use this upload module or else a string |
| 46 | + * identifying the missing permission. |
| 47 | + * Can be overriden by subclasses. |
| 48 | + */ |
| 49 | + static function isAllowed( $user ) { |
| 50 | + if( !$user->isAllowed( 'upload' ) ) |
| 51 | + return 'upload'; |
| 52 | + return true; |
| 53 | + } |
| 54 | + |
| 55 | + // Upload handlers. Should probably just be a global |
| 56 | + static $uploadHandlers = array( 'Stash', 'File', 'Url' ); |
| 57 | + /** |
| 58 | + * Create a form of UploadBase depending on wpSourceType and initializes it |
| 59 | + */ |
| 60 | + static function createFromRequest( &$request, $type = null ) { |
| 61 | + $type = $type ? $type : $request->getVal( 'wpSourceType' ); |
| 62 | + |
| 63 | + if( !$type ) |
| 64 | + return null; |
| 65 | + |
| 66 | + $type = ucfirst($type); |
| 67 | + $className = 'UploadFrom'.$type; |
| 68 | + |
| 69 | + if( !in_array( $type, self::$uploadHandlers ) ) |
| 70 | + return null; |
| 71 | + |
| 72 | + if( !call_user_func( array( $className, 'isEnabled' ) ) ) |
| 73 | + return null; |
| 74 | + |
| 75 | + |
| 76 | + if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) |
| 77 | + return null; |
| 78 | + |
| 79 | + $handler = new $className; |
| 80 | + |
| 81 | + $handler->initializeFromRequest( $request ); |
| 82 | + return $handler; |
| 83 | + } |
| 84 | + /** |
| 85 | + * Check whether a request if valid for this handler |
| 86 | + */ |
| 87 | + static function isValidRequest( $request ) { |
| 88 | + return false; |
| 89 | + } |
| 90 | + |
| 91 | + function __construct() {} |
| 92 | + |
| 93 | + /** |
| 94 | + * Do the real variable initialization |
| 95 | + */ |
| 96 | + function initialize( $name, $tempPath, $fileSize, $removeTempFile = false ) { |
| 97 | + $this->mDesiredDestName = $name; |
| 98 | + $this->mTempPath = $tempPath; |
| 99 | + $this->mFileSize = $fileSize; |
| 100 | + $this->mRemoveTempFile = $removeTempFile; |
| 101 | + } |
| 102 | + |
| 103 | + /** |
| 104 | + * Fetch the file. Usually a no-op |
| 105 | + */ |
| 106 | + function fetchFile() { |
| 107 | + return Status::newGood(); |
| 108 | + } |
| 109 | + //return the file size |
| 110 | + function isEmptyFile(){ |
| 111 | + return empty( $this->mFileSize); |
| 112 | + } |
| 113 | + /** |
| 114 | + * Verify whether the upload is sane. |
| 115 | + * Returns self::OK or else an array with error information |
| 116 | + */ |
| 117 | + function verifyUpload() { |
| 118 | + /** |
| 119 | + * If there was no filename or a zero size given, give up quick. |
| 120 | + */ |
| 121 | + |
| 122 | + if( $this->isEmptyFile() ) |
| 123 | + return array( 'status' => self::EMPTY_FILE ); |
| 124 | + |
| 125 | + $nt = $this->getTitle(); |
| 126 | + if( is_null( $nt ) ) { |
| 127 | + $result = array( 'status' => $this->mTitleError ); |
| 128 | + if( $this->mTitleError == self::ILLEGAL_FILENAME ) |
| 129 | + $result['filtered'] = $this->mFilteredName; |
| 130 | + if ( $this->mTitleError == self::FILETYPE_BADTYPE ) |
| 131 | + $result['finalExt'] = $this->mFinalExtension; |
| 132 | + return $result; |
| 133 | + } |
| 134 | + $this->mLocalFile = wfLocalFile( $nt ); |
| 135 | + $this->mDestName = $this->mLocalFile->getName(); |
| 136 | + |
| 137 | + /** |
| 138 | + * In some cases we may forbid overwriting of existing files. |
| 139 | + */ |
| 140 | + $overwrite = $this->checkOverwrite(); |
| 141 | + if( $overwrite !== true ) |
| 142 | + return array( 'status' => self::OVERWRITE_EXISTING_FILE, 'overwrite' => $overwrite ); |
| 143 | + |
| 144 | + /** |
| 145 | + * Look at the contents of the file; if we can recognize the |
| 146 | + * type but it's corrupt or data of the wrong type, we should |
| 147 | + * probably not accept it. |
| 148 | + */ |
| 149 | + $verification = $this->verifyFile( $this->mTempPath ); |
| 150 | + |
| 151 | + if( $verification !== true ) { |
| 152 | + if( !is_array( $verification ) ) |
| 153 | + $verification = array( $verification ); |
| 154 | + $verification['status'] = self::VERIFICATION_ERROR; |
| 155 | + return $verification; |
| 156 | + } |
| 157 | + |
| 158 | + $error = ''; |
| 159 | + if( !wfRunHooks( 'UploadVerification', |
| 160 | + array( $this->mDestName, $this->mTempPath, &$error ) ) ) { |
| 161 | + return array( 'status' => self::UPLOAD_VERIFICATION_ERROR, 'error' => $error ); |
| 162 | + } |
| 163 | + |
| 164 | + return self::OK; |
| 165 | + } |
| 166 | + |
| 167 | + /** |
| 168 | + * Verifies that it's ok to include the uploaded file |
| 169 | + * |
| 170 | + * this function seems to intermixes tmpfile and $this->mTempPath .. no idea why this is |
| 171 | + * |
| 172 | + * @param string $tmpfile the full path of the temporary file to verify |
| 173 | + * @return mixed true of the file is verified, a string or array otherwise. |
| 174 | + */ |
| 175 | + protected function verifyFile( $tmpfile ) { |
| 176 | + $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $this->mFinalExtension ); |
| 177 | + $this->checkMacBinary( ); |
| 178 | + |
| 179 | + #magically determine mime type |
| 180 | + $magic = MimeMagic::singleton(); |
| 181 | + $mime = $magic->guessMimeType( $tmpfile, false ); |
| 182 | + |
| 183 | + #check mime type, if desired |
| 184 | + global $wgVerifyMimeType; |
| 185 | + if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) { |
| 186 | + if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) |
| 187 | + return array( 'filetype-badmime', $mime ); |
| 188 | + |
| 189 | + # Check IE type |
| 190 | + $fp = fopen( $tmpfile, 'rb' ); |
| 191 | + $chunk = fread( $fp, 256 ); |
| 192 | + fclose( $fp ); |
| 193 | + $extMime = $magic->guessTypesForExtension( $this->mFinalExtension ); |
| 194 | + $ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime ); |
| 195 | + foreach ( $ieTypes as $ieType ) { |
| 196 | + if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) { |
| 197 | + return array( 'filetype-bad-ie-mime', $ieType ); |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + |
| 202 | + |
| 203 | + #check for htmlish code and javascript |
| 204 | + if( $this->detectScript ( $tmpfile, $mime, $this->mFinalExtension ) ) { |
| 205 | + return 'uploadscripted'; |
| 206 | + } |
| 207 | + if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { |
| 208 | + if( $this->detectScriptInSvg( $tmpfile ) ) { |
| 209 | + return 'uploadscripted'; |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + /** |
| 214 | + * Scan the uploaded file for viruses |
| 215 | + */ |
| 216 | + $virus = $this->detectVirus($tmpfile); |
| 217 | + if ( $virus ) { |
| 218 | + return array( 'uploadvirus', $virus ); |
| 219 | + } |
| 220 | + wfDebug( __METHOD__.": all clear; passing.\n" ); |
| 221 | + return true; |
| 222 | + } |
| 223 | + |
| 224 | + /** |
| 225 | + * Check whether the user can edit, upload and create the image |
| 226 | + */ |
| 227 | + function verifyPermissions( $user ) { |
| 228 | + /** |
| 229 | + * If the image is protected, non-sysop users won't be able |
| 230 | + * to modify it by uploading a new revision. |
| 231 | + */ |
| 232 | + $nt = $this->getTitle(); |
| 233 | + if( is_null( $nt ) ) |
| 234 | + return true; |
| 235 | + $permErrors = $nt->getUserPermissionsErrors( 'edit', $user ); |
| 236 | + $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user ); |
| 237 | + $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $user ) ); |
| 238 | + if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { |
| 239 | + $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); |
| 240 | + $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) ); |
| 241 | + return $permErrors; |
| 242 | + } |
| 243 | + return true; |
| 244 | + } |
| 245 | + |
| 246 | + /** |
| 247 | + * Check for non fatal problems with the file |
| 248 | + */ |
| 249 | + function checkWarnings() { |
| 250 | + $warning = array(); |
| 251 | + |
| 252 | + $filename = $this->mLocalFile->getName(); |
| 253 | + $n = strrpos( $filename, '.' ); |
| 254 | + $partname = $n ? substr( $filename, 0, $n ) : $filename; |
| 255 | + |
| 256 | + /* |
| 257 | + * Check whether the resulting filename is different from the desired one, |
| 258 | + * but ignore things like ucfirst() and spaces/underscore things |
| 259 | + **/ |
| 260 | + $comparableName = str_replace( ' ', '_', $this->mDesiredDestName ); |
| 261 | + global $wgCapitalLinks, $wgContLang; |
| 262 | + if ( $wgCapitalLinks ) { |
| 263 | + $comparableName = $wgContLang->ucfirst( $comparableName ); |
| 264 | + } |
| 265 | + if( $this->mDesiredDestName != $filename && $comparableName != $filename ) |
| 266 | + $warning['badfilename'] = $filename; |
| 267 | + |
| 268 | + // Check whether the file extension is on the unwanted list |
| 269 | + global $wgCheckFileExtensions, $wgFileExtensions; |
| 270 | + if ( $wgCheckFileExtensions ) { |
| 271 | + if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) |
| 272 | + $warning['filetype-unwanted-type'] = $this->mFinalExtension; |
| 273 | + } |
| 274 | + |
| 275 | + global $wgUploadSizeWarning; |
| 276 | + if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) |
| 277 | + $warning['large-file'] = $wgUploadSizeWarning; |
| 278 | + |
| 279 | + if ( $this->mFileSize == 0 ) |
| 280 | + $warning['emptyfile'] = true; |
| 281 | + |
| 282 | + |
| 283 | + $exists = self::getExistsWarning( $this->mLocalFile ); |
| 284 | + if( $exists !== false ) |
| 285 | + $warning['exists'] = $exists; |
| 286 | + |
| 287 | + // Check whether this may be a thumbnail |
| 288 | + if( $exists !== false && $exists[0] != 'thumb' |
| 289 | + && self::isThumbName( $this->mLocalFile->getName() ) ){ |
| 290 | + //make the title: |
| 291 | + $nt = $this->getTitle(); |
| 292 | + $warning['file-thumbnail-no'] = substr( $filename , 0, |
| 293 | + strpos( $nt->getText() , '-' ) +1 ); |
| 294 | + } |
| 295 | + |
| 296 | + // Check dupes against existing files |
| 297 | + $hash = File::sha1Base36( $this->mTempPath ); |
| 298 | + $dupes = RepoGroup::singleton()->findBySha1( $hash ); |
| 299 | + $title = $this->getTitle(); |
| 300 | + // Remove all matches against self |
| 301 | + foreach ( $dupes as $key => $dupe ) { |
| 302 | + if( $title->equals( $dupe->getTitle() ) ) |
| 303 | + unset( $dupes[$key] ); |
| 304 | + } |
| 305 | + if( $dupes ) |
| 306 | + $warning['duplicate'] = $dupes; |
| 307 | + |
| 308 | + // Check dupes against archives |
| 309 | + $archivedImage = new ArchivedFile( null, 0, "{$hash}.{$this->mFinalExtension}" ); |
| 310 | + if ( $archivedImage->getID() > 0 ) |
| 311 | + $warning['duplicate-archive'] = $archivedImage->getName(); |
| 312 | + |
| 313 | + $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist(); |
| 314 | + foreach( $filenamePrefixBlacklist as $prefix ) { |
| 315 | + if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { |
| 316 | + $warning['filename-bad-prefix'] = $prefix; |
| 317 | + break; |
| 318 | + } |
| 319 | + } |
| 320 | + |
| 321 | + # If the file existed before and was deleted, warn the user of this |
| 322 | + # Don't bother doing so if the file exists now, however |
| 323 | + if( $this->mLocalFile->wasDeleted() && !$this->mLocalFile->exists() ) |
| 324 | + $warning['filewasdeleted'] = $this->mLocalFile->getTitle(); |
| 325 | + |
| 326 | + return $warning; |
| 327 | + } |
| 328 | + |
| 329 | + /** |
| 330 | + * Really perform the upload. |
| 331 | + */ |
| 332 | + function performUpload( $comment, $pageText, $watch, $user ) { |
| 333 | + wfDebug("\n\n\performUpload: sum:" . $comment . ' c: ' . $pageText . ' w:' .$watch); |
| 334 | + $status = $this->mLocalFile->upload( $this->mTempPath, $comment, $pageText, |
| 335 | + File::DELETE_SOURCE, $this->mFileProps, false, $user ); |
| 336 | + |
| 337 | + if( $status->isGood() && $watch ) |
| 338 | + $user->addWatch( $this->mLocalFile->getTitle() ); |
| 339 | + |
| 340 | + if( $status->isGood() ) |
| 341 | + wfRunHooks( 'UploadComplete', array( &$this ) ); |
| 342 | + |
| 343 | + return $status; |
| 344 | + } |
| 345 | + |
| 346 | + /** |
| 347 | + * Returns a title or null |
| 348 | + */ |
| 349 | + function getTitle() { |
| 350 | + if ( $this->mTitle !== false ) |
| 351 | + return $this->mTitle; |
| 352 | + |
| 353 | + /** |
| 354 | + * Chop off any directories in the given filename. Then |
| 355 | + * filter out illegal characters, and try to make a legible name |
| 356 | + * out of it. We'll strip some silently that Title would die on. |
| 357 | + */ |
| 358 | + |
| 359 | + $basename = $this->mDesiredDestName; |
| 360 | + |
| 361 | + $this->mFilteredName = wfStripIllegalFilenameChars( $basename ); |
| 362 | + /* Normalize to title form before we do any further processing */ |
| 363 | + $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName ); |
| 364 | + if( is_null( $nt ) ) { |
| 365 | + $this->mTitleError = self::ILLEGAL_FILENAME; |
| 366 | + return $this->mTitle = null; |
| 367 | + } |
| 368 | + $this->mFilteredName = $nt->getDBkey(); |
| 369 | + |
| 370 | + /** |
| 371 | + * We'll want to blacklist against *any* 'extension', and use |
| 372 | + * only the final one for the whitelist. |
| 373 | + */ |
| 374 | + list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName ); |
| 375 | + |
| 376 | + if( count( $ext ) ) { |
| 377 | + $this->mFinalExtension = trim( $ext[count( $ext ) - 1] ); |
| 378 | + } else { |
| 379 | + $this->mFinalExtension = ''; |
| 380 | + } |
| 381 | + |
| 382 | + /* Don't allow users to override the blacklist (check file extension) */ |
| 383 | + global $wgCheckFileExtensions, $wgStrictFileExtensions; |
| 384 | + global $wgFileExtensions, $wgFileBlacklist; |
| 385 | + if ( $this->mFinalExtension == '' ) { |
| 386 | + $this->mTitleError = self::FILETYPE_MISSING; |
| 387 | + return $this->mTitle = null; |
| 388 | + } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || |
| 389 | + ( $wgCheckFileExtensions && $wgStrictFileExtensions && |
| 390 | + !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) { |
| 391 | + $this->mTitleError = self::FILETYPE_BADTYPE; |
| 392 | + return $this->mTitle = null; |
| 393 | + } |
| 394 | + |
| 395 | + # If there was more than one "extension", reassemble the base |
| 396 | + # filename to prevent bogus complaints about length |
| 397 | + if( count( $ext ) > 1 ) { |
| 398 | + for( $i = 0; $i < count( $ext ) - 1; $i++ ) |
| 399 | + $partname .= '.' . $ext[$i]; |
| 400 | + } |
| 401 | + |
| 402 | + if( strlen( $partname ) < 1 ) { |
| 403 | + $this->mTitleError = self::MIN_LENGTH_PARTNAME; |
| 404 | + return $this->mTitle = null; |
| 405 | + } |
| 406 | + |
| 407 | + $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName ); |
| 408 | + if( is_null( $nt ) ) { |
| 409 | + $this->mTitleError = self::ILLEGAL_FILENAME; |
| 410 | + return $this->mTitle = null; |
| 411 | + } |
| 412 | + return $this->mTitle = $nt; |
| 413 | + } |
| 414 | + |
| 415 | + function getLocalFile() { |
| 416 | + if( is_null( $this->mLocalFile ) ) { |
| 417 | + $nt = $this->getTitle(); |
| 418 | + $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt ); |
| 419 | + } |
| 420 | + return $this->mLocalFile; |
| 421 | + } |
| 422 | + |
| 423 | + /** |
| 424 | + * Stash a file in a temporary directory for later processing |
| 425 | + * after the user has confirmed it. |
| 426 | + * |
| 427 | + * If the user doesn't explicitly cancel or accept, these files |
| 428 | + * can accumulate in the temp directory. |
| 429 | + * |
| 430 | + * @param string $saveName - the destination filename |
| 431 | + * @param string $tempName - the source temporary file to save |
| 432 | + * @return string - full path the stashed file, or false on failure |
| 433 | + * @access private |
| 434 | + */ |
| 435 | + function saveTempUploadedFile( $saveName, $tempName ) { |
| 436 | + $repo = RepoGroup::singleton()->getLocalRepo(); |
| 437 | + $status = $repo->storeTemp( $saveName, $tempName ); |
| 438 | + return $status; |
| 439 | + } |
| 440 | + /* append to a stashed file */ |
| 441 | + function appendToUploadFile($srcPath, $toAppendPath ){ |
| 442 | + $repo = RepoGroup::singleton()->getLocalRepo(); |
| 443 | + $status = $repo->append($srcPath, $toAppendPath); |
| 444 | + return $status; |
| 445 | + } |
| 446 | + |
| 447 | + /** |
| 448 | + * Stash a file in a temporary directory for later processing, |
| 449 | + * and save the necessary descriptive info into the session. |
| 450 | + * Returns a key value which will be passed through a form |
| 451 | + * to pick up the path info on a later invocation. |
| 452 | + * |
| 453 | + * @return int |
| 454 | + * @access private |
| 455 | + */ |
| 456 | + function stashSession() { |
| 457 | + $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
| 458 | + if( !$status->isOK() ) { |
| 459 | + # Couldn't save the file. |
| 460 | + return false; |
| 461 | + } |
| 462 | + $mTempPath = $status->value; |
| 463 | + session_start();//start up the session (might have been previously closed to prevent php session locking) |
| 464 | + $key = $this->getSessionKey (); |
| 465 | + $_SESSION['wsUploadData'][$key] = array( |
| 466 | + 'mTempPath' => $mTempPath, |
| 467 | + 'mFileSize' => $this->mFileSize, |
| 468 | + 'mSrcName' => $this->mSrcName, |
| 469 | + 'mFileProps' => $this->mFileProps, |
| 470 | + 'version' => self::SESSION_VERSION, |
| 471 | + ); |
| 472 | + session_write_close(); |
| 473 | + return $key; |
| 474 | + } |
| 475 | + //pull session Key gen from stash in cases where we want to start an upload without much information |
| 476 | + function getSessionKey(){ |
| 477 | + $key = mt_rand( 0, 0x7fffffff ); |
| 478 | + $_SESSION['wsUploadData'][$key] = array(); |
| 479 | + return $key; |
| 480 | + } |
| 481 | + |
| 482 | + /** |
| 483 | + * Remove a temporarily kept file stashed by saveTempUploadedFile(). |
| 484 | + * @return success |
| 485 | + */ |
| 486 | + function unsaveUploadedFile() { |
| 487 | + $repo = RepoGroup::singleton()->getLocalRepo(); |
| 488 | + $success = $repo->freeTemp( $this->mTempPath ); |
| 489 | + return $success; |
| 490 | + } |
| 491 | + |
| 492 | + /** |
| 493 | + * If we've modified the upload file we need to manually remove it |
| 494 | + * on exit to clean up. |
| 495 | + * @access private |
| 496 | + */ |
| 497 | + function cleanupTempFile() { |
| 498 | + if ( $this->mRemoveTempFile && $this->mTempPath && file_exists( $this->mTempPath ) ) { |
| 499 | + wfDebug( __METHOD__.": Removing temporary file {$this->mTempPath}\n" ); |
| 500 | + unlink( $this->mTempPath ); |
| 501 | + } |
| 502 | + } |
| 503 | + |
| 504 | + function getTempPath() { |
| 505 | + return $this->mTempPath; |
| 506 | + } |
| 507 | + |
| 508 | + |
| 509 | + /** |
| 510 | + * Split a file into a base name and all dot-delimited 'extensions' |
| 511 | + * on the end. Some web server configurations will fall back to |
| 512 | + * earlier pseudo-'extensions' to determine type and execute |
| 513 | + * scripts, so the blacklist needs to check them all. |
| 514 | + * |
| 515 | + * @return array |
| 516 | + */ |
| 517 | + public static function splitExtensions( $filename ) { |
| 518 | + $bits = explode( '.', $filename ); |
| 519 | + $basename = array_shift( $bits ); |
| 520 | + return array( $basename, $bits ); |
| 521 | + } |
| 522 | + |
| 523 | + /** |
| 524 | + * Perform case-insensitive match against a list of file extensions. |
| 525 | + * Returns true if the extension is in the list. |
| 526 | + * |
| 527 | + * @param string $ext |
| 528 | + * @param array $list |
| 529 | + * @return bool |
| 530 | + */ |
| 531 | + public static function checkFileExtension( $ext, $list ) { |
| 532 | + return in_array( strtolower( $ext ), $list ); |
| 533 | + } |
| 534 | + |
| 535 | + /** |
| 536 | + * Perform case-insensitive match against a list of file extensions. |
| 537 | + * Returns true if any of the extensions are in the list. |
| 538 | + * |
| 539 | + * @param array $ext |
| 540 | + * @param array $list |
| 541 | + * @return bool |
| 542 | + */ |
| 543 | + public static function checkFileExtensionList( $ext, $list ) { |
| 544 | + foreach( $ext as $e ) { |
| 545 | + if( in_array( strtolower( $e ), $list ) ) { |
| 546 | + return true; |
| 547 | + } |
| 548 | + } |
| 549 | + return false; |
| 550 | + } |
| 551 | + |
| 552 | + |
| 553 | + /** |
| 554 | + * Checks if the mime type of the uploaded file matches the file extension. |
| 555 | + * |
| 556 | + * @param string $mime the mime type of the uploaded file |
| 557 | + * @param string $extension The filename extension that the file is to be served with |
| 558 | + * @return bool |
| 559 | + */ |
| 560 | + public static function verifyExtension( $mime, $extension ) { |
| 561 | + $magic = MimeMagic::singleton(); |
| 562 | + |
| 563 | + if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) |
| 564 | + if ( ! $magic->isRecognizableExtension( $extension ) ) { |
| 565 | + wfDebug( __METHOD__.": passing file with unknown detected mime type; " . |
| 566 | + "unrecognized extension '$extension', can't verify\n" ); |
| 567 | + return true; |
| 568 | + } else { |
| 569 | + wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ". |
| 570 | + "recognized extension '$extension', so probably invalid file\n" ); |
| 571 | + return false; |
| 572 | + } |
| 573 | + |
| 574 | + $match= $magic->isMatchingExtension($extension,$mime); |
| 575 | + |
| 576 | + if ($match===NULL) { |
| 577 | + wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" ); |
| 578 | + return true; |
| 579 | + } elseif ($match===true) { |
| 580 | + wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" ); |
| 581 | + |
| 582 | + #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! |
| 583 | + return true; |
| 584 | + |
| 585 | + } else { |
| 586 | + wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" ); |
| 587 | + return false; |
| 588 | + } |
| 589 | + } |
| 590 | + |
| 591 | + /** |
| 592 | + * Heuristic for detecting files that *could* contain JavaScript instructions or |
| 593 | + * things that may look like HTML to a browser and are thus |
| 594 | + * potentially harmful. The present implementation will produce false positives in some situations. |
| 595 | + * |
| 596 | + * @param string $file Pathname to the temporary upload file |
| 597 | + * @param string $mime The mime type of the file |
| 598 | + * @param string $extension The extension of the file |
| 599 | + * @return bool true if the file contains something looking like embedded scripts |
| 600 | + */ |
| 601 | + function detectScript($file, $mime, $extension) { |
| 602 | + global $wgAllowTitlesInSVG; |
| 603 | + |
| 604 | + #ugly hack: for text files, always look at the entire file. |
| 605 | + #For binary field, just check the first K. |
| 606 | + |
| 607 | + if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file ); |
| 608 | + else { |
| 609 | + $fp = fopen( $file, 'rb' ); |
| 610 | + $chunk = fread( $fp, 1024 ); |
| 611 | + fclose( $fp ); |
| 612 | + } |
| 613 | + |
| 614 | + $chunk= strtolower( $chunk ); |
| 615 | + |
| 616 | + if (!$chunk) return false; |
| 617 | + |
| 618 | + #decode from UTF-16 if needed (could be used for obfuscation). |
| 619 | + if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE"; |
| 620 | + elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE"; |
| 621 | + else $enc= NULL; |
| 622 | + |
| 623 | + if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk); |
| 624 | + |
| 625 | + $chunk= trim($chunk); |
| 626 | + |
| 627 | + #FIXME: convert from UTF-16 if necessarry! |
| 628 | + |
| 629 | + wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n"); |
| 630 | + |
| 631 | + #check for HTML doctype |
| 632 | + if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true; |
| 633 | + |
| 634 | + /** |
| 635 | + * Internet Explorer for Windows performs some really stupid file type |
| 636 | + * autodetection which can cause it to interpret valid image files as HTML |
| 637 | + * and potentially execute JavaScript, creating a cross-site scripting |
| 638 | + * attack vectors. |
| 639 | + * |
| 640 | + * Apple's Safari browser also performs some unsafe file type autodetection |
| 641 | + * which can cause legitimate files to be interpreted as HTML if the |
| 642 | + * web server is not correctly configured to send the right content-type |
| 643 | + * (or if you're really uploading plain text and octet streams!) |
| 644 | + * |
| 645 | + * Returns true if IE is likely to mistake the given file for HTML. |
| 646 | + * Also returns true if Safari would mistake the given file for HTML |
| 647 | + * when served with a generic content-type. |
| 648 | + */ |
| 649 | + |
| 650 | + $tags = array( |
| 651 | + '<a', |
| 652 | + '<body', |
| 653 | + '<head', |
| 654 | + '<html', #also in safari |
| 655 | + '<img', |
| 656 | + '<pre', |
| 657 | + '<script', #also in safari |
| 658 | + '<table' |
| 659 | + ); |
| 660 | + if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) { |
| 661 | + $tags[] = '<title'; |
| 662 | + } |
| 663 | + |
| 664 | + foreach( $tags as $tag ) { |
| 665 | + if( false !== strpos( $chunk, $tag ) ) { |
| 666 | + return true; |
| 667 | + } |
| 668 | + } |
| 669 | + |
| 670 | + /* |
| 671 | + * look for javascript |
| 672 | + */ |
| 673 | + |
| 674 | + #resolve entity-refs to look at attributes. may be harsh on big files... cache result? |
| 675 | + $chunk = Sanitizer::decodeCharReferences( $chunk ); |
| 676 | + |
| 677 | + #look for script-types |
| 678 | + if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true; |
| 679 | + |
| 680 | + #look for html-style script-urls |
| 681 | + if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; |
| 682 | + |
| 683 | + #look for css-style script-urls |
| 684 | + if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; |
| 685 | + |
| 686 | + wfDebug("SpecialUpload::detectScript: no scripts found\n"); |
| 687 | + return false; |
| 688 | + } |
| 689 | + |
| 690 | + function detectScriptInSvg( $filename ) { |
| 691 | + $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) ); |
| 692 | + return $check->filterMatch; |
| 693 | + } |
| 694 | + |
| 695 | + /** |
| 696 | + * @todo Replace this with a whitelist filter! |
| 697 | + */ |
| 698 | + function checkSvgScriptCallback( $element, $attribs ) { |
| 699 | + $stripped = $this->stripXmlNamespace( $element ); |
| 700 | + |
| 701 | + if( $stripped == 'script' ) { |
| 702 | + wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" ); |
| 703 | + return true; |
| 704 | + } |
| 705 | + |
| 706 | + foreach( $attribs as $attrib => $value ) { |
| 707 | + $stripped = $this->stripXmlNamespace( $attrib ); |
| 708 | + if( substr( $stripped, 0, 2 ) == 'on' ) { |
| 709 | + wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" ); |
| 710 | + return true; |
| 711 | + } |
| 712 | + if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) { |
| 713 | + wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" ); |
| 714 | + return true; |
| 715 | + } |
| 716 | + } |
| 717 | + } |
| 718 | + |
| 719 | + private function stripXmlNamespace( $name ) { |
| 720 | + // 'http://www.w3.org/2000/svg:script' -> 'script' |
| 721 | + $parts = explode( ':', strtolower( $name ) ); |
| 722 | + return array_pop( $parts ); |
| 723 | + } |
| 724 | + |
| 725 | + |
| 726 | + |
| 727 | + /** |
| 728 | + * Generic wrapper function for a virus scanner program. |
| 729 | + * This relies on the $wgAntivirus and $wgAntivirusSetup variables. |
| 730 | + * $wgAntivirusRequired may be used to deny upload if the scan fails. |
| 731 | + * |
| 732 | + * @param string $file Pathname to the temporary upload file |
| 733 | + * @return mixed false if not virus is found, NULL if the scan fails or is disabled, |
| 734 | + * or a string containing feedback from the virus scanner if a virus was found. |
| 735 | + * If textual feedback is missing but a virus was found, this function returns true. |
| 736 | + */ |
| 737 | + function detectVirus($file) { |
| 738 | + global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut; |
| 739 | + |
| 740 | + if ( !$wgAntivirus ) { |
| 741 | + wfDebug( __METHOD__.": virus scanner disabled\n"); |
| 742 | + return NULL; |
| 743 | + } |
| 744 | + |
| 745 | + if ( !$wgAntivirusSetup[$wgAntivirus] ) { |
| 746 | + wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" ); |
| 747 | + $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) ); |
| 748 | + return wfMsg('virus-unknownscanner') . " $wgAntivirus"; |
| 749 | + } |
| 750 | + |
| 751 | + # look up scanner configuration |
| 752 | + $command = $wgAntivirusSetup[$wgAntivirus]["command"]; |
| 753 | + $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"]; |
| 754 | + $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ? |
| 755 | + $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null; |
| 756 | + |
| 757 | + if ( strpos( $command,"%f" ) === false ) { |
| 758 | + # simple pattern: append file to scan |
| 759 | + $command .= " " . wfEscapeShellArg( $file ); |
| 760 | + } else { |
| 761 | + # complex pattern: replace "%f" with file to scan |
| 762 | + $command = str_replace( "%f", wfEscapeShellArg( $file ), $command ); |
| 763 | + } |
| 764 | + |
| 765 | + wfDebug( __METHOD__.": running virus scan: $command \n" ); |
| 766 | + |
| 767 | + # execute virus scanner |
| 768 | + $exitCode = false; |
| 769 | + |
| 770 | + #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. |
| 771 | + # that does not seem to be worth the pain. |
| 772 | + # Ask me (Duesentrieb) about it if it's ever needed. |
| 773 | + $output = array(); |
| 774 | + if ( wfIsWindows() ) { |
| 775 | + exec( "$command", $output, $exitCode ); |
| 776 | + } else { |
| 777 | + exec( "$command 2>&1", $output, $exitCode ); |
| 778 | + } |
| 779 | + |
| 780 | + # map exit code to AV_xxx constants. |
| 781 | + $mappedCode = $exitCode; |
| 782 | + if ( $exitCodeMap ) { |
| 783 | + if ( isset( $exitCodeMap[$exitCode] ) ) { |
| 784 | + $mappedCode = $exitCodeMap[$exitCode]; |
| 785 | + } elseif ( isset( $exitCodeMap["*"] ) ) { |
| 786 | + $mappedCode = $exitCodeMap["*"]; |
| 787 | + } |
| 788 | + } |
| 789 | + |
| 790 | + if ( $mappedCode === AV_SCAN_FAILED ) { |
| 791 | + # scan failed (code was mapped to false by $exitCodeMap) |
| 792 | + wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" ); |
| 793 | + |
| 794 | + if ( $wgAntivirusRequired ) { |
| 795 | + return wfMsg('virus-scanfailed', array( $exitCode ) ); |
| 796 | + } else { |
| 797 | + return NULL; |
| 798 | + } |
| 799 | + } else if ( $mappedCode === AV_SCAN_ABORTED ) { |
| 800 | + # scan failed because filetype is unknown (probably imune) |
| 801 | + wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" ); |
| 802 | + return NULL; |
| 803 | + } else if ( $mappedCode === AV_NO_VIRUS ) { |
| 804 | + # no virus found |
| 805 | + wfDebug( __METHOD__.": file passed virus scan.\n" ); |
| 806 | + return false; |
| 807 | + } else { |
| 808 | + $output = join( "\n", $output ); |
| 809 | + $output = trim( $output ); |
| 810 | + |
| 811 | + if ( !$output ) { |
| 812 | + $output = true; #if there's no output, return true |
| 813 | + } elseif ( $msgPattern ) { |
| 814 | + $groups = array(); |
| 815 | + if ( preg_match( $msgPattern, $output, $groups ) ) { |
| 816 | + if ( $groups[1] ) { |
| 817 | + $output = $groups[1]; |
| 818 | + } |
| 819 | + } |
| 820 | + } |
| 821 | + |
| 822 | + wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output \n" ); |
| 823 | + return $output; |
| 824 | + } |
| 825 | + } |
| 826 | + |
| 827 | + /** |
| 828 | + * Check if the temporary file is MacBinary-encoded, as some uploads |
| 829 | + * from Internet Explorer on Mac OS Classic and Mac OS X will be. |
| 830 | + * If so, the data fork will be extracted to a second temporary file, |
| 831 | + * which will then be checked for validity and either kept or discarded. |
| 832 | + * |
| 833 | + * @access private |
| 834 | + */ |
| 835 | + function checkMacBinary() { |
| 836 | + $macbin = new MacBinary( $this->mTempPath ); |
| 837 | + if( $macbin->isValid() ) { |
| 838 | + $dataFile = tempnam( wfTempDir(), "WikiMacBinary" ); |
| 839 | + $dataHandle = fopen( $dataFile, 'wb' ); |
| 840 | + |
| 841 | + wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" ); |
| 842 | + $macbin->extractData( $dataHandle ); |
| 843 | + |
| 844 | + $this->mTempPath = $dataFile; |
| 845 | + $this->mFileSize = $macbin->dataForkLength(); |
| 846 | + |
| 847 | + // We'll have to manually remove the new file if it's not kept. |
| 848 | + $this->mRemoveTempFile = true; |
| 849 | + } |
| 850 | + $macbin->close(); |
| 851 | + } |
| 852 | + |
| 853 | + /** |
| 854 | + * Check if there's an overwrite conflict and, if so, if restrictions |
| 855 | + * forbid this user from performing the upload. |
| 856 | + * |
| 857 | + * @return mixed true on success, WikiError on failure |
| 858 | + * @access private |
| 859 | + */ |
| 860 | + function checkOverwrite() { |
| 861 | + global $wgUser; |
| 862 | + // First check whether the local file can be overwritten |
| 863 | + if( $this->mLocalFile->exists() ) |
| 864 | + if( !self::userCanReUpload( $wgUser, $this->mLocalFile ) ) |
| 865 | + return 'fileexists-forbidden'; |
| 866 | + |
| 867 | + // Check shared conflicts |
| 868 | + $file = wfFindFile( $this->mLocalFile->getName() ); |
| 869 | + if ( $file && ( !$wgUser->isAllowed( 'reupload' ) || |
| 870 | + !$wgUser->isAllowed( 'reupload-shared' ) ) ) |
| 871 | + return 'fileexists-shared-forbidden'; |
| 872 | + |
| 873 | + return true; |
| 874 | + |
| 875 | + } |
| 876 | + /** |
| 877 | + * Check if a user is the last uploader |
| 878 | + * |
| 879 | + * @param User $user |
| 880 | + * @param string $img, image name |
| 881 | + * @return bool |
| 882 | + */ |
| 883 | + public static function userCanReUpload( User $user, $img ) { |
| 884 | + if( $user->isAllowed( 'reupload' ) ) |
| 885 | + return true; // non-conditional |
| 886 | + if( !$user->isAllowed( 'reupload-own' ) ) |
| 887 | + return false; |
| 888 | + if( is_string( $img ) ) |
| 889 | + $img = wfLocalFile( $img ); |
| 890 | + if ( !( $img instanceof LocalFile ) ) |
| 891 | + return false; |
| 892 | + |
| 893 | + return $user->getId() == $img->getUser( 'id' ); |
| 894 | + } |
| 895 | + |
| 896 | + public static function getExistsWarning( $file ) { |
| 897 | + if( $file->exists() ) |
| 898 | + return array( 'exists', $file ); |
| 899 | + |
| 900 | + if( $file->getTitle()->getArticleID() ) |
| 901 | + return array( 'page-exists', $file ); |
| 902 | + |
| 903 | + if( strpos( $file->getName(), '.' ) == false ) { |
| 904 | + $partname = $file->getName(); |
| 905 | + $rawExtension = ''; |
| 906 | + } else { |
| 907 | + $n = strrpos( $file->getName(), '.' ); |
| 908 | + $rawExtension = substr( $file->getName(), $n + 1 ); |
| 909 | + $partname = substr( $file->getName(), 0, $n ); |
| 910 | + } |
| 911 | + |
| 912 | + if ( $rawExtension != $file->getExtension() ) { |
| 913 | + // We're not using the normalized form of the extension. |
| 914 | + // Normal form is lowercase, using most common of alternate |
| 915 | + // extensions (eg 'jpg' rather than 'JPEG'). |
| 916 | + // |
| 917 | + // Check for another file using the normalized form... |
| 918 | + $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() ); |
| 919 | + $file_lc = wfLocalFile( $nt_lc ); |
| 920 | + |
| 921 | + if( $file_lc->exists() ) |
| 922 | + return array( 'exists-normalized', $file_lc ); |
| 923 | + } |
| 924 | + |
| 925 | + if ( self::isThumbName( $file->getName() ) ) { |
| 926 | + # Check for filenames like 50px- or 180px-, these are mostly thumbnails |
| 927 | + $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); |
| 928 | + $file_thb = wfLocalFile( $nt_thb ); |
| 929 | + if( $file_thb->exists() ) |
| 930 | + return array( 'thumb', $file_thb ); |
| 931 | + } |
| 932 | + |
| 933 | + return false; |
| 934 | + } |
| 935 | + |
| 936 | + public static function isThumbName( $filename ) { |
| 937 | + $n = strrpos( $filename, '.' ); |
| 938 | + $partname = $n ? substr( $filename, 0, $n ) : $filename; |
| 939 | + return ( |
| 940 | + substr( $partname , 3, 3 ) == 'px-' || |
| 941 | + substr( $partname , 2, 3 ) == 'px-' |
| 942 | + ) && |
| 943 | + ereg( "[0-9]{2}" , substr( $partname , 0, 2) ); |
| 944 | + } |
| 945 | + |
| 946 | + /** |
| 947 | + * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]] |
| 948 | + * |
| 949 | + * @return array list of prefixes |
| 950 | + */ |
| 951 | + public static function getFilenamePrefixBlacklist() { |
| 952 | + $blacklist = array(); |
| 953 | + $message = wfMsgForContent( 'filename-prefix-blacklist' ); |
| 954 | + if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) { |
| 955 | + $lines = explode( "\n", $message ); |
| 956 | + foreach( $lines as $line ) { |
| 957 | + // Remove comment lines |
| 958 | + $comment = substr( trim( $line ), 0, 1 ); |
| 959 | + if ( $comment == '#' || $comment == '' ) { |
| 960 | + continue; |
| 961 | + } |
| 962 | + // Remove additional comments after a prefix |
| 963 | + $comment = strpos( $line, '#' ); |
| 964 | + if ( $comment > 0 ) { |
| 965 | + $line = substr( $line, 0, $comment-1 ); |
| 966 | + } |
| 967 | + $blacklist[] = trim( $line ); |
| 968 | + } |
| 969 | + } |
| 970 | + return $blacklist; |
| 971 | + } |
| 972 | + |
| 973 | + |
| 974 | +} |
Property changes on: branches/new-upload/phase3/includes/upload/UploadBase.php |
___________________________________________________________________ |
Name: svn:keywords |
1 | 975 | + Author Date Id Revision |
Name: svn:mergeinfo |
2 | 976 | + /branches/REL1_15/phase3/includes/UploadBase.php:51646 |
/trunk/phase3/includes/UploadBase.php:46619-47809 |
Name: svn:eol-style |
3 | 977 | + native |
Index: branches/new-upload/phase3/includes/upload/UploadFromChunks.php |
— | — | @@ -0,0 +1,231 @@ |
| 2 | +<?php |
| 3 | +/* |
| 4 | +* first destination checks are made (if ignorewarnings is not checked) errors / warning is returned. |
| 5 | +* |
| 6 | +* we return the uploadUrl |
| 7 | +* we then accept chunk uploads from the client. |
| 8 | +* return chunk id on each POSTED chunk |
| 9 | +* once the client posts done=1 concatenated the files together. |
| 10 | +* more info at: http://firefogg.org/dev/chunk_post.html |
| 11 | +*/ |
| 12 | +class UploadFromChunks extends UploadBase { |
| 13 | + |
| 14 | + var $chunk_mode; //init, chunk, done |
| 15 | + var $mSessionKey = false; |
| 16 | + var $status = array(); |
| 17 | + |
| 18 | + const INIT = 1; |
| 19 | + const CHUNK = 2; |
| 20 | + const DONE = 3; |
| 21 | + |
| 22 | + function initializeFromParams( $param , &$request) { |
| 23 | + $this->initFromSessionKey( $param['chunksessionkey'], $request ); |
| 24 | + //set the chunk mode: |
| 25 | + if( !$this->mSessionKey && !$param['done'] ){ |
| 26 | + //session key not set init the chunk upload system: |
| 27 | + $this->chunk_mode = UploadFromChunks::INIT; |
| 28 | + $this->mDesiredDestName = $param['filename']; |
| 29 | + |
| 30 | + }else if( $this->mSessionKey && !$param['done']){ |
| 31 | + //this is a chunk piece |
| 32 | + $this->chunk_mode = UploadFromChunks::CHUNK; |
| 33 | + }else if( $this->mSessionKey && $param['done']){ |
| 34 | + //this is the last chunk |
| 35 | + $this->chunk_mode = UploadFromChunks::DONE; |
| 36 | + } |
| 37 | + if( $this->chunk_mode == UploadFromChunks::CHUNK || |
| 38 | + $this->chunk_mode == UploadFromChunks::DONE ){ |
| 39 | + //set chunk related vars: |
| 40 | + $this->mTempPath = $request->getFileTempName( 'chunk' ); |
| 41 | + $this->mFileSize = $request->getFileSize( 'chunk' ); |
| 42 | + } |
| 43 | + |
| 44 | + return $this->status; |
| 45 | + } |
| 46 | + |
| 47 | + static function isValidRequest( $request ) { |
| 48 | + $sessionData = $request->getSessionData('wsUploadData'); |
| 49 | + if(! self::isValidSessionKey( |
| 50 | + $request->getInt( 'wpSessionKey' ), |
| 51 | + $sessionData) ) |
| 52 | + return false; |
| 53 | + //check for the file: |
| 54 | + return (bool)$request->getFileTempName( 'file' ); |
| 55 | + } |
| 56 | + |
| 57 | + /* check warnings depending on chunk_mode*/ |
| 58 | + function checkWarnings(){ |
| 59 | + $warning = array(); |
| 60 | + return $warning; |
| 61 | + } |
| 62 | + |
| 63 | + function isEmptyFile(){ |
| 64 | + //does not apply to chunk init |
| 65 | + if( $this->chunk_mode == UploadFromChunks::INIT ){ |
| 66 | + return false; |
| 67 | + }else{ |
| 68 | + return parent::isEmptyFile(); |
| 69 | + } |
| 70 | + } |
| 71 | + /* Verify whether the upload is sane. |
| 72 | + * Returns self::OK or else an array with error information |
| 73 | + */ |
| 74 | + function verifyUpload( $resultDetails ) { |
| 75 | + //no checks on chunk upload mode: |
| 76 | + if( $this->chunk_mode == UploadFromChunks::INIT ) |
| 77 | + return self::OK; |
| 78 | + |
| 79 | + //verify on init and last chunk request |
| 80 | + if( $this->chunk_mode == UploadFromChunks::CHUNK || |
| 81 | + $this->chunk_mode == UploadFromChunks::DONE ) |
| 82 | + return parent::verifyUpload( $resultDetails ); |
| 83 | + } |
| 84 | + //only run verifyFile on completed uploaded chunks |
| 85 | + function verifyFile( $tmpFile ){ |
| 86 | + if( $this->chunk_mode == UploadFromChunks::DONE ){ |
| 87 | + //first append last chunk (so we can do a real verifyFile check... (check file type etc) |
| 88 | + $status = $this->doChunkAppend(); |
| 89 | + if( $status->isOK() ){ |
| 90 | + $this->mTempPath = $this->getRealPath( $this->mTempAppendPath ); |
| 91 | + //verify the completed merged chunks as if it was the file that got uploaded: |
| 92 | + return parent::verifyFile( $this->mTempPath ) ; |
| 93 | + }else{ |
| 94 | + //conflict of status returns (have to return the error ary) ... why we don't consistantly use a status object is beyond me.. |
| 95 | + return $status->getErrorsArray(); |
| 96 | + } |
| 97 | + }else{ |
| 98 | + return true; |
| 99 | + } |
| 100 | + } |
| 101 | + function getRealPath($srcPath){ |
| 102 | + $repo = RepoGroup::singleton()->getLocalRepo(); |
| 103 | + if ( $repo->isVirtualUrl( $srcPath) ) { |
| 104 | + return $repo->resolveVirtualUrl( $srcPath ); |
| 105 | + } |
| 106 | + } |
| 107 | + //pretty ugly inter-mixing of mParam and local vars |
| 108 | + function setupChunkSession( $summary, $comment, $watch ) { |
| 109 | + $this->mSessionKey = $this->getSessionKey(); |
| 110 | + $_SESSION['wsUploadData'][ $this->mSessionKey ] = array( |
| 111 | + 'mComment' => $comment, |
| 112 | + 'mSummary' => $summary, |
| 113 | + 'mWatch' => $watch, |
| 114 | + 'mFilteredName' => $this->mFilteredName, |
| 115 | + 'mTempAppendPath' => null, //the repo append path (not temporary local node mTempPath) |
| 116 | + 'mDesiredDestName' => $this->mDesiredDestName, |
| 117 | + 'version' => self::SESSION_VERSION, |
| 118 | + ); |
| 119 | + return $this->mSessionKey; |
| 120 | + } |
| 121 | + function initFromSessionKey( $sessionKey, $request ){ |
| 122 | + if( !$sessionKey || empty( $sessionKey ) ){ |
| 123 | + return false; |
| 124 | + } |
| 125 | + $this->mSessionKey = $sessionKey; |
| 126 | + //load the sessionData array: |
| 127 | + $sessionData = $request->getSessionData('wsUploadData'); |
| 128 | + |
| 129 | + if( isset( $sessionData[$this->mSessionKey]['version'] ) && |
| 130 | + $sessionData[$this->mSessionKey]['version'] == self::SESSION_VERSION ) { |
| 131 | + //update the local object from the session // |
| 132 | + $this->mComment = $sessionData[ $this->mSessionKey ][ 'mComment' ]; |
| 133 | + $this->mSummary = $sessionData[ $this->mSessionKey ][ 'mSummary' ]; |
| 134 | + $this->mWatch = $sessionData[ $this->mSessionKey ][ 'mWatch' ]; |
| 135 | + $this->mIgnorewarnings = $sessionData[ $this->mSessionKey ][ 'mIgnorewarnings' ]; |
| 136 | + $this->mFilteredName = $sessionData[ $this->mSessionKey ][ 'mFilteredName' ]; |
| 137 | + $this->mTempAppendPath = $sessionData[ $this->mSessionKey ][ 'mTempAppendPath' ]; |
| 138 | + $this->mDesiredDestName = $sessionData[ $this->mSessionKey ][ 'mDesiredDestName' ]; |
| 139 | + }else{ |
| 140 | + $this->status = Array( 'error'=> 'missing session data'); |
| 141 | + return false; |
| 142 | + } |
| 143 | + } |
| 144 | + //lets us return an api result (as flow for chunk uploads is kind of different than others. |
| 145 | + function performUpload($summary='', $comment='', $watch='', $user){ |
| 146 | + global $wgServer, $wgScriptPath, $wgUser; |
| 147 | + if( $this->chunk_mode == UploadFromChunks::INIT ){ |
| 148 | + //firefogg expects a specific result per: |
| 149 | + //http://www.firefogg.org/dev/chunk_post.html |
| 150 | + |
| 151 | + //its oky to return the token here because |
| 152 | + //a) the user must have requested the token to get here and |
| 153 | + //b) should only happen over POST |
| 154 | + //c) (we need the token to validate chunks are coming from a non-xss request) |
| 155 | + $token = urlencode( $wgUser->editToken() ); |
| 156 | + ob_clean(); |
| 157 | + echo ApiFormatJson::getJsonEncode( array( |
| 158 | + "uploadUrl" => "{$wgServer}{$wgScriptPath}/api.php?action=upload&". |
| 159 | + "token={$token}&format=json&enablechunks=true&chunksessionkey=". |
| 160 | + $this->setupChunkSession($summary, $comment, $watch ) ) ); |
| 161 | + exit(0); |
| 162 | + }else if( $this->chunk_mode == UploadFromChunks::CHUNK ){ |
| 163 | + $status = $this->doChunkAppend(); |
| 164 | + if( $status->isOK() ){ |
| 165 | + //return success: |
| 166 | + //firefogg expects a specific result per: |
| 167 | + //http://www.firefogg.org/dev/chunk_post.html |
| 168 | + ob_clean(); |
| 169 | + echo ApiFormatJson::getJsonEncode( array( |
| 170 | + "result"=>1, |
| 171 | + "filesize"=> filesize( $this->getRealPath( $this->mTempAppendPath ) ) |
| 172 | + ) |
| 173 | + ); |
| 174 | + exit(0); |
| 175 | + /*return array( |
| 176 | + 'result' => 1 |
| 177 | + );*/ |
| 178 | + }else{ |
| 179 | + return $status; |
| 180 | + } |
| 181 | + }else if( $this->chunk_mode == UploadFromChunks::DONE ){ |
| 182 | + //update the values from the local (session init) if not paseed again) |
| 183 | + if($summary == '') |
| 184 | + $summary = $this->mSummary; |
| 185 | + |
| 186 | + if($comment == '') |
| 187 | + $comment = $this->mComment; |
| 188 | + |
| 189 | + if($watch == '') |
| 190 | + $watch = $this->mWatch; |
| 191 | + $status = parent::performUpload($summary, $comment, $watch, $user ); |
| 192 | + if( !$status->isGood() ) { |
| 193 | + return $status; |
| 194 | + } |
| 195 | + $file = $this->getLocalFile(); |
| 196 | + //firefogg expects a specific result per: |
| 197 | + //http://www.firefogg.org/dev/chunk_post.html |
| 198 | + ob_clean(); |
| 199 | + echo ApiFormatJson::getJsonEncode( array( |
| 200 | + "result"=>1, |
| 201 | + "done"=>1, |
| 202 | + "resultUrl"=> $file->getDescriptionUrl() |
| 203 | + ) |
| 204 | + ); |
| 205 | + exit(0); |
| 206 | + |
| 207 | + } |
| 208 | + } |
| 209 | + //append the given chunk to the temporary uploaded file. (if no temporary uploaded file exists created it. |
| 210 | + function doChunkAppend(){ |
| 211 | + //if we don't have a mTempAppendPath to generate a file from the chunk packaged var: |
| 212 | + if( ! $this->mTempAppendPath ){ |
| 213 | + //die(); |
| 214 | + //get temp name: |
| 215 | + //make a chunk store path. (append tmp file to chunk) |
| 216 | + $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); |
| 217 | + |
| 218 | + if( $status->isOK() ) { |
| 219 | + $this->mTempAppendPath = $status->value; |
| 220 | + $_SESSION[ 'wsUploadData' ][ $this->mSessionKey ][ 'mTempAppendPath' ] = $this->mTempAppendPath; |
| 221 | + } |
| 222 | + return $status; |
| 223 | + }else{ |
| 224 | + if( is_file( $this->getRealPath( $this->mTempAppendPath ) ) ){ |
| 225 | + $status = $this->appendToUploadFile( $this->mTempAppendPath, $this->mTempPath ); |
| 226 | + }else{ |
| 227 | + $status->fatal( 'filenotfound', $this->mTempAppendPath ); |
| 228 | + } |
| 229 | + return $status; |
| 230 | + } |
| 231 | + } |
| 232 | +} |
Property changes on: branches/new-upload/phase3/includes/upload/UploadFromChunks.php |
___________________________________________________________________ |
Name: svn:mergeinfo |
1 | 233 | + /branches/REL1_15/phase3/includes/UploadFromChunks.php:51646 |
/trunk/phase3/includes/UploadFromChunks.php:46619-47809 |
Index: branches/new-upload/phase3/includes/upload/UploadFromFile.php |
— | — | @@ -0,0 +1,19 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class UploadFromFile extends UploadBase { |
| 5 | + |
| 6 | + function initializeFromRequest( &$request ) { |
| 7 | + $desiredDestName = $request->getText( 'wpDestFile' ); |
| 8 | + if( !$desiredDestName ) |
| 9 | + $desiredDestName = $request->getText( 'wpUploadFile' ); |
| 10 | + return $this->initialize( |
| 11 | + $desiredDestName, |
| 12 | + $request->getFileTempName( 'wpUploadFile' ), |
| 13 | + $request->getFileSize( 'wpUploadFile' ) |
| 14 | + ); |
| 15 | + } |
| 16 | + |
| 17 | + static function isValidRequest( $request ) { |
| 18 | + return (bool)$request->getFileTempName( 'wpUploadFile' ); |
| 19 | + } |
| 20 | +} |
Property changes on: branches/new-upload/phase3/includes/upload/UploadFromFile.php |
___________________________________________________________________ |
Name: svn:mergeinfo |
1 | 21 | + /branches/REL1_15/phase3/includes/UploadFromUpload.php:51646 |
/trunk/phase3/includes/UploadFromUpload.php:46619-47809 |
Name: svn:eol-style |
2 | 22 | + native |
Index: branches/new-upload/phase3/includes/AutoLoader.php |
— | — | @@ -221,11 +221,11 @@ |
222 | 222 | 'TransformParameterError' => 'includes/MediaTransformOutput.php', |
223 | 223 | 'TurckBagOStuff' => 'includes/BagOStuff.php', |
224 | 224 | 'UnlistedSpecialPage' => 'includes/SpecialPage.php', |
225 | | - 'UploadBase' => 'includes/UploadBase.php', |
226 | | - 'UploadFromStash' => 'includes/UploadFromStash.php', |
227 | | - 'UploadFromUpload' => 'includes/UploadFromUpload.php', |
228 | | - 'UploadFromUrl' => 'includes/UploadFromUrl.php', |
229 | | - 'UploadFromChunks' => 'includes/UploadFromChunks.php', |
| 225 | + 'UploadBase' => 'includes/upload/UploadBase.php', |
| 226 | + 'UploadFromStash' => 'includes/upload/UploadFromStash.php', |
| 227 | + 'UploadFromFile' => 'includes/upload/UploadFromFile.php', |
| 228 | + 'UploadFromUrl' => 'includes/upload/UploadFromUrl.php', |
| 229 | + 'UploadFromChunks' => 'includes/upload/UploadFromChunks.php', |
230 | 230 | 'User' => 'includes/User.php', |
231 | 231 | 'UserArray' => 'includes/UserArray.php', |
232 | 232 | 'UserArrayFromResult' => 'includes/UserArray.php', |
— | — | @@ -359,7 +359,7 @@ |
360 | 360 | 'PostgresField' => 'includes/db/DatabasePostgres.php', |
361 | 361 | 'ResultWrapper' => 'includes/db/Database.php', |
362 | 362 | 'SQLiteField' => 'includes/db/DatabaseSqlite.php', |
363 | | - |
| 363 | + |
364 | 364 | 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php', |
365 | 365 | 'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php', |
366 | 366 | 'IBM_DB2SearchResultSet' => 'includes/SearchIBM_DB2.php', |
— | — | @@ -579,9 +579,9 @@ |
580 | 580 | |
581 | 581 | ); |
582 | 582 | |
583 | | -//autoloader for javascript files (path is from the mediawiki folder |
584 | | -global $wgJSAutoloadLocalClasses; |
585 | | -$wgJSAutoloadLocalClasses = array( |
| 583 | +//autoloader for javascript files (path is from the mediawiki folder |
| 584 | +global $wgJSAutoloadLocalClasses; |
| 585 | +$wgJSAutoloadLocalClasses = array( |
586 | 586 | 'ajax' => 'skins/common/ajax.js', |
587 | 587 | 'ajaxwatch' => 'skins/common/ajaxwatch.js', |
588 | 588 | 'allmessages' => 'skins/common/allmessages.js', |
— | — | @@ -591,7 +591,7 @@ |
592 | 592 | 'edit' => 'skins/common/edit.js', |
593 | 593 | 'enhancedchanges.js' => 'skins/common/enhancedchanges.js', |
594 | 594 | 'history' => 'skins/common/history.js', |
595 | | - 'IEFixes' => 'skins/common/IEFixes.js', |
| 595 | + 'IEFixes' => 'skins/common/IEFixes.js', |
596 | 596 | 'metadata' => 'skins/common/metadata.js', |
597 | 597 | 'mwsuggest' => 'skins/common/mwsuggest.js', |
598 | 598 | 'prefs' => 'skins/common/prefs.js', |
— | — | @@ -600,16 +600,16 @@ |
601 | 601 | 'rightclickedit' => 'skins/common/rightclickedit.js', |
602 | 602 | 'sticky' => 'skins/common/sticky.js', |
603 | 603 | 'upload' => 'skins/common/upload.js', |
604 | | - 'wikibits' => 'skins/common/wikibits.js', |
605 | | - |
606 | | - //phase 2 javascript: |
| 604 | + 'wikibits' => 'skins/common/wikibits.js', |
| 605 | + |
| 606 | + //phase 2 javascript: |
607 | 607 | 'uploadPage' => 'js2/uploadPage.js', |
608 | 608 | 'editPage' => 'js2/editPage.js', |
609 | 609 | ); |
610 | 610 | |
611 | 611 | //add the mwEmbed set of classes that we want to expose: (could be conditional) |
612 | 612 | //@@todo move jsAutoloadLocalClasses.php to post Setup so we have $wgMwEmbedDirectory var |
613 | | -$wgMwEmbedDirectory = "js2/mwEmbed/"; |
| 613 | +$wgMwEmbedDirectory = "js2/mwEmbed/"; |
614 | 614 | require_once("$IP/js2/mwEmbed/php/jsAutoloadLocalClasses.php"); |
615 | 615 | |
616 | 616 | class AutoLoader { |
— | — | @@ -639,7 +639,7 @@ |
640 | 640 | } |
641 | 641 | } |
642 | 642 | if ( !$filename ) { |
643 | | - if( function_exists( 'wfDebug' ) ) |
| 643 | + if( function_exists( 'wfDebug' ) ) |
644 | 644 | wfDebug( "Class {$className} not found; skipped loading\n" ); |
645 | 645 | # Give up |
646 | 646 | return false; |
Index: branches/new-upload/phase3/includes/DefaultSettings.php |
— | — | @@ -1349,6 +1349,15 @@ |
1350 | 1350 | */ |
1351 | 1351 | # $wgGroupPermissions['developer']['siteadmin'] = true; |
1352 | 1352 | |
| 1353 | +/** |
| 1354 | + * Permission keys revoked from users in each group. |
| 1355 | + * This acts the same way as wgGroupPermissions above, except that |
| 1356 | + * if the user is in a group here, the permission will be removed from them. |
| 1357 | + * |
| 1358 | + * Improperly setting this could mean that your users will be unable to perform |
| 1359 | + * certain essential tasks, so use at your own risk! |
| 1360 | + */ |
| 1361 | +$wgRevokePermissions = array(); |
1353 | 1362 | |
1354 | 1363 | /** |
1355 | 1364 | * Implicit groups, aren't shown on Special:Listusers or somewhere else |
Index: branches/new-upload/phase3/js2/mwEmbed/mv_embed.js |
— | — | @@ -836,18 +836,18 @@ |
837 | 837 | iObj={}; |
838 | 838 | //add base theme css: |
839 | 839 | loadExternalCss( mv_jquery_skin_path + 'jquery-ui-1.7.1.custom.css'); |
840 | | - loadExternalCss( mv_embed_path + 'skins/'+mv_skin_name+'/styles.css' ); |
841 | | - |
842 | | - //check if we already have firefogg loaded |
843 | | - var sElm = $j(this.selector).get(0); |
844 | | - if(sElm['firefogg']){ |
| 840 | + loadExternalCss( mv_embed_path + 'skins/'+mv_skin_name+'/styles.css' ); |
| 841 | + |
| 842 | + //check if we already have firefogg loaded (the call just updates properties for that element) |
| 843 | + var sElm = $j(this.selector).get(0); |
| 844 | + if(sElm['firefogg']){ |
845 | 845 | if(sElm['firefogg']=='loading'){ |
846 | 846 | js_log("Error: called firefogg operations on Firefogg selector that is not done loading"); |
847 | 847 | return false; |
848 | 848 | } |
849 | 849 | //update properties: |
850 | 850 | for(var i in iObj){ |
851 | | - js_log("updated: "+ i + ' to '+ iObj[i]); |
| 851 | + js_log("firefogg::updated: "+ i + ' to '+ iObj[i]); |
852 | 852 | sElm['firefogg'][i] = iObj[i]; |
853 | 853 | } |
854 | 854 | return sElm['firefogg']; |
Index: branches/new-upload/phase3/js2/remoteMwEmbed.js |
— | — | @@ -8,37 +8,43 @@ |
9 | 9 | var mwEmbedHostPath =urlparts[0]; |
10 | 10 | var reqAguments =urlparts[1]; |
11 | 11 | |
12 | | -//add media wizard: |
13 | | -if( wgAction == 'edit' || wgAction == 'submit' ){ |
14 | | - load_mv_embed( function(){ |
15 | | - importScriptURI(mwEmbedHostPath + '/editPage.js' + reqAguments); |
16 | | - }); |
17 | | -} |
| 12 | +//check if mvEmbed is already loaded (ie the js2 branch is active) in which case do nothing |
| 13 | +if( typeof MV_EMBED_VERSION == 'undefined' ){ |
| 14 | + doPageSpecificRewrite(); |
| 15 | +} |
18 | 16 | |
19 | | -//firefogg integration: |
20 | | -if( wgPageName== "Special:Upload" ){ |
21 | | - load_mv_embed( function(){ |
22 | | - importScriptURI(mwEmbedHostPath + '/uploadPage.js' +reqAguments); |
23 | | - }); |
24 | | -} |
| 17 | +function doPageSpecificRewrite(){ |
| 18 | + //add media wizard: |
| 19 | + if( wgAction == 'edit' || wgAction == 'submit' ){ |
| 20 | + load_mv_embed( function(){ |
| 21 | + importScriptURI(mwEmbedHostPath + '/editPage.js' + reqAguments); |
| 22 | + }); |
| 23 | + } |
25 | 24 | |
26 | | -//oggHandler rewrite: |
27 | | -var vidIdList = []; |
28 | | -var divs = document.getElementsByTagName('div'); |
29 | | -for(var i = 0; i < divs.length; i++){ |
30 | | - if( divs[i].id && divs[i].id.substring(0,11) == 'ogg_player_'){ |
31 | | - vidIdList.push( divs[i].getAttribute("id") ); |
32 | | - } |
33 | | -} |
34 | | -if( vidIdList.length > 0){ |
35 | | - load_mv_embed( function(){ |
36 | | - mvJsLoader.embedVideoCheck(function(){ |
37 | | - //do utilty rewrite of oggHanlder content: |
38 | | - rewrite_for_oggHanlder( vidIdList ); |
39 | | - }); |
40 | | - }); |
| 25 | + //firefogg integration: |
| 26 | + if( wgPageName== "Special:Upload" ){ |
| 27 | + load_mv_embed( function(){ |
| 28 | + importScriptURI(mwEmbedHostPath + '/uploadPage.js' +reqAguments); |
| 29 | + }); |
| 30 | + } |
| 31 | + |
| 32 | + //oggHandler rewrite: |
| 33 | + var vidIdList = []; |
| 34 | + var divs = document.getElementsByTagName('div'); |
| 35 | + for(var i = 0; i < divs.length; i++){ |
| 36 | + if( divs[i].id && divs[i].id.substring(0,11) == 'ogg_player_'){ |
| 37 | + vidIdList.push( divs[i].getAttribute("id") ); |
| 38 | + } |
| 39 | + } |
| 40 | + if( vidIdList.length > 0){ |
| 41 | + load_mv_embed( function(){ |
| 42 | + mvJsLoader.embedVideoCheck(function(){ |
| 43 | + //do utilty rewrite of oggHanlder content: |
| 44 | + rewrite_for_oggHanlder( vidIdList ); |
| 45 | + }); |
| 46 | + }); |
| 47 | + } |
41 | 48 | } |
42 | | - |
43 | 49 | function getRemoteEmbedPath(){ |
44 | 50 | for(var i=0; i < document.getElementsByTagName('script').length; i++){ |
45 | 51 | var s = document.getElementsByTagName('script')[i]; |