MediaWiki  master
thumb.php
Go to the documentation of this file.
00001 <?php
00024 define( 'MW_NO_OUTPUT_COMPRESSION', 1 );
00025 if ( isset( $_SERVER['MW_COMPILED'] ) ) {
00026         require( 'core/includes/WebStart.php' );
00027 } else {
00028         require( __DIR__ . '/includes/WebStart.php' );
00029 }
00030 
00031 // Don't use fancy mime detection, just check the file extension for jpg/gif/png
00032 $wgTrivialMimeDetection = true;
00033 
00034 if ( defined( 'THUMB_HANDLER' ) ) {
00035         // Called from thumb_handler.php via 404; extract params from the URI...
00036         wfThumbHandle404();
00037 } else {
00038         // Called directly, use $_REQUEST params
00039         wfThumbHandleRequest();
00040 }
00041 
00042 wfLogProfilingData();
00043 
00044 //--------------------------------------------------------------------------
00045 
00051 function wfThumbHandleRequest() {
00052         $params = get_magic_quotes_gpc()
00053                 ? array_map( 'stripslashes', $_REQUEST )
00054                 : $_REQUEST;
00055 
00056         wfStreamThumb( $params ); // stream the thumbnail
00057 }
00058 
00064 function wfThumbHandle404() {
00065         global $wgArticlePath;
00066 
00067         # Set action base paths so that WebRequest::getPathInfo()
00068         # recognizes the "X" as the 'title' in ../thumb_handler.php/X urls.
00069         $wgArticlePath = false; # Don't let a "/*" article path clober our action path
00070 
00071         $matches = WebRequest::getPathInfo();
00072         if ( !isset( $matches['title'] ) ) {
00073                 wfThumbError( 404, 'Could not determine the name of the requested thumbnail.' );
00074                 return;
00075         }
00076 
00077         $params = wfExtractThumbParams( $matches['title'] ); // basic wiki URL param extracting
00078         if ( $params == null ) {
00079                 wfThumbError( 400, 'The specified thumbnail parameters are not recognized.' );
00080                 return;
00081         }
00082 
00083         wfStreamThumb( $params ); // stream the thumbnail
00084 }
00085 
00092 function wfStreamThumb( array $params ) {
00093         global $wgVaryOnXFP;
00094         wfProfileIn( __METHOD__ );
00095 
00096         $headers = array(); // HTTP headers to send
00097 
00098         $fileName = isset( $params['f'] ) ? $params['f'] : '';
00099         unset( $params['f'] );
00100 
00101         // Backwards compatibility parameters
00102         if ( isset( $params['w'] ) ) {
00103                 $params['width'] = $params['w'];
00104                 unset( $params['w'] );
00105         }
00106         if ( isset( $params['p'] ) ) {
00107                 $params['page'] = $params['p'];
00108         }
00109         unset( $params['r'] ); // ignore 'r' because we unconditionally pass File::RENDER
00110 
00111         // Is this a thumb of an archived file?
00112         $isOld = ( isset( $params['archived'] ) && $params['archived'] );
00113         unset( $params['archived'] ); // handlers don't care
00114 
00115         // Is this a thumb of a temp file?
00116         $isTemp = ( isset( $params['temp'] ) && $params['temp'] );
00117         unset( $params['temp'] ); // handlers don't care
00118 
00119         // Some basic input validation
00120         $fileName = strtr( $fileName, '\\/', '__' );
00121 
00122         // Actually fetch the image. Method depends on whether it is archived or not.
00123         if ( $isTemp ) {
00124                 $repo = RepoGroup::singleton()->getLocalRepo()->getTempRepo();
00125                 $img = new UnregisteredLocalFile( null, $repo,
00126                         # Temp files are hashed based on the name without the timestamp.
00127                         # The thumbnails will be hashed based on the entire name however.
00128                         # @TODO: fix this convention to actually be reasonable.
00129                         $repo->getZonePath( 'public' ) . '/' . $repo->getTempHashPath( $fileName ) . $fileName
00130                 );
00131         } elseif ( $isOld ) {
00132                 // Format is <timestamp>!<name>
00133                 $bits = explode( '!', $fileName, 2 );
00134                 if ( count( $bits ) != 2 ) {
00135                         wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
00136                         wfProfileOut( __METHOD__ );
00137                         return;
00138                 }
00139                 $title = Title::makeTitleSafe( NS_FILE, $bits[1] );
00140                 if ( !$title ) {
00141                         wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
00142                         wfProfileOut( __METHOD__ );
00143                         return;
00144                 }
00145                 $img = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $fileName );
00146         } else {
00147                 $img = wfLocalFile( $fileName );
00148         }
00149 
00150         // Check the source file title
00151         if ( !$img ) {
00152                 wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
00153                 wfProfileOut( __METHOD__ );
00154                 return;
00155         }
00156 
00157         // Check permissions if there are read restrictions
00158         $varyHeader = array();
00159         if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
00160                 if ( !$img->getTitle() || !$img->getTitle()->userCan( 'read' ) ) {
00161                         wfThumbError( 403, 'Access denied. You do not have permission to access ' .
00162                                 'the source file.' );
00163                         wfProfileOut( __METHOD__ );
00164                         return;
00165                 }
00166                 $headers[] = 'Cache-Control: private';
00167                 $varyHeader[] = 'Cookie';
00168         }
00169 
00170         // Check the source file storage path
00171         if ( !$img->exists() ) {
00172                 wfThumbError( 404, 'The source file for the specified thumbnail does not exist.' );
00173                 wfProfileOut( __METHOD__ );
00174                 return;
00175         }
00176         $sourcePath = $img->getPath();
00177         if ( $sourcePath === false ) {
00178                 wfThumbError( 500, 'The source file is not locally accessible.' );
00179                 wfProfileOut( __METHOD__ );
00180                 return;
00181         }
00182 
00183         // Check IMS against the source file
00184         // This means that clients can keep a cached copy even after it has been deleted on the server
00185         if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
00186                 // Fix IE brokenness
00187                 $imsString = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
00188                 // Calculate time
00189                 wfSuppressWarnings();
00190                 $imsUnix = strtotime( $imsString );
00191                 wfRestoreWarnings();
00192                 $sourceTsUnix = wfTimestamp( TS_UNIX, $img->getTimestamp() );
00193                 if ( $sourceTsUnix <= $imsUnix ) {
00194                         header( 'HTTP/1.1 304 Not Modified' );
00195                         wfProfileOut( __METHOD__ );
00196                         return;
00197                 }
00198         }
00199 
00200         $thumbName = $img->thumbName( $params );
00201         if ( !strlen( $thumbName ) ) { // invalid params?
00202                 wfThumbError( 400, 'The specified thumbnail parameters are not valid.' );
00203                 wfProfileOut( __METHOD__ );
00204                 return;
00205         }
00206 
00207         $disposition = $img->getThumbDisposition( $thumbName );
00208         $headers[] = "Content-Disposition: $disposition";
00209 
00210         // Stream the file if it exists already...
00211         try {
00212                 $thumbName2 = $img->thumbName( $params, File::THUMB_FULL_NAME ); // b/c; "long" style
00213                 // For 404 handled thumbnails, we only use the the base name of the URI
00214                 // for the thumb params and the parent directory for the source file name.
00215                 // Check that the zone relative path matches up so squid caches won't pick
00216                 // up thumbs that would not be purged on source file deletion (bug 34231).
00217                 if ( isset( $params['rel404'] ) ) { // thumbnail was handled via 404
00218                         if ( urldecode( $params['rel404'] ) === $img->getThumbRel( $thumbName ) ) {
00219                                 // Request for the canonical thumbnail name
00220                         } elseif ( urldecode( $params['rel404'] ) === $img->getThumbRel( $thumbName2 ) ) {
00221                                 // Request for the "long" thumbnail name; redirect to canonical name
00222                                 $response = RequestContext::getMain()->getRequest()->response();
00223                                 $response->header( "HTTP/1.1 301 " . HttpStatus::getMessage( 301 ) );
00224                                 $response->header( 'Location: ' . wfExpandUrl( $img->getThumbUrl( $thumbName ), PROTO_CURRENT ) );
00225                                 $response->header( 'Expires: ' .
00226                                         gmdate( 'D, d M Y H:i:s', time() + 7*86400 ) . ' GMT' );
00227                                 if ( $wgVaryOnXFP ) {
00228                                         $varyHeader[] = 'X-Forwarded-Proto';
00229                                 }
00230                                 if ( count( $varyHeader ) ) {
00231                                         $response->header( 'Vary: ' . implode( ', ', $varyHeader ) );
00232                                 }
00233                                 wfProfileOut( __METHOD__ );
00234                                 return;
00235                         } else {
00236                                 wfThumbError( 404, 'The given path of the specified thumbnail is incorrect.' );
00237                                 wfProfileOut( __METHOD__ );
00238                                 return;
00239                         }
00240                 }
00241                 $thumbPath = $img->getThumbPath( $thumbName );
00242                 if ( $img->getRepo()->fileExists( $thumbPath ) ) {
00243                         if ( count( $varyHeader ) ) {
00244                                 $headers[] = 'Vary: ' . implode( ', ', $varyHeader );
00245                         }
00246                         $img->getRepo()->streamFile( $thumbPath, $headers );
00247                         wfProfileOut( __METHOD__ );
00248                         return;
00249                 }
00250         } catch ( MWException $e ) {
00251                 wfThumbError( 500, $e->getHTML() );
00252                 wfProfileOut( __METHOD__ );
00253                 return;
00254         }
00255 
00256         if ( count( $varyHeader ) ) {
00257                 $headers[] = 'Vary: ' . implode( ', ', $varyHeader );
00258         }
00259 
00260         // Thumbnail isn't already there, so create the new thumbnail...
00261         try {
00262                 $thumb = $img->transform( $params, File::RENDER_NOW );
00263         } catch ( Exception $ex ) {
00264                 // Tried to select a page on a non-paged file?
00265                 $thumb = false;
00266         }
00267 
00268         // Check for thumbnail generation errors...
00269         $errorMsg = false;
00270         $msg = wfMessage( 'thumbnail_error' );
00271         if ( !$thumb ) {
00272                 $errorMsg = $msg->rawParams( 'File::transform() returned false' )->escaped();
00273         } elseif ( $thumb->isError() ) {
00274                 $errorMsg = $thumb->getHtmlMsg();
00275         } elseif ( !$thumb->hasFile() ) {
00276                 $errorMsg = $msg->rawParams( 'No path supplied in thumbnail object' )->escaped();
00277         } elseif ( $thumb->fileIsSource() ) {
00278                 $errorMsg = $msg->
00279                         rawParams( 'Image was not scaled, is the requested width bigger than the source?' )->escaped();
00280         }
00281 
00282         if ( $errorMsg !== false ) {
00283                 wfThumbError( 500, $errorMsg );
00284         } else {
00285                 // Stream the file if there were no errors
00286                 $thumb->streamFile( $headers );
00287         }
00288 
00289         wfProfileOut( __METHOD__ );
00290 }
00291 
00299 function wfExtractThumbParams( $thumbRel ) {
00300         $repo = RepoGroup::singleton()->getLocalRepo();
00301 
00302         $hashDirReg = $subdirReg = '';
00303         for ( $i = 0; $i < $repo->getHashLevels(); $i++ ) {
00304                 $subdirReg .= '[0-9a-f]';
00305                 $hashDirReg .= "$subdirReg/";
00306         }
00307 
00308         // Check if this is a thumbnail of an original in the local file repo
00309         if ( preg_match( "!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
00310                 list( /*all*/, $rel, $archOrTemp, $filename, $thumbname ) = $m;
00311         // Check if this is a thumbnail of an temp file in the local file repo
00312         } elseif ( preg_match( "!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
00313                 list( /*all*/, $archOrTemp, $rel, $filename, $thumbname ) = $m;
00314         } else {
00315                 return null; // not a valid looking thumbnail request
00316         }
00317 
00318         $params = array( 'f' => $filename, 'rel404' => $rel );
00319         if ( $archOrTemp === 'archive/' ) {
00320                 $params['archived'] = 1;
00321         } elseif ( $archOrTemp === 'temp/' ) {
00322                 $params['temp'] = 1;
00323         }
00324 
00325         // Check hooks if parameters can be extracted
00326         // Hooks return false if they manage to *resolve* the parameters
00327         if ( !wfRunHooks( 'ExtractThumbParameters', array( $thumbname, &$params ) ) ) {
00328                 return $params; // valid thumbnail URL (via extension or config)
00329         // Check if the parameters can be extracted from the thumbnail name...
00330         } elseif ( preg_match( '!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname, $matches ) ) {
00331                 list( /* all */, $pagefull, $pagenum, $size ) = $matches;
00332                 $params['width'] = $size;
00333                 if ( $pagenum ) {
00334                         $params['page'] = $pagenum;
00335                 }
00336                 return $params; // valid thumbnail URL
00337         }
00338 
00339         return null; // not a valid thumbnail URL
00340 }
00341 
00349 function wfThumbError( $status, $msg ) {
00350         global $wgShowHostnames;
00351 
00352         header( 'Cache-Control: no-cache' );
00353         header( 'Content-Type: text/html; charset=utf-8' );
00354         if ( $status == 404 ) {
00355                 header( 'HTTP/1.1 404 Not found' );
00356         } elseif ( $status == 403 ) {
00357                 header( 'HTTP/1.1 403 Forbidden' );
00358                 header( 'Vary: Cookie' );
00359         } else {
00360                 header( 'HTTP/1.1 500 Internal server error' );
00361         }
00362         if ( $wgShowHostnames ) {
00363                 $url = htmlspecialchars( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' );
00364                 $hostname = htmlspecialchars( wfHostname() );
00365                 $debug = "<!-- $url -->\n<!-- $hostname -->\n";
00366         } else {
00367                 $debug = "";
00368         }
00369         echo <<<EOT
00370 <html><head><title>Error generating thumbnail</title></head>
00371 <body>
00372 <h1>Error generating thumbnail</h1>
00373 <p>
00374 $msg
00375 </p>
00376 $debug
00377 </body>
00378 </html>
00379 
00380 EOT;
00381 }