MediaWiki  REL1_24
UploadFromUrl.php
Go to the documentation of this file.
00001 <?php
00031 class UploadFromUrl extends UploadBase {
00032     protected $mAsync, $mUrl;
00033     protected $mIgnoreWarnings = true;
00034 
00035     protected $mTempPath, $mTmpHandle;
00036 
00037     protected static $allowedUrls = array();
00038 
00048     public static function isAllowed( $user ) {
00049         if ( !$user->isAllowed( 'upload_by_url' ) ) {
00050             return 'upload_by_url';
00051         }
00052 
00053         return parent::isAllowed( $user );
00054     }
00055 
00060     public static function isEnabled() {
00061         global $wgAllowCopyUploads;
00062 
00063         return $wgAllowCopyUploads && parent::isEnabled();
00064     }
00065 
00074     public static function isAllowedHost( $url ) {
00075         global $wgCopyUploadsDomains;
00076         if ( !count( $wgCopyUploadsDomains ) ) {
00077             return true;
00078         }
00079         $parsedUrl = wfParseUrl( $url );
00080         if ( !$parsedUrl ) {
00081             return false;
00082         }
00083         $valid = false;
00084         foreach ( $wgCopyUploadsDomains as $domain ) {
00085             // See if the domain for the upload matches this whitelisted domain
00086             $whitelistedDomainPieces = explode( '.', $domain );
00087             $uploadDomainPieces = explode( '.', $parsedUrl['host'] );
00088             if ( count( $whitelistedDomainPieces ) === count( $uploadDomainPieces ) ) {
00089                 $valid = true;
00090                 // See if all the pieces match or not (excluding wildcards)
00091                 foreach ( $whitelistedDomainPieces as $index => $piece ) {
00092                     if ( $piece !== '*' && $piece !== $uploadDomainPieces[$index] ) {
00093                         $valid = false;
00094                     }
00095                 }
00096                 if ( $valid ) {
00097                     // We found a match, so quit comparing against the list
00098                     break;
00099                 }
00100             }
00101             /* Non-wildcard test
00102             if ( $parsedUrl['host'] === $domain ) {
00103                 $valid = true;
00104                 break;
00105             }
00106             */
00107         }
00108 
00109         return $valid;
00110     }
00111 
00118     public static function isAllowedUrl( $url ) {
00119         if ( !isset( self::$allowedUrls[$url] ) ) {
00120             $allowed = true;
00121             wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
00122             self::$allowedUrls[$url] = $allowed;
00123         }
00124 
00125         return self::$allowedUrls[$url];
00126     }
00127 
00138     public function initialize( $name, $url, $async = false ) {
00139         global $wgAllowAsyncCopyUploads;
00140 
00141         $this->mUrl = $url;
00142         $this->mAsync = $wgAllowAsyncCopyUploads ? $async : false;
00143         if ( $async ) {
00144             throw new MWException( 'Asynchronous copy uploads are no longer possible as of r81612.' );
00145         }
00146 
00147         $tempPath = $this->mAsync ? null : $this->makeTemporaryFile();
00148         # File size and removeTempFile will be filled in later
00149         $this->initializePathInfo( $name, $tempPath, 0, false );
00150     }
00151 
00156     public function initializeFromRequest( &$request ) {
00157         $desiredDestName = $request->getText( 'wpDestFile' );
00158         if ( !$desiredDestName ) {
00159             $desiredDestName = $request->getText( 'wpUploadFileURL' );
00160         }
00161         $this->initialize(
00162             $desiredDestName,
00163             trim( $request->getVal( 'wpUploadFileURL' ) ),
00164             false
00165         );
00166     }
00167 
00172     public static function isValidRequest( $request ) {
00173         global $wgUser;
00174 
00175         $url = $request->getVal( 'wpUploadFileURL' );
00176 
00177         return !empty( $url )
00178             && Http::isValidURI( $url )
00179             && $wgUser->isAllowed( 'upload_by_url' );
00180     }
00181 
00185     public function getSourceType() {
00186         return 'url';
00187     }
00188 
00196     public function fetchFile( $httpOptions = array() ) {
00197         if ( !Http::isValidURI( $this->mUrl ) ) {
00198             return Status::newFatal( 'http-invalid-url' );
00199         }
00200 
00201         if ( !self::isAllowedHost( $this->mUrl ) ) {
00202             return Status::newFatal( 'upload-copy-upload-invalid-domain' );
00203         }
00204         if ( !self::isAllowedUrl( $this->mUrl ) ) {
00205             return Status::newFatal( 'upload-copy-upload-invalid-url' );
00206         }
00207         if ( !$this->mAsync ) {
00208             return $this->reallyFetchFile( $httpOptions );
00209         }
00210 
00211         return Status::newGood();
00212     }
00213 
00219     protected function makeTemporaryFile() {
00220         $tmpFile = TempFSFile::factory( 'URL' );
00221         $tmpFile->bind( $this );
00222 
00223         return $tmpFile->getPath();
00224     }
00225 
00233     public function saveTempFileChunk( $req, $buffer ) {
00234         $nbytes = fwrite( $this->mTmpHandle, $buffer );
00235 
00236         if ( $nbytes == strlen( $buffer ) ) {
00237             $this->mFileSize += $nbytes;
00238         } else {
00239             // Well... that's not good!
00240             fclose( $this->mTmpHandle );
00241             $this->mTmpHandle = false;
00242         }
00243 
00244         return $nbytes;
00245     }
00246 
00254     protected function reallyFetchFile( $httpOptions = array() ) {
00255         global $wgCopyUploadProxy, $wgCopyUploadTimeout;
00256         if ( $this->mTempPath === false ) {
00257             return Status::newFatal( 'tmp-create-error' );
00258         }
00259 
00260         // Note the temporary file should already be created by makeTemporaryFile()
00261         $this->mTmpHandle = fopen( $this->mTempPath, 'wb' );
00262         if ( !$this->mTmpHandle ) {
00263             return Status::newFatal( 'tmp-create-error' );
00264         }
00265 
00266         $this->mRemoveTempFile = true;
00267         $this->mFileSize = 0;
00268 
00269         $options = $httpOptions + array( 'followRedirects' => true );
00270 
00271         if ( $wgCopyUploadProxy !== false ) {
00272             $options['proxy'] = $wgCopyUploadProxy;
00273         }
00274 
00275         if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) {
00276             $options['timeout'] = $wgCopyUploadTimeout;
00277         }
00278         $req = MWHttpRequest::factory( $this->mUrl, $options );
00279         $req->setCallback( array( $this, 'saveTempFileChunk' ) );
00280         $status = $req->execute();
00281 
00282         if ( $this->mTmpHandle ) {
00283             // File got written ok...
00284             fclose( $this->mTmpHandle );
00285             $this->mTmpHandle = null;
00286         } else {
00287             // We encountered a write error during the download...
00288             return Status::newFatal( 'tmp-write-error' );
00289         }
00290 
00291         if ( !$status->isOk() ) {
00292             return $status;
00293         }
00294 
00295         return $status;
00296     }
00297 
00303     public function verifyUpload() {
00304         if ( $this->mAsync ) {
00305             return array( 'status' => UploadBase::OK );
00306         }
00307 
00308         return parent::verifyUpload();
00309     }
00310 
00316     public function checkWarnings() {
00317         if ( $this->mAsync ) {
00318             $this->mIgnoreWarnings = false;
00319 
00320             return array();
00321         }
00322 
00323         return parent::checkWarnings();
00324     }
00325 
00332     public function verifyTitlePermissions( $user ) {
00333         if ( $this->mAsync ) {
00334             return true;
00335         }
00336 
00337         return parent::verifyTitlePermissions( $user );
00338     }
00339 
00349     public function performUpload( $comment, $pageText, $watch, $user ) {
00350         if ( $this->mAsync ) {
00351             $sessionKey = $this->insertJob( $comment, $pageText, $watch, $user );
00352 
00353             return Status::newFatal( 'async', $sessionKey );
00354         }
00355 
00356         return parent::performUpload( $comment, $pageText, $watch, $user );
00357     }
00358 
00366     protected function insertJob( $comment, $pageText, $watch, $user ) {
00367         $sessionKey = $this->stashSession();
00368         $job = new UploadFromUrlJob( $this->getTitle(), array(
00369             'url' => $this->mUrl,
00370             'comment' => $comment,
00371             'pageText' => $pageText,
00372             'watch' => $watch,
00373             'userName' => $user->getName(),
00374             'leaveMessage' => $this->mAsync == 'async-leavemessage',
00375             'ignoreWarnings' => $this->mIgnoreWarnings,
00376             'sessionId' => session_id(),
00377             'sessionKey' => $sessionKey,
00378         ) );
00379         $job->initializeSessionData();
00380         JobQueueGroup::singleton()->push( $job );
00381 
00382         return $sessionKey;
00383     }
00384 }