// UploadStash
private $stash;
+ // is the edit request authorized? boolean
+ private $isEditAuthorized;
+
+ // did the user request us to clear the stash? boolean
+ private $requestedClear;
+
// Since we are directly writing the file to STDOUT,
// we should not be reading in really big files and serving them out.
//
// uploading.
const MAX_SERVE_BYTES = 262144; // 256K
- public function __construct( ) {
+ public function __construct( $request = null ) {
+ global $wgRequest;
+
parent::__construct( 'UploadStash', 'upload' );
try {
$this->stash = new UploadStash( );
- } catch (UploadStashNotAvailableException $e) {
+ } catch ( UploadStashNotAvailableException $e ) {
return null;
}
+
+ $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
}
/**
- * If file available in stash, cats it out to the client as a simple HTTP response.
- * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
+ * Execute page -- can output a file directly or show a listing of them.
*
* @param $subPage String: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part
* @return Boolean: success
return;
}
+ if ( !isset( $subPage ) || $subPage === '' ) {
+ return $this->showUploads();
+ }
+
+ return $this->showUpload( $subPage );
+ }
+
+
+ /**
+ * If file available in stash, cats it out to the client as a simple HTTP response.
+ * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward.
+ *
+ * @param $key String: the key of a particular requested file
+ */
+ public function showUpload( $key ) {
+ global $wgOut;
+
// prevent callers from doing standard HTML output -- we'll take it from here
$wgOut->disable();
- if ( !isset( $subPage ) || $subPage === '' ) {
- // the user probably visited the page just to see what would happen, so explain it a bit.
- $code = '400';
- $message = "Missing key\n\n"
- . 'This page provides access to temporarily stashed files for the user that '
- . 'uploaded those files. See the upload API documentation. To access a stashed file, '
- . 'use the URL of this page, with a slash and the key of the stashed file appended.';
- } else {
- try {
- if ( preg_match( '/^(\d+)px-(.*)$/', $subPage, $matches ) ) {
- list( /* full match */, $width, $key ) = $matches;
- return $this->outputThumbFromStash( $key, $width );
- } else {
- return $this->outputFileFromStash( $subPage );
- }
- } catch( UploadStashFileNotFoundException $e ) {
- $code = 404;
- $message = $e->getMessage();
- } catch( UploadStashZeroLengthFileException $e ) {
- $code = 500;
- $message = $e->getMessage();
- } catch( UploadStashBadPathException $e ) {
- $code = 500;
- $message = $e->getMessage();
- } catch( SpecialUploadStashTooLargeException $e ) {
- $code = 500;
- $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
- } catch( Exception $e ) {
- $code = 500;
- $message = $e->getMessage();
+ try {
+ if ( preg_match( '/^(\d+)px-(.*)$/', $key, $matches ) ) {
+ list( /* full match */, $width, $key ) = $matches;
+ return $this->outputThumbFromStash( $key, $width );
+ } else {
+ return $this->outputFileFromStash( $key );
}
+ } catch( UploadStashFileNotFoundException $e ) {
+ $code = 404;
+ $message = $e->getMessage();
+ } catch( UploadStashZeroLengthFileException $e ) {
+ $code = 500;
+ $message = $e->getMessage();
+ } catch( UploadStashBadPathException $e ) {
+ $code = 500;
+ $message = $e->getMessage();
+ } catch( SpecialUploadStashTooLargeException $e ) {
+ $code = 500;
+ $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage();
+ } catch( Exception $e ) {
+ $code = 500;
+ $message = $e->getMessage();
}
wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message );
return false;
}
-
+
/**
* Get a file from stash and stream it out. Rely on parent to catch exceptions and transform them into HTTP
* @param String: $key - key of this file in the stash, which probably looks like a filename with extension.
- * @throws ....?
* @return boolean
*/
private function outputFileFromStash( $key ) {
$file = $this->stash->getFile( $key );
- $this->outputLocalFile( $file );
- return true;
+ return $this->outputLocalFile( $file );
}
* Get a thumbnail for file, either generated locally or remotely, and stream it out
* @param String $key: key for the file in the stash
* @param int $width: width of desired thumbnail
- * @return ??
+ * @return boolean success
*/
private function outputThumbFromStash( $key, $width ) {
// this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb()
+ // this is part of our horrible NFS-based system, we create a file on a mount point here, but fetch the scaled file from somewhere else that
+ // happens to share it over NFS
global $wgUploadStashScalerBaseUrl;
// let exceptions propagate to caller.
if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException();
}
- self::outputHeaders( $file->getMimeType(), $file->getSize() );
+ self::outputFileHeaders( $file->getMimeType(), $file->getSize() );
readfile( $file->getPath() );
return true;
}
if ( $size > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException();
}
- self::outputHeaders( $contentType, $size );
+ self::outputFileHeaders( $contentType, $size );
print $content;
return true;
}
* @param String $contentType : string suitable for content-type header
* @param String $size: length in bytes
*/
- private static function outputHeaders( $contentType, $size ) {
+ private static function outputFileHeaders( $contentType, $size ) {
header( "Content-Type: $contentType", true );
header( 'Content-Transfer-Encoding: binary', true );
header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true );
header( "Content-Length: $size", true );
}
+
+ /**
+ * Initialize authorization & actions to take, from the request
+ * @param $request: WebRequest
+ */
+ private function loadRequest( $request ) {
+ global $wgUser;
+ if ( $request->wasPosted() ) {
+
+ $token = $request->getVal( 'wpEditToken' );
+ $this->isEditAuthorized = $wgUser->matchEditToken( $token );
+
+ $this->requestedClear = $request->getBool( 'clear' );
+
+ }
+ }
+
+ /**
+ * Static callback for the HTMLForm in showUploads, to process
+ * Note the stash has to be recreated since this is being called in a static context.
+ * This works, because there really is only one stash per logged-in user, despite appearances.
+ *
+ * @return Status
+ */
+ public static function tryClearStashedUploads( $formData ) {
+ wfDebug( __METHOD__ . " form data : " . print_r( $formData, 1 ) );
+ if ( isset( $formData['clear'] ) and $formData['clear'] ) {
+ $stash = new UploadStash();
+ wfDebug( "stash has: " . print_r( $stash->listFiles(), 1 ) );
+ if ( ! $stash->clear() ) {
+ return Status::newFatal( 'uploadstash-errclear' );
+ }
+ }
+ return Status::newGood();
+ }
+
+ /**
+ * Default action when we don't have a subpage -- just show links to the uploads we have,
+ * Also show a button to clear stashed files
+ * @param Status : $status - the result of processRequest
+ */
+ private function showUploads( $status = null ) {
+ global $wgOut;
+ if ( $status === null ) {
+ $status = Status::newGood();
+ }
+
+ // sets the title, etc.
+ $this->setHeaders();
+ $this->outputHeader();
+
+
+ // create the form, which will also be used to execute a callback to process incoming form data
+ // this design is extremely dubious, but supposedly HTMLForm is our standard now?
+
+ $form = new HTMLForm( array( 'clear' => array( 'class' => 'HTMLHiddenField', 'default' => true ) ), 'clearStashedUploads' );
+ $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) );
+ $form->setTitle( $this->getTitle() );
+ $form->addHiddenField( 'clear', true, array( 'type' => 'boolean' ) );
+ $form->setSubmitText( wfMsg( 'uploadstash-clear' ) );
+
+ $form->prepareForm();
+ $formResult = $form->tryAuthorizedSubmit();
+
+
+ // show the files + form, if there are any, or just say there are none
+ $refreshHtml = Html::element( 'a', array( 'href' => $this->getTitle()->getLocalURL() ), wfMsg( 'uploadstash-refresh' ) );
+ $files = $this->stash->listFiles();
+ if ( count( $files ) ) {
+ sort( $files );
+ $fileListItemsHtml = '';
+ foreach ( $files as $file ) {
+ $fileListItemsHtml .= Html::rawElement( 'li', array(),
+ Html::element( 'a', array( 'href' => $this->getTitle( $file )->getLocalURL() ), $file )
+ );
+ }
+ $wgOut->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) );
+ $form->displayForm( $formResult );
+ $wgOut->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) );
+ } else {
+ $wgOut->addHtml( Html::rawElement( 'p', array(),
+ Html::element( 'span', array(), wfMsg( 'uploadstash-nofiles' ) )
+ . ' '
+ . $refreshHtml
+ ) );
+ }
+
+ return true;
+ }
}
class SpecialUploadStashTooLargeException extends MWException {};
/**
* Represents the session which contains temporarily stored files.
* Designed to be compatible with the session stashing code in UploadBase (should replace it eventually)
- *
- * @param $repo FileRepo: optional -- repo in which to store files. Will choose LocalRepo if not supplied.
*/
- public function __construct( $repo = null ) {
-
- if ( is_null( $repo ) ) {
- $repo = RepoGroup::singleton()->getLocalRepo();
- }
+ public function __construct() {
- $this->repo = $repo;
+ // this might change based on wiki's configuration.
+ $this->repo = RepoGroup::singleton()->getLocalRepo();
if ( ! isset( $_SESSION ) ) {
throw new UploadStashNotAvailableException( 'no session variable' );
}
+
/**
* Get a file and its metadata from the stash.
* May throw exception if session data cannot be parsed due to schema change, or key not found.
unset( $data['mTempPath'] );
$file = new UploadStashFile( $this, $this->repo, $path, $key, $data );
- if ( $file->getSize === 0 ) {
+ if ( $file->getSize() === 0 ) {
throw new UploadStashZeroLengthFileException( "File is zero length" );
}
$this->files[$key] = $file;
return $this->getFile( $key );
}
+ /**
+ * Remove all files from the stash.
+ * Does not clean up files in the repo, just the record of them.
+ * @return boolean: success
+ */
+ public function clear() {
+ $_SESSION[UploadBase::SESSION_KEYNAME] = array();
+ return true;
+ }
+
+
+ /**
+ * Remove a particular file from the stash.
+ * Does not clean up file in the repo, just the record of it.
+ * @return boolean: success
+ */
+ public function removeFile( $key ) {
+ unset ( $_SESSION[UploadBase::SESSION_KEYNAME][$key] );
+ return true;
+ }
+
+
+ /**
+ * List all files in the stash.
+ */
+ public function listFiles() {
+ return array_keys( $_SESSION[UploadBase::SESSION_KEYNAME] );
+ }
+
+
/**
* Find or guess extension -- ensuring that our extension matches our mime type.
* Since these files are constructed from php tempnames they may not start off