[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/upload/ -> UploadFromUrl.php (source)

   1  <?php
   2  /**
   3   * Backend for uploading files from a HTTP resource.
   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   * Implements uploading from a HTTP resource.
  26   *
  27   * @ingroup Upload
  28   * @author Bryan Tong Minh
  29   * @author Michael Dale
  30   */
  31  class UploadFromUrl extends UploadBase {
  32      protected $mAsync, $mUrl;
  33      protected $mIgnoreWarnings = true;
  34  
  35      protected $mTempPath, $mTmpHandle;
  36  
  37      protected static $allowedUrls = array();
  38  
  39      /**
  40       * Checks if the user is allowed to use the upload-by-URL feature. If the
  41       * user is not allowed, return the name of the user right as a string. If
  42       * the user is allowed, have the parent do further permissions checking.
  43       *
  44       * @param User $user
  45       *
  46       * @return bool|string
  47       */
  48  	public static function isAllowed( $user ) {
  49          if ( !$user->isAllowed( 'upload_by_url' ) ) {
  50              return 'upload_by_url';
  51          }
  52  
  53          return parent::isAllowed( $user );
  54      }
  55  
  56      /**
  57       * Checks if the upload from URL feature is enabled
  58       * @return bool
  59       */
  60  	public static function isEnabled() {
  61          global $wgAllowCopyUploads;
  62  
  63          return $wgAllowCopyUploads && parent::isEnabled();
  64      }
  65  
  66      /**
  67       * Checks whether the URL is for an allowed host
  68       * The domains in the whitelist can include wildcard characters (*) in place
  69       * of any of the domain levels, e.g. '*.flickr.com' or 'upload.*.gov.uk'.
  70       *
  71       * @param string $url
  72       * @return bool
  73       */
  74  	public static function isAllowedHost( $url ) {
  75          global $wgCopyUploadsDomains;
  76          if ( !count( $wgCopyUploadsDomains ) ) {
  77              return true;
  78          }
  79          $parsedUrl = wfParseUrl( $url );
  80          if ( !$parsedUrl ) {
  81              return false;
  82          }
  83          $valid = false;
  84          foreach ( $wgCopyUploadsDomains as $domain ) {
  85              // See if the domain for the upload matches this whitelisted domain
  86              $whitelistedDomainPieces = explode( '.', $domain );
  87              $uploadDomainPieces = explode( '.', $parsedUrl['host'] );
  88              if ( count( $whitelistedDomainPieces ) === count( $uploadDomainPieces ) ) {
  89                  $valid = true;
  90                  // See if all the pieces match or not (excluding wildcards)
  91                  foreach ( $whitelistedDomainPieces as $index => $piece ) {
  92                      if ( $piece !== '*' && $piece !== $uploadDomainPieces[$index] ) {
  93                          $valid = false;
  94                      }
  95                  }
  96                  if ( $valid ) {
  97                      // We found a match, so quit comparing against the list
  98                      break;
  99                  }
 100              }
 101              /* Non-wildcard test
 102              if ( $parsedUrl['host'] === $domain ) {
 103                  $valid = true;
 104                  break;
 105              }
 106              */
 107          }
 108  
 109          return $valid;
 110      }
 111  
 112      /**
 113       * Checks whether the URL is not allowed.
 114       *
 115       * @param string $url
 116       * @return bool
 117       */
 118  	public static function isAllowedUrl( $url ) {
 119          if ( !isset( self::$allowedUrls[$url] ) ) {
 120              $allowed = true;
 121              wfRunHooks( 'IsUploadAllowedFromUrl', array( $url, &$allowed ) );
 122              self::$allowedUrls[$url] = $allowed;
 123          }
 124  
 125          return self::$allowedUrls[$url];
 126      }
 127  
 128      /**
 129       * Entry point for API upload
 130       *
 131       * @param string $name
 132       * @param string $url
 133       * @param bool|string $async Whether the download should be performed
 134       * asynchronous. False for synchronous, async or async-leavemessage for
 135       * asynchronous download.
 136       * @throws MWException
 137       */
 138  	public function initialize( $name, $url, $async = false ) {
 139          global $wgAllowAsyncCopyUploads;
 140  
 141          $this->mUrl = $url;
 142          $this->mAsync = $wgAllowAsyncCopyUploads ? $async : false;
 143          if ( $async ) {
 144              throw new MWException( 'Asynchronous copy uploads are no longer possible as of r81612.' );
 145          }
 146  
 147          $tempPath = $this->mAsync ? null : $this->makeTemporaryFile();
 148          # File size and removeTempFile will be filled in later
 149          $this->initializePathInfo( $name, $tempPath, 0, false );
 150      }
 151  
 152      /**
 153       * Entry point for SpecialUpload
 154       * @param WebRequest $request
 155       */
 156  	public function initializeFromRequest( &$request ) {
 157          $desiredDestName = $request->getText( 'wpDestFile' );
 158          if ( !$desiredDestName ) {
 159              $desiredDestName = $request->getText( 'wpUploadFileURL' );
 160          }
 161          $this->initialize(
 162              $desiredDestName,
 163              trim( $request->getVal( 'wpUploadFileURL' ) ),
 164              false
 165          );
 166      }
 167  
 168      /**
 169       * @param WebRequest $request
 170       * @return bool
 171       */
 172  	public static function isValidRequest( $request ) {
 173          global $wgUser;
 174  
 175          $url = $request->getVal( 'wpUploadFileURL' );
 176  
 177          return !empty( $url )
 178              && Http::isValidURI( $url )
 179              && $wgUser->isAllowed( 'upload_by_url' );
 180      }
 181  
 182      /**
 183       * @return string
 184       */
 185  	public function getSourceType() {
 186          return 'url';
 187      }
 188  
 189      /**
 190       * Download the file (if not async)
 191       *
 192       * @param array $httpOptions Array of options for MWHttpRequest. Ignored if async.
 193       *   This could be used to override the timeout on the http request.
 194       * @return Status
 195       */
 196  	public function fetchFile( $httpOptions = array() ) {
 197          if ( !Http::isValidURI( $this->mUrl ) ) {
 198              return Status::newFatal( 'http-invalid-url' );
 199          }
 200  
 201          if ( !self::isAllowedHost( $this->mUrl ) ) {
 202              return Status::newFatal( 'upload-copy-upload-invalid-domain' );
 203          }
 204          if ( !self::isAllowedUrl( $this->mUrl ) ) {
 205              return Status::newFatal( 'upload-copy-upload-invalid-url' );
 206          }
 207          if ( !$this->mAsync ) {
 208              return $this->reallyFetchFile( $httpOptions );
 209          }
 210  
 211          return Status::newGood();
 212      }
 213  
 214      /**
 215       * Create a new temporary file in the URL subdirectory of wfTempDir().
 216       *
 217       * @return string Path to the file
 218       */
 219  	protected function makeTemporaryFile() {
 220          $tmpFile = TempFSFile::factory( 'URL' );
 221          $tmpFile->bind( $this );
 222  
 223          return $tmpFile->getPath();
 224      }
 225  
 226      /**
 227       * Callback: save a chunk of the result of a HTTP request to the temporary file
 228       *
 229       * @param mixed $req
 230       * @param string $buffer
 231       * @return int Number of bytes handled
 232       */
 233  	public function saveTempFileChunk( $req, $buffer ) {
 234          $nbytes = fwrite( $this->mTmpHandle, $buffer );
 235  
 236          if ( $nbytes == strlen( $buffer ) ) {
 237              $this->mFileSize += $nbytes;
 238          } else {
 239              // Well... that's not good!
 240              fclose( $this->mTmpHandle );
 241              $this->mTmpHandle = false;
 242          }
 243  
 244          return $nbytes;
 245      }
 246  
 247      /**
 248       * Download the file, save it to the temporary file and update the file
 249       * size and set $mRemoveTempFile to true.
 250       *
 251       * @param array $httpOptions Array of options for MWHttpRequest
 252       * @return Status
 253       */
 254  	protected function reallyFetchFile( $httpOptions = array() ) {
 255          global $wgCopyUploadProxy, $wgCopyUploadTimeout;
 256          if ( $this->mTempPath === false ) {
 257              return Status::newFatal( 'tmp-create-error' );
 258          }
 259  
 260          // Note the temporary file should already be created by makeTemporaryFile()
 261          $this->mTmpHandle = fopen( $this->mTempPath, 'wb' );
 262          if ( !$this->mTmpHandle ) {
 263              return Status::newFatal( 'tmp-create-error' );
 264          }
 265  
 266          $this->mRemoveTempFile = true;
 267          $this->mFileSize = 0;
 268  
 269          $options = $httpOptions + array( 'followRedirects' => true );
 270  
 271          if ( $wgCopyUploadProxy !== false ) {
 272              $options['proxy'] = $wgCopyUploadProxy;
 273          }
 274  
 275          if ( $wgCopyUploadTimeout && !isset( $options['timeout'] ) ) {
 276              $options['timeout'] = $wgCopyUploadTimeout;
 277          }
 278          $req = MWHttpRequest::factory( $this->mUrl, $options );
 279          $req->setCallback( array( $this, 'saveTempFileChunk' ) );
 280          $status = $req->execute();
 281  
 282          if ( $this->mTmpHandle ) {
 283              // File got written ok...
 284              fclose( $this->mTmpHandle );
 285              $this->mTmpHandle = null;
 286          } else {
 287              // We encountered a write error during the download...
 288              return Status::newFatal( 'tmp-write-error' );
 289          }
 290  
 291          if ( !$status->isOk() ) {
 292              return $status;
 293          }
 294  
 295          return $status;
 296      }
 297  
 298      /**
 299       * Wrapper around the parent function in order to defer verifying the
 300       * upload until the file really has been fetched.
 301       * @return array|mixed
 302       */
 303  	public function verifyUpload() {
 304          if ( $this->mAsync ) {
 305              return array( 'status' => UploadBase::OK );
 306          }
 307  
 308          return parent::verifyUpload();
 309      }
 310  
 311      /**
 312       * Wrapper around the parent function in order to defer checking warnings
 313       * until the file really has been fetched.
 314       * @return array
 315       */
 316  	public function checkWarnings() {
 317          if ( $this->mAsync ) {
 318              $this->mIgnoreWarnings = false;
 319  
 320              return array();
 321          }
 322  
 323          return parent::checkWarnings();
 324      }
 325  
 326      /**
 327       * Wrapper around the parent function in order to defer checking protection
 328       * until we are sure that the file can actually be uploaded
 329       * @param User $user
 330       * @return bool|mixed
 331       */
 332  	public function verifyTitlePermissions( $user ) {
 333          if ( $this->mAsync ) {
 334              return true;
 335          }
 336  
 337          return parent::verifyTitlePermissions( $user );
 338      }
 339  
 340      /**
 341       * Wrapper around the parent function in order to defer uploading to the
 342       * job queue for asynchronous uploads
 343       * @param string $comment
 344       * @param string $pageText
 345       * @param bool $watch
 346       * @param User $user
 347       * @return Status
 348       */
 349  	public function performUpload( $comment, $pageText, $watch, $user ) {
 350          if ( $this->mAsync ) {
 351              $sessionKey = $this->insertJob( $comment, $pageText, $watch, $user );
 352  
 353              return Status::newFatal( 'async', $sessionKey );
 354          }
 355  
 356          return parent::performUpload( $comment, $pageText, $watch, $user );
 357      }
 358  
 359      /**
 360       * @param string $comment
 361       * @param string $pageText
 362       * @param bool $watch
 363       * @param User $user
 364       * @return string
 365       */
 366  	protected function insertJob( $comment, $pageText, $watch, $user ) {
 367          $sessionKey = $this->stashSession();
 368          $job = new UploadFromUrlJob( $this->getTitle(), array(
 369              'url' => $this->mUrl,
 370              'comment' => $comment,
 371              'pageText' => $pageText,
 372              'watch' => $watch,
 373              'userName' => $user->getName(),
 374              'leaveMessage' => $this->mAsync == 'async-leavemessage',
 375              'ignoreWarnings' => $this->mIgnoreWarnings,
 376              'sessionId' => session_id(),
 377              'sessionKey' => $sessionKey,
 378          ) );
 379          $job->initializeSessionData();
 380          JobQueueGroup::singleton()->push( $job );
 381  
 382          return $sessionKey;
 383      }
 384  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1