http://www.mediawiki.org/wiki/Special:Code/MediaWiki/74014

Revision: 74014
Author:   neilk
Date:     2010-09-30 11:04:11 +0000 (Thu, 30 Sep 2010)

Log Message:
-----------
SpecialSessionStash now can serve thumbnails to client, with proper HTTP errors 
on failure

Modified Paths:
--------------
    branches/uploadwizard/phase3/includes/AutoLoader.php
    branches/uploadwizard/phase3/includes/SpecialPage.php
    branches/uploadwizard/phase3/includes/upload/SessionStash.php
    branches/uploadwizard/phase3/languages/messages/MessagesEn.php

Added Paths:
-----------
    branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php

Modified: branches/uploadwizard/phase3/includes/AutoLoader.php
===================================================================
--- branches/uploadwizard/phase3/includes/AutoLoader.php        2010-09-30 
07:46:37 UTC (rev 74013)
+++ branches/uploadwizard/phase3/includes/AutoLoader.php        2010-09-30 
11:04:11 UTC (rev 74014)
@@ -632,6 +632,7 @@
        'SpecialRecentChanges' => 'includes/specials/SpecialRecentchanges.php',
        'SpecialRecentchangeslinked' => 
'includes/specials/SpecialRecentchangeslinked.php',
        'SpecialSearch' => 'includes/specials/SpecialSearch.php',
+       'SpecialSessionStash' => 'includes/specials/SpecialSessionStash.php',
        'SpecialSpecialpages' => 'includes/specials/SpecialSpecialpages.php',
        'SpecialStatistics' => 'includes/specials/SpecialStatistics.php',
        'SpecialTags' => 'includes/specials/SpecialTags.php',

Modified: branches/uploadwizard/phase3/includes/SpecialPage.php
===================================================================
--- branches/uploadwizard/phase3/includes/SpecialPage.php       2010-09-30 
07:46:37 UTC (rev 74013)
+++ branches/uploadwizard/phase3/includes/SpecialPage.php       2010-09-30 
11:04:11 UTC (rev 74014)
@@ -149,6 +149,7 @@
                'MIMEsearch'                => array( 'SpecialPage', 
'MIMEsearch' ),
                'FileDuplicateSearch'       => array( 'SpecialPage', 
'FileDuplicateSearch' ),
                'Upload'                    => 'SpecialUpload',
+               'SessionStash'              => array( 'SpecialSessionStash', 
'SessionStash', 'upload' ),
 
                # Wiki data and tools
                'Statistics'                => 'SpecialStatistics',

Added: branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php
===================================================================
--- branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php      
                        (rev 0)
+++ branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php      
2010-09-30 11:04:11 UTC (rev 74014)
@@ -0,0 +1,117 @@
+<?php
+/**
+ * Special:SessionStash
+ *
+ * Web access for files temporarily stored by SessionStash.
+ *
+ * For example -- files that were uploaded with the UploadWizard extension are 
stored temporarily 
+ * before committing them to the db. But we want to see their thumbnails and 
get other information
+ * about them.
+ *
+ * Since this is based on the user's session, in effect this creates a private 
temporary file area.
+ * However, the URLs for the files cannot be shared.
+ *
+ * @file
+ * @ingroup SpecialPage
+ * @ingroup Upload
+ */
+
+class SpecialSessionStash extends SpecialPage {
+
+       static $HttpErrors = array(
+               400 => 'Bad Request',
+               403 => 'Access Denied',
+               404 => 'File not found',
+               500 => 'Internal Server Error',
+       );
+
+       // SessionStash
+       private $stash;
+
+       // we should not be reading in really big files and serving them out
+       private $maxServeFileSize = 262144; // 256K
+
+       // $request is the request (usually wgRequest)
+       // $subpage is everything in the URL after Special:SessionStash
+       public function __construct( $request=null, $subpage=null ) {
+               global $wgRequest;
+
+                parent::__construct( 'SessionStash', 'upload' );
+
+               $this->stash = new SessionStash();
+
+       }
+
+       /**
+        * If file available in stash, cats it out to the client as a simple 
HTTP response.
+        * n.b. Most sanity checking done in SessionStashLocalFile, so this is 
straightforward.
+        * 
+        * @param {String} subpage, e.g. in 
http://sample.com/wiki/Special:SessionStash/foo.jpg, the "foo".
+        * @return {Boolean} success 
+        */
+       public function execute( $subPage ) {
+               global $wgOut;
+
+               // prevent callers from doing standard HTML output -- we'll 
take it from here
+               $wgOut->disable();
+
+               // global $wgScriptPath, $wgLang, $wgUser, $wgOut;
+               wfDebug( __METHOD__ . " in subpage for $subPage \n" );
+
+               try { 
+                       $file = $this->getStashFile( $subPage );
+                       if ( $file->getSize() > $this->maxServeFileSize ) {
+                               throw new MWException( 'file size too large' );
+                       }
+                       $this->outputFile( $file );
+                       return true;
+
+               } catch( SessionStashFileNotFoundException $e ) {
+                       wfHttpError( 404, self::$HttpErrors[404], 
$e->getCode(), $e->getMessage() );
+
+               } catch( SessionStashBadPathException $e ) {
+                       wfHttpError( 403, self::$HttpErrors[403], 
$e->getCode(), $e->getMessage() );
+
+               } catch( Exception $e ) {
+                       wfHttpError( $code, self::$HttpErrors[$code], 
$e->getCode(), $e->getMessage() );
+
+               }
+                       
+               return false;
+       }
+
+
+       /** 
+        * Convert the incoming url portion (subpage of Special page) into a 
stashed file, if available.
+        * @param {String} $subPage 
+        * @return {File} file object
+        * @throws MWException, SessionStashFileNotFoundException, 
SessionStashBadPathException
+        */
+       private function getStashFile( $subPage );
+               // due to an implementation quirk (and trying to be compatible 
with older method) 
+               // the stash key doesn't have an extension 
+               $key = $subPage;
+               $n = strrpos( $subPage, '.' );
+                if ( $n !== false ) {
+                        $key = $n ? substr( $subPage, 0, $n ) : $subPage;
+               }
+               
+               $file = $this->stash->getFile( $key );
+               return $file;
+       }
+
+       /**
+        * Output HTTP response for file
+        * Side effects, obviously, of echoing lots of stuff to stdout.
+        * @param {File} file
+        */             
+       private function outputFile( $file ) { 
+               header( 'Content-Type: ' . $file->getMimeType() );
+               header( 'Content-Transfer-Encoding: binary' );
+               header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT' );
+               header( 'Pragma: public' );
+               header( 'Content-Length: ' . $file->getSize() );
+               readfile( $file->getPath() );
+       }
+}
+


Property changes on: 
branches/uploadwizard/phase3/includes/specials/SpecialSessionStash.php
___________________________________________________________________
Added: svn:eol-style
   + native

Modified: branches/uploadwizard/phase3/includes/upload/SessionStash.php
===================================================================
--- branches/uploadwizard/phase3/includes/upload/SessionStash.php       
2010-09-30 07:46:37 UTC (rev 74013)
+++ branches/uploadwizard/phase3/includes/upload/SessionStash.php       
2010-09-30 11:04:11 UTC (rev 74014)
@@ -3,11 +3,13 @@
  * SessionStash is intended to accomplish a few things:
  *   - enable applications to temporarily stash files without publishing them 
to the wiki.
  *      - Several parts of MediaWiki do this in similar ways: UploadBase, 
UploadWizard, and FirefoggChunkedExtension
- *        the idea is to unify them all here
+ *        And there are several that reimplement stashing from scratch, in 
idiosyncratic ways. The idea is to unify them all here.
+ *       Mostly all of them are the same except for storing some custom 
fields, which we subsume into the data array.
  *   - enable applications to find said files later, as long as the session or 
temp files haven't been purged. 
  *   - enable the uploading user (and *ONLY* the uploading user) to access 
said files, and thumbnails of said files, via a URL.
  *     We accomplish this by making the session serve as a URL->file mapping, 
on the assumption that nobody else can access 
- *     the session, even the uploading user.
+ *     the session, even the uploading user. See SpecialSessionStash, which 
implements a web interface to some files stored this way.
+ *
  */
 
 class SessionStash {
@@ -34,6 +36,11 @@
                if ( is_null( $repo ) ) {
                        $repo = RepoGroup::singleton()->getLocalRepo();
                }
+
+               // sanity check repo. If we want to mock the repo later this 
should be modified.
+               if ( ! is_dir( $repo->getZonePath( 'temp' ) ) ) {
+                       throw new MWException( 'invalid repo or cannot read 
repo temp dir' );
+               }
                $this->repo = $repo;
 
                if ( ! isset( $_SESSION ) ) {
@@ -47,6 +54,10 @@
                $this->baseUrl = SpecialPage::getTitleFor( 'SessionStash' 
)->getLocalURL(); 
        }
 
+       /**
+        * Get the base of URLs by which one can access the files 
+        * @return {String} url
+        */
        public function getBaseUrl() { 
                return $this->baseUrl;
        }
@@ -55,14 +66,22 @@
         * Get a file from the stash.
         * May throw exception if session data cannot be parsed due to schema 
change.
         * @param {Integer} key
-        * @return {null|SessionStashItem} null if no such item, or the item
+        * @return {null|SessionStashItem} null if no such item or item out of 
date, or the item
         */
        public function getFile( $key ) { 
-               if ( !array_key_exists( $key, $this->files ) ) {
+               if ( !isset( $this->files[$key] ) ) {
+                       wfDebug( "checking key = <$key> is in session\n" );
+                       if ( !isset( 
$_SESSION[UploadBase::SESSION_KEYNAME][$key] ) ) {
+                               wfDebug( "checking key = <$key> is in session - 
it isn't\n" );
+                               wfDebug( print_r( 
$_SESSION[UploadBase::SESSION_KEYNAME], 1 ) );
+                               throw new SessionStashFileNotFoundException();
+                       }
+
                        $stashData = 
$_SESSION[UploadBase::SESSION_KEYNAME][$key];
-
+       
+                       // guards against PHP class changing while session data 
doesn't
                        if ($stashData['version'] !== 
UploadBase::SESSION_VERSION ) {
-                               throw new MWException( 'session item schema 
does not match current software' );
+                               return self::$error['outdated session version'];
                        }
                        
                        // The path is flattened in with the other random props 
so we have to dig it out.
@@ -74,7 +93,10 @@
                                        $data[ $stashKey ] = $stashVal;
                                }
                        } 
-                       $this->files[$key] = new SessionStashFile( $this, 
$this->repo, $path, $key, $data );
+
+                       $file = new SessionStashFile( $this, $this->repo, 
$path, $key, $data );
+                       $this->files[$key] = $file;
+
                }
                return $this->files[$key];
        }
@@ -145,9 +167,25 @@
                $this->sessionStash = $stash;
                $this->sessionKey = $key;
                $this->sessionData = $data;
+               
+               // resolve mwrepo:// urls
                if ( $repo->isVirtualUrl( $path ) ) {
                        $path = $repo->resolveVirtualUrl( $path );      
                }
+
+               // check if path appears to be sane, no parent traverals, and 
is in this repo's temp zone.
+               if ( ( ! $repo->validateFilename( $path ) ) || 
+                       ( strpos( $path, $repo->getZonePath( 'temp' ) ) !== 0 ) 
) {
+                       throw new SessionStashBadPathException();
+               }
+
+               wfDebug( "checking if path exists and is good: $path " );
+               // check if path exists! and is a plain file.
+               if ( ! $repo->fileExists( $path, $repo::FILES_ONLY ) ) {
+                       wfDebug( "checking if path exists and is good: $path  
-- no!! " );
+                       throw new SessionStashFileNotFoundException();
+               }
+
                parent::__construct( false, $repo, $path, false );
 
                // we will be initializing from some tmpnam files that don't 
have extensions.
@@ -158,6 +196,36 @@
        }
 
        /**
+        * Test if a path looks like it's in the right place
+        *
+        * @param {String} $path 
+        * @return {Boolean}
+        */
+       public function isPathValid( $path ) {
+
+                if ( strval( $filename ) == '' ) { 
+                        return false; 
+                } 
+
+                /** 
+                * Lifted this bit from extensions/WebStore::validateFilename.
+                 * Use the same traversal protection as 
Title::secureAndSplit() 
+                 */ 
+                if ( strpos( $filename, '.' ) !== false && 
+                     ( $filename === '.' || $filename === '..' || 
+                       strpos( $filename, './' ) === 0  || 
+                       strpos( $filename, '../' ) === 0 || 
+                       strpos( $filename, '/./' ) !== false || 
+                       strpos( $filename, '/../' ) !== false ) ) { 
+                        return false; 
+                } 
+
+               
+               return true;
+
+       }
+
+       /**
         * A method needed by the file transforming and scaling routines in 
File.php
         * We do not necessarily care about doing the description at this point
         * @return {String} the empty string
@@ -193,7 +261,7 @@
                }
 
                if ( is_null( $extension ) ) {
-                       throw 'cannot determine extension';
+                       throw new MWException( 'cannot determine extension' );
                }
 
                $this->extension = parent::normalizeExtension( $extension );
@@ -325,3 +393,7 @@
        }
 
 }
+
+class SessionStashFileNotFoundException extends MWException {};
+class SessionStashBadPathException extends MWException {};
+

Modified: branches/uploadwizard/phase3/languages/messages/MessagesEn.php
===================================================================
--- branches/uploadwizard/phase3/languages/messages/MessagesEn.php      
2010-09-30 07:46:37 UTC (rev 74013)
+++ branches/uploadwizard/phase3/languages/messages/MessagesEn.php      
2010-09-30 11:04:11 UTC (rev 74014)
@@ -460,6 +460,7 @@
        'RevisionMove'              => array( 'RevisionMove' ),
        'ComparePages'              => array( 'ComparePages' ),
        'Badtitle'                  => array( 'Badtitle' ),
+       'SessionStash'              => array( 'SessionStash' ),         
 );
 
 /**



_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to