47// Format of the key for files -- has to be suitable as a filename itself (e.g. ab12cd34ef.jpg) 49privateconst MAX_US_PROPS_SIZE = 65535;
80// this might change based on wiki's configuration. 83// if a user was passed, use it. otherwise, attempt to use the global request context. 84// this keeps LocalRepo from breaking when it creates an UploadStash object 85 $this->user = $user ?? RequestContext::getMain()->getUser();
101publicfunctiongetFile( $key, $noAuth =
false ) {
102if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
104wfMessage(
'uploadstash-bad-path-bad-format', $key )
108if ( !$noAuth && !$this->user->isRegistered() ) {
114if ( !isset( $this->fileMetadata[$key] ) ) {
116// If nothing was received, it's likely due to replication lag. 117// Check the primary DB to see if the record is there. 121if ( !isset( $this->fileMetadata[$key] ) ) {
123wfMessage(
'uploadstash-file-not-found', $key )
127// create $this->files[$key] 132 isset( $this->fileMetadata[$key][
'us_props'] ) && strlen( $this->fileMetadata[$key][
'us_props'] )
134 $this->fileProps[$key] = unserialize( $this->fileMetadata[$key][
'us_props'] );
135 }
else {
// b/c for rows with no us_props 136wfDebug( __METHOD__ .
" fetched props for $key from file" );
137$path = $this->fileMetadata[$key][
'us_path'];
138 $this->fileProps[$key] = $this->repo->getFileProps(
$path );
142if ( !$this->files[$key]->exists() ) {
143wfDebug( __METHOD__ .
" tried to get file at $key, but it doesn't exist" );
144// @todo Is this not an UploadStashFileNotFoundException case? 150if ( !$noAuth && $this->fileMetadata[$key][
'us_user'] != $this->user->getId() ) {
152wfMessage(
'uploadstash-wrong-owner', $key )
156return $this->files[$key];
168return $this->fileMetadata[$key];
180return $this->fileProps[$key];
197if ( !is_file(
$path ) ) {
198wfDebug( __METHOD__ .
" tried to stash file at '$path', but it doesn't exist" );
204// File props is expensive to generate for large files, so reuse if possible. 206 $mwProps =
newMWFileProps( MediaWikiServices::getInstance()->getMimeAnalyzer() );
209wfDebug( __METHOD__ .
" stashing file at '$path'" );
211// we will be initializing from some tmpnam files that don't have extensions. 212// most of MediaWiki assumes all uploaded files have good extensions. So, we fix this. 213 $extension = self::getExtensionForPath(
$path );
214if ( !preg_match(
"/\\.\\Q$extension\\E$/",
$path ) ) {
215 $pathWithGoodExtension =
"$path.$extension";
217 $pathWithGoodExtension =
$path;
220// If no key was supplied, make one. a mysql insertid would be totally 221// reasonable here, except that for historical reasons, the key is this 222// random thing instead. At least it's not guessable. 223// Some things that when combined will make a suitably unique key. 224// see: http://www.jwz.org/doc/mid.html 225 [ $usec, $sec ] = explode(
' ', microtime() );
226 $usec = substr( $usec, 2 );
227 $key = Wikimedia\base_convert( $sec . $usec, 10, 36 ) .
'.' .
228 Wikimedia\base_convert( (
string)mt_rand(), 10, 36 ) .
'.' .
229 $this->user->getId() .
'.' .
234if ( !preg_match( self::KEY_FORMAT_REGEX, $key ) ) {
236wfMessage(
'uploadstash-bad-path-bad-format', $key )
240wfDebug( __METHOD__ .
" key for '$path': $key" );
242// if not already in a temporary area, put it there 243 $storeStatus = $this->repo->storeTemp( basename( $pathWithGoodExtension ),
$path );
245if ( !$storeStatus->isOK() ) {
246// It is a convention in MediaWiki to only return one error per API 247// exception, even if multiple errors are available.[citation needed] 248// Pick the "first" thing that was wrong, preferring errors to warnings. 249// This is a bit lame, as we may have more info in the 250// $storeStatus and we're throwing it away, but to fix it means 251// redesigning API errors significantly. 252// $storeStatus->value just contains the virtual URL (if anything) 253// which is probably useless to the caller. 254foreach ( $storeStatus->getMessages(
'error' ) as $msg ) {
257foreach ( $storeStatus->getMessages(
'warning' ) as $msg ) {
260// XXX: This isn't a real message, hopefully this case is unreachable 263 $stashPath = $storeStatus->value;
265// fetch the current user ID 266if ( !$this->user->isRegistered() ) {
272// insert the file metadata into the db. 273wfDebug( __METHOD__ .
" inserting $stashPath under $key" );
274 $dbw = $this->repo->getPrimaryDB();
276 $serializedFileProps = serialize(
$fileProps );
277if ( strlen( $serializedFileProps ) > self::MAX_US_PROPS_SIZE ) {
278// Database is going to truncate this and make the field invalid. 279// Prioritize important metadata over file handler metadata. 280// File handler should be prepared to regenerate invalid metadata if needed. 282 $serializedFileProps = serialize(
$fileProps );
286'us_user' => $this->user->getId(),
288'us_orig_path' =>
$path,
289'us_path' => $stashPath,
// virtual URL 290'us_props' => $dbw->encodeBlob( $serializedFileProps ),
298'us_source_type' => $sourceType,
299'us_timestamp' => $dbw->timestamp(),
300'us_status' =>
'finished' 303 $dbw->newInsertQueryBuilder()
304 ->insertInto(
'uploadstash' )
306 ->caller( __METHOD__ )->execute();
308// store the insertid in the class variable so immediate retrieval 309// (possibly laggy) isn't necessary. 310 $insertRow[
'us_id'] = $dbw->insertId();
312 $this->fileMetadata[$key] = $insertRow;
314 # create the UploadStashFile object for this file. 328if ( !$this->user->isRegistered() ) {
334wfDebug( __METHOD__ .
' clearing all rows for user ' . $this->user->getId() );
335 $dbw = $this->repo->getPrimaryDB();
336 $dbw->newDeleteQueryBuilder()
337 ->deleteFrom(
'uploadstash' )
338 ->where( [
'us_user' => $this->user->getId() ] )
339 ->caller( __METHOD__ )->execute();
343 $this->fileMetadata = [];
357if ( !$this->user->isRegistered() ) {
363 $dbw = $this->repo->getPrimaryDB();
365// this is a cheap query. it runs on the primary DB so that this function 366// still works when there's lag. It won't be called all that often. 367 $row = $dbw->newSelectQueryBuilder()
369 ->from(
'uploadstash' )
370 ->where( [
'us_key' => $key ] )
371 ->caller( __METHOD__ )->fetchRow();
375wfMessage(
'uploadstash-no-such-key', $key )
379if ( $row->us_user != $this->user->getId() ) {
381wfMessage(
'uploadstash-wrong-owner', $key )
395wfDebug( __METHOD__ .
" clearing row $key" );
397// Ensure we have the UploadStashFile loaded for this key 400 $dbw = $this->repo->getPrimaryDB();
402 $dbw->newDeleteQueryBuilder()
403 ->deleteFrom(
'uploadstash' )
404 ->where( [
'us_key' => $key ] )
405 ->caller( __METHOD__ )->execute();
410 $this->files[$key]->remove();
412 unset( $this->files[$key] );
413 unset( $this->fileMetadata[$key] );
425if ( !$this->user->isRegistered() ) {
431 $res = $this->repo->getReplicaDB()->newSelectQueryBuilder()
433 ->from(
'uploadstash' )
434 ->where( [
'us_user' => $this->user->getId() ] )
435 ->caller( __METHOD__ )->fetchResultSet();
437if ( $res->numRows() == 0 ) {
442// finish the read before starting writes. 444foreach ( $res as $row ) {
445 $keys[] = $row->us_key;
461 $prohibitedFileExtensions = MediaWikiServices::getInstance()
462 ->getMainConfig()->get( MainConfigNames::ProhibitedFileExtensions );
463// Does this have an extension? 464 $n = strrpos(
$path,
'.' );
467 $extension = $n ? substr(
$path, $n + 1 ) :
'';
469// If not, assume that it should be related to the MIME type of the original file. 470 $magic = MediaWikiServices::getInstance()->getMimeAnalyzer();
471 $mimeType = $magic->guessMimeType(
$path );
472 $extension = $magic->getExtensionFromMimeTypeOrNull( $mimeType ) ??
'';
475 $extension = File::normalizeExtension( $extension );
476if ( in_array( $extension, $prohibitedFileExtensions ) ) {
477// The file should already be checked for being evil. 478// However, if somehow we got here, we definitely 479// don't want to give it an extension of .php and 480// put it in a web accessible directory. 495// populate $fileMetadata[$key] 497// sometimes reading from the primary DB is necessary, if there's replication lag. 498 $dbr = $this->repo->getPrimaryDB();
500 $dbr = $this->repo->getReplicaDB();
503 $row = $dbr->newSelectQueryBuilder()
505'us_user',
'us_key',
'us_orig_path',
'us_path',
'us_props',
506'us_size',
'us_sha1',
'us_mime',
'us_media_type',
507'us_image_width',
'us_image_height',
'us_image_bits',
508'us_source_type',
'us_timestamp',
'us_status',
510 ->from(
'uploadstash' )
511 ->where( [
'us_key' => $key ] )
512 ->caller( __METHOD__ )->fetchRow();
514if ( !is_object( $row ) ) {
515// key wasn't present in the database. this will happen sometimes. 519 $this->fileMetadata[$key] = (array)$row;
520 $this->fileMetadata[$key][
'us_props'] = $dbr->decodeBlob( $row->us_props );
535 $this->fileMetadata[$key][
'us_path'],
537 $this->fileMetadata[$key][
'us_sha1'],
538 $this->fileMetadata[$key][
'us_mime'] ??
false 540if ( $file->getSize() === 0 ) {
545 $this->files[$key] = $file;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
MimeMagic helper wrapper.
Group all the pieces relevant to the context of a request into one instance.
Implements some public methods and some protected utility functions which are required by multiple ch...
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
UploadStash is intended to accomplish a few things:
static getExtensionForPath( $path)
Find or guess extension – ensuring that our extension matches our MIME type.
removeFile( $key)
Remove a particular file from the stash.
fetchFileMetadata( $key, $readFromDB=DB_REPLICA)
Helper function: do the actual database query to fetch file metadata.
getFileProps( $key)
Getter for fileProps.
clear()
Remove all files from the stash.
array $fileMetadata
cache of the file metadata that's stored in the database
array $fileProps
fileprops cache
listFiles()
List all files in the stash.
getMetadata( $key)
Getter for file metadata.
removeFileNoAuth( $key)
Remove a file (see removeFile), but doesn't check ownership first.
initFile( $key)
Helper function: Initialize the UploadStashFile for a given file.
__construct(LocalRepo $repo, ?UserIdentity $user=null)
Represents a temporary filestore, with metadata in the database.
stashFile( $path, $sourceType=null, $fileProps=null)
Stash a file in a temp directory and record that we did this in the database, along with other metada...
getFile( $key, $noAuth=false)
Get a file and its metadata from the stash.
LocalRepo $repo
repository that this uses to store temp files public because we sometimes need to get a LocalFile wit...
array $files
array of initialized repo objects
Interface for objects representing user identity.