MediaWiki
REL1_24
|
00001 <?php 00024 define( 'MW_NO_OUTPUT_COMPRESSION', 1 ); 00025 require __DIR__ . '/includes/WebStart.php'; 00026 00027 // Don't use fancy MIME detection, just check the file extension for jpg/gif/png 00028 $wgTrivialMimeDetection = true; 00029 00030 if ( defined( 'THUMB_HANDLER' ) ) { 00031 // Called from thumb_handler.php via 404; extract params from the URI... 00032 wfThumbHandle404(); 00033 } else { 00034 // Called directly, use $_GET params 00035 wfThumbHandleRequest(); 00036 } 00037 00038 wfLogProfilingData(); 00039 // Commit and close up! 00040 $factory = wfGetLBFactory(); 00041 $factory->commitMasterChanges(); 00042 $factory->shutdown(); 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 # Note: If Custom per-extension repo paths are set, this may break. 00070 $repo = RepoGroup::singleton()->getLocalRepo(); 00071 $oldArticlePath = $wgArticlePath; 00072 $wgArticlePath = $repo->getZoneUrl( 'thumb' ) . '/$1'; 00073 00074 $matches = WebRequest::getPathInfo(); 00075 00076 $wgArticlePath = $oldArticlePath; 00077 00078 if ( !isset( $matches['title'] ) ) { 00079 wfThumbError( 404, 'Could not determine the name of the requested thumbnail.' ); 00080 return; 00081 } 00082 00083 $params = wfExtractThumbRequestInfo( $matches['title'] ); // basic wiki URL param extracting 00084 if ( $params == null ) { 00085 wfThumbError( 400, 'The specified thumbnail parameters are not recognized.' ); 00086 return; 00087 } 00088 00089 wfStreamThumb( $params ); // stream the thumbnail 00090 } 00091 00105 function wfStreamThumb( array $params ) { 00106 global $wgVaryOnXFP; 00107 00108 $section = new ProfileSection( __METHOD__ ); 00109 00110 $headers = array(); // HTTP headers to send 00111 00112 $fileName = isset( $params['f'] ) ? $params['f'] : ''; 00113 00114 // Backwards compatibility parameters 00115 if ( isset( $params['w'] ) ) { 00116 $params['width'] = $params['w']; 00117 unset( $params['w'] ); 00118 } 00119 if ( isset( $params['width'] ) && substr( $params['width'], -2 ) == 'px' ) { 00120 // strip the px (pixel) suffix, if found 00121 $params['width'] = substr( $params['width'], 0, -2 ); 00122 } 00123 if ( isset( $params['p'] ) ) { 00124 $params['page'] = $params['p']; 00125 } 00126 00127 // Is this a thumb of an archived file? 00128 $isOld = ( isset( $params['archived'] ) && $params['archived'] ); 00129 unset( $params['archived'] ); // handlers don't care 00130 00131 // Is this a thumb of a temp file? 00132 $isTemp = ( isset( $params['temp'] ) && $params['temp'] ); 00133 unset( $params['temp'] ); // handlers don't care 00134 00135 // Some basic input validation 00136 $fileName = strtr( $fileName, '\\/', '__' ); 00137 00138 // Actually fetch the image. Method depends on whether it is archived or not. 00139 if ( $isTemp ) { 00140 $repo = RepoGroup::singleton()->getLocalRepo()->getTempRepo(); 00141 $img = new UnregisteredLocalFile( null, $repo, 00142 # Temp files are hashed based on the name without the timestamp. 00143 # The thumbnails will be hashed based on the entire name however. 00144 # @todo fix this convention to actually be reasonable. 00145 $repo->getZonePath( 'public' ) . '/' . $repo->getTempHashPath( $fileName ) . $fileName 00146 ); 00147 } elseif ( $isOld ) { 00148 // Format is <timestamp>!<name> 00149 $bits = explode( '!', $fileName, 2 ); 00150 if ( count( $bits ) != 2 ) { 00151 wfThumbError( 404, wfMessage( 'badtitletext' )->text() ); 00152 return; 00153 } 00154 $title = Title::makeTitleSafe( NS_FILE, $bits[1] ); 00155 if ( !$title ) { 00156 wfThumbError( 404, wfMessage( 'badtitletext' )->text() ); 00157 return; 00158 } 00159 $img = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $fileName ); 00160 } else { 00161 $img = wfLocalFile( $fileName ); 00162 } 00163 00164 // Check the source file title 00165 if ( !$img ) { 00166 wfThumbError( 404, wfMessage( 'badtitletext' )->text() ); 00167 return; 00168 } 00169 00170 // Check permissions if there are read restrictions 00171 $varyHeader = array(); 00172 if ( !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) ) { 00173 if ( !$img->getTitle() || !$img->getTitle()->userCan( 'read' ) ) { 00174 wfThumbError( 403, 'Access denied. You do not have permission to access ' . 00175 'the source file.' ); 00176 return; 00177 } 00178 $headers[] = 'Cache-Control: private'; 00179 $varyHeader[] = 'Cookie'; 00180 } 00181 00182 // Check if the file is hidden 00183 if ( $img->isDeleted( File::DELETED_FILE ) ) { 00184 wfThumbError( 404, "The source file '$fileName' does not exist." ); 00185 return; 00186 } 00187 00188 // Do rendering parameters extraction from thumbnail name. 00189 if ( isset( $params['thumbName'] ) ) { 00190 $params = wfExtractThumbParams( $img, $params ); 00191 } 00192 if ( $params == null ) { 00193 wfThumbError( 400, 'The specified thumbnail parameters are not recognized.' ); 00194 return; 00195 } 00196 00197 // Check the source file storage path 00198 if ( !$img->exists() ) { 00199 $redirectedLocation = false; 00200 if ( !$isTemp ) { 00201 // Check for file redirect 00202 // Since redirects are associated with pages, not versions of files, 00203 // we look for the most current version to see if its a redirect. 00204 $possRedirFile = RepoGroup::singleton()->getLocalRepo()->findFile( $img->getName() ); 00205 if ( $possRedirFile && !is_null( $possRedirFile->getRedirected() ) ) { 00206 $redirTarget = $possRedirFile->getName(); 00207 $targetFile = wfLocalFile( Title::makeTitleSafe( NS_FILE, $redirTarget ) ); 00208 if ( $targetFile->exists() ) { 00209 $newThumbName = $targetFile->thumbName( $params ); 00210 if ( $isOld ) { 00211 $newThumbUrl = $targetFile->getArchiveThumbUrl( 00212 $bits[0] . '!' . $targetFile->getName(), $newThumbName ); 00213 } else { 00214 $newThumbUrl = $targetFile->getThumbUrl( $newThumbName ); 00215 } 00216 $redirectedLocation = wfExpandUrl( $newThumbUrl, PROTO_CURRENT ); 00217 } 00218 } 00219 } 00220 00221 if ( $redirectedLocation ) { 00222 // File has been moved. Give redirect. 00223 $response = RequestContext::getMain()->getRequest()->response(); 00224 $response->header( "HTTP/1.1 302 " . HttpStatus::getMessage( 302 ) ); 00225 $response->header( 'Location: ' . $redirectedLocation ); 00226 $response->header( 'Expires: ' . 00227 gmdate( 'D, d M Y H:i:s', time() + 12 * 3600 ) . ' GMT' ); 00228 if ( $wgVaryOnXFP ) { 00229 $varyHeader[] = 'X-Forwarded-Proto'; 00230 } 00231 if ( count( $varyHeader ) ) { 00232 $response->header( 'Vary: ' . implode( ', ', $varyHeader ) ); 00233 } 00234 return; 00235 } 00236 00237 // If its not a redirect that has a target as a local file, give 404. 00238 wfThumbError( 404, "The source file '$fileName' does not exist." ); 00239 return; 00240 } elseif ( $img->getPath() === false ) { 00241 wfThumbError( 500, "The source file '$fileName' is not locally accessible." ); 00242 return; 00243 } 00244 00245 // Check IMS against the source file 00246 // This means that clients can keep a cached copy even after it has been deleted on the server 00247 if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) { 00248 // Fix IE brokenness 00249 $imsString = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] ); 00250 // Calculate time 00251 wfSuppressWarnings(); 00252 $imsUnix = strtotime( $imsString ); 00253 wfRestoreWarnings(); 00254 if ( wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) { 00255 header( 'HTTP/1.1 304 Not Modified' ); 00256 return; 00257 } 00258 } 00259 00260 $rel404 = isset( $params['rel404'] ) ? $params['rel404'] : null; 00261 unset( $params['r'] ); // ignore 'r' because we unconditionally pass File::RENDER 00262 unset( $params['f'] ); // We're done with 'f' parameter. 00263 unset( $params['rel404'] ); // moved to $rel404 00264 00265 // Get the normalized thumbnail name from the parameters... 00266 try { 00267 $thumbName = $img->thumbName( $params ); 00268 if ( !strlen( $thumbName ) ) { // invalid params? 00269 wfThumbError( 400, 'The specified thumbnail parameters are not valid.' ); 00270 return; 00271 } 00272 $thumbName2 = $img->thumbName( $params, File::THUMB_FULL_NAME ); // b/c; "long" style 00273 } catch ( MWException $e ) { 00274 wfThumbError( 500, $e->getHTML() ); 00275 return; 00276 } 00277 00278 // For 404 handled thumbnails, we only use the the base name of the URI 00279 // for the thumb params and the parent directory for the source file name. 00280 // Check that the zone relative path matches up so squid caches won't pick 00281 // up thumbs that would not be purged on source file deletion (bug 34231). 00282 if ( $rel404 !== null ) { // thumbnail was handled via 404 00283 if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) { 00284 // Request for the canonical thumbnail name 00285 } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) { 00286 // Request for the "long" thumbnail name; redirect to canonical name 00287 $response = RequestContext::getMain()->getRequest()->response(); 00288 $response->header( "HTTP/1.1 301 " . HttpStatus::getMessage( 301 ) ); 00289 $response->header( 'Location: ' . 00290 wfExpandUrl( $img->getThumbUrl( $thumbName ), PROTO_CURRENT ) ); 00291 $response->header( 'Expires: ' . 00292 gmdate( 'D, d M Y H:i:s', time() + 7 * 86400 ) . ' GMT' ); 00293 if ( $wgVaryOnXFP ) { 00294 $varyHeader[] = 'X-Forwarded-Proto'; 00295 } 00296 if ( count( $varyHeader ) ) { 00297 $response->header( 'Vary: ' . implode( ', ', $varyHeader ) ); 00298 } 00299 return; 00300 } else { 00301 wfThumbError( 404, "The given path of the specified thumbnail is incorrect; 00302 expected '" . $img->getThumbRel( $thumbName ) . "' but got '" . 00303 rawurldecode( $rel404 ) . "'." ); 00304 return; 00305 } 00306 } 00307 00308 $dispositionType = isset( $params['download'] ) ? 'attachment' : 'inline'; 00309 00310 // Suggest a good name for users downloading this thumbnail 00311 $headers[] = "Content-Disposition: {$img->getThumbDisposition( $thumbName, $dispositionType )}"; 00312 00313 if ( count( $varyHeader ) ) { 00314 $headers[] = 'Vary: ' . implode( ', ', $varyHeader ); 00315 } 00316 00317 // Stream the file if it exists already... 00318 $thumbPath = $img->getThumbPath( $thumbName ); 00319 if ( $img->getRepo()->fileExists( $thumbPath ) ) { 00320 $img->getRepo()->streamFile( $thumbPath, $headers ); 00321 return; 00322 } 00323 00324 $user = RequestContext::getMain()->getUser(); 00325 if ( !wfThumbIsStandard( $img, $params ) && $user->pingLimiter( 'renderfile-nonstandard' ) ) { 00326 wfThumbError( 500, wfMessage( 'actionthrottledtext' ) ); 00327 return; 00328 } elseif ( $user->pingLimiter( 'renderfile' ) ) { 00329 wfThumbError( 500, wfMessage( 'actionthrottledtext' ) ); 00330 return; 00331 } 00332 00333 // Actually generate a new thumbnail 00334 list( $thumb, $errorMsg ) = wfGenerateThumbnail( $img, $params, $thumbName, $thumbPath ); 00335 00336 // Check for thumbnail generation errors... 00337 $msg = wfMessage( 'thumbnail_error' ); 00338 if ( !$thumb ) { 00339 $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped(); 00340 } elseif ( $thumb->isError() ) { 00341 $errorMsg = $thumb->getHtmlMsg(); 00342 } elseif ( !$thumb->hasFile() ) { 00343 $errorMsg = $msg->rawParams( 'No path supplied in thumbnail object' )->escaped(); 00344 } elseif ( $thumb->fileIsSource() ) { 00345 $errorMsg = $msg-> 00346 rawParams( 'Image was not scaled, is the requested width bigger than the source?' )->escaped(); 00347 } 00348 00349 if ( $errorMsg !== false ) { 00350 wfThumbError( 500, $errorMsg ); 00351 } else { 00352 // Stream the file if there were no errors 00353 $thumb->streamFile( $headers ); 00354 } 00355 } 00356 00366 function wfGenerateThumbnail( File $file, array $params, $thumbName, $thumbPath ) { 00367 global $wgMemc, $wgAttemptFailureEpoch; 00368 00369 $key = wfMemcKey( 'attempt-failures', $wgAttemptFailureEpoch, 00370 $file->getRepo()->getName(), $file->getSha1(), md5( $thumbName ) ); 00371 00372 // Check if this file keeps failing to render 00373 if ( $wgMemc->get( $key ) >= 4 ) { 00374 return array( false, wfMessage( 'thumbnail_image-failure-limit', 4 ) ); 00375 } 00376 00377 $done = false; 00378 // Record failures on PHP fatals in addition to caching exceptions 00379 register_shutdown_function( function () use ( &$done, $key ) { 00380 if ( !$done ) { // transform() gave a fatal 00381 global $wgMemc; 00382 // Randomize TTL to reduce stampedes 00383 $wgMemc->incrWithInit( $key, 3600 + mt_rand( 0, 300 ) ); 00384 } 00385 } ); 00386 00387 $thumb = false; 00388 $errorHtml = false; 00389 00390 // guard thumbnail rendering with PoolCounter to avoid stampedes 00391 // expensive files use a separate PoolCounter config so it is possible 00392 // to set up a global limit on them 00393 if ( $file->isExpensiveToThumbnail() ) { 00394 $poolCounterType = 'FileRenderExpensive'; 00395 } else { 00396 $poolCounterType = 'FileRender'; 00397 } 00398 00399 // Thumbnail isn't already there, so create the new thumbnail... 00400 try { 00401 $work = new PoolCounterWorkViaCallback( $poolCounterType, sha1( $file->getName() ), 00402 array( 00403 'doWork' => function () use ( $file, $params ) { 00404 return $file->transform( $params, File::RENDER_NOW ); 00405 }, 00406 'getCachedWork' => function () use ( $file, $params, $thumbPath ) { 00407 // If the worker that finished made this thumbnail then use it. 00408 // Otherwise, it probably made a different thumbnail for this file. 00409 return $file->getRepo()->fileExists( $thumbPath ) 00410 ? $file->transform( $params, File::RENDER_NOW ) 00411 : false; // retry once more in exclusive mode 00412 }, 00413 'fallback' => function () { 00414 return wfMessage( 'generic-pool-error' )->parse(); 00415 }, 00416 'error' => function ( $status ) { 00417 return $status->getHTML(); 00418 } 00419 ) 00420 ); 00421 $result = $work->execute(); 00422 if ( $result instanceof MediaTransformOutput ) { 00423 $thumb = $result; 00424 } elseif ( is_string( $result ) ) { // error 00425 $errorHtml = $result; 00426 } 00427 } catch ( Exception $e ) { 00428 // Tried to select a page on a non-paged file? 00429 } 00430 00431 $done = true; // no PHP fatal occured 00432 00433 if ( !$thumb || $thumb->isError() ) { 00434 // Randomize TTL to reduce stampedes 00435 $wgMemc->incrWithInit( $key, 3600 + mt_rand( 0, 300 ) ); 00436 } 00437 00438 return array( $thumb, $errorHtml ); 00439 } 00440 00454 function wfThumbIsStandard( File $file, array $params ) { 00455 global $wgThumbLimits, $wgImageLimits; 00456 00457 $handler = $file->getHandler(); 00458 if ( !$handler || !isset( $params['width'] ) ) { 00459 return false; 00460 } 00461 00462 $basicParams = array(); 00463 if ( isset( $params['page'] ) ) { 00464 $basicParams['page'] = $params['page']; 00465 } 00466 00467 // Check if the width matches one of $wgThumbLimits 00468 if ( in_array( $params['width'], $wgThumbLimits ) ) { 00469 $normalParams = $basicParams + array( 'width' => $params['width'] ); 00470 // Append any default values to the map (e.g. "lossy", "lossless", ...) 00471 $handler->normaliseParams( $file, $normalParams ); 00472 } else { 00473 // If not, then check if the width matchs one of $wgImageLimits 00474 $match = false; 00475 foreach ( $wgImageLimits as $pair ) { 00476 $normalParams = $basicParams + array( 'width' => $pair[0], 'height' => $pair[1] ); 00477 // Decide whether the thumbnail should be scaled on width or height. 00478 // Also append any default values to the map (e.g. "lossy", "lossless", ...) 00479 $handler->normaliseParams( $file, $normalParams ); 00480 // Check if this standard thumbnail size maps to the given width 00481 if ( $normalParams['width'] == $params['width'] ) { 00482 $match = true; 00483 break; 00484 } 00485 } 00486 if ( !$match ) { 00487 return false; // not standard for description pages 00488 } 00489 } 00490 00491 // Check that the given values for non-page, non-width, params are just defaults 00492 foreach ( $params as $key => $value ) { 00493 if ( !isset( $normalParams[$key] ) || $normalParams[$key] != $value ) { 00494 return false; 00495 } 00496 } 00497 00498 return true; 00499 } 00500 00520 function wfExtractThumbRequestInfo( $thumbRel ) { 00521 $repo = RepoGroup::singleton()->getLocalRepo(); 00522 00523 $hashDirReg = $subdirReg = ''; 00524 $hashLevels = $repo->getHashLevels(); 00525 for ( $i = 0; $i < $hashLevels; $i++ ) { 00526 $subdirReg .= '[0-9a-f]'; 00527 $hashDirReg .= "$subdirReg/"; 00528 } 00529 00530 // Check if this is a thumbnail of an original in the local file repo 00531 if ( preg_match( "!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) { 00532 list( /*all*/, $rel, $archOrTemp, $filename, $thumbname ) = $m; 00533 // Check if this is a thumbnail of an temp file in the local file repo 00534 } elseif ( preg_match( "!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) { 00535 list( /*all*/, $archOrTemp, $rel, $filename, $thumbname ) = $m; 00536 } else { 00537 return null; // not a valid looking thumbnail request 00538 } 00539 00540 $params = array( 'f' => $filename, 'rel404' => $rel ); 00541 if ( $archOrTemp === 'archive/' ) { 00542 $params['archived'] = 1; 00543 } elseif ( $archOrTemp === 'temp/' ) { 00544 $params['temp'] = 1; 00545 } 00546 00547 $params['thumbName'] = $thumbname; 00548 return $params; 00549 } 00550 00559 function wfExtractThumbParams( $file, $params ) { 00560 if ( !isset( $params['thumbName'] ) ) { 00561 throw new MWException( "No thumbnail name passed to wfExtractThumbParams" ); 00562 } 00563 00564 $thumbname = $params['thumbName']; 00565 unset( $params['thumbName'] ); 00566 00567 // Do the hook first for older extensions that rely on it. 00568 if ( !wfRunHooks( 'ExtractThumbParameters', array( $thumbname, &$params ) ) ) { 00569 // Check hooks if parameters can be extracted 00570 // Hooks return false if they manage to *resolve* the parameters 00571 // This hook should be considered deprecated 00572 wfDeprecated( 'ExtractThumbParameters', '1.22' ); 00573 return $params; // valid thumbnail URL (via extension or config) 00574 } 00575 00576 // FIXME: Files in the temp zone don't set a MIME type, which means 00577 // they don't have a handler. Which means we can't parse the param 00578 // string. However, not a big issue as what good is a param string 00579 // if you have no handler to make use of the param string and 00580 // actually generate the thumbnail. 00581 $handler = $file->getHandler(); 00582 00583 // Based on UploadStash::parseKey 00584 $fileNamePos = strrpos( $thumbname, $params['f'] ); 00585 if ( $fileNamePos === false ) { 00586 // Maybe using a short filename? (see FileRepo::nameForThumb) 00587 $fileNamePos = strrpos( $thumbname, 'thumbnail' ); 00588 } 00589 00590 if ( $handler && $fileNamePos !== false ) { 00591 $paramString = substr( $thumbname, 0, $fileNamePos - 1 ); 00592 $extraParams = $handler->parseParamString( $paramString ); 00593 if ( $extraParams !== false ) { 00594 return $params + $extraParams; 00595 } 00596 } 00597 00598 // As a last ditch fallback, use the traditional common parameters 00599 if ( preg_match( '!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname, $matches ) ) { 00600 list( /* all */, $pagefull, $pagenum, $size ) = $matches; 00601 $params['width'] = $size; 00602 if ( $pagenum ) { 00603 $params['page'] = $pagenum; 00604 } 00605 return $params; // valid thumbnail URL 00606 } 00607 return null; 00608 } 00609 00617 function wfThumbError( $status, $msg ) { 00618 global $wgShowHostnames; 00619 00620 header( 'Cache-Control: no-cache' ); 00621 header( 'Content-Type: text/html; charset=utf-8' ); 00622 if ( $status == 404 ) { 00623 header( 'HTTP/1.1 404 Not found' ); 00624 } elseif ( $status == 403 ) { 00625 header( 'HTTP/1.1 403 Forbidden' ); 00626 header( 'Vary: Cookie' ); 00627 } else { 00628 header( 'HTTP/1.1 500 Internal server error' ); 00629 } 00630 if ( $wgShowHostnames ) { 00631 header( 'X-MW-Thumbnail-Renderer: ' . wfHostname() ); 00632 $url = htmlspecialchars( isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ); 00633 $hostname = htmlspecialchars( wfHostname() ); 00634 $debug = "<!-- $url -->\n<!-- $hostname -->\n"; 00635 } else { 00636 $debug = ''; 00637 } 00638 echo <<<EOT 00639 <html><head><title>Error generating thumbnail</title></head> 00640 <body> 00641 <h1>Error generating thumbnail</h1> 00642 <p> 00643 $msg 00644 </p> 00645 $debug 00646 </body> 00647 </html> 00648 00649 EOT; 00650 }