Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 209 |
|
0.00% |
0 / 12 |
CRAP | |
0.00% |
0 / 1 |
UploadStash | |
0.00% |
0 / 209 |
|
0.00% |
0 / 12 |
2070 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFile | |
0.00% |
0 / 31 |
|
0.00% |
0 / 1 |
156 | |||
getMetadata | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getFileProps | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
stashFile | |
0.00% |
0 / 72 |
|
0.00% |
0 / 1 |
110 | |||
clear | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
6 | |||
removeFile | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
removeFileNoAuth | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 | |||
listFiles | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
20 | |||
getExtensionForPath | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
fetchFileMetadata | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
initFile | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | /** |
3 | * Temporary storage for uploaded files. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | */ |
22 | |
23 | use MediaWiki\Context\RequestContext; |
24 | use MediaWiki\MainConfigNames; |
25 | use MediaWiki\MediaWikiServices; |
26 | use MediaWiki\User\UserIdentity; |
27 | |
28 | /** |
29 | * UploadStash is intended to accomplish a few things: |
30 | * - Enable applications to temporarily stash files without publishing them to |
31 | * the wiki. |
32 | * - Several parts of MediaWiki do this in similar ways: UploadBase, |
33 | * UploadWizard, and FirefoggChunkedExtension. |
34 | * And there are several that reimplement stashing from scratch, in |
35 | * idiosyncratic ways. The idea is to unify them all here. |
36 | * Mostly all of them are the same except for storing some custom fields, |
37 | * which we subsume into the data array. |
38 | * - Enable applications to find said files later, as long as the db table or |
39 | * temp files haven't been purged. |
40 | * - Enable the uploading user (and *ONLY* the uploading user) to access said |
41 | * files, and thumbnails of said files, via a URL. We accomplish this using |
42 | * a database table, with ownership checking as you might expect. See |
43 | * SpecialUploadStash, which implements a web interface to some files stored |
44 | * this way. |
45 | * |
46 | * UploadStash right now is *mostly* intended to show you one user's slice of |
47 | * the entire stash. The user parameter is only optional because there are few |
48 | * cases where we clean out the stash from an automated script. In the future we |
49 | * might refactor this. |
50 | * |
51 | * UploadStash represents the entire stash of temporary files. |
52 | * UploadStashFile is a filestore for the actual physical disk files. |
53 | * UploadFromStash extends UploadBase, and represents a single stashed file as |
54 | * it is moved from the stash to the regular file repository |
55 | * |
56 | * @ingroup Upload |
57 | */ |
58 | class UploadStash { |
59 | // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg) |
60 | public const KEY_FORMAT_REGEX = '/^[\w\-\.]+\.\w*$/'; |
61 | private const MAX_US_PROPS_SIZE = 65535; |
62 | |
63 | /** |
64 | * repository that this uses to store temp files |
65 | * public because we sometimes need to get a LocalFile within the same repo. |
66 | * |
67 | * @var LocalRepo |
68 | */ |
69 | public $repo; |
70 | |
71 | /** @var array array of initialized repo objects */ |
72 | protected $files = []; |
73 | |
74 | /** @var array cache of the file metadata that's stored in the database */ |
75 | protected $fileMetadata = []; |
76 | |
77 | /** @var array fileprops cache */ |
78 | protected $fileProps = []; |
79 | |
80 | /** @var UserIdentity */ |
81 | private $user; |
82 | |
83 | /** |
84 | * Represents a temporary filestore, with metadata in the database. |
85 | * Designed to be compatible with the session stashing code in UploadBase |
86 | * (should replace it eventually). |
87 | * |
88 | * @param FileRepo $repo |
89 | * @param UserIdentity|null $user |
90 | */ |
91 | public function __construct( FileRepo $repo, UserIdentity $user = null ) { |
92 | // this might change based on wiki's configuration. |
93 | $this->repo = $repo; |
94 | |
95 | // if a user was passed, use it. otherwise, attempt to use the global request context. |
96 | // this keeps FileRepo from breaking when it creates an UploadStash object |
97 | $this->user = $user ?? RequestContext::getMain()->getUser(); |
98 | } |
99 | |
100 | /** |
101 | * Get a file and its metadata from the stash. |
102 | * The noAuth param is a bit janky but is required for automated scripts |
103 | * which clean out the stash. |
104 | * |
105 | * @param string $key Key under which file information is stored |
106 | * @param bool $noAuth (optional) Don't check authentication. Used by maintenance scripts. |
107 | * @throws UploadStashFileNotFoundException |
108 | * @throws UploadStashNotLoggedInException |
109 | * @throws UploadStashWrongOwnerException |
110 | * @throws UploadStashBadPathException |
111 | * @return UploadStashFile |
112 | */ |
113 | public function getFile( $key, $noAuth = false ) { |
114 | if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
115 | throw new UploadStashBadPathException( |
116 | wfMessage( 'uploadstash-bad-path-bad-format', $key ) |
117 | ); |
118 | } |
119 | |
120 | if ( !$noAuth && !$this->user->isRegistered() ) { |
121 | throw new UploadStashNotLoggedInException( |
122 | wfMessage( 'uploadstash-not-logged-in' ) |
123 | ); |
124 | } |
125 | |
126 | if ( !isset( $this->fileMetadata[$key] ) ) { |
127 | if ( !$this->fetchFileMetadata( $key ) ) { |
128 | // If nothing was received, it's likely due to replication lag. |
129 | // Check the primary DB to see if the record is there. |
130 | $this->fetchFileMetadata( $key, DB_PRIMARY ); |
131 | } |
132 | |
133 | if ( !isset( $this->fileMetadata[$key] ) ) { |
134 | throw new UploadStashFileNotFoundException( |
135 | wfMessage( 'uploadstash-file-not-found', $key ) |
136 | ); |
137 | } |
138 | |
139 | // create $this->files[$key] |
140 | $this->initFile( $key ); |
141 | |
142 | // fetch fileprops |
143 | if ( |
144 | isset( $this->fileMetadata[$key]['us_props'] ) && strlen( $this->fileMetadata[$key]['us_props'] ) |
145 | ) { |
146 | $this->fileProps[$key] = unserialize( $this->fileMetadata[$key]['us_props'] ); |
147 | } else { // b/c for rows with no us_props |
148 | wfDebug( __METHOD__ . " fetched props for $key from file" ); |
149 | $path = $this->fileMetadata[$key]['us_path']; |
150 | $this->fileProps[$key] = $this->repo->getFileProps( $path ); |
151 | } |
152 | } |
153 | |
154 | if ( !$this->files[$key]->exists() ) { |
155 | wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist" ); |
156 | // @todo Is this not an UploadStashFileNotFoundException case? |
157 | throw new UploadStashBadPathException( |
158 | wfMessage( 'uploadstash-bad-path' ) |
159 | ); |
160 | } |
161 | |
162 | if ( !$noAuth && $this->fileMetadata[$key]['us_user'] != $this->user->getId() ) { |
163 | throw new UploadStashWrongOwnerException( |
164 | wfMessage( 'uploadstash-wrong-owner', $key ) |
165 | ); |
166 | } |
167 | |
168 | return $this->files[$key]; |
169 | } |
170 | |
171 | /** |
172 | * Getter for file metadata. |
173 | * |
174 | * @param string $key Key under which file information is stored |
175 | * @return array |
176 | */ |
177 | public function getMetadata( $key ) { |
178 | $this->getFile( $key ); |
179 | |
180 | return $this->fileMetadata[$key]; |
181 | } |
182 | |
183 | /** |
184 | * Getter for fileProps |
185 | * |
186 | * @param string $key Key under which file information is stored |
187 | * @return array |
188 | */ |
189 | public function getFileProps( $key ) { |
190 | $this->getFile( $key ); |
191 | |
192 | return $this->fileProps[$key]; |
193 | } |
194 | |
195 | /** |
196 | * Stash a file in a temp directory and record that we did this in the |
197 | * database, along with other metadata. |
198 | * |
199 | * @param string $path Path to file you want stashed |
200 | * @param string|null $sourceType The type of upload that generated this file |
201 | * (currently, I believe, 'file' or null) |
202 | * @param array|null $fileProps File props or null to regenerate |
203 | * @throws UploadStashBadPathException |
204 | * @throws UploadStashFileException |
205 | * @throws UploadStashNotLoggedInException |
206 | * @return UploadStashFile|null File, or null on failure |
207 | */ |
208 | public function stashFile( $path, $sourceType = null, $fileProps = null ) { |
209 | if ( !is_file( $path ) ) { |
210 | wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist" ); |
211 | throw new UploadStashBadPathException( |
212 | wfMessage( 'uploadstash-bad-path' ) |
213 | ); |
214 | } |
215 | |
216 | // File props is expensive to generate for large files, so reuse if possible. |
217 | if ( !$fileProps ) { |
218 | $mwProps = new MWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() ); |
219 | $fileProps = $mwProps->getPropsFromPath( $path, true ); |
220 | } |
221 | wfDebug( __METHOD__ . " stashing file at '$path'" ); |
222 | |
223 | // we will be initializing from some tmpnam files that don't have extensions. |
224 | // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. |
225 | $extension = self::getExtensionForPath( $path ); |
226 | if ( !preg_match( "/\\.\\Q$extension\\E$/", $path ) ) { |
227 | $pathWithGoodExtension = "$path.$extension"; |
228 | } else { |
229 | $pathWithGoodExtension = $path; |
230 | } |
231 | |
232 | // If no key was supplied, make one. a mysql insertid would be totally |
233 | // reasonable here, except that for historical reasons, the key is this |
234 | // random thing instead. At least it's not guessable. |
235 | // Some things that when combined will make a suitably unique key. |
236 | // see: http://www.jwz.org/doc/mid.html |
237 | [ $usec, $sec ] = explode( ' ', microtime() ); |
238 | $usec = substr( $usec, 2 ); |
239 | $key = Wikimedia\base_convert( $sec . $usec, 10, 36 ) . '.' . |
240 | Wikimedia\base_convert( (string)mt_rand(), 10, 36 ) . '.' . |
241 | $this->user->getId() . '.' . |
242 | $extension; |
243 | |
244 | $this->fileProps[$key] = $fileProps; |
245 | |
246 | if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
247 | throw new UploadStashBadPathException( |
248 | wfMessage( 'uploadstash-bad-path-bad-format', $key ) |
249 | ); |
250 | } |
251 | |
252 | wfDebug( __METHOD__ . " key for '$path': $key" ); |
253 | |
254 | // if not already in a temporary area, put it there |
255 | $storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ), $path ); |
256 | |
257 | if ( !$storeStatus->isOK() ) { |
258 | // It is a convention in MediaWiki to only return one error per API |
259 | // exception, even if multiple errors are available. We use reset() |
260 | // to pick the "first" thing that was wrong, preferring errors to |
261 | // warnings. This is a bit lame, as we may have more info in the |
262 | // $storeStatus and we're throwing it away, but to fix it means |
263 | // redesigning API errors significantly. |
264 | // $storeStatus->value just contains the virtual URL (if anything) |
265 | // which is probably useless to the caller. |
266 | $error = $storeStatus->getErrorsArray(); |
267 | $error = reset( $error ); |
268 | if ( !count( $error ) ) { |
269 | $error = $storeStatus->getWarningsArray(); |
270 | $error = reset( $error ); |
271 | if ( !count( $error ) ) { |
272 | $error = [ 'unknown', 'no error recorded' ]; |
273 | } |
274 | } |
275 | // At this point, $error should contain the single "most important" |
276 | // error, plus any parameters. |
277 | $errorMsg = array_shift( $error ); |
278 | throw new UploadStashFileException( wfMessage( $errorMsg, $error ) ); |
279 | } |
280 | $stashPath = $storeStatus->value; |
281 | |
282 | // fetch the current user ID |
283 | if ( !$this->user->isRegistered() ) { |
284 | throw new UploadStashNotLoggedInException( |
285 | wfMessage( 'uploadstash-not-logged-in' ) |
286 | ); |
287 | } |
288 | |
289 | // insert the file metadata into the db. |
290 | wfDebug( __METHOD__ . " inserting $stashPath under $key" ); |
291 | $dbw = $this->repo->getPrimaryDB(); |
292 | |
293 | $serializedFileProps = serialize( $fileProps ); |
294 | if ( strlen( $serializedFileProps ) > self::MAX_US_PROPS_SIZE ) { |
295 | // Database is going to truncate this and make the field invalid. |
296 | // Prioritize important metadata over file handler metadata. |
297 | // File handler should be prepared to regenerate invalid metadata if needed. |
298 | $fileProps['metadata'] = []; |
299 | $serializedFileProps = serialize( $fileProps ); |
300 | } |
301 | |
302 | $insertRow = [ |
303 | 'us_user' => $this->user->getId(), |
304 | 'us_key' => $key, |
305 | 'us_orig_path' => $path, |
306 | 'us_path' => $stashPath, // virtual URL |
307 | 'us_props' => $dbw->encodeBlob( $serializedFileProps ), |
308 | 'us_size' => $fileProps['size'], |
309 | 'us_sha1' => $fileProps['sha1'], |
310 | 'us_mime' => $fileProps['mime'], |
311 | 'us_media_type' => $fileProps['media_type'], |
312 | 'us_image_width' => $fileProps['width'], |
313 | 'us_image_height' => $fileProps['height'], |
314 | 'us_image_bits' => $fileProps['bits'], |
315 | 'us_source_type' => $sourceType, |
316 | 'us_timestamp' => $dbw->timestamp(), |
317 | 'us_status' => 'finished' |
318 | ]; |
319 | |
320 | $dbw->newInsertQueryBuilder() |
321 | ->insertInto( 'uploadstash' ) |
322 | ->row( $insertRow ) |
323 | ->caller( __METHOD__ )->execute(); |
324 | |
325 | // store the insertid in the class variable so immediate retrieval |
326 | // (possibly laggy) isn't necessary. |
327 | $insertRow['us_id'] = $dbw->insertId(); |
328 | |
329 | $this->fileMetadata[$key] = $insertRow; |
330 | |
331 | # create the UploadStashFile object for this file. |
332 | $this->initFile( $key ); |
333 | |
334 | return $this->getFile( $key ); |
335 | } |
336 | |
337 | /** |
338 | * Remove all files from the stash. |
339 | * Does not clean up files in the repo, just the record of them. |
340 | * |
341 | * @throws UploadStashNotLoggedInException |
342 | * @return bool Success |
343 | */ |
344 | public function clear() { |
345 | if ( !$this->user->isRegistered() ) { |
346 | throw new UploadStashNotLoggedInException( |
347 | wfMessage( 'uploadstash-not-logged-in' ) |
348 | ); |
349 | } |
350 | |
351 | wfDebug( __METHOD__ . ' clearing all rows for user ' . $this->user->getId() ); |
352 | $dbw = $this->repo->getPrimaryDB(); |
353 | $dbw->newDeleteQueryBuilder() |
354 | ->deleteFrom( 'uploadstash' ) |
355 | ->where( [ 'us_user' => $this->user->getId() ] ) |
356 | ->caller( __METHOD__ )->execute(); |
357 | |
358 | # destroy objects. |
359 | $this->files = []; |
360 | $this->fileMetadata = []; |
361 | |
362 | return true; |
363 | } |
364 | |
365 | /** |
366 | * Remove a particular file from the stash. Also removes it from the repo. |
367 | * |
368 | * @param string $key |
369 | * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException |
370 | * @throws UploadStashWrongOwnerException |
371 | * @return bool Success |
372 | */ |
373 | public function removeFile( $key ) { |
374 | if ( !$this->user->isRegistered() ) { |
375 | throw new UploadStashNotLoggedInException( |
376 | wfMessage( 'uploadstash-not-logged-in' ) |
377 | ); |
378 | } |
379 | |
380 | $dbw = $this->repo->getPrimaryDB(); |
381 | |
382 | // this is a cheap query. it runs on the primary DB so that this function |
383 | // still works when there's lag. It won't be called all that often. |
384 | $row = $dbw->newSelectQueryBuilder() |
385 | ->select( 'us_user' ) |
386 | ->from( 'uploadstash' ) |
387 | ->where( [ 'us_key' => $key ] ) |
388 | ->caller( __METHOD__ )->fetchRow(); |
389 | |
390 | if ( !$row ) { |
391 | throw new UploadStashNoSuchKeyException( |
392 | wfMessage( 'uploadstash-no-such-key', $key ) |
393 | ); |
394 | } |
395 | |
396 | if ( $row->us_user != $this->user->getId() ) { |
397 | throw new UploadStashWrongOwnerException( |
398 | wfMessage( 'uploadstash-wrong-owner', $key ) |
399 | ); |
400 | } |
401 | |
402 | return $this->removeFileNoAuth( $key ); |
403 | } |
404 | |
405 | /** |
406 | * Remove a file (see removeFile), but doesn't check ownership first. |
407 | * |
408 | * @param string $key |
409 | * @return bool Success |
410 | */ |
411 | public function removeFileNoAuth( $key ) { |
412 | wfDebug( __METHOD__ . " clearing row $key" ); |
413 | |
414 | // Ensure we have the UploadStashFile loaded for this key |
415 | $this->getFile( $key, true ); |
416 | |
417 | $dbw = $this->repo->getPrimaryDB(); |
418 | |
419 | $dbw->newDeleteQueryBuilder() |
420 | ->deleteFrom( 'uploadstash' ) |
421 | ->where( [ 'us_key' => $key ] ) |
422 | ->caller( __METHOD__ )->execute(); |
423 | |
424 | /** @todo Look into UnregisteredLocalFile and find out why the rv here is |
425 | * sometimes wrong (false when file was removed). For now, ignore. |
426 | */ |
427 | $this->files[$key]->remove(); |
428 | |
429 | unset( $this->files[$key] ); |
430 | unset( $this->fileMetadata[$key] ); |
431 | |
432 | return true; |
433 | } |
434 | |
435 | /** |
436 | * List all files in the stash. |
437 | * |
438 | * @throws UploadStashNotLoggedInException |
439 | * @return array|false |
440 | */ |
441 | public function listFiles() { |
442 | if ( !$this->user->isRegistered() ) { |
443 | throw new UploadStashNotLoggedInException( |
444 | wfMessage( 'uploadstash-not-logged-in' ) |
445 | ); |
446 | } |
447 | |
448 | $res = $this->repo->getReplicaDB()->newSelectQueryBuilder() |
449 | ->select( 'us_key' ) |
450 | ->from( 'uploadstash' ) |
451 | ->where( [ 'us_user' => $this->user->getId() ] ) |
452 | ->caller( __METHOD__ )->fetchResultSet(); |
453 | |
454 | if ( $res->numRows() == 0 ) { |
455 | // nothing to do. |
456 | return false; |
457 | } |
458 | |
459 | // finish the read before starting writes. |
460 | $keys = []; |
461 | foreach ( $res as $row ) { |
462 | $keys[] = $row->us_key; |
463 | } |
464 | |
465 | return $keys; |
466 | } |
467 | |
468 | /** |
469 | * Find or guess extension -- ensuring that our extension matches our MIME type. |
470 | * Since these files are constructed from php tempnames they may not start off |
471 | * with an extension. |
472 | * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming |
473 | * uploads versus the desired filename. Maybe we can get that passed to us... |
474 | * @param string $path |
475 | * @return string |
476 | */ |
477 | public static function getExtensionForPath( $path ) { |
478 | $prohibitedFileExtensions = MediaWikiServices::getInstance() |
479 | ->getMainConfig()->get( MainConfigNames::ProhibitedFileExtensions ); |
480 | // Does this have an extension? |
481 | $n = strrpos( $path, '.' ); |
482 | |
483 | if ( $n !== false ) { |
484 | $extension = $n ? substr( $path, $n + 1 ) : ''; |
485 | } else { |
486 | // If not, assume that it should be related to the MIME type of the original file. |
487 | $magic = MediaWikiServices::getInstance()->getMimeAnalyzer(); |
488 | $mimeType = $magic->guessMimeType( $path ); |
489 | $extension = $magic->getExtensionFromMimeTypeOrNull( $mimeType ) ?? ''; |
490 | } |
491 | |
492 | $extension = File::normalizeExtension( $extension ); |
493 | if ( in_array( $extension, $prohibitedFileExtensions ) ) { |
494 | // The file should already be checked for being evil. |
495 | // However, if somehow we got here, we definitely |
496 | // don't want to give it an extension of .php and |
497 | // put it in a web accessible directory. |
498 | return ''; |
499 | } |
500 | |
501 | return $extension; |
502 | } |
503 | |
504 | /** |
505 | * Helper function: do the actual database query to fetch file metadata. |
506 | * |
507 | * @param string $key |
508 | * @param int $readFromDB Constant (default: DB_REPLICA) |
509 | * @return bool |
510 | */ |
511 | protected function fetchFileMetadata( $key, $readFromDB = DB_REPLICA ) { |
512 | // populate $fileMetadata[$key] |
513 | if ( $readFromDB === DB_PRIMARY ) { |
514 | // sometimes reading from the primary DB is necessary, if there's replication lag. |
515 | $dbr = $this->repo->getPrimaryDB(); |
516 | } else { |
517 | $dbr = $this->repo->getReplicaDB(); |
518 | } |
519 | |
520 | $row = $dbr->newSelectQueryBuilder() |
521 | ->select( [ |
522 | 'us_user', 'us_key', 'us_orig_path', 'us_path', 'us_props', |
523 | 'us_size', 'us_sha1', 'us_mime', 'us_media_type', |
524 | 'us_image_width', 'us_image_height', 'us_image_bits', |
525 | 'us_source_type', 'us_timestamp', 'us_status', |
526 | ] ) |
527 | ->from( 'uploadstash' ) |
528 | ->where( [ 'us_key' => $key ] ) |
529 | ->caller( __METHOD__ )->fetchRow(); |
530 | |
531 | if ( !is_object( $row ) ) { |
532 | // key wasn't present in the database. this will happen sometimes. |
533 | return false; |
534 | } |
535 | |
536 | $this->fileMetadata[$key] = (array)$row; |
537 | $this->fileMetadata[$key]['us_props'] = $dbr->decodeBlob( $row->us_props ); |
538 | |
539 | return true; |
540 | } |
541 | |
542 | /** |
543 | * Helper function: Initialize the UploadStashFile for a given file. |
544 | * |
545 | * @param string $key Key under which to store the object |
546 | * @throws UploadStashZeroLengthFileException |
547 | * @return bool |
548 | */ |
549 | protected function initFile( $key ) { |
550 | $file = new UploadStashFile( |
551 | $this->repo, |
552 | $this->fileMetadata[$key]['us_path'], |
553 | $key, |
554 | $this->fileMetadata[$key]['us_sha1'] |
555 | ); |
556 | if ( $file->getSize() === 0 ) { |
557 | throw new UploadStashZeroLengthFileException( |
558 | wfMessage( 'uploadstash-zero-length' ) |
559 | ); |
560 | } |
561 | $this->files[$key] = $file; |
562 | |
563 | return true; |
564 | } |
565 | } |