MediaWiki  master
UploadFromUrl.php
Go to the documentation of this file.
1 <?php
26 
34 class UploadFromUrl extends UploadBase {
35  protected $mUrl;
36 
37  protected $mTempPath, $mTmpHandle;
38 
39  protected static $allowedUrls = [];
40 
50  public static function isAllowed( UserIdentity $user ) {
51  if ( !MediaWikiServices::getInstance()
53  ->userHasRight( $user, 'upload_by_url' )
54  ) {
55  return 'upload_by_url';
56  }
57 
58  return parent::isAllowed( $user );
59  }
60 
65  public static function isEnabled() {
66  global $wgAllowCopyUploads;
67 
68  return $wgAllowCopyUploads && parent::isEnabled();
69  }
70 
79  public static function isAllowedHost( $url ) {
80  global $wgCopyUploadsDomains;
81  if ( !count( $wgCopyUploadsDomains ) ) {
82  return true;
83  }
84  $parsedUrl = wfParseUrl( $url );
85  if ( !$parsedUrl ) {
86  return false;
87  }
88  $valid = false;
89  foreach ( $wgCopyUploadsDomains as $domain ) {
90  // See if the domain for the upload matches this whitelisted domain
91  $whitelistedDomainPieces = explode( '.', $domain );
92  $uploadDomainPieces = explode( '.', $parsedUrl['host'] );
93  if ( count( $whitelistedDomainPieces ) === count( $uploadDomainPieces ) ) {
94  $valid = true;
95  // See if all the pieces match or not (excluding wildcards)
96  foreach ( $whitelistedDomainPieces as $index => $piece ) {
97  if ( $piece !== '*' && $piece !== $uploadDomainPieces[$index] ) {
98  $valid = false;
99  }
100  }
101  if ( $valid ) {
102  // We found a match, so quit comparing against the list
103  break;
104  }
105  }
106  /* Non-wildcard test
107  if ( $parsedUrl['host'] === $domain ) {
108  $valid = true;
109  break;
110  }
111  */
112  }
113 
114  return $valid;
115  }
116 
123  public static function isAllowedUrl( $url ) {
124  if ( !isset( self::$allowedUrls[$url] ) ) {
125  $allowed = true;
126  Hooks::run( 'IsUploadAllowedFromUrl', [ $url, &$allowed ] );
127  self::$allowedUrls[$url] = $allowed;
128  }
129 
130  return self::$allowedUrls[$url];
131  }
132 
140  public function initialize( $name, $url ) {
141  $this->mUrl = $url;
142 
143  $tempPath = $this->makeTemporaryFile();
144  # File size and removeTempFile will be filled in later
145  $this->initializePathInfo( $name, $tempPath, 0, false );
146  }
147 
152  public function initializeFromRequest( &$request ) {
153  $desiredDestName = $request->getText( 'wpDestFile' );
154  if ( !$desiredDestName ) {
155  $desiredDestName = $request->getText( 'wpUploadFileURL' );
156  }
157  $this->initialize(
158  $desiredDestName,
159  trim( $request->getVal( 'wpUploadFileURL' ) )
160  );
161  }
162 
167  public static function isValidRequest( $request ) {
168  global $wgUser;
169 
170  $url = $request->getVal( 'wpUploadFileURL' );
171 
172  return !empty( $url )
173  && MediaWikiServices::getInstance()
174  ->getPermissionManager()
175  ->userHasRight( $wgUser, 'upload_by_url' );
176  }
177 
181  public function getSourceType() {
182  return 'url';
183  }
184 
192  public function fetchFile( $httpOptions = [] ) {
193  if ( !Http::isValidURI( $this->mUrl ) ) {
194  return Status::newFatal( 'http-invalid-url', $this->mUrl );
195  }
196 
197  if ( !self::isAllowedHost( $this->mUrl ) ) {
198  return Status::newFatal( 'upload-copy-upload-invalid-domain' );
199  }
200  if ( !self::isAllowedUrl( $this->mUrl ) ) {
201  return Status::newFatal( 'upload-copy-upload-invalid-url' );
202  }
203  return $this->reallyFetchFile( $httpOptions );
204  }
205 
211  protected function makeTemporaryFile() {
212  $tmpFile = MediaWikiServices::getInstance()->getTempFSFileFactory()
213  ->newTempFSFile( 'URL', 'urlupload_' );
214  $tmpFile->bind( $this );
215 
216  return $tmpFile->getPath();
217  }
218 
226  public function saveTempFileChunk( $req, $buffer ) {
227  wfDebugLog( 'fileupload', 'Received chunk of ' . strlen( $buffer ) . ' bytes' );
228  $nbytes = fwrite( $this->mTmpHandle, $buffer );
229 
230  if ( $nbytes == strlen( $buffer ) ) {
231  $this->mFileSize += $nbytes;
232  } else {
233  // Well... that's not good!
234  wfDebugLog(
235  'fileupload',
236  'Short write ' . $nbytes . '/' . strlen( $buffer ) .
237  ' bytes, aborting with ' . $this->mFileSize . ' uploaded so far'
238  );
239  fclose( $this->mTmpHandle );
240  $this->mTmpHandle = false;
241  }
242 
243  return $nbytes;
244  }
245 
253  protected function reallyFetchFile( $httpOptions = [] ) {
255  if ( $this->mTempPath === false ) {
256  return Status::newFatal( 'tmp-create-error' );
257  }
258 
259  // Note the temporary file should already be created by makeTemporaryFile()
260  $this->mTmpHandle = fopen( $this->mTempPath, 'wb' );
261  if ( !$this->mTmpHandle ) {
262  return Status::newFatal( 'tmp-create-error' );
263  }
264  wfDebugLog( 'fileupload', 'Temporary file created "' . $this->mTempPath . '"' );
265 
266  $this->mRemoveTempFile = true;
267  $this->mFileSize = 0;
268 
269  $options = $httpOptions + [ '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  wfDebugLog(
279  'fileupload',
280  'Starting download from "' . $this->mUrl . '" ' .
281  '<' . implode( ',', array_keys( array_filter( $options ) ) ) . '>'
282  );
283  $req = MWHttpRequest::factory( $this->mUrl, $options, __METHOD__ );
284  $req->setCallback( [ $this, 'saveTempFileChunk' ] );
285  $status = $req->execute();
286 
287  if ( $this->mTmpHandle ) {
288  // File got written ok...
289  fclose( $this->mTmpHandle );
290  $this->mTmpHandle = null;
291  } else {
292  // We encountered a write error during the download...
293  return Status::newFatal( 'tmp-write-error' );
294  }
295 
296  wfDebugLog( 'fileupload', $status );
297  if ( $status->isOK() ) {
298  wfDebugLog( 'fileupload', 'Download by URL completed successfully.' );
299  } else {
300  wfDebugLog(
301  'fileupload',
302  'Download by URL completed with HTTP status ' . $req->getStatus()
303  );
304  }
305 
306  return $status;
307  }
308 }
makeTemporaryFile()
Create a new temporary file in the URL subdirectory of wfTempDir().
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:69
static isValidRequest( $request)
static factory( $url, array $options=null, $caller=__METHOD__)
Generate a new request object.
$wgCopyUploadsDomains
A list of domains copy uploads can come from.
static $allowedUrls
initializeFromRequest(&$request)
Entry point for SpecialUpload.
wfParseUrl( $url)
parse_url() work-alike, but non-broken.
initializePathInfo( $name, $tempPath, $fileSize, $removeTempFile=false)
Initialize the path information.
Definition: UploadBase.php:249
static isAllowedHost( $url)
Checks whether the URL is for an allowed host The domains in the whitelist can include wildcard chara...
$wgAllowCopyUploads
Allow for upload to be copied from an URL.
reallyFetchFile( $httpOptions=[])
Download the file, save it to the temporary file and update the file size and set $mRemoveTempFile to...
Implements uploading from a HTTP resource.
initialize( $name, $url)
Entry point for API upload.
Interface for objects representing user identity.
getPermissionManager()
static isAllowed(UserIdentity $user)
Checks if the user is allowed to use the upload-by-URL feature.
static isEnabled()
Checks if the upload from URL feature is enabled.
static isAllowedUrl( $url)
Checks whether the URL is not allowed.
UploadBase and subclasses are the backend of MediaWiki&#39;s file uploads.
Definition: UploadBase.php:42
int bool $wgCopyUploadTimeout
Different timeout for upload by url This could be useful since when fetching large files...
saveTempFileChunk( $req, $buffer)
Callback: save a chunk of the result of a HTTP request to the temporary file.
static isValidURI( $uri)
Check that the given URI is a valid one.
Definition: Http.php:118
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
$wgCopyUploadProxy
Proxy to use for copy upload requests.
return true
Definition: router.php:92
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
fetchFile( $httpOptions=[])
Download the file.