35 use Wikimedia\AtEase\AtEase;
37 define(
'MW_NO_OUTPUT_COMPRESSION', 1 );
40 if ( !defined(
'MW_ENTRY_POINT' ) ) {
41 define(
'MW_ENTRY_POINT',
'thumb' );
43 require __DIR__ .
'/includes/WebStart.php';
55 if ( defined(
'THUMB_HANDLER' ) ) {
76 $relPath = WebRequest::getRequestPathSuffix(
$wgThumbPath );
79 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
80 $baseUrl = $repo->getZoneUrl(
'thumb' );
81 if ( substr( $baseUrl, 0, 1 ) ===
'/' ) {
84 $basePath = parse_url( $baseUrl, PHP_URL_PATH );
86 $relPath = WebRequest::getRequestPathSuffix( $basePath );
90 if ( $params ==
null ) {
91 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
116 $fileName = $params[
'f'] ??
'';
119 if ( isset( $params[
'w'] ) ) {
120 $params[
'width'] = $params[
'w'];
121 unset( $params[
'w'] );
123 if ( isset( $params[
'width'] ) && substr( $params[
'width'], -2 ) ==
'px' ) {
125 $params[
'width'] = substr( $params[
'width'], 0, -2 );
127 if ( isset( $params[
'p'] ) ) {
128 $params[
'page'] = $params[
'p'];
132 $isOld = ( isset( $params[
'archived'] ) && $params[
'archived'] );
133 unset( $params[
'archived'] );
136 $isTemp = ( isset( $params[
'temp'] ) && $params[
'temp'] );
137 unset( $params[
'temp'] );
139 $services = MediaWikiServices::getInstance();
142 $fileName = strtr( $fileName,
'\\/',
'__' );
143 $localRepo = $services->getRepoGroup()->getLocalRepo();
147 $repo = $localRepo->getTempRepo();
149 # Temp files are hashed based on the name without the timestamp.
150 # The thumbnails will be hashed based on the entire name however.
151 # @todo fix
this convention to actually be reasonable.
152 $repo->getZonePath(
'public' ) .
'/' . $repo->getTempHashPath( $fileName ) . $fileName
154 } elseif ( $isOld ) {
156 $bits = explode(
'!', $fileName, 2 );
157 if ( count( $bits ) != 2 ) {
161 $title = Title::makeTitleSafe(
NS_FILE, $bits[1] );
166 $img = $localRepo->newFromArchiveName( $title, $fileName );
168 $img = $localRepo->newFile( $fileName );
179 if ( !$services->getGroupPermissionsLookup()->groupHasPermission(
'*',
'read' ) ) {
181 $imgTitle = $img->getTitle();
183 if ( !$imgTitle || !$services->getPermissionManager()->userCan(
'read', $user, $imgTitle ) ) {
184 wfThumbError( 403,
'Access denied. You do not have permission to access ' .
185 'the source file.' );
188 $headers[] =
'Cache-Control: private';
189 $varyHeader[] =
'Cookie';
199 if ( isset( $params[
'thumbName'] ) ) {
202 if ( $params ==
null ) {
203 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
208 if ( !$img->exists() ) {
209 $redirectedLocation =
false;
214 $possRedirFile = $localRepo->findFile( $img->getName() );
215 if ( $possRedirFile && $possRedirFile->getRedirected() !==
null ) {
216 $redirTarget = $possRedirFile->getName();
217 $targetFile = $localRepo->newFile( Title::makeTitleSafe(
NS_FILE, $redirTarget ) );
218 if ( $targetFile->exists() ) {
219 $newThumbName = $targetFile->thumbName( $params );
222 $newThumbUrl = $targetFile->getArchiveThumbUrl(
223 $bits[0] .
'!' . $targetFile->getName(), $newThumbName );
225 $newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
232 if ( $redirectedLocation ) {
235 $response->statusHeader( 302 );
236 $response->header(
'Location: ' . $redirectedLocation );
237 $response->header(
'Expires: ' .
238 gmdate(
'D, d M Y H:i:s', time() + 12 * 3600 ) .
' GMT' );
240 $varyHeader[] =
'X-Forwarded-Proto';
242 if ( count( $varyHeader ) ) {
243 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
245 $response->header(
'Content-Length: 0' );
252 } elseif ( $img->getPath() ===
false ) {
253 wfThumbErrorText( 400,
"The source file '$fileName' is not locally accessible." );
259 if ( !empty( $_SERVER[
'HTTP_IF_MODIFIED_SINCE'] ) ) {
261 $imsString = preg_replace(
'/;.*$/',
'', $_SERVER[
"HTTP_IF_MODIFIED_SINCE"] );
263 AtEase::suppressWarnings();
264 $imsUnix = strtotime( $imsString );
265 AtEase::restoreWarnings();
266 if (
wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
272 $rel404 = $params[
'rel404'] ??
null;
273 unset( $params[
'r'] );
274 unset( $params[
'f'] );
275 unset( $params[
'rel404'] );
279 $thumbName = $img->thumbName( $params );
280 if ( !strlen( $thumbName ) ) {
282 'Empty return from File::thumbName'
289 'The specified thumbnail parameters are not valid: ' . $e->getMessage()
293 wfThumbError( 500, $e->getHTML(),
'Exception caught while extracting thumb name',
294 [
'exception' => $e ] );
302 if ( $rel404 !==
null ) {
303 if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
305 } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
308 $response->statusHeader( 301 );
309 $response->header(
'Location: ' .
311 $response->header(
'Expires: ' .
312 gmdate(
'D, d M Y H:i:s', time() + 7 * 86400 ) .
' GMT' );
314 $varyHeader[] =
'X-Forwarded-Proto';
316 if ( count( $varyHeader ) ) {
317 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
321 wfThumbErrorText( 404,
"The given path of the specified thumbnail is incorrect;
322 expected '" . $img->getThumbRel( $thumbName ) .
"' but got '" .
323 rawurldecode( $rel404 ) .
"'." );
328 $dispositionType = isset( $params[
'download'] ) ?
'attachment' :
'inline';
332 'Content-Disposition: ' . $img->getThumbDisposition( $thumbName, $dispositionType );
334 if ( count( $varyHeader ) ) {
335 $headers[] =
'Vary: ' . implode(
', ', $varyHeader );
339 $thumbPath = $img->getThumbPath( $thumbName );
340 if ( $img->getRepo()->fileExists( $thumbPath ) ) {
341 $starttime = microtime(
true );
342 $status = $img->getRepo()->streamFileWithStatus( $thumbPath, $headers );
343 $streamtime = microtime(
true ) - $starttime;
345 if ( $status->isOK() ) {
346 $services->getStatsdDataFactory()->timing(
347 'media.thumbnail.stream', $streamtime
350 wfThumbError( 500,
'Could not stream the file',
null, [
'file' => $thumbName,
351 'path' => $thumbPath,
'error' => $status->getWikiText(
false,
false,
'en' ) ] );
357 if ( !
wfThumbIsStandard( $img, $params ) && $user->pingLimiter(
'renderfile-nonstandard' ) ) {
360 } elseif ( $user->pingLimiter(
'renderfile' ) ) {
365 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
367 if ( strlen( $thumbProxyUrl ) ) {
383 $errorMsg = $errorMsg ?: $msg->rawParams(
'File::transform() returned false' )->escaped();
385 $errorMsg->
getKey() ===
'thumbnail_image-failure-limit'
389 } elseif ( $thumb->isError() ) {
390 $errorMsg = $thumb->getHtmlMsg();
391 $errorCode = $thumb->getHttpStatusCode();
392 } elseif ( !$thumb->hasFile() ) {
393 $errorMsg = $msg->rawParams(
'No path supplied in thumbnail object' )->escaped();
394 } elseif ( $thumb->fileIsSource() ) {
396 ->rawParams(
'Image was not scaled, is the requested width bigger than the source?' )
401 if ( $errorMsg !==
false ) {
402 wfThumbError( $errorCode, $errorMsg,
null, [
'file' => $thumbName,
'path' => $thumbPath ] );
405 $status = $thumb->streamFileWithStatus( $headers );
406 if ( !$status->isOK() ) {
408 'file' => $thumbName,
'path' => $thumbPath,
409 'error' => $status->getWikiText(
false,
false,
'en' ) ] );
421 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
424 $thumbProxiedUrl = $thumbProxyUrl . $img->getThumbRel( $thumbName );
426 $req = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $thumbProxiedUrl );
427 $secret = $img->getRepo()->getThumbProxySecret();
430 if ( strlen( $secret ) ) {
431 $req->setHeader(
'X-Swift-Secret', $secret );
437 \MediaWiki\Request\HeaderCallback::warnIfHeadersSent();
440 header(
'HTTP/1.1 ' . $req->getStatus() );
442 $headers = $req->getResponseHeaders();
444 foreach ( $headers as $key => $values ) {
445 foreach ( $values as $value ) {
446 header( $key .
': ' . $value,
false );
450 echo $req->getContent();
466 $key = $cache->makeKey(
469 $file->getRepo()->getName(),
475 if ( $cache->get( $key ) >= 4 ) {
476 return [
false,
wfMessage(
'thumbnail_image-failure-limit', 4 ) ];
481 register_shutdown_function(
static function () use ( $cache, &$done, $key ) {
484 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
494 if (
$file->isExpensiveToThumbnail() ) {
495 $poolCounterType =
'FileRenderExpensive';
497 $poolCounterType =
'FileRender';
504 'doWork' =>
static function () use (
$file, $params ) {
507 'doCachedWork' =>
static function () use (
$file, $params, $thumbPath ) {
510 return $file->getRepo()->fileExists( $thumbPath )
514 'error' =>
static function (
Status $status ) {
515 return wfMessage(
'generic-pool-error' )->parse() .
'<hr>' . $status->getHTML();
522 } elseif ( is_string( $result ) ) {
523 $errorHtml = $result;
525 }
catch ( Exception $e ) {
532 if ( !$thumb || $thumb->isError() ) {
534 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
537 return [ $thumb, $errorHtml ];
560 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
562 $hashDirReg = $subdirReg =
'';
563 $hashLevels = $repo->getHashLevels();
564 for ( $i = 0; $i < $hashLevels; $i++ ) {
565 $subdirReg .=
'[0-9a-f]';
566 $hashDirReg .=
"$subdirReg/";
570 if ( preg_match(
"!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
571 [ , $rel, $archOrTemp, $filename, $thumbname ] = $m;
573 } elseif ( preg_match(
"!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
574 [ , $archOrTemp, $rel, $filename, $thumbname ] = $m;
579 $params = [
'f' => $filename,
'rel404' => $rel ];
580 if ( $archOrTemp ===
'archive/' ) {
581 $params[
'archived'] = 1;
582 } elseif ( $archOrTemp ===
'temp/' ) {
586 $params[
'thumbName'] = $thumbname;
599 if ( !isset( $params[
'thumbName'] ) ) {
600 throw new InvalidArgumentException(
"No thumbnail name passed to wfExtractThumbParams" );
603 $thumbname = $params[
'thumbName'];
604 unset( $params[
'thumbName'] );
611 $handler =
$file->getHandler();
614 $fileNamePos = strrpos( $thumbname, $params[
'f'] );
615 if ( $fileNamePos ===
false ) {
617 $fileNamePos = strrpos( $thumbname,
'thumbnail' );
620 if ( $handler && $fileNamePos !==
false ) {
621 $paramString = substr( $thumbname, 0, $fileNamePos - 1 );
622 $extraParams = $handler->parseParamString( $paramString );
623 if ( $extraParams !==
false ) {
624 return $params + $extraParams;
629 if ( preg_match(
'!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname,
$matches ) ) {
631 $params[
'width'] = $size;
633 $params[
'page'] = $pagenum;
648 wfThumbError( $status, htmlspecialchars( $msgText, ENT_NOQUOTES ) );
661 function wfThumbError( $status, $msgHtml, $msgText =
null, $context = [] ) {
664 \MediaWiki\Request\HeaderCallback::warnIfHeadersSent();
666 if ( headers_sent() ) {
667 LoggerFactory::getInstance(
'thumbnail' )->error(
668 'Error after output had been started. Output may be corrupt or truncated. ' .
669 'Original error: ' . ( $msgText ?: $msgHtml ) .
" (Status $status)",
675 header(
'Cache-Control: no-cache' );
676 header(
'Content-Type: text/html; charset=utf-8' );
677 if ( $status == 400 || $status == 404 || $status == 429 ) {
679 } elseif ( $status == 403 ) {
681 header(
'Vary: Cookie' );
683 LoggerFactory::getInstance(
'thumbnail' )->error( $msgText ?: $msgHtml, $context );
687 header(
'X-MW-Thumbnail-Renderer: ' .
wfHostname() );
688 $url = htmlspecialchars(
689 $_SERVER[
'REQUEST_URI'] ??
'',
692 $hostname = htmlspecialchars(
wfHostname(), ENT_NOQUOTES );
693 $debug =
"<!-- $url -->\n<!-- $hostname -->\n";
700 <meta charset=
"UTF-8" />
701 <title>Error generating thumbnail</title>
704 <h1>Error generating thumbnail</h1>
713 header(
'Content-Length: ' . strlen(
$content ) );
wfThumbIsStandard(File $file, array $params)
Returns true if these thumbnail parameters match one that MediaWiki requests from file description pa...
wfHostname()
Get host name of the current machine, for use in error reporting.
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL using $wgServer (or one of its alternatives).
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Implements some public methods and some protected utility functions which are required by multiple ch...
const RENDER_NOW
Force rendering in the current process.
static header( $code)
Output an HTTP status code header.
Class for tracking request-level classification information for profiling/stats/logging.
static getLocalClusterInstance()
Get the main cluster-local cache object.
Convenience class for dealing with PoolCounter using callbacks.
execute( $skipcache=false)
Get the result of the work (whatever it is), or the result of the error() function.
static getMain()
Get the RequestContext object associated with the main request.
File without associated database record.
$wgShowHostnames
Config variable stub for the ShowHostnames setting, for use by phpdoc and IDEs.
$wgThumbPath
Config variable stub for the ThumbPath setting, for use by phpdoc and IDEs.
$wgVaryOnXFP
Config variable stub for the VaryOnXFP setting, for use by phpdoc and IDEs.
$wgAttemptFailureEpoch
Config variable stub for the AttemptFailureEpoch setting, for use by phpdoc and IDEs.
$wgTrivialMimeDetection
Config variable stub for the TrivialMimeDetection setting, for use by phpdoc and IDEs.
getKey()
Returns the message key.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
wfThumbHandle404()
Handle a thumbnail request via thumbnail file URL.
wfThumbError( $status, $msgHtml, $msgText=null, $context=[])
Output a thumbnail generation error message.
wfStreamThumb(array $params)
Stream a thumbnail specified by parameters.
wfGenerateThumbnail(File $file, array $params, $thumbName, $thumbPath)
Actually try to generate a new thumbnail.
wfProxyThumbnailRequest( $img, $thumbName)
Proxies thumbnail request to a service that handles thumbnailing.
wfExtractThumbRequestInfo( $thumbRel)
Convert pathinfo type parameter, into normal request parameters.
wfThumbErrorText( $status, $msgText)
Output a thumbnail generation error message.
wfExtractThumbParams( $file, $params)
Convert a thumbnail name (122px-foo.png) to parameters, using file handler.