MediaWiki  REL1_21
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 $_GET params
00039         wfThumbHandleRequest();
00040 }
00041 
00042 wfLogProfilingData();
00043 
00044 //--------------------------------------------------------------------------
00045 
00051 function wfThumbHandleRequest() {
00052         $params = get_magic_quotes_gpc()
00053                 ? array_map( 'stripslashes', $_GET )
00054                 : $_GET;
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 
00095         wfProfileIn( __METHOD__ );
00096 
00097         $headers = array(); // HTTP headers to send
00098 
00099         $fileName = isset( $params['f'] ) ? $params['f'] : '';
00100         unset( $params['f'] );
00101 
00102         // Backwards compatibility parameters
00103         if ( isset( $params['w'] ) ) {
00104                 $params['width'] = $params['w'];
00105                 unset( $params['w'] );
00106         }
00107         if ( isset( $params['p'] ) ) {
00108                 $params['page'] = $params['p'];
00109         }
00110         unset( $params['r'] ); // ignore 'r' because we unconditionally pass File::RENDER
00111 
00112         // Is this a thumb of an archived file?
00113         $isOld = ( isset( $params['archived'] ) && $params['archived'] );
00114         unset( $params['archived'] ); // handlers don't care
00115 
00116         // Is this a thumb of a temp file?
00117         $isTemp = ( isset( $params['temp'] ) && $params['temp'] );
00118         unset( $params['temp'] ); // handlers don't care
00119 
00120         // Some basic input validation
00121         $fileName = strtr( $fileName, '\\/', '__' );
00122 
00123         // Actually fetch the image. Method depends on whether it is archived or not.
00124         if ( $isTemp ) {
00125                 $repo = RepoGroup::singleton()->getLocalRepo()->getTempRepo();
00126                 $img = new UnregisteredLocalFile( null, $repo,
00127                         # Temp files are hashed based on the name without the timestamp.
00128                         # The thumbnails will be hashed based on the entire name however.
00129                         # @TODO: fix this convention to actually be reasonable.
00130                         $repo->getZonePath( 'public' ) . '/' . $repo->getTempHashPath( $fileName ) . $fileName
00131                 );
00132         } elseif ( $isOld ) {
00133                 // Format is <timestamp>!<name>
00134                 $bits = explode( '!', $fileName, 2 );
00135                 if ( count( $bits ) != 2 ) {
00136                         wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
00137                         wfProfileOut( __METHOD__ );
00138                         return;
00139                 }
00140                 $title = Title::makeTitleSafe( NS_FILE, $bits[1] );
00141                 if ( !$title ) {
00142                         wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
00143                         wfProfileOut( __METHOD__ );
00144                         return;
00145                 }
00146                 $img = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $fileName );
00147         } else {
00148                 $img = wfLocalFile( $fileName );
00149         }
00150 
00151         // Check the source file title
00152         if ( !$img ) {
00153                 wfThumbError( 404, wfMessage( 'badtitletext' )->text() );
00154                 wfProfileOut( __METHOD__ );
00155                 return;
00156         }
00157 
00158         // Check permissions if there are read restrictions
00159         $varyHeader = array();
00160         if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) {
00161                 if ( !$img->getTitle() || !$img->getTitle()->userCan( 'read' ) ) {
00162                         wfThumbError( 403, 'Access denied. You do not have permission to access ' .
00163                                 'the source file.' );
00164                         wfProfileOut( __METHOD__ );
00165                         return;
00166                 }
00167                 $headers[] = 'Cache-Control: private';
00168                 $varyHeader[] = 'Cookie';
00169         }
00170 
00171         // Check the source file storage path
00172         if ( !$img->exists() ) {
00173                 wfThumbError( 404, "The source file '$fileName' does not exist." );
00174                 wfProfileOut( __METHOD__ );
00175                 return;
00176         } elseif ( $img->getPath() === false ) {
00177                 wfThumbError( 500, "The source file '$fileName' is not locally accessible." );
00178                 wfProfileOut( __METHOD__ );
00179                 return;
00180         }
00181 
00182         // Check IMS against the source file
00183         // This means that clients can keep a cached copy even after it has been deleted on the server
00184         if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
00185                 // Fix IE brokenness
00186                 $imsString = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
00187                 // Calculate time
00188                 wfSuppressWarnings();
00189                 $imsUnix = strtotime( $imsString );
00190                 wfRestoreWarnings();
00191                 if ( wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
00192                         header( 'HTTP/1.1 304 Not Modified' );
00193                         wfProfileOut( __METHOD__ );
00194                         return;
00195                 }
00196         }
00197 
00198         // Get the normalized thumbnail name from the parameters...
00199         try {
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                 $thumbName2 = $img->thumbName( $params, File::THUMB_FULL_NAME ); // b/c; "long" style
00207         } catch ( MWException $e ) {
00208                 wfThumbError( 500, $e->getHTML() );
00209                 wfProfileOut( __METHOD__ );
00210                 return;
00211         }
00212 
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 ( rawurldecode( $params['rel404'] ) === $img->getThumbRel( $thumbName ) ) {
00219                         // Request for the canonical thumbnail name
00220                 } elseif ( rawurldecode( $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: ' .
00225                                 wfExpandUrl( $img->getThumbUrl( $thumbName ), PROTO_CURRENT ) );
00226                         $response->header( 'Expires: ' .
00227                                 gmdate( 'D, d M Y H:i:s', time() + 7 * 86400 ) . ' GMT' );
00228                         if ( $wgVaryOnXFP ) {
00229                                 $varyHeader[] = 'X-Forwarded-Proto';
00230                         }
00231                         if ( count( $varyHeader ) ) {
00232                                 $response->header( 'Vary: ' . implode( ', ', $varyHeader ) );
00233                         }
00234                         wfProfileOut( __METHOD__ );
00235                         return;
00236                 } else {
00237                         wfThumbError( 404, "The given path of the specified thumbnail is incorrect;
00238                                 expected '" . $img->getThumbRel( $thumbName ) . "' but got '" .
00239                                 rawurldecode( $params['rel404'] ) . "'." );
00240                         wfProfileOut( __METHOD__ );
00241                         return;
00242                 }
00243         }
00244 
00245         // Suggest a good name for users downloading this thumbnail
00246         $headers[] = "Content-Disposition: {$img->getThumbDisposition( $thumbName )}";
00247 
00248         if ( count( $varyHeader ) ) {
00249                 $headers[] = 'Vary: ' . implode( ', ', $varyHeader );
00250         }
00251 
00252         // Stream the file if it exists already...
00253         $thumbPath = $img->getThumbPath( $thumbName );
00254         if ( $img->getRepo()->fileExists( $thumbPath ) ) {
00255                 $img->getRepo()->streamFile( $thumbPath, $headers );
00256                 wfProfileOut( __METHOD__ );
00257                 return;
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 }