wikimedia /
mediawiki
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 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 | * @ingroup Upload |
||
| 22 | */ |
||
| 23 | |||
| 24 | /** |
||
| 25 | * UploadStash is intended to accomplish a few things: |
||
| 26 | * - Enable applications to temporarily stash files without publishing them to |
||
| 27 | * the wiki. |
||
| 28 | * - Several parts of MediaWiki do this in similar ways: UploadBase, |
||
| 29 | * UploadWizard, and FirefoggChunkedExtension. |
||
| 30 | * And there are several that reimplement stashing from scratch, in |
||
| 31 | * idiosyncratic ways. The idea is to unify them all here. |
||
| 32 | * Mostly all of them are the same except for storing some custom fields, |
||
| 33 | * which we subsume into the data array. |
||
| 34 | * - Enable applications to find said files later, as long as the db table or |
||
| 35 | * temp files haven't been purged. |
||
| 36 | * - Enable the uploading user (and *ONLY* the uploading user) to access said |
||
| 37 | * files, and thumbnails of said files, via a URL. We accomplish this using |
||
| 38 | * a database table, with ownership checking as you might expect. See |
||
| 39 | * SpecialUploadStash, which implements a web interface to some files stored |
||
| 40 | * this way. |
||
| 41 | * |
||
| 42 | * UploadStash right now is *mostly* intended to show you one user's slice of |
||
| 43 | * the entire stash. The user parameter is only optional because there are few |
||
| 44 | * cases where we clean out the stash from an automated script. In the future we |
||
| 45 | * might refactor this. |
||
| 46 | * |
||
| 47 | * UploadStash represents the entire stash of temporary files. |
||
| 48 | * UploadStashFile is a filestore for the actual physical disk files. |
||
| 49 | * UploadFromStash extends UploadBase, and represents a single stashed file as |
||
| 50 | * it is moved from the stash to the regular file repository |
||
| 51 | * |
||
| 52 | * @ingroup Upload |
||
| 53 | */ |
||
| 54 | class UploadStash { |
||
| 55 | // Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg) |
||
| 56 | const KEY_FORMAT_REGEX = '/^[\w-\.]+\.\w*$/'; |
||
| 57 | const MAX_US_PROPS_SIZE = 65535; |
||
| 58 | |||
| 59 | /** |
||
| 60 | * repository that this uses to store temp files |
||
| 61 | * public because we sometimes need to get a LocalFile within the same repo. |
||
| 62 | * |
||
| 63 | * @var LocalRepo |
||
| 64 | */ |
||
| 65 | public $repo; |
||
| 66 | |||
| 67 | // array of initialized repo objects |
||
| 68 | protected $files = []; |
||
| 69 | |||
| 70 | // cache of the file metadata that's stored in the database |
||
| 71 | protected $fileMetadata = []; |
||
| 72 | |||
| 73 | // fileprops cache |
||
| 74 | protected $fileProps = []; |
||
| 75 | |||
| 76 | // current user |
||
| 77 | protected $user, $userId, $isLoggedIn; |
||
|
0 ignored issues
–
show
|
|||
| 78 | |||
| 79 | /** |
||
| 80 | * Represents a temporary filestore, with metadata in the database. |
||
| 81 | * Designed to be compatible with the session stashing code in UploadBase |
||
| 82 | * (should replace it eventually). |
||
| 83 | * |
||
| 84 | * @param FileRepo $repo |
||
| 85 | * @param User $user (default null) |
||
| 86 | */ |
||
| 87 | public function __construct( FileRepo $repo, $user = null ) { |
||
| 88 | // this might change based on wiki's configuration. |
||
| 89 | $this->repo = $repo; |
||
|
0 ignored issues
–
show
$repo is of type object<FileRepo>, but the property $repo was declared to be of type object<LocalRepo>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly. Either this assignment is in error or an instanceof check should be added for that assignment. class Alien {}
class Dalek extends Alien {}
class Plot
{
/** @var Dalek */
public $villain;
}
$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
$plot->villain = $alien;
}
Loading history...
|
|||
| 90 | |||
| 91 | // if a user was passed, use it. otherwise, attempt to use the global. |
||
| 92 | // this keeps FileRepo from breaking when it creates an UploadStash object |
||
| 93 | if ( $user ) { |
||
| 94 | $this->user = $user; |
||
| 95 | } else { |
||
| 96 | global $wgUser; |
||
| 97 | $this->user = $wgUser; |
||
| 98 | } |
||
| 99 | |||
| 100 | if ( is_object( $this->user ) ) { |
||
| 101 | $this->userId = $this->user->getId(); |
||
| 102 | $this->isLoggedIn = $this->user->isLoggedIn(); |
||
| 103 | } |
||
| 104 | } |
||
| 105 | |||
| 106 | /** |
||
| 107 | * Get a file and its metadata from the stash. |
||
| 108 | * The noAuth param is a bit janky but is required for automated scripts |
||
| 109 | * which clean out the stash. |
||
| 110 | * |
||
| 111 | * @param string $key Key under which file information is stored |
||
| 112 | * @param bool $noAuth (optional) Don't check authentication. Used by maintenance scripts. |
||
| 113 | * @throws UploadStashFileNotFoundException |
||
| 114 | * @throws UploadStashNotLoggedInException |
||
| 115 | * @throws UploadStashWrongOwnerException |
||
| 116 | * @throws UploadStashBadPathException |
||
| 117 | * @return UploadStashFile |
||
| 118 | */ |
||
| 119 | public function getFile( $key, $noAuth = false ) { |
||
| 120 | if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
||
| 121 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
||
| 122 | } |
||
| 123 | |||
| 124 | if ( !$noAuth && !$this->isLoggedIn ) { |
||
| 125 | throw new UploadStashNotLoggedInException( __METHOD__ . |
||
| 126 | ' No user is logged in, files must belong to users' ); |
||
| 127 | } |
||
| 128 | |||
| 129 | if ( !isset( $this->fileMetadata[$key] ) ) { |
||
| 130 | if ( !$this->fetchFileMetadata( $key ) ) { |
||
| 131 | // If nothing was received, it's likely due to replication lag. |
||
| 132 | // Check the master to see if the record is there. |
||
| 133 | $this->fetchFileMetadata( $key, DB_MASTER ); |
||
| 134 | } |
||
| 135 | |||
| 136 | if ( !isset( $this->fileMetadata[$key] ) ) { |
||
| 137 | throw new UploadStashFileNotFoundException( "key '$key' not found in stash" ); |
||
| 138 | } |
||
| 139 | |||
| 140 | // create $this->files[$key] |
||
| 141 | $this->initFile( $key ); |
||
| 142 | |||
| 143 | // fetch fileprops |
||
| 144 | if ( strlen( $this->fileMetadata[$key]['us_props'] ) ) { |
||
| 145 | $this->fileProps[$key] = unserialize( $this->fileMetadata[$key]['us_props'] ); |
||
| 146 | } else { // b/c for rows with no us_props |
||
| 147 | wfDebug( __METHOD__ . " fetched props for $key from file\n" ); |
||
| 148 | $path = $this->fileMetadata[$key]['us_path']; |
||
| 149 | $this->fileProps[$key] = $this->repo->getFileProps( $path ); |
||
| 150 | } |
||
| 151 | } |
||
| 152 | |||
| 153 | if ( !$this->files[$key]->exists() ) { |
||
| 154 | wfDebug( __METHOD__ . " tried to get file at $key, but it doesn't exist\n" ); |
||
| 155 | // @todo Is this not an UploadStashFileNotFoundException case? |
||
| 156 | throw new UploadStashBadPathException( "path doesn't exist" ); |
||
| 157 | } |
||
| 158 | |||
| 159 | if ( !$noAuth ) { |
||
| 160 | if ( $this->fileMetadata[$key]['us_user'] != $this->userId ) { |
||
| 161 | throw new UploadStashWrongOwnerException( "This file ($key) doesn't " |
||
| 162 | . "belong to the current user." ); |
||
| 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 $sourceType The type of upload that generated this file |
||
| 199 | * (currently, I believe, 'file' or null) |
||
| 200 | * @throws UploadStashBadPathException |
||
| 201 | * @throws UploadStashFileException |
||
| 202 | * @throws UploadStashNotLoggedInException |
||
| 203 | * @return UploadStashFile|null File, or null on failure |
||
| 204 | */ |
||
| 205 | public function stashFile( $path, $sourceType = null ) { |
||
| 206 | if ( !is_file( $path ) ) { |
||
| 207 | wfDebug( __METHOD__ . " tried to stash file at '$path', but it doesn't exist\n" ); |
||
| 208 | throw new UploadStashBadPathException( "path doesn't exist" ); |
||
| 209 | } |
||
| 210 | |||
| 211 | $mwProps = new MWFileProps( MimeMagic::singleton() ); |
||
| 212 | $fileProps = $mwProps->getPropsFromPath( $path, true ); |
||
| 213 | wfDebug( __METHOD__ . " stashing file at '$path'\n" ); |
||
| 214 | |||
| 215 | // we will be initializing from some tmpnam files that don't have extensions. |
||
| 216 | // most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. |
||
| 217 | $extension = self::getExtensionForPath( $path ); |
||
| 218 | if ( !preg_match( "/\\.\\Q$extension\\E$/", $path ) ) { |
||
| 219 | $pathWithGoodExtension = "$path.$extension"; |
||
| 220 | } else { |
||
| 221 | $pathWithGoodExtension = $path; |
||
| 222 | } |
||
| 223 | |||
| 224 | // If no key was supplied, make one. a mysql insertid would be totally |
||
| 225 | // reasonable here, except that for historical reasons, the key is this |
||
| 226 | // random thing instead. At least it's not guessable. |
||
| 227 | // Some things that when combined will make a suitably unique key. |
||
| 228 | // see: http://www.jwz.org/doc/mid.html |
||
| 229 | list( $usec, $sec ) = explode( ' ', microtime() ); |
||
| 230 | $usec = substr( $usec, 2 ); |
||
| 231 | $key = Wikimedia\base_convert( $sec . $usec, 10, 36 ) . '.' . |
||
| 232 | Wikimedia\base_convert( mt_rand(), 10, 36 ) . '.' . |
||
| 233 | $this->userId . '.' . |
||
| 234 | $extension; |
||
| 235 | |||
| 236 | $this->fileProps[$key] = $fileProps; |
||
| 237 | |||
| 238 | if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) { |
||
| 239 | throw new UploadStashBadPathException( "key '$key' is not in a proper format" ); |
||
| 240 | } |
||
| 241 | |||
| 242 | wfDebug( __METHOD__ . " key for '$path': $key\n" ); |
||
| 243 | |||
| 244 | // if not already in a temporary area, put it there |
||
| 245 | $storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ), $path ); |
||
| 246 | |||
| 247 | if ( !$storeStatus->isOK() ) { |
||
| 248 | // It is a convention in MediaWiki to only return one error per API |
||
| 249 | // exception, even if multiple errors are available. We use reset() |
||
| 250 | // to pick the "first" thing that was wrong, preferring errors to |
||
| 251 | // warnings. This is a bit lame, as we may have more info in the |
||
| 252 | // $storeStatus and we're throwing it away, but to fix it means |
||
| 253 | // redesigning API errors significantly. |
||
| 254 | // $storeStatus->value just contains the virtual URL (if anything) |
||
| 255 | // which is probably useless to the caller. |
||
| 256 | $error = $storeStatus->getErrorsArray(); |
||
| 257 | $error = reset( $error ); |
||
| 258 | View Code Duplication | if ( !count( $error ) ) { |
|
| 259 | $error = $storeStatus->getWarningsArray(); |
||
| 260 | $error = reset( $error ); |
||
| 261 | if ( !count( $error ) ) { |
||
| 262 | $error = [ 'unknown', 'no error recorded' ]; |
||
| 263 | } |
||
| 264 | } |
||
| 265 | // At this point, $error should contain the single "most important" |
||
| 266 | // error, plus any parameters. |
||
| 267 | $errorMsg = array_shift( $error ); |
||
| 268 | throw new UploadStashFileException( "Error storing file in '$path': " |
||
| 269 | . wfMessage( $errorMsg, $error )->text() ); |
||
| 270 | } |
||
| 271 | $stashPath = $storeStatus->value; |
||
| 272 | |||
| 273 | // fetch the current user ID |
||
| 274 | if ( !$this->isLoggedIn ) { |
||
| 275 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
| 276 | . ' No user is logged in, files must belong to users' ); |
||
| 277 | } |
||
| 278 | |||
| 279 | // insert the file metadata into the db. |
||
| 280 | wfDebug( __METHOD__ . " inserting $stashPath under $key\n" ); |
||
| 281 | $dbw = $this->repo->getMasterDB(); |
||
| 282 | |||
| 283 | $serializedFileProps = serialize( $fileProps ); |
||
| 284 | if ( strlen( $serializedFileProps ) > self::MAX_US_PROPS_SIZE ) { |
||
| 285 | // Database is going to truncate this and make the field invalid. |
||
| 286 | // Prioritize important metadata over file handler metadata. |
||
| 287 | // File handler should be prepared to regenerate invalid metadata if needed. |
||
| 288 | $fileProps['metadata'] = false; |
||
| 289 | $serializedFileProps = serialize( $fileProps ); |
||
| 290 | } |
||
| 291 | |||
| 292 | $this->fileMetadata[$key] = [ |
||
| 293 | 'us_id' => $dbw->nextSequenceValue( 'uploadstash_us_id_seq' ), |
||
| 294 | 'us_user' => $this->userId, |
||
| 295 | 'us_key' => $key, |
||
| 296 | 'us_orig_path' => $path, |
||
| 297 | 'us_path' => $stashPath, // virtual URL |
||
| 298 | 'us_props' => $dbw->encodeBlob( $serializedFileProps ), |
||
| 299 | 'us_size' => $fileProps['size'], |
||
| 300 | 'us_sha1' => $fileProps['sha1'], |
||
| 301 | 'us_mime' => $fileProps['mime'], |
||
| 302 | 'us_media_type' => $fileProps['media_type'], |
||
| 303 | 'us_image_width' => $fileProps['width'], |
||
| 304 | 'us_image_height' => $fileProps['height'], |
||
| 305 | 'us_image_bits' => $fileProps['bits'], |
||
| 306 | 'us_source_type' => $sourceType, |
||
| 307 | 'us_timestamp' => $dbw->timestamp(), |
||
| 308 | 'us_status' => 'finished' |
||
| 309 | ]; |
||
| 310 | |||
| 311 | $dbw->insert( |
||
| 312 | 'uploadstash', |
||
| 313 | $this->fileMetadata[$key], |
||
| 314 | __METHOD__ |
||
| 315 | ); |
||
| 316 | |||
| 317 | // store the insertid in the class variable so immediate retrieval |
||
| 318 | // (possibly laggy) isn't necesary. |
||
| 319 | $this->fileMetadata[$key]['us_id'] = $dbw->insertId(); |
||
| 320 | |||
| 321 | # create the UploadStashFile object for this file. |
||
| 322 | $this->initFile( $key ); |
||
| 323 | |||
| 324 | return $this->getFile( $key ); |
||
| 325 | } |
||
| 326 | |||
| 327 | /** |
||
| 328 | * Remove all files from the stash. |
||
| 329 | * Does not clean up files in the repo, just the record of them. |
||
| 330 | * |
||
| 331 | * @throws UploadStashNotLoggedInException |
||
| 332 | * @return bool Success |
||
| 333 | */ |
||
| 334 | public function clear() { |
||
| 335 | if ( !$this->isLoggedIn ) { |
||
| 336 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
| 337 | . ' No user is logged in, files must belong to users' ); |
||
| 338 | } |
||
| 339 | |||
| 340 | wfDebug( __METHOD__ . ' clearing all rows for user ' . $this->userId . "\n" ); |
||
| 341 | $dbw = $this->repo->getMasterDB(); |
||
| 342 | $dbw->delete( |
||
| 343 | 'uploadstash', |
||
| 344 | [ 'us_user' => $this->userId ], |
||
| 345 | __METHOD__ |
||
| 346 | ); |
||
| 347 | |||
| 348 | # destroy objects. |
||
| 349 | $this->files = []; |
||
| 350 | $this->fileMetadata = []; |
||
| 351 | |||
| 352 | return true; |
||
| 353 | } |
||
| 354 | |||
| 355 | /** |
||
| 356 | * Remove a particular file from the stash. Also removes it from the repo. |
||
| 357 | * |
||
| 358 | * @param string $key |
||
| 359 | * @throws UploadStashNoSuchKeyException|UploadStashNotLoggedInException |
||
| 360 | * @throws UploadStashWrongOwnerException |
||
| 361 | * @return bool Success |
||
| 362 | */ |
||
| 363 | public function removeFile( $key ) { |
||
| 364 | if ( !$this->isLoggedIn ) { |
||
| 365 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
| 366 | . ' No user is logged in, files must belong to users' ); |
||
| 367 | } |
||
| 368 | |||
| 369 | $dbw = $this->repo->getMasterDB(); |
||
| 370 | |||
| 371 | // this is a cheap query. it runs on the master so that this function |
||
| 372 | // still works when there's lag. It won't be called all that often. |
||
| 373 | $row = $dbw->selectRow( |
||
| 374 | 'uploadstash', |
||
| 375 | 'us_user', |
||
| 376 | [ 'us_key' => $key ], |
||
| 377 | __METHOD__ |
||
| 378 | ); |
||
| 379 | |||
| 380 | if ( !$row ) { |
||
| 381 | throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" ); |
||
| 382 | } |
||
| 383 | |||
| 384 | if ( $row->us_user != $this->userId ) { |
||
| 385 | throw new UploadStashWrongOwnerException( "Can't delete: " |
||
| 386 | . "the file ($key) doesn't belong to this user." ); |
||
| 387 | } |
||
| 388 | |||
| 389 | return $this->removeFileNoAuth( $key ); |
||
| 390 | } |
||
| 391 | |||
| 392 | /** |
||
| 393 | * Remove a file (see removeFile), but doesn't check ownership first. |
||
| 394 | * |
||
| 395 | * @param string $key |
||
| 396 | * @return bool Success |
||
| 397 | */ |
||
| 398 | public function removeFileNoAuth( $key ) { |
||
| 399 | wfDebug( __METHOD__ . " clearing row $key\n" ); |
||
| 400 | |||
| 401 | // Ensure we have the UploadStashFile loaded for this key |
||
| 402 | $this->getFile( $key, true ); |
||
| 403 | |||
| 404 | $dbw = $this->repo->getMasterDB(); |
||
| 405 | |||
| 406 | $dbw->delete( |
||
| 407 | 'uploadstash', |
||
| 408 | [ 'us_key' => $key ], |
||
| 409 | __METHOD__ |
||
| 410 | ); |
||
| 411 | |||
| 412 | /** @todo Look into UnregisteredLocalFile and find out why the rv here is |
||
| 413 | * sometimes wrong (false when file was removed). For now, ignore. |
||
| 414 | */ |
||
| 415 | $this->files[$key]->remove(); |
||
| 416 | |||
| 417 | unset( $this->files[$key] ); |
||
| 418 | unset( $this->fileMetadata[$key] ); |
||
| 419 | |||
| 420 | return true; |
||
| 421 | } |
||
| 422 | |||
| 423 | /** |
||
| 424 | * List all files in the stash. |
||
| 425 | * |
||
| 426 | * @throws UploadStashNotLoggedInException |
||
| 427 | * @return array |
||
| 428 | */ |
||
| 429 | public function listFiles() { |
||
| 430 | if ( !$this->isLoggedIn ) { |
||
| 431 | throw new UploadStashNotLoggedInException( __METHOD__ |
||
| 432 | . ' No user is logged in, files must belong to users' ); |
||
| 433 | } |
||
| 434 | |||
| 435 | $dbr = $this->repo->getSlaveDB(); |
||
| 436 | $res = $dbr->select( |
||
| 437 | 'uploadstash', |
||
| 438 | 'us_key', |
||
| 439 | [ 'us_user' => $this->userId ], |
||
| 440 | __METHOD__ |
||
| 441 | ); |
||
| 442 | |||
| 443 | if ( !is_object( $res ) || $res->numRows() == 0 ) { |
||
| 444 | // nothing to do. |
||
| 445 | return false; |
||
| 446 | } |
||
| 447 | |||
| 448 | // finish the read before starting writes. |
||
| 449 | $keys = []; |
||
| 450 | foreach ( $res as $row ) { |
||
| 451 | array_push( $keys, $row->us_key ); |
||
| 452 | } |
||
| 453 | |||
| 454 | return $keys; |
||
| 455 | } |
||
| 456 | |||
| 457 | /** |
||
| 458 | * Find or guess extension -- ensuring that our extension matches our MIME type. |
||
| 459 | * Since these files are constructed from php tempnames they may not start off |
||
| 460 | * with an extension. |
||
| 461 | * XXX this is somewhat redundant with the checks that ApiUpload.php does with incoming |
||
| 462 | * uploads versus the desired filename. Maybe we can get that passed to us... |
||
| 463 | * @param string $path |
||
| 464 | * @throws UploadStashFileException |
||
| 465 | * @return string |
||
| 466 | */ |
||
| 467 | public static function getExtensionForPath( $path ) { |
||
| 468 | global $wgFileBlacklist; |
||
| 469 | // Does this have an extension? |
||
| 470 | $n = strrpos( $path, '.' ); |
||
| 471 | $extension = null; |
||
| 472 | if ( $n !== false ) { |
||
| 473 | $extension = $n ? substr( $path, $n + 1 ) : ''; |
||
| 474 | } else { |
||
| 475 | // If not, assume that it should be related to the MIME type of the original file. |
||
| 476 | $magic = MimeMagic::singleton(); |
||
| 477 | $mimeType = $magic->guessMimeType( $path ); |
||
| 478 | $extensions = explode( ' ', MimeMagic::singleton()->getExtensionsForType( $mimeType ) ); |
||
| 479 | if ( count( $extensions ) ) { |
||
| 480 | $extension = $extensions[0]; |
||
| 481 | } |
||
| 482 | } |
||
| 483 | |||
| 484 | if ( is_null( $extension ) ) { |
||
| 485 | throw new UploadStashFileException( "extension is null" ); |
||
| 486 | } |
||
| 487 | |||
| 488 | $extension = File::normalizeExtension( $extension ); |
||
| 489 | if ( in_array( $extension, $wgFileBlacklist ) ) { |
||
| 490 | // The file should already be checked for being evil. |
||
| 491 | // However, if somehow we got here, we definitely |
||
| 492 | // don't want to give it an extension of .php and |
||
| 493 | // put it in a web accesible directory. |
||
| 494 | return ''; |
||
| 495 | } |
||
| 496 | |||
| 497 | return $extension; |
||
| 498 | } |
||
| 499 | |||
| 500 | /** |
||
| 501 | * Helper function: do the actual database query to fetch file metadata. |
||
| 502 | * |
||
| 503 | * @param string $key |
||
| 504 | * @param int $readFromDB Constant (default: DB_REPLICA) |
||
| 505 | * @return bool |
||
| 506 | */ |
||
| 507 | protected function fetchFileMetadata( $key, $readFromDB = DB_REPLICA ) { |
||
| 508 | // populate $fileMetadata[$key] |
||
| 509 | $dbr = null; |
||
|
0 ignored issues
–
show
$dbr is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the Loading history...
|
|||
| 510 | if ( $readFromDB === DB_MASTER ) { |
||
| 511 | // sometimes reading from the master is necessary, if there's replication lag. |
||
| 512 | $dbr = $this->repo->getMasterDB(); |
||
| 513 | } else { |
||
| 514 | $dbr = $this->repo->getSlaveDB(); |
||
| 515 | } |
||
| 516 | |||
| 517 | $row = $dbr->selectRow( |
||
| 518 | 'uploadstash', |
||
| 519 | '*', |
||
| 520 | [ 'us_key' => $key ], |
||
| 521 | __METHOD__ |
||
| 522 | ); |
||
| 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( $this->repo, $this->fileMetadata[$key]['us_path'], $key ); |
||
| 544 | if ( $file->getSize() === 0 ) { |
||
| 545 | throw new UploadStashZeroLengthFileException( "File is zero length" ); |
||
| 546 | } |
||
| 547 | $this->files[$key] = $file; |
||
| 548 | |||
| 549 | return true; |
||
| 550 | } |
||
| 551 | } |
||
| 552 | |||
| 553 | class UploadStashFile extends UnregisteredLocalFile { |
||
| 554 | private $fileKey; |
||
| 555 | private $urlName; |
||
| 556 | protected $url; |
||
| 557 | |||
| 558 | /** |
||
| 559 | * A LocalFile wrapper around a file that has been temporarily stashed, |
||
| 560 | * so we can do things like create thumbnails for it. Arguably |
||
| 561 | * UnregisteredLocalFile should be handling its own file repo but that |
||
| 562 | * class is a bit retarded currently. |
||
| 563 | * |
||
| 564 | * @param FileRepo $repo Repository where we should find the path |
||
| 565 | * @param string $path Path to file |
||
| 566 | * @param string $key Key to store the path and any stashed data under |
||
| 567 | * @throws UploadStashBadPathException |
||
| 568 | * @throws UploadStashFileNotFoundException |
||
| 569 | */ |
||
| 570 | public function __construct( $repo, $path, $key ) { |
||
| 571 | $this->fileKey = $key; |
||
| 572 | |||
| 573 | // resolve mwrepo:// urls |
||
| 574 | if ( $repo->isVirtualUrl( $path ) ) { |
||
| 575 | $path = $repo->resolveVirtualUrl( $path ); |
||
| 576 | } else { |
||
| 577 | // check if path appears to be sane, no parent traversals, |
||
| 578 | // and is in this repo's temp zone. |
||
| 579 | $repoTempPath = $repo->getZonePath( 'temp' ); |
||
| 580 | if ( ( !$repo->validateFilename( $path ) ) || |
||
| 581 | ( strpos( $path, $repoTempPath ) !== 0 ) |
||
| 582 | ) { |
||
| 583 | wfDebug( "UploadStash: tried to construct an UploadStashFile " |
||
| 584 | . "from a file that should already exist at '$path', but path is not valid\n" ); |
||
| 585 | throw new UploadStashBadPathException( 'path is not valid' ); |
||
| 586 | } |
||
| 587 | |||
| 588 | // check if path exists! and is a plain file. |
||
| 589 | if ( !$repo->fileExists( $path ) ) { |
||
| 590 | wfDebug( "UploadStash: tried to construct an UploadStashFile from " |
||
| 591 | . "a file that should already exist at '$path', but path is not found\n" ); |
||
| 592 | throw new UploadStashFileNotFoundException( 'cannot find path, or not a plain file' ); |
||
| 593 | } |
||
| 594 | } |
||
| 595 | |||
| 596 | parent::__construct( false, $repo, $path, false ); |
||
| 597 | |||
| 598 | $this->name = basename( $this->path ); |
||
| 599 | } |
||
| 600 | |||
| 601 | /** |
||
| 602 | * A method needed by the file transforming and scaling routines in File.php |
||
| 603 | * We do not necessarily care about doing the description at this point |
||
| 604 | * However, we also can't return the empty string, as the rest of MediaWiki |
||
| 605 | * demands this (and calls to imagemagick convert require it to be there) |
||
| 606 | * |
||
| 607 | * @return string Dummy value |
||
| 608 | */ |
||
| 609 | public function getDescriptionUrl() { |
||
| 610 | return $this->getUrl(); |
||
| 611 | } |
||
| 612 | |||
| 613 | /** |
||
| 614 | * Get the path for the thumbnail (actually any transformation of this file) |
||
| 615 | * The actual argument is the result of thumbName although we seem to have |
||
| 616 | * buggy code elsewhere that expects a boolean 'suffix' |
||
| 617 | * |
||
| 618 | * @param string $thumbName Name of thumbnail (e.g. "120px-123456.jpg" ), |
||
| 619 | * or false to just get the path |
||
| 620 | * @return string Path thumbnail should take on filesystem, or containing |
||
| 621 | * directory if thumbname is false |
||
| 622 | */ |
||
| 623 | public function getThumbPath( $thumbName = false ) { |
||
| 624 | $path = dirname( $this->path ); |
||
| 625 | if ( $thumbName !== false ) { |
||
| 626 | $path .= "/$thumbName"; |
||
| 627 | } |
||
| 628 | |||
| 629 | return $path; |
||
| 630 | } |
||
| 631 | |||
| 632 | /** |
||
| 633 | * Return the file/url base name of a thumbnail with the specified parameters. |
||
| 634 | * We override this because we want to use the pretty url name instead of the |
||
| 635 | * ugly file name. |
||
| 636 | * |
||
| 637 | * @param array $params Handler-specific parameters |
||
| 638 | * @param int $flags Bitfield that supports THUMB_* constants |
||
| 639 | * @return string|null Base name for URL, like '120px-12345.jpg', or null if there is no handler |
||
| 640 | */ |
||
| 641 | function thumbName( $params, $flags = 0 ) { |
||
| 642 | return $this->generateThumbName( $this->getUrlName(), $params ); |
||
| 643 | } |
||
| 644 | |||
| 645 | /** |
||
| 646 | * Helper function -- given a 'subpage', return the local URL, |
||
| 647 | * e.g. /wiki/Special:UploadStash/subpage |
||
| 648 | * @param string $subPage |
||
| 649 | * @return string Local URL for this subpage in the Special:UploadStash space. |
||
| 650 | */ |
||
| 651 | private function getSpecialUrl( $subPage ) { |
||
| 652 | return SpecialPage::getTitleFor( 'UploadStash', $subPage )->getLocalURL(); |
||
| 653 | } |
||
| 654 | |||
| 655 | /** |
||
| 656 | * Get a URL to access the thumbnail |
||
| 657 | * This is required because the model of how files work requires that |
||
| 658 | * the thumbnail urls be predictable. However, in our model the URL is |
||
| 659 | * not based on the filename (that's hidden in the db) |
||
| 660 | * |
||
| 661 | * @param string $thumbName Basename of thumbnail file -- however, we don't |
||
| 662 | * want to use the file exactly |
||
| 663 | * @return string URL to access thumbnail, or URL with partial path |
||
| 664 | */ |
||
| 665 | public function getThumbUrl( $thumbName = false ) { |
||
| 666 | wfDebug( __METHOD__ . " getting for $thumbName \n" ); |
||
| 667 | |||
| 668 | return $this->getSpecialUrl( 'thumb/' . $this->getUrlName() . '/' . $thumbName ); |
||
| 669 | } |
||
| 670 | |||
| 671 | /** |
||
| 672 | * The basename for the URL, which we want to not be related to the filename. |
||
| 673 | * Will also be used as the lookup key for a thumbnail file. |
||
| 674 | * |
||
| 675 | * @return string Base url name, like '120px-123456.jpg' |
||
| 676 | */ |
||
| 677 | public function getUrlName() { |
||
| 678 | if ( !$this->urlName ) { |
||
| 679 | $this->urlName = $this->fileKey; |
||
| 680 | } |
||
| 681 | |||
| 682 | return $this->urlName; |
||
| 683 | } |
||
| 684 | |||
| 685 | /** |
||
| 686 | * Return the URL of the file, if for some reason we wanted to download it |
||
| 687 | * We tend not to do this for the original file, but we do want thumb icons |
||
| 688 | * |
||
| 689 | * @return string Url |
||
| 690 | */ |
||
| 691 | public function getUrl() { |
||
| 692 | if ( !isset( $this->url ) ) { |
||
| 693 | $this->url = $this->getSpecialUrl( 'file/' . $this->getUrlName() ); |
||
| 694 | } |
||
| 695 | |||
| 696 | return $this->url; |
||
| 697 | } |
||
| 698 | |||
| 699 | /** |
||
| 700 | * Parent classes use this method, for no obvious reason, to return the path |
||
| 701 | * (relative to wiki root, I assume). But with this class, the URL is |
||
| 702 | * unrelated to the path. |
||
| 703 | * |
||
| 704 | * @return string Url |
||
| 705 | */ |
||
| 706 | public function getFullUrl() { |
||
| 707 | return $this->getUrl(); |
||
| 708 | } |
||
| 709 | |||
| 710 | /** |
||
| 711 | * Getter for file key (the unique id by which this file's location & |
||
| 712 | * metadata is stored in the db) |
||
| 713 | * |
||
| 714 | * @return string File key |
||
| 715 | */ |
||
| 716 | public function getFileKey() { |
||
| 717 | return $this->fileKey; |
||
| 718 | } |
||
| 719 | |||
| 720 | /** |
||
| 721 | * Remove the associated temporary file |
||
| 722 | * @return status Success |
||
| 723 | */ |
||
| 724 | public function remove() { |
||
| 725 | if ( !$this->repo->fileExists( $this->path ) ) { |
||
| 726 | // Maybe the file's already been removed? This could totally happen in UploadBase. |
||
| 727 | return true; |
||
| 728 | } |
||
| 729 | |||
| 730 | return $this->repo->freeTemp( $this->path ); |
||
| 731 | } |
||
| 732 | |||
| 733 | public function exists() { |
||
| 734 | return $this->repo->fileExists( $this->path ); |
||
| 735 | } |
||
| 736 | } |
||
| 737 | |||
| 738 | class UploadStashException extends MWException { |
||
| 739 | } |
||
| 740 | |||
| 741 | class UploadStashFileNotFoundException extends UploadStashException { |
||
| 742 | } |
||
| 743 | |||
| 744 | class UploadStashBadPathException extends UploadStashException { |
||
| 745 | } |
||
| 746 | |||
| 747 | class UploadStashFileException extends UploadStashException { |
||
| 748 | } |
||
| 749 | |||
| 750 | class UploadStashZeroLengthFileException extends UploadStashException { |
||
| 751 | } |
||
| 752 | |||
| 753 | class UploadStashNotLoggedInException extends UploadStashException { |
||
| 754 | } |
||
| 755 | |||
| 756 | class UploadStashWrongOwnerException extends UploadStashException { |
||
| 757 | } |
||
| 758 | |||
| 759 | class UploadStashNoSuchKeyException extends UploadStashException { |
||
| 760 | } |
||
| 761 |
Only declaring a single property per statement allows you to later on add doc comments more easily.
It is also recommended by PSR2, so it is a common style that many people expect.