diff options
Diffstat (limited to 'includes/upload')
-rw-r--r-- | includes/upload/UploadBase.php | 202 | ||||
-rw-r--r-- | includes/upload/UploadFromChunks.php | 26 | ||||
-rw-r--r-- | includes/upload/UploadFromFile.php | 2 | ||||
-rw-r--r-- | includes/upload/UploadFromStash.php | 6 | ||||
-rw-r--r-- | includes/upload/UploadFromUrl.php | 49 | ||||
-rw-r--r-- | includes/upload/UploadStash.php | 29 |
6 files changed, 178 insertions, 136 deletions
diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index e72669d4..2260241d 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -72,19 +72,20 @@ abstract class UploadBase { * @return string */ public function getVerificationErrorCode( $error ) { - $code_to_status = array(self::EMPTY_FILE => 'empty-file', - self::FILE_TOO_LARGE => 'file-too-large', - self::FILETYPE_MISSING => 'filetype-missing', - self::FILETYPE_BADTYPE => 'filetype-banned', - self::MIN_LENGTH_PARTNAME => 'filename-tooshort', - self::ILLEGAL_FILENAME => 'illegal-filename', - self::OVERWRITE_EXISTING_FILE => 'overwrite', - self::VERIFICATION_ERROR => 'verification-error', - self::HOOK_ABORTED => 'hookaborted', - self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename', - self::FILENAME_TOO_LONG => 'filename-toolong', + $code_to_status = array( + self::EMPTY_FILE => 'empty-file', + self::FILE_TOO_LARGE => 'file-too-large', + self::FILETYPE_MISSING => 'filetype-missing', + self::FILETYPE_BADTYPE => 'filetype-banned', + self::MIN_LENGTH_PARTNAME => 'filename-tooshort', + self::ILLEGAL_FILENAME => 'illegal-filename', + self::OVERWRITE_EXISTING_FILE => 'overwrite', + self::VERIFICATION_ERROR => 'verification-error', + self::HOOK_ABORTED => 'hookaborted', + self::WINDOWS_NONASCII_FILENAME => 'windows-nonascii-filename', + self::FILENAME_TOO_LONG => 'filename-toolong', ); - if( isset( $code_to_status[$error] ) ) { + if ( isset( $code_to_status[$error] ) ) { return $code_to_status[$error]; } @@ -137,7 +138,7 @@ abstract class UploadBase { public static function createFromRequest( &$request, $type = null ) { $type = $type ? $type : $request->getVal( 'wpSourceType', 'File' ); - if( !$type ) { + if ( !$type ) { return null; } @@ -150,18 +151,18 @@ abstract class UploadBase { if ( is_null( $className ) ) { $className = 'UploadFrom' . $type; wfDebug( __METHOD__ . ": class name: $className\n" ); - if( !in_array( $type, self::$uploadHandlers ) ) { + if ( !in_array( $type, self::$uploadHandlers ) ) { return null; } } // Check whether this upload class is enabled - if( !call_user_func( array( $className, 'isEnabled' ) ) ) { + if ( !call_user_func( array( $className, 'isEnabled' ) ) ) { return null; } // Check whether the request is valid - if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) { + if ( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) { return null; } @@ -188,7 +189,9 @@ abstract class UploadBase { * @since 1.18 * @return string */ - public function getSourceType() { return null; } + public function getSourceType() { + return null; + } /** * Initialize the path information @@ -253,7 +256,7 @@ abstract class UploadBase { wfProfileIn( __METHOD__ ); $repo = RepoGroup::singleton()->getLocalRepo(); if ( $repo->isVirtualUrl( $srcPath ) ) { - // @TODO: just make uploads work with storage paths + // @todo just make uploads work with storage paths // UploadFromStash loads files via virtual URLs $tmpFile = $repo->getLocalCopy( $srcPath ); $tmpFile->bind( $this ); // keep alive with $this @@ -274,7 +277,7 @@ abstract class UploadBase { /** * If there was no filename or a zero size given, give up quick. */ - if( $this->isEmptyFile() ) { + if ( $this->isEmptyFile() ) { wfProfileOut( __METHOD__ ); return array( 'status' => self::EMPTY_FILE ); } @@ -283,7 +286,7 @@ abstract class UploadBase { * Honor $wgMaxUploadSize */ $maxSize = self::getMaxUploadSize( $this->getSourceType() ); - if( $this->mFileSize > $maxSize ) { + if ( $this->mFileSize > $maxSize ) { wfProfileOut( __METHOD__ ); return array( 'status' => self::FILE_TOO_LARGE, @@ -297,7 +300,7 @@ abstract class UploadBase { * probably not accept it. */ $verification = $this->verifyFile(); - if( $verification !== true ) { + if ( $verification !== true ) { wfProfileOut( __METHOD__ ); return array( 'status' => self::VERIFICATION_ERROR, @@ -309,13 +312,13 @@ abstract class UploadBase { * Make sure this file can be created */ $result = $this->validateName(); - if( $result !== true ) { + if ( $result !== true ) { wfProfileOut( __METHOD__ ); return $result; } $error = ''; - if( !wfRunHooks( 'UploadVerification', + if ( !wfRunHooks( 'UploadVerification', array( $this->mDestName, $this->mTempPath, &$error ) ) ) { wfProfileOut( __METHOD__ ); @@ -332,11 +335,11 @@ abstract class UploadBase { * @return mixed true if valid, otherwise and array with 'status' * and other keys **/ - protected function validateName() { + public function validateName() { $nt = $this->getTitle(); - if( is_null( $nt ) ) { + if ( is_null( $nt ) ) { $result = array( 'status' => $this->mTitleError ); - if( $this->mTitleError == self::ILLEGAL_FILENAME ) { + if ( $this->mTitleError == self::ILLEGAL_FILENAME ) { $result['filtered'] = $this->mFilteredName; } if ( $this->mTitleError == self::FILETYPE_BADTYPE ) { @@ -353,7 +356,7 @@ abstract class UploadBase { } /** - * Verify the mime type + * Verify the mime type. * * @note Only checks that it is not an evil mime. The does it have * correct extension given its mime type check is in verifyFile. @@ -364,7 +367,7 @@ abstract class UploadBase { global $wgVerifyMimeType; wfProfileIn( __METHOD__ ); if ( $wgVerifyMimeType ) { - wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n" ); + wfDebug( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n" ); global $wgMimeTypeBlacklist; if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { wfProfileOut( __METHOD__ ); @@ -391,6 +394,7 @@ abstract class UploadBase { return true; } + /** * Verifies that it's ok to include the uploaded file * @@ -417,6 +421,7 @@ abstract class UploadBase { } } + $handler = MediaHandler::getHandler( $mime ); if ( $handler ) { $handlerStatus = $handler->verifyUpload( $this->mTempPath ); @@ -450,8 +455,7 @@ abstract class UploadBase { global $wgAllowJavaUploads, $wgDisableUploadScriptChecks; wfProfileIn( __METHOD__ ); - # get the title, even though we are doing nothing with it, because - # we need to populate mFinalExtension + # getTitle() sets some internal parameters like $this->mFinalExtension $this->getTitle(); $this->mFileProps = FSFile::getPropsFromPath( $this->mTempPath, $this->mFinalExtension ); @@ -466,12 +470,12 @@ abstract class UploadBase { # check for htmlish code and javascript if ( !$wgDisableUploadScriptChecks ) { - if( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) { + if ( self::detectScript( $this->mTempPath, $mime, $this->mFinalExtension ) ) { wfProfileOut( __METHOD__ ); return array( 'uploadscripted' ); } - if( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { - if( $this->detectScriptInSvg( $this->mTempPath ) ) { + if ( $this->mFinalExtension == 'svg' || $mime == 'image/svg+xml' ) { + if ( $this->detectScriptInSvg( $this->mTempPath ) ) { wfProfileOut( __METHOD__ ); return array( 'uploadscripted' ); } @@ -560,7 +564,7 @@ abstract class UploadBase { * to modify it by uploading a new revision. */ $nt = $this->getTitle(); - if( is_null( $nt ) ) { + if ( is_null( $nt ) ) { return true; } $permErrors = $nt->getUserPermissionsErrors( 'edit', $user ); @@ -570,7 +574,7 @@ abstract class UploadBase { } else { $permErrorsCreate = array(); } - if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { + if ( $permErrors || $permErrorsUpload || $permErrorsCreate ) { $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) ); return $permErrors; @@ -607,16 +611,17 @@ abstract class UploadBase { $comparableName = str_replace( ' ', '_', $this->mDesiredDestName ); $comparableName = Title::capitalize( $comparableName, NS_FILE ); - if( $this->mDesiredDestName != $filename && $comparableName != $filename ) { + if ( $this->mDesiredDestName != $filename && $comparableName != $filename ) { $warnings['badfilename'] = $filename; } // Check whether the file extension is on the unwanted list global $wgCheckFileExtensions, $wgFileExtensions; if ( $wgCheckFileExtensions ) { - if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) { + $extensions = array_unique( $wgFileExtensions ); + if ( !$this->checkFileExtension( $this->mFinalExtension, $extensions ) ) { $warnings['filetype-unwanted-type'] = array( $this->mFinalExtension, - $wgLang->commaList( $wgFileExtensions ), count( $wgFileExtensions ) ); + $wgLang->commaList( $extensions ), count( $extensions ) ); } } @@ -630,7 +635,7 @@ abstract class UploadBase { } $exists = self::getExistsWarning( $localFile ); - if( $exists !== false ) { + if ( $exists !== false ) { $warnings['exists'] = $exists; } @@ -640,11 +645,11 @@ abstract class UploadBase { $title = $this->getTitle(); // Remove all matches against self foreach ( $dupes as $key => $dupe ) { - if( $title->equals( $dupe->getTitle() ) ) { + if ( $title->equals( $dupe->getTitle() ) ) { unset( $dupes[$key] ); } } - if( $dupes ) { + if ( $dupes ) { $warnings['duplicate'] = $dupes; } @@ -682,9 +687,9 @@ abstract class UploadBase { $user ); - if( $status->isGood() ) { + if ( $status->isGood() ) { if ( $watch ) { - $user->addWatch( $this->getLocalFile()->getTitle() ); + WatchAction::doWatch( $this->getLocalFile()->getTitle(), $user, WatchedItem::IGNORE_USER_RIGHTS ); } wfRunHooks( 'UploadComplete', array( &$this ) ); } @@ -703,7 +708,6 @@ abstract class UploadBase { if ( $this->mTitle !== false ) { return $this->mTitle; } - /* Assume that if a user specified File:Something.jpg, this is an error * and that the namespace prefix needs to be stripped of. */ @@ -729,7 +733,7 @@ abstract class UploadBase { $this->mFilteredName = wfStripIllegalFilenameChars( $this->mFilteredName ); /* Normalize to title form before we do any further processing */ $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName ); - if( is_null( $nt ) ) { + if ( is_null( $nt ) ) { $this->mTitleError = self::ILLEGAL_FILENAME; return $this->mTitle = null; } @@ -741,7 +745,7 @@ abstract class UploadBase { */ list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName ); - if( count( $ext ) ) { + if ( count( $ext ) ) { $this->mFinalExtension = trim( $ext[count( $ext ) - 1] ); } else { $this->mFinalExtension = ''; @@ -789,13 +793,13 @@ abstract class UploadBase { # If there was more than one "extension", reassemble the base # filename to prevent bogus complaints about length - if( count( $ext ) > 1 ) { - for( $i = 0; $i < count( $ext ) - 1; $i++ ) { + if ( count( $ext ) > 1 ) { + for ( $i = 0; $i < count( $ext ) - 1; $i++ ) { $partname .= '.' . $ext[$i]; } } - if( strlen( $partname ) < 1 ) { + if ( strlen( $partname ) < 1 ) { $this->mTitleError = self::MIN_LENGTH_PARTNAME; return $this->mTitle = null; } @@ -809,7 +813,7 @@ abstract class UploadBase { * @return LocalFile|null */ public function getLocalFile() { - if( is_null( $this->mLocalFile ) ) { + if ( is_null( $this->mLocalFile ) ) { $nt = $this->getTitle(); $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt ); } @@ -922,23 +926,29 @@ abstract class UploadBase { public static function verifyExtension( $mime, $extension ) { $magic = MimeMagic::singleton(); - if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) + if ( !$mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) { if ( !$magic->isRecognizableExtension( $extension ) ) { wfDebug( __METHOD__ . ": passing file with unknown detected mime type; " . "unrecognized extension '$extension', can't verify\n" ); return true; } else { - wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; ". + wfDebug( __METHOD__ . ": rejecting file with unknown detected mime type; " . "recognized extension '$extension', so probably invalid file\n" ); return false; } + } $match = $magic->isMatchingExtension( $extension, $mime ); if ( $match === null ) { - wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" ); - return true; - } elseif( $match === true ) { + if ( $magic->getTypesForExtension( $extension ) !== null ) { + wfDebug( __METHOD__ . ": No extension known for $mime, but we know a mime for $extension\n" ); + return false; + } else { + wfDebug( __METHOD__ . ": no file extension known for mime type $mime, passing file\n" ); + return true; + } + } elseif ( $match === true ) { wfDebug( __METHOD__ . ": mime type $mime matches extension $extension, passing file\n" ); #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! @@ -968,7 +978,7 @@ abstract class UploadBase { # ugly hack: for text files, always look at the entire file. # For binary field, just check the first K. - if( strpos( $mime, 'text/' ) === 0 ) { + if ( strpos( $mime, 'text/' ) === 0 ) { $chunk = file_get_contents( $file ); } else { $fp = fopen( $file, 'rb' ); @@ -978,21 +988,21 @@ abstract class UploadBase { $chunk = strtolower( $chunk ); - if( !$chunk ) { + if ( !$chunk ) { wfProfileOut( __METHOD__ ); return false; } # decode from UTF-16 if needed (could be used for obfuscation). - if( substr( $chunk, 0, 2 ) == "\xfe\xff" ) { + if ( substr( $chunk, 0, 2 ) == "\xfe\xff" ) { $enc = 'UTF-16BE'; - } elseif( substr( $chunk, 0, 2 ) == "\xff\xfe" ) { + } elseif ( substr( $chunk, 0, 2 ) == "\xff\xfe" ) { $enc = 'UTF-16LE'; } else { $enc = null; } - if( $enc ) { + if ( $enc ) { $chunk = iconv( $enc, "ASCII//IGNORE", $chunk ); } @@ -1042,12 +1052,12 @@ abstract class UploadBase { '<table' ); - if( !$wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) { + if ( !$wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) { $tags[] = '<title'; } - foreach( $tags as $tag ) { - if( false !== strpos( $chunk, $tag ) ) { + foreach ( $tags as $tag ) { + if ( false !== strpos( $chunk, $tag ) ) { wfDebug( __METHOD__ . ": found something that may make it be mistaken for html: $tag\n" ); wfProfileOut( __METHOD__ ); return true; @@ -1062,21 +1072,21 @@ abstract class UploadBase { $chunk = Sanitizer::decodeCharReferences( $chunk ); # look for script-types - if( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) { + if ( preg_match( '!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim', $chunk ) ) { wfDebug( __METHOD__ . ": found script types\n" ); wfProfileOut( __METHOD__ ); return true; } # look for html-style script-urls - if( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) { + if ( preg_match( '!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) { wfDebug( __METHOD__ . ": found html-style script urls\n" ); wfProfileOut( __METHOD__ ); return true; } # look for css-style script-urls - if( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) { + if ( preg_match( '!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim', $chunk ) ) { wfDebug( __METHOD__ . ": found css-style script urls\n" ); wfProfileOut( __METHOD__ ); return true; @@ -1112,7 +1122,7 @@ abstract class UploadBase { // bytes. There shouldn't be a legitimate reason for this to happen. wfDebug( __METHOD__ . ": Unmatched XML declaration start\n" ); return true; - } elseif ( substr( $contents, 0, 4) == "\x4C\x6F\xA7\x94" ) { + } elseif ( substr( $contents, 0, 4 ) == "\x4C\x6F\xA7\x94" ) { // EBCDIC encoded XML wfDebug( __METHOD__ . ": EBCDIC Encoded XML\n" ); return true; @@ -1164,77 +1174,77 @@ abstract class UploadBase { /* * check for elements that can contain javascript */ - if( $strippedElement == 'script' ) { + if ( $strippedElement == 'script' ) { wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" ); return true; } # e.g., <svg xmlns="http://www.w3.org/2000/svg"> <handler xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="load">alert(1)</handler> </svg> - if( $strippedElement == 'handler' ) { + if ( $strippedElement == 'handler' ) { wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" ); return true; } # SVG reported in Feb '12 that used xml:stylesheet to generate javascript block - if( $strippedElement == 'stylesheet' ) { + if ( $strippedElement == 'stylesheet' ) { wfDebug( __METHOD__ . ": Found scriptable element '$element' in uploaded file.\n" ); return true; } - foreach( $attribs as $attrib => $value ) { + foreach ( $attribs as $attrib => $value ) { $stripped = $this->stripXmlNamespace( $attrib ); $value = strtolower( $value ); - if( substr( $stripped, 0, 2 ) == 'on' ) { + if ( substr( $stripped, 0, 2 ) == 'on' ) { wfDebug( __METHOD__ . ": Found event-handler attribute '$attrib'='$value' in uploaded file.\n" ); return true; } # href with javascript target - if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) { + if ( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) { wfDebug( __METHOD__ . ": Found script in href attribute '$attrib'='$value' in uploaded file.\n" ); return true; } # href with embedded svg as target - if( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) { + if ( $stripped == 'href' && preg_match( '!data:[^,]*image/svg[^,]*,!sim', $value ) ) { wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); return true; } # href with embedded (text/xml) svg as target - if( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) { + if ( $stripped == 'href' && preg_match( '!data:[^,]*text/xml[^,]*,!sim', $value ) ) { wfDebug( __METHOD__ . ": Found href to embedded svg \"<$strippedElement '$attrib'='$value'...\" in uploaded file.\n" ); return true; } # use set/animate to add event-handler attribute to parent - if( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) { + if ( ( $strippedElement == 'set' || $strippedElement == 'animate' ) && $stripped == 'attributename' && substr( $value, 0, 2 ) == 'on' ) { wfDebug( __METHOD__ . ": Found svg setting event-handler attribute with \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" ); return true; } # use set to add href attribute to parent element - if( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) { + if ( $strippedElement == 'set' && $stripped == 'attributename' && strpos( $value, 'href' ) !== false ) { wfDebug( __METHOD__ . ": Found svg setting href attribute '$value' in uploaded file.\n" ); return true; } # use set to add a remote / data / script target to an element - if( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) { + if ( $strippedElement == 'set' && $stripped == 'to' && preg_match( '!(http|https|data|script):!sim', $value ) ) { wfDebug( __METHOD__ . ": Found svg setting attribute to '$value' in uploaded file.\n" ); return true; } # use handler attribute with remote / data / script - if( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) { + if ( $stripped == 'handler' && preg_match( '!(http|https|data|script):!sim', $value ) ) { wfDebug( __METHOD__ . ": Found svg setting handler with remote/data/script '$attrib'='$value' in uploaded file.\n" ); return true; } # use CSS styles to bring in remote code # catch url("http:..., url('http:..., url(http:..., but not url("#..., url('#..., url(#.... - if( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) { + if ( $stripped == 'style' && preg_match_all( '!((?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*["\']?\s*[^#]+.*?\))!sim', $value, $matches ) ) { foreach ( $matches[1] as $match ) { if ( !preg_match( '!(?:font|clip-path|fill|filter|marker|marker-end|marker-mid|marker-start|mask|stroke)\s*:\s*url\s*\(\s*(#|\'#|"#)!sim', $match ) ) { wfDebug( __METHOD__ . ": Found svg setting a style with remote url '$attrib'='$value' in uploaded file.\n" ); @@ -1244,7 +1254,7 @@ abstract class UploadBase { } # image filters can pull in url, which could be svg that executes scripts - if( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) { + if ( $strippedElement == 'image' && $stripped == 'filter' && preg_match( '!url\s*\(!sim', $value ) ) { wfDebug( __METHOD__ . ": Found image filter with url: \"<$strippedElement $stripped='$value'...\" in uploaded file.\n" ); return true; } @@ -1314,7 +1324,7 @@ abstract class UploadBase { # NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. # that does not seem to be worth the pain. # Ask me (Duesentrieb) about it if it's ever needed. - $output = wfShellExec( "$command 2>&1", $exitCode ); + $output = wfShellExecWithStderr( $command, $exitCode ); # map exit code to AV_xxx constants. $mappedCode = $exitCode; @@ -1374,8 +1384,8 @@ abstract class UploadBase { private function checkOverwrite( $user ) { // First check whether the local file can be overwritten $file = $this->getLocalFile(); - if( $file->exists() ) { - if( !self::userCanReUpload( $user, $file ) ) { + if ( $file->exists() ) { + if ( !self::userCanReUpload( $user, $file ) ) { return array( 'fileexists-forbidden', $file->getName() ); } else { return true; @@ -1401,13 +1411,13 @@ abstract class UploadBase { * @return Boolean */ public static function userCanReUpload( User $user, $img ) { - if( $user->isAllowed( 'reupload' ) ) { + if ( $user->isAllowed( 'reupload' ) ) { return true; // non-conditional } - if( !$user->isAllowed( 'reupload-own' ) ) { + if ( !$user->isAllowed( 'reupload-own' ) ) { return false; } - if( is_string( $img ) ) { + if ( is_string( $img ) ) { $img = wfLocalFile( $img ); } if ( !( $img instanceof LocalFile ) ) { @@ -1429,11 +1439,11 @@ abstract class UploadBase { * @return mixed False if the file does not exists, else an array */ public static function getExistsWarning( $file ) { - if( $file->exists() ) { + if ( $file->exists() ) { return array( 'warning' => 'exists', 'file' => $file ); } - if( $file->getTitle()->getArticleID() ) { + if ( $file->getTitle()->getArticleID() ) { return array( 'warning' => 'page-exists', 'file' => $file ); } @@ -1441,7 +1451,7 @@ abstract class UploadBase { return array( 'warning' => 'was-deleted', 'file' => $file ); } - if( strpos( $file->getName(), '.' ) == false ) { + if ( strpos( $file->getName(), '.' ) == false ) { $partname = $file->getName(); $extension = ''; } else { @@ -1460,7 +1470,7 @@ abstract class UploadBase { $nt_lc = Title::makeTitle( NS_FILE, "{$partname}.{$normalizedExtension}" ); $file_lc = wfLocalFile( $nt_lc ); - if( $file_lc->exists() ) { + if ( $file_lc->exists() ) { return array( 'warning' => 'exists-normalized', 'file' => $file, @@ -1484,7 +1494,7 @@ abstract class UploadBase { # Check for filenames like 50px- or 180px-, these are mostly thumbnails $nt_thb = Title::newFromText( substr( $partname, strpos( $partname, '-' ) + 1 ) . '.' . $extension, NS_FILE ); $file_thb = wfLocalFile( $nt_thb ); - if( $file_thb->exists() ) { + if ( $file_thb->exists() ) { return array( 'warning' => 'thumb', 'file' => $file, @@ -1500,7 +1510,7 @@ abstract class UploadBase { } } - foreach( self::getFilenamePrefixBlacklist() as $prefix ) { + foreach ( self::getFilenamePrefixBlacklist() as $prefix ) { if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { return array( 'warning' => 'bad-prefix', @@ -1536,9 +1546,9 @@ abstract class UploadBase { public static function getFilenamePrefixBlacklist() { $blacklist = array(); $message = wfMessage( 'filename-prefix-blacklist' )->inContentLanguage(); - if( !$message->isDisabled() ) { + if ( !$message->isDisabled() ) { $lines = explode( "\n", $message->plain() ); - foreach( $lines as $line ) { + foreach ( $lines as $line ) { // Remove comment lines $comment = substr( trim( $line ), 0, 1 ); if ( $comment == '#' || $comment == '' ) { @@ -1547,7 +1557,7 @@ abstract class UploadBase { // Remove additional comments after a prefix $comment = strpos( $line, '#' ); if ( $comment > 0 ) { - $line = substr( $line, 0, $comment-1 ); + $line = substr( $line, 0, $comment - 1 ); } $blacklist[] = trim( $line ); } diff --git a/includes/upload/UploadFromChunks.php b/includes/upload/UploadFromChunks.php index 4b331e98..2e0b9444 100644 --- a/includes/upload/UploadFromChunks.php +++ b/includes/upload/UploadFromChunks.php @@ -31,7 +31,7 @@ class UploadFromChunks extends UploadFromFile { protected $mOffset, $mChunkIndex, $mFileKey, $mVirtualTempPath; /** - * Setup local pointers to stash, repo and user ( similar to UploadFromStash ) + * Setup local pointers to stash, repo and user (similar to UploadFromStash) * * @param $user User * @param $stash UploadStash @@ -41,16 +41,16 @@ class UploadFromChunks extends UploadFromFile { // user object. sometimes this won't exist, as when running from cron. $this->user = $user; - if( $repo ) { + if ( $repo ) { $this->repo = $repo; } else { $this->repo = RepoGroup::singleton()->getLocalRepo(); } - if( $stash ) { + if ( $stash ) { $this->stash = $stash; } else { - if( $user ) { + if ( $user ) { wfDebug( __METHOD__ . " creating new UploadFromChunks instance for " . $user->getId() . "\n" ); } else { wfDebug( __METHOD__ . " creating new UploadFromChunks instance with no user\n" ); @@ -74,7 +74,7 @@ class UploadFromChunks extends UploadFromFile { $this->verifyChunk(); // Create a local stash target $this->mLocalFile = parent::stashFile(); - // Update the initial file offset ( based on file size ) + // Update the initial file offset (based on file size) $this->mOffset = $this->mLocalFile->getSize(); $this->mFileKey = $this->mLocalFile->getFileKey(); @@ -114,7 +114,7 @@ class UploadFromChunks extends UploadFromFile { // Concatenate all the chunks to mVirtualTempPath $fileList = Array(); // The first chunk is stored at the mVirtualTempPath path so we start on "chunk 1" - for( $i = 0; $i <= $this->getChunkIndex(); $i++ ) { + for ( $i = 0; $i <= $this->getChunkIndex(); $i++ ) { $fileList[] = $this->getVirtualChunkLocation( $i ); } @@ -129,7 +129,7 @@ class UploadFromChunks extends UploadFromFile { $tStart = microtime( true ); $status = $this->repo->concatenate( $fileList, $tmpPath, FileRepo::DELETE_SOURCE ); $tAmount = microtime( true ) - $tStart; - if( !$status->isOk() ) { + if ( !$status->isOk() ) { return $status; } wfDebugLog( 'fileconcatenate', "Combined $i chunks in $tAmount seconds.\n" ); @@ -144,7 +144,7 @@ class UploadFromChunks extends UploadFromFile { } // Update the mTempPath and mLocalFile - // ( for FileUpload or normal Stash to take over ) + // (for FileUpload or normal Stash to take over) $tStart = microtime( true ); $this->mLocalFile = parent::stashFile( $this->user ); $tAmount = microtime( true ) - $tStart; @@ -193,7 +193,7 @@ class UploadFromChunks extends UploadFromFile { // Get the offset before we add the chunk to the file system $preAppendOffset = $this->getOffset(); - if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize()) { + if ( $preAppendOffset + $chunkSize > $this->getMaxUploadSize() ) { $status = Status::newFatal( 'file-too-large' ); } else { // Make sure the client is uploading the correct chunk with a matching offset. @@ -210,7 +210,7 @@ class UploadFromChunks extends UploadFromFile { return Status::newFatal( $e->getMessage() ); } $status = $this->outputChunk( $chunkPath ); - if( $status->isGood() ) { + if ( $status->isGood() ) { // Update local offset: $this->mOffset = $preAppendOffset + $chunkSize; // Update chunk table status db @@ -277,7 +277,7 @@ class UploadFromChunks extends UploadFromFile { * @return Integer index of the current chunk */ private function getChunkIndex() { - if( $this->mChunkIndex !== null ) { + if ( $this->mChunkIndex !== null ) { return $this->mChunkIndex; } return 0; @@ -327,7 +327,7 @@ class UploadFromChunks extends UploadFromFile { } private function getChunkFileKey( $index = null ) { - if( $index === null ) { + if ( $index === null ) { $index = $this->getChunkIndex(); } return $this->mFileKey . '.' . $index; @@ -346,7 +346,7 @@ class UploadFromChunks extends UploadFromFile { $res = $this->verifyPartialFile(); $this->mDesiredDestName = $oldDesiredDestName; $this->mTitle = false; - if( is_array( $res ) ) { + if ( is_array( $res ) ) { throw new UploadChunkVerificationException( $res[0] ); } } diff --git a/includes/upload/UploadFromFile.php b/includes/upload/UploadFromFile.php index ab2a7a39..a00ed327 100644 --- a/includes/upload/UploadFromFile.php +++ b/includes/upload/UploadFromFile.php @@ -40,7 +40,7 @@ class UploadFromFile extends UploadBase { function initializeFromRequest( &$request ) { $upload = $request->getUpload( 'wpUploadFile' ); $desiredDestName = $request->getText( 'wpDestFile' ); - if( !$desiredDestName ) { + if ( !$desiredDestName ) { $desiredDestName = $upload->getName(); } diff --git a/includes/upload/UploadFromStash.php b/includes/upload/UploadFromStash.php index c82103cb..cb85fc63 100644 --- a/includes/upload/UploadFromStash.php +++ b/includes/upload/UploadFromStash.php @@ -45,16 +45,16 @@ class UploadFromStash extends UploadBase { // user object. sometimes this won't exist, as when running from cron. $this->user = $user; - if( $repo ) { + if ( $repo ) { $this->repo = $repo; } else { $this->repo = RepoGroup::singleton()->getLocalRepo(); } - if( $stash ) { + if ( $stash ) { $this->stash = $stash; } else { - if( $user ) { + if ( $user ) { wfDebug( __METHOD__ . " creating new UploadStash instance for " . $user->getId() . "\n" ); } else { wfDebug( __METHOD__ . " creating new UploadStash instance with no user\n" ); diff --git a/includes/upload/UploadFromUrl.php b/includes/upload/UploadFromUrl.php index 70b69034..0201d5f4 100644 --- a/includes/upload/UploadFromUrl.php +++ b/includes/upload/UploadFromUrl.php @@ -34,6 +34,8 @@ class UploadFromUrl extends UploadBase { protected $mTempPath, $mTmpHandle; + protected static $allowedUrls = array(); + /** * Checks if the user is allowed to use the upload-by-URL feature. If the * user is not allowed, return the name of the user right as a string. If @@ -77,7 +79,7 @@ class UploadFromUrl extends UploadBase { return false; } $valid = false; - foreach( $wgCopyUploadsDomains as $domain ) { + foreach ( $wgCopyUploadsDomains as $domain ) { // See if the domain for the upload matches this whitelisted domain $whitelistedDomainPieces = explode( '.', $domain ); $uploadDomainPieces = explode( '.', $parsedUrl['host'] ); @@ -105,6 +107,21 @@ class UploadFromUrl extends UploadBase { } /** + * Checks whether the URL is not allowed. + * + * @param $url string + * @return bool + */ + public static function isAllowedUrl( $url ) { + if ( !isset( self::$allowedUrls[$url] ) ) { + $allowed = true; + wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) ); + self::$allowedUrls[$url] = $allowed; + } + return self::$allowedUrls[$url]; + } + + /** * Entry point for API upload * * @param $name string @@ -160,21 +177,30 @@ class UploadFromUrl extends UploadBase { /** * @return string */ - public function getSourceType() { return 'url'; } + public function getSourceType() { + return 'url'; + } /** + * Download the file (if not async) + * + * @param Array $httpOptions Array of options for MWHttpRequest. Ignored if async. + * This could be used to override the timeout on the http request. * @return Status */ - public function fetchFile() { + public function fetchFile( $httpOptions = array() ) { if ( !Http::isValidURI( $this->mUrl ) ) { return Status::newFatal( 'http-invalid-url' ); } - if( !self::isAllowedHost( $this->mUrl ) ) { + if ( !self::isAllowedHost( $this->mUrl ) ) { return Status::newFatal( 'upload-copy-upload-invalid-domain' ); } + if ( !self::isAllowedUrl( $this->mUrl ) ) { + return Status::newFatal( 'upload-copy-upload-invalid-url' ); + } if ( !$this->mAsync ) { - return $this->reallyFetchFile(); + return $this->reallyFetchFile( $httpOptions ); } return Status::newGood(); } @@ -211,9 +237,12 @@ class UploadFromUrl extends UploadBase { /** * Download the file, save it to the temporary file and update the file * size and set $mRemoveTempFile to true. + * + * @param Array $httpOptions Array of options for MWHttpRequest * @return Status */ - protected function reallyFetchFile() { + protected function reallyFetchFile( $httpOptions = array() ) { + global $wgCopyUploadProxy, $wgCopyUploadTimeout; if ( $this->mTempPath === false ) { return Status::newFatal( 'tmp-create-error' ); } @@ -227,13 +256,15 @@ class UploadFromUrl extends UploadBase { $this->mRemoveTempFile = true; $this->mFileSize = 0; - $options = array( - 'followRedirects' => true + $options = $httpOptions + array( + 'followRedirects' => true, ); - global $wgCopyUploadProxy; if ( $wgCopyUploadProxy !== false ) { $options['proxy'] = $wgCopyUploadProxy; } + if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) { + $options['timeout'] = $wgCopyUploadTimeout; + } $req = MWHttpRequest::factory( $this->mUrl, $options ); $req->setCallback( array( $this, 'saveTempFileChunk' ) ); $status = $req->execute(); diff --git a/includes/upload/UploadStash.php b/includes/upload/UploadStash.php index 089bd8b7..7db6c64b 100644 --- a/includes/upload/UploadStash.php +++ b/includes/upload/UploadStash.php @@ -158,7 +158,7 @@ class UploadStash { * @param string $key key under which file information is stored * @return Array */ - public function getMetadata ( $key ) { + public function getMetadata( $key ) { $this->getFile( $key ); return $this->fileMetadata[$key]; } @@ -169,7 +169,7 @@ class UploadStash { * @param string $key key under which file information is stored * @return Array */ - public function getFileProps ( $key ) { + public function getFileProps( $key ) { $this->getFile( $key ); return $this->fileProps[$key]; } @@ -209,7 +209,7 @@ class UploadStash { list( $usec, $sec ) = explode( ' ', microtime() ); $usec = substr( $usec, 2 ); $key = wfBaseConvert( $sec . $usec, 10, 36 ) . '.' . - wfBaseConvert( mt_rand(), 10, 36 ) . '.'. + wfBaseConvert( mt_rand(), 10, 36 ) . '.' . $this->userId . '.' . $extension; @@ -338,7 +338,7 @@ class UploadStash { __METHOD__ ); - if( !$row ) { + if ( !$row ) { throw new UploadStashNoSuchKeyException( "No such key ($key), cannot remove" ); } @@ -358,7 +358,7 @@ class UploadStash { wfDebug( __METHOD__ . " clearing row $key\n" ); // Ensure we have the UploadStashFile loaded for this key - $this->getFile( $key ); + $this->getFile( $key, true ); $dbw = $this->repo->getMasterDb(); @@ -463,7 +463,7 @@ class UploadStash { protected function fetchFileMetadata( $key, $readFromDB = DB_SLAVE ) { // populate $fileMetadata[$key] $dbr = null; - if( $readFromDB === DB_MASTER ) { + if ( $readFromDB === DB_MASTER ) { // sometimes reading from the master is necessary, if there's replication lag. $dbr = $this->repo->getMasterDb(); } else { @@ -675,11 +675,12 @@ class UploadStashFile extends UnregisteredLocalFile { } -class UploadStashNotAvailableException extends MWException {}; -class UploadStashFileNotFoundException extends MWException {}; -class UploadStashBadPathException extends MWException {}; -class UploadStashFileException extends MWException {}; -class UploadStashZeroLengthFileException extends MWException {}; -class UploadStashNotLoggedInException extends MWException {}; -class UploadStashWrongOwnerException extends MWException {}; -class UploadStashNoSuchKeyException extends MWException {}; +class UploadStashException extends MWException {}; +class UploadStashNotAvailableException extends UploadStashException {}; +class UploadStashFileNotFoundException extends UploadStashException {}; +class UploadStashBadPathException extends UploadStashException {}; +class UploadStashFileException extends UploadStashException {}; +class UploadStashZeroLengthFileException extends UploadStashException {}; +class UploadStashNotLoggedInException extends UploadStashException {}; +class UploadStashWrongOwnerException extends UploadStashException {}; +class UploadStashNoSuchKeyException extends UploadStashException {}; |