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