MediaWiki  1.23.13
SpecialUploadStash.php
Go to the documentation of this file.
1 <?php
36  // UploadStash
37  private $stash;
38 
39  // Since we are directly writing the file to STDOUT,
40  // we should not be reading in really big files and serving them out.
41  //
42  // We also don't want people using this as a file drop, even if they
43  // share credentials.
44  //
45  // This service is really for thumbnails and other such previews while
46  // uploading.
47  const MAX_SERVE_BYTES = 1048576; // 1MB
48 
49  public function __construct() {
50  parent::__construct( 'UploadStash', 'upload' );
51  try {
52  $this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
54  }
55  }
56 
64  public function execute( $subPage ) {
65  $this->checkPermissions();
66 
67  if ( $subPage === null || $subPage === '' ) {
68  return $this->showUploads();
69  }
70 
71  return $this->showUpload( $subPage );
72  }
73 
82  public function showUpload( $key ) {
83  // prevent callers from doing standard HTML output -- we'll take it from here
84  $this->getOutput()->disable();
85 
86  try {
87  $params = $this->parseKey( $key );
88  if ( $params['type'] === 'thumb' ) {
89  return $this->outputThumbFromStash( $params['file'], $params['params'] );
90  } else {
91  return $this->outputLocalFile( $params['file'] );
92  }
94  $code = 404;
95  $message = $e->getMessage();
97  $code = 500;
98  $message = $e->getMessage();
99  } catch ( UploadStashBadPathException $e ) {
100  $code = 500;
101  $message = $e->getMessage();
103  $code = 500;
104  $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES .
105  ' bytes. ' . $e->getMessage();
106  } catch ( Exception $e ) {
107  $code = 500;
108  $message = $e->getMessage();
109  }
110 
111  throw new HttpError( $code, $message );
112  }
113 
123  private function parseKey( $key ) {
124  $type = strtok( $key, '/' );
125 
126  if ( $type !== 'file' && $type !== 'thumb' ) {
127  throw new UploadStashBadPathException( "Unknown type '$type'" );
128  }
129  $fileName = strtok( '/' );
130  $thumbPart = strtok( '/' );
131  $file = $this->stash->getFile( $fileName );
132  if ( $type === 'thumb' ) {
133  $srcNamePos = strrpos( $thumbPart, $fileName );
134  if ( $srcNamePos === false || $srcNamePos < 1 ) {
135  throw new UploadStashBadPathException( 'Unrecognized thumb name' );
136  }
137  $paramString = substr( $thumbPart, 0, $srcNamePos - 1 );
138 
139  $handler = $file->getHandler();
140  if ( $handler ) {
141  $params = $handler->parseParamString( $paramString );
142 
143  return array( 'file' => $file, 'type' => $type, 'params' => $params );
144  } else {
145  throw new UploadStashBadPathException( 'No handler found for ' .
146  "mime {$file->getMimeType()} of file {$file->getPath()}" );
147  }
148  }
149 
150  return array( 'file' => $file, 'type' => $type );
151  }
152 
161  private function outputThumbFromStash( $file, $params ) {
162  // this global, if it exists, points to a "scaler", as you might find in
163  // the Wikimedia Foundation cluster. See outputRemoteScaledThumb(). This
164  // is part of our horrible NFS-based system, we create a file on a mount
165  // point here, but fetch the scaled file from somewhere else that
166  // happens to share it over NFS.
167  global $wgUploadStashScalerBaseUrl;
168 
169  $flags = 0;
170  if ( $wgUploadStashScalerBaseUrl ) {
172  } else {
174  }
175  }
176 
187  // n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely
188  // on HTTP caching to ensure this doesn't happen.
189 
191 
192  $thumbnailImage = $file->transform( $params, $flags );
193  if ( !$thumbnailImage ) {
194  throw new MWException( 'Could not obtain thumbnail' );
195  }
196 
197  // we should have just generated it locally
198  if ( !$thumbnailImage->getStoragePath() ) {
199  throw new UploadStashFileNotFoundException( "no local path for scaled item" );
200  }
201 
202  // now we should construct a File, so we can get mime and other such info in a standard way
203  // n.b. mimetype may be different from original (ogx original -> jpeg thumb)
204  $thumbFile = new UnregisteredLocalFile( false,
205  $this->stash->repo, $thumbnailImage->getStoragePath(), false );
206  if ( !$thumbFile ) {
207  throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" );
208  }
209 
210  return $this->outputLocalFile( $thumbFile );
211  }
212 
232  private function outputRemoteScaledThumb( $file, $params, $flags ) {
233  // This global probably looks something like
234  // 'http://upload.wikimedia.org/wikipedia/test/thumb/temp'. Do not use
235  // trailing slash.
236  global $wgUploadStashScalerBaseUrl;
237  $scalerBaseUrl = $wgUploadStashScalerBaseUrl;
238 
239  if ( preg_match( '/^\/\//', $scalerBaseUrl ) ) {
240  // this is apparently a protocol-relative URL, which makes no sense in this context,
241  // since this is used for communication that's internal to the application.
242  // default to http.
243  $scalerBaseUrl = wfExpandUrl( $scalerBaseUrl, PROTO_CANONICAL );
244  }
245 
246  // We need to use generateThumbName() instead of thumbName(), because
247  // the suffix needs to match the file name for the remote thumbnailer
248  // to work
249  $scalerThumbName = $file->generateThumbName( $file->getName(), $params );
250  $scalerThumbUrl = $scalerBaseUrl . '/' . $file->getUrlRel() .
251  '/' . rawurlencode( $scalerThumbName );
252 
253  // make a curl call to the scaler to create a thumbnail
254  $httpOptions = array(
255  'method' => 'GET',
256  'timeout' => 'default'
257  );
258  $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions );
259  $status = $req->execute();
260  if ( !$status->isOK() ) {
261  $errors = $status->getErrorsArray();
262  $errorStr = "Fetching thumbnail failed: " . print_r( $errors, 1 );
263  $errorStr .= "\nurl = $scalerThumbUrl\n";
264  throw new MWException( $errorStr );
265  }
266  $contentType = $req->getResponseHeader( "content-type" );
267  if ( !$contentType ) {
268  throw new MWException( "Missing content-type header" );
269  }
270 
271  return $this->outputContents( $req->getContent(), $contentType );
272  }
273 
283  private function outputLocalFile( File $file ) {
284  if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
286  }
287 
288  return $file->getRepo()->streamFile( $file->getPath(),
289  array( 'Content-Transfer-Encoding: binary',
290  'Expires: Sun, 17-Jan-2038 19:14:07 GMT' )
291  );
292  }
293 
302  private function outputContents( $content, $contentType ) {
303  $size = strlen( $content );
304  if ( $size > self::MAX_SERVE_BYTES ) {
306  }
307  self::outputFileHeaders( $contentType, $size );
308  print $content;
309 
310  return true;
311  }
312 
322  private static function outputFileHeaders( $contentType, $size ) {
323  header( "Content-Type: $contentType", true );
324  header( 'Content-Transfer-Encoding: binary', true );
325  header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
326  // Bug 53032 - It shouldn't be a problem here, but let's be safe and not cache
327  header( 'Cache-Control: private' );
328  header( "Content-Length: $size", true );
329  }
330 
339  public static function tryClearStashedUploads( $formData ) {
340  if ( isset( $formData['Clear'] ) ) {
341  $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash();
342  wfDebug( 'stash has: ' . print_r( $stash->listFiles(), true ) . "\n" );
343 
344  if ( !$stash->clear() ) {
345  return Status::newFatal( 'uploadstash-errclear' );
346  }
347  }
348 
349  return Status::newGood();
350  }
351 
357  private function showUploads() {
358  // sets the title, etc.
359  $this->setHeaders();
360  $this->outputHeader();
361 
362  // create the form, which will also be used to execute a callback to process incoming form data
363  // this design is extremely dubious, but supposedly HTMLForm is our standard now?
364 
365  $context = new DerivativeContext( $this->getContext() );
366  $context->setTitle( $this->getPageTitle() ); // Remove subpage
367  $form = new HTMLForm( array(
368  'Clear' => array(
369  'type' => 'hidden',
370  'default' => true,
371  'name' => 'clear',
372  )
373  ), $context, 'clearStashedUploads' );
374  $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) );
375  $form->setSubmitTextMsg( 'uploadstash-clear' );
376 
377  $form->prepareForm();
378  $formResult = $form->tryAuthorizedSubmit();
379 
380  // show the files + form, if there are any, or just say there are none
381  $refreshHtml = Html::element( 'a',
382  array( 'href' => $this->getPageTitle()->getLocalURL() ),
383  $this->msg( 'uploadstash-refresh' )->text() );
384  $files = $this->stash->listFiles();
385  if ( $files && count( $files ) ) {
386  sort( $files );
387  $fileListItemsHtml = '';
388  foreach ( $files as $file ) {
389  // TODO: Use Linker::link or even construct the list in plain wikitext
390  $fileListItemsHtml .= Html::rawElement( 'li', array(),
391  Html::element( 'a', array( 'href' =>
392  $this->getPageTitle( "file/$file" )->getLocalURL() ), $file )
393  );
394  }
395  $this->getOutput()->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
396  $form->displayForm( $formResult );
397  $this->getOutput()->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) );
398  } else {
399  $this->getOutput()->addHtml( Html::rawElement( 'p', array(),
400  Html::element( 'span', array(), $this->msg( 'uploadstash-nofiles' )->text() )
401  . ' '
402  . $refreshHtml
403  ) );
404  }
405 
406  return true;
407  }
408 }
409 
411 }
SpecialPage\getPageTitle
getPageTitle( $subpage=false)
Get a self-referential title object.
Definition: SpecialPage.php:488
SpecialUploadStash\tryClearStashedUploads
static tryClearStashedUploads( $formData)
Static callback for the HTMLForm in showUploads, to process Note the stash has to be recreated since ...
Definition: SpecialUploadStash.php:339
PROTO_CANONICAL
const PROTO_CANONICAL
Definition: Defines.php:271
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:53
SpecialUploadStash\$stash
$stash
Definition: SpecialUploadStash.php:37
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
$files
$files
Definition: importImages.php:67
UploadStashBadPathException
Definition: UploadStash.php:682
SpecialPage\getOutput
getOutput()
Get the OutputPage being used for this instance.
Definition: SpecialPage.php:535
UnlistedSpecialPage
Shortcut to construct a special page which is unlisted by default.
Definition: UnlistedSpecialPage.php:29
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
$form
usually copyright or history_copyright This message must be in HTML not wikitext $subpages will be ignored and the rest of subPageSubtitle() will run. 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink' whether MediaWiki currently thinks this is a CSS JS page Hooks may change this value to override the return value of Title::isCssOrJsPage(). 'TitleIsAlwaysKnown' whether MediaWiki currently thinks this page is known isMovable() always returns false. $title whether MediaWiki currently thinks this page is movable Hooks may change this value to override the return value of Title::isMovable(). 'TitleIsWikitextPage' whether MediaWiki currently thinks this is a wikitext page Hooks may change this value to override the return value of Title::isWikitextPage() 'TitleMove' use UploadVerification and UploadVerifyFile instead $form
Definition: hooks.txt:2578
UnregisteredLocalFile
A file object referring to either a standalone local file, or a file in a local repository with no da...
Definition: UnregisteredLocalFile.php:36
SpecialPage\checkPermissions
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
Definition: SpecialPage.php:287
Status\newGood
static newGood( $value=null)
Factory function for good results.
Definition: Status.php:77
$params
$params
Definition: styleTest.css.php:40
SpecialUploadStash\outputRemoteScaledThumb
outputRemoteScaledThumb( $file, $params, $flags)
Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster,...
Definition: SpecialUploadStash.php:232
SpecialUploadStash\execute
execute( $subPage)
Execute page – can output a file directly or show a listing of them.
Definition: SpecialUploadStash.php:64
$flags
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2118
SpecialUploadStash\outputLocalFile
outputLocalFile(File $file)
Output HTTP response for file Side effect: writes HTTP response to STDOUT.
Definition: SpecialUploadStash.php:283
HttpError
Show an error that looks like an HTTP server error.
Definition: HttpError.php:28
true
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return true
Definition: hooks.txt:1530
DerivativeContext
An IContextSource implementation which will inherit context from another source but allow individual ...
Definition: DerivativeContext.php:32
File
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:50
MWException
MediaWiki exception.
Definition: MWException.php:26
UploadStashNotAvailableException
Definition: UploadStash.php:680
SpecialUploadStash
Web access for files temporarily stored by UploadStash.
Definition: SpecialUploadStash.php:35
Html\element
static element( $element, $attribs=array(), $contents='')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:148
SpecialUploadStash\parseKey
parseKey( $key)
Parse the key passed to the SpecialPage.
Definition: SpecialUploadStash.php:123
UploadStashZeroLengthFileException
Definition: UploadStash.php:684
SpecialUploadStash\outputFileHeaders
static outputFileHeaders( $contentType, $size)
Output headers for streaming.
Definition: SpecialUploadStash.php:322
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
SpecialPage\setHeaders
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
Definition: SpecialPage.php:352
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:188
SpecialPage\getContext
getContext()
Gets the context this SpecialPage is executed in.
Definition: SpecialPage.php:508
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:980
$size
$size
Definition: RandomTest.php:75
SpecialUploadStash\MAX_SERVE_BYTES
const MAX_SERVE_BYTES
Definition: SpecialUploadStash.php:47
SpecialPage\msg
msg()
Wrapper around wfMessage that sets the current context.
Definition: SpecialPage.php:609
UploadStashFileNotFoundException
Definition: UploadStash.php:681
SpecialUploadStash\outputLocallyScaledThumb
outputLocallyScaledThumb( $file, $params, $flags)
Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT.
Definition: SpecialUploadStash.php:186
SpecialUploadStash\showUploads
showUploads()
Default action when we don't have a subpage – just show links to the uploads we have,...
Definition: SpecialUploadStash.php:357
$file
if(PHP_SAPI !='cli') $file
Definition: UtfNormalTest2.php:30
File\RENDER_NOW
const RENDER_NOW
Force rendering in the current process.
Definition: File.php:58
SpecialUploadStash\__construct
__construct()
Definition: SpecialUploadStash.php:49
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
SpecialUploadStash\outputThumbFromStash
outputThumbFromStash( $file, $params)
Get a thumbnail for file, either generated locally or remotely, and stream it out.
Definition: SpecialUploadStash.php:161
SpecialUploadStash\outputContents
outputContents( $content, $contentType)
Output HTTP response of raw content Side effect: writes HTTP response to STDOUT.
Definition: SpecialUploadStash.php:302
Html\rawElement
static rawElement( $element, $attribs=array(), $contents='')
Returns an HTML element in a string.
Definition: Html.php:124
SpecialUploadStashTooLargeException
Definition: SpecialUploadStash.php:410
MWHttpRequest\factory
static factory( $url, $options=null)
Generate a new request object.
Definition: HttpFunctions.php:290
SpecialPage\outputHeader
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
Definition: SpecialPage.php:443
SpecialUploadStash\showUpload
showUpload( $key)
If file available in stash, cats it out to the client as a simple HTTP response.
Definition: SpecialUploadStash.php:82
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:544
$e
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:1632
Status\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: Status.php:63
HTMLForm
Object handling generic submission, CSRF protection, layout and other logic for UI forms.
Definition: HTMLForm.php:100
$type
$type
Definition: testCompression.php:46