r74014 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r74013‎ | r74014 | r74015 >
Date:11:04, 30 September 2010
Author:neilk
Status:deferred (Comments)
Tags:
Comment:
SpecialSessionStash now can serve thumbnails to client, with proper HTTP errors on failure
Modified paths:
  • /branches/uploadwizard/phase3/includes/AutoLoader.php (modified) (history)
  • /branches/uploadwizard/phase3/includes/SpecialPage.php (modified) (history)
  • /branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php (added) (history)
  • /branches/uploadwizard/phase3/includes/upload/SessionStash.php (modified) (history)
  • /branches/uploadwizard/phase3/languages/messages/MessagesEn.php (modified) (history)

Diff [purge]

Index: branches/uploadwizard/phase3/includes/upload/SessionStash.php
@@ -3,11 +3,13 @@
44 * SessionStash is intended to accomplish a few things:
55 * - enable applications to temporarily stash files without publishing them to the wiki.
66 * - Several parts of MediaWiki do this in similar ways: UploadBase, UploadWizard, and FirefoggChunkedExtension
7 - * the idea is to unify them all here
 7+ * And there are several that reimplement stashing from scratch, in idiosyncratic ways. The idea is to unify them all here.
 8+ * Mostly all of them are the same except for storing some custom fields, which we subsume into the data array.
89 * - enable applications to find said files later, as long as the session or temp files haven't been purged.
910 * - enable the uploading user (and *ONLY* the uploading user) to access said files, and thumbnails of said files, via a URL.
1011 * We accomplish this by making the session serve as a URL->file mapping, on the assumption that nobody else can access
11 - * the session, even the uploading user.
 12+ * the session, even the uploading user. See SpecialSessionStash, which implements a web interface to some files stored this way.
 13+ *
1214 */
1315
1416 class SessionStash {
@@ -34,6 +36,11 @@
3537 if ( is_null( $repo ) ) {
3638 $repo = RepoGroup::singleton()->getLocalRepo();
3739 }
 40+
 41+ // sanity check repo. If we want to mock the repo later this should be modified.
 42+ if ( ! is_dir( $repo->getZonePath( 'temp' ) ) ) {
 43+ throw new MWException( 'invalid repo or cannot read repo temp dir' );
 44+ }
3845 $this->repo = $repo;
3946
4047 if ( ! isset( $_SESSION ) ) {
@@ -47,6 +54,10 @@
4855 $this->baseUrl = SpecialPage::getTitleFor( 'SessionStash' )->getLocalURL();
4956 }
5057
 58+ /**
 59+ * Get the base of URLs by which one can access the files
 60+ * @return {String} url
 61+ */
5162 public function getBaseUrl() {
5263 return $this->baseUrl;
5364 }
@@ -55,14 +66,22 @@
5667 * Get a file from the stash.
5768 * May throw exception if session data cannot be parsed due to schema change.
5869 * @param {Integer} key
59 - * @return {null|SessionStashItem} null if no such item, or the item
 70+ * @return {null|SessionStashItem} null if no such item or item out of date, or the item
6071 */
6172 public function getFile( $key ) {
62 - if ( !array_key_exists( $key, $this->files ) ) {
 73+ if ( !isset( $this->files[$key] ) ) {
 74+ wfDebug( "checking key = <$key> is in session\n" );
 75+ if ( !isset( $_SESSION[UploadBase::SESSION_KEYNAME][$key] ) ) {
 76+ wfDebug( "checking key = <$key> is in session - it isn't\n" );
 77+ wfDebug( print_r( $_SESSION[UploadBase::SESSION_KEYNAME], 1 ) );
 78+ throw new SessionStashFileNotFoundException();
 79+ }
 80+
6381 $stashData = $_SESSION[UploadBase::SESSION_KEYNAME][$key];
64 -
 82+
 83+ // guards against PHP class changing while session data doesn't
6584 if ($stashData['version'] !== UploadBase::SESSION_VERSION ) {
66 - throw new MWException( 'session item schema does not match current software' );
 85+ return self::$error['outdated session version'];
6786 }
6887
6988 // The path is flattened in with the other random props so we have to dig it out.
@@ -74,7 +93,10 @@
7594 $data[ $stashKey ] = $stashVal;
7695 }
7796 }
78 - $this->files[$key] = new SessionStashFile( $this, $this->repo, $path, $key, $data );
 97+
 98+ $file = new SessionStashFile( $this, $this->repo, $path, $key, $data );
 99+ $this->files[$key] = $file;
 100+
79101 }
80102 return $this->files[$key];
81103 }
@@ -145,9 +167,25 @@
146168 $this->sessionStash = $stash;
147169 $this->sessionKey = $key;
148170 $this->sessionData = $data;
 171+
 172+ // resolve mwrepo:// urls
149173 if ( $repo->isVirtualUrl( $path ) ) {
150174 $path = $repo->resolveVirtualUrl( $path );
151175 }
 176+
 177+ // check if path appears to be sane, no parent traverals, and is in this repo's temp zone.
 178+ if ( ( ! $repo->validateFilename( $path ) ) ||
 179+ ( strpos( $path, $repo->getZonePath( 'temp' ) ) !== 0 ) ) {
 180+ throw new SessionStashBadPathException();
 181+ }
 182+
 183+ wfDebug( "checking if path exists and is good: $path " );
 184+ // check if path exists! and is a plain file.
 185+ if ( ! $repo->fileExists( $path, $repo::FILES_ONLY ) ) {
 186+ wfDebug( "checking if path exists and is good: $path -- no!! " );
 187+ throw new SessionStashFileNotFoundException();
 188+ }
 189+
152190 parent::__construct( false, $repo, $path, false );
153191
154192 // we will be initializing from some tmpnam files that don't have extensions.
@@ -158,6 +196,36 @@
159197 }
160198
161199 /**
 200+ * Test if a path looks like it's in the right place
 201+ *
 202+ * @param {String} $path
 203+ * @return {Boolean}
 204+ */
 205+ public function isPathValid( $path ) {
 206+
 207+ if ( strval( $filename ) == '' ) {
 208+ return false;
 209+ }
 210+
 211+ /**
 212+ * Lifted this bit from extensions/WebStore::validateFilename.
 213+ * Use the same traversal protection as Title::secureAndSplit()
 214+ */
 215+ if ( strpos( $filename, '.' ) !== false &&
 216+ ( $filename === '.' || $filename === '..' ||
 217+ strpos( $filename, './' ) === 0 ||
 218+ strpos( $filename, '../' ) === 0 ||
 219+ strpos( $filename, '/./' ) !== false ||
 220+ strpos( $filename, '/../' ) !== false ) ) {
 221+ return false;
 222+ }
 223+
 224+
 225+ return true;
 226+
 227+ }
 228+
 229+ /**
162230 * A method needed by the file transforming and scaling routines in File.php
163231 * We do not necessarily care about doing the description at this point
164232 * @return {String} the empty string
@@ -193,7 +261,7 @@
194262 }
195263
196264 if ( is_null( $extension ) ) {
197 - throw 'cannot determine extension';
 265+ throw new MWException( 'cannot determine extension' );
198266 }
199267
200268 $this->extension = parent::normalizeExtension( $extension );
@@ -325,3 +393,7 @@
326394 }
327395
328396 }
 397+
 398+class SessionStashFileNotFoundException extends MWException {};
 399+class SessionStashBadPathException extends MWException {};
 400+
Index: branches/uploadwizard/phase3/includes/AutoLoader.php
@@ -632,6 +632,7 @@
633633 'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
634634 'SpecialRecentchangeslinked' => 'includes/specials/SpecialRecentchangeslinked.php',
635635 'SpecialSearch' => 'includes/specials/SpecialSearch.php',
 636+ 'SpecialSessionStash' => 'includes/specials/SpecialSessionStash.php',
636637 'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php',
637638 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
638639 'SpecialTags' => 'includes/specials/SpecialTags.php',
Index: branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php
@@ -0,0 +1,117 @@
 2+<?php
 3+/**
 4+ * Special:SessionStash
 5+ *
 6+ * Web access for files temporarily stored by SessionStash.
 7+ *
 8+ * For example -- files that were uploaded with the UploadWizard extension are stored temporarily
 9+ * before committing them to the db. But we want to see their thumbnails and get other information
 10+ * about them.
 11+ *
 12+ * Since this is based on the user's session, in effect this creates a private temporary file area.
 13+ * However, the URLs for the files cannot be shared.
 14+ *
 15+ * @file
 16+ * @ingroup SpecialPage
 17+ * @ingroup Upload
 18+ */
 19+
 20+class SpecialSessionStash extends SpecialPage {
 21+
 22+ static $HttpErrors = array(
 23+ 400 => 'Bad Request',
 24+ 403 => 'Access Denied',
 25+ 404 => 'File not found',
 26+ 500 => 'Internal Server Error',
 27+ );
 28+
 29+ // SessionStash
 30+ private $stash;
 31+
 32+ // we should not be reading in really big files and serving them out
 33+ private $maxServeFileSize = 262144; // 256K
 34+
 35+ // $request is the request (usually wgRequest)
 36+ // $subpage is everything in the URL after Special:SessionStash
 37+ public function __construct( $request=null, $subpage=null ) {
 38+ global $wgRequest;
 39+
 40+ parent::__construct( 'SessionStash', 'upload' );
 41+
 42+ $this->stash = new SessionStash();
 43+
 44+ }
 45+
 46+ /**
 47+ * If file available in stash, cats it out to the client as a simple HTTP response.
 48+ * n.b. Most sanity checking done in SessionStashLocalFile, so this is straightforward.
 49+ *
 50+ * @param {String} subpage, e.g. in http://sample.com/wiki/Special:SessionStash/foo.jpg, the "foo".
 51+ * @return {Boolean} success
 52+ */
 53+ public function execute( $subPage ) {
 54+ global $wgOut;
 55+
 56+ // prevent callers from doing standard HTML output -- we'll take it from here
 57+ $wgOut->disable();
 58+
 59+ // global $wgScriptPath, $wgLang, $wgUser, $wgOut;
 60+ wfDebug( __METHOD__ . " in subpage for $subPage \n" );
 61+
 62+ try {
 63+ $file = $this->getStashFile( $subPage );
 64+ if ( $file->getSize() > $this->maxServeFileSize ) {
 65+ throw new MWException( 'file size too large' );
 66+ }
 67+ $this->outputFile( $file );
 68+ return true;
 69+
 70+ } catch( SessionStashFileNotFoundException $e ) {
 71+ wfHttpError( 404, self::$HttpErrors[404], $e->getCode(), $e->getMessage() );
 72+
 73+ } catch( SessionStashBadPathException $e ) {
 74+ wfHttpError( 403, self::$HttpErrors[403], $e->getCode(), $e->getMessage() );
 75+
 76+ } catch( Exception $e ) {
 77+ wfHttpError( $code, self::$HttpErrors[$code], $e->getCode(), $e->getMessage() );
 78+
 79+ }
 80+
 81+ return false;
 82+ }
 83+
 84+
 85+ /**
 86+ * Convert the incoming url portion (subpage of Special page) into a stashed file, if available.
 87+ * @param {String} $subPage
 88+ * @return {File} file object
 89+ * @throws MWException, SessionStashFileNotFoundException, SessionStashBadPathException
 90+ */
 91+ private function getStashFile( $subPage );
 92+ // due to an implementation quirk (and trying to be compatible with older method)
 93+ // the stash key doesn't have an extension
 94+ $key = $subPage;
 95+ $n = strrpos( $subPage, '.' );
 96+ if ( $n !== false ) {
 97+ $key = $n ? substr( $subPage, 0, $n ) : $subPage;
 98+ }
 99+
 100+ $file = $this->stash->getFile( $key );
 101+ return $file;
 102+ }
 103+
 104+ /**
 105+ * Output HTTP response for file
 106+ * Side effects, obviously, of echoing lots of stuff to stdout.
 107+ * @param {File} file
 108+ */
 109+ private function outputFile( $file ) {
 110+ header( 'Content-Type: ' . $file->getMimeType() );
 111+ header( 'Content-Transfer-Encoding: binary' );
 112+ header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT' );
 113+ header( 'Pragma: public' );
 114+ header( 'Content-Length: ' . $file->getSize() );
 115+ readfile( $file->getPath() );
 116+ }
 117+}
 118+
Property changes on: branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php
___________________________________________________________________
Added: svn:eol-style
1119 + native
Index: branches/uploadwizard/phase3/includes/SpecialPage.php
@@ -149,6 +149,7 @@
150150 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ),
151151 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ),
152152 'Upload' => 'SpecialUpload',
 153+ 'SessionStash' => array( 'SpecialSessionStash', 'SessionStash', 'upload' ),
153154
154155 # Wiki data and tools
155156 'Statistics' => 'SpecialStatistics',
Index: branches/uploadwizard/phase3/languages/messages/MessagesEn.php
@@ -460,6 +460,7 @@
461461 'RevisionMove' => array( 'RevisionMove' ),
462462 'ComparePages' => array( 'ComparePages' ),
463463 'Badtitle' => array( 'Badtitle' ),
 464+ 'SessionStash' => array( 'SessionStash' ),
464465 );
465466
466467 /**

Comments

#Comment by Nikerabbit (talk | contribs)   11:16, 30 September 2010

$code seems to be undefined in execute()

isPathValid and __construct (maybe others) have spaces in indentation.

+// global $wgScriptPath, $wgLang, $wgUser, $wgOut;

Unused code?

+	public function __construct( $request=null, $subpage=null ) {

Should have spaces around =.

+ throw new MWException( 'cannot determine extension' );

This could be improved to be more elaborate for cases where the user doesn't see the backtrace.

+ return self::$error['outdated session version'];

I don't see self::$error being defined anywhere.

#Comment by NeilK (talk | contribs)   11:27, 30 September 2010

thanks, issues fixed (mostly) in r74017

#Comment by NeilK (talk | contribs)   11:26, 30 September 2010

thanks, issues fixed (mostly) in r74017

Status & tagging log