27 define(
'MW_NO_OUTPUT_COMPRESSION', 1 );
30 if ( !defined(
'MW_ENTRY_POINT' ) ) {
31 define(
'MW_ENTRY_POINT',
'thumb' );
33 require __DIR__ .
'/includes/WebStart.php';
38 if ( defined(
'THUMB_HANDLER' ) ) {
59 # Set action base paths so that WebRequest::getPathInfo()
60 # recognizes the "X" as the 'title' in ../thumb_handler.php/X urls.
61 # Note: If Custom per-extension repo paths are set, this may break.
71 wfThumbError( 404,
'Could not determine the name of the requested thumbnail.' );
76 if ( $params ==
null ) {
77 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
99 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
103 $fileName = $params[
'f'] ??
'';
106 if ( isset( $params[
'w'] ) ) {
107 $params[
'width'] = $params[
'w'];
108 unset( $params[
'w'] );
110 if ( isset( $params[
'width'] ) && substr( $params[
'width'], -2 ) ==
'px' ) {
112 $params[
'width'] = substr( $params[
'width'], 0, -2 );
114 if ( isset( $params[
'p'] ) ) {
115 $params[
'page'] = $params[
'p'];
119 $isOld = ( isset( $params[
'archived'] ) && $params[
'archived'] );
120 unset( $params[
'archived'] );
123 $isTemp = ( isset( $params[
'temp'] ) && $params[
'temp'] );
124 unset( $params[
'temp'] );
127 $fileName = strtr( $fileName,
'\\/',
'__' );
133 # Temp files are hashed based on the name without the timestamp.
134 # The thumbnails will be hashed based on the entire name however.
135 # @todo fix
this convention to actually be reasonable.
136 $repo->getZonePath(
'public' ) .
'/' . $repo->getTempHashPath( $fileName ) . $fileName
138 } elseif ( $isOld ) {
140 $bits = explode(
'!', $fileName, 2 );
141 if ( count( $bits ) != 2 ) {
163 if ( !in_array(
'read', $permissionManager->getGroupPermissions( [
'*' ] ),
true ) ) {
165 $imgTitle = $img->getTitle();
167 if ( !$imgTitle || !$permissionManager->userCan(
'read', $user, $imgTitle ) ) {
168 wfThumbError( 403,
'Access denied. You do not have permission to access ' .
169 'the source file.' );
172 $headers[] =
'Cache-Control: private';
173 $varyHeader[] =
'Cookie';
183 if ( isset( $params[
'thumbName'] ) ) {
186 if ( $params ==
null ) {
187 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
192 if ( !$img->exists() ) {
193 $redirectedLocation =
false;
199 if ( $possRedirFile && !is_null( $possRedirFile->getRedirected() ) ) {
200 $redirTarget = $possRedirFile->getName();
202 if ( $targetFile->exists() ) {
203 $newThumbName = $targetFile->thumbName( $params );
206 $newThumbUrl = $targetFile->getArchiveThumbUrl(
207 $bits[0] .
'!' . $targetFile->getName(), $newThumbName );
209 $newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
216 if ( $redirectedLocation ) {
220 $response->header(
'Location: ' . $redirectedLocation );
222 gmdate(
'D, d M Y H:i:s', time() + 12 * 3600 ) .
' GMT' );
224 $varyHeader[] =
'X-Forwarded-Proto';
226 if ( count( $varyHeader ) ) {
227 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
229 $response->header(
'Content-Length: 0' );
236 } elseif ( $img->getPath() ===
false ) {
237 wfThumbErrorText( 400,
"The source file '$fileName' is not locally accessible." );
243 if ( !empty( $_SERVER[
'HTTP_IF_MODIFIED_SINCE'] ) ) {
245 $imsString = preg_replace(
'/;.*$/',
'', $_SERVER[
"HTTP_IF_MODIFIED_SINCE"] );
247 Wikimedia\suppressWarnings();
248 $imsUnix = strtotime( $imsString );
249 Wikimedia\restoreWarnings();
250 if (
wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
256 $rel404 = $params[
'rel404'] ??
null;
257 unset( $params[
'r'] );
258 unset( $params[
'f'] );
259 unset( $params[
'rel404'] );
263 $thumbName = $img->thumbName( $params );
264 if ( !strlen( $thumbName ) ) {
266 'Empty return from File::thumbName'
273 'The specified thumbnail parameters are not valid: ' . $e->getMessage()
278 [
'exception' => $e ] );
286 if ( $rel404 !==
null ) {
287 if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
289 } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
296 gmdate(
'D, d M Y H:i:s', time() + 7 * 86400 ) .
' GMT' );
298 $varyHeader[] =
'X-Forwarded-Proto';
300 if ( count( $varyHeader ) ) {
301 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
305 wfThumbErrorText( 404,
"The given path of the specified thumbnail is incorrect;
306 expected '" . $img->getThumbRel( $thumbName ) .
"' but got '" .
307 rawurldecode( $rel404 ) .
"'." );
312 $dispositionType = isset( $params[
'download'] ) ?
'attachment' :
'inline';
316 "Content-Disposition: {$img->getThumbDisposition( $thumbName, $dispositionType )}";
318 if ( count( $varyHeader ) ) {
319 $headers[] =
'Vary: ' . implode(
', ', $varyHeader );
323 $thumbPath = $img->getThumbPath( $thumbName );
324 if ( $img->getRepo()->fileExists( $thumbPath ) ) {
326 $status = $img->getRepo()->streamFileWithStatus( $thumbPath, $headers );
329 if ( $status->isOK() ) {
330 MediaWikiServices::getInstance()->getStatsdDataFactory()->timing(
331 'media.thumbnail.stream', $streamtime
334 wfThumbError( 500,
'Could not stream the file',
null, [
'file' => $thumbName,
335 'path' => $thumbPath,
'error' => $status->getWikiText(
false,
false,
'en' ) ] );
341 if ( !
wfThumbIsStandard( $img, $params ) && $user->pingLimiter(
'renderfile-nonstandard' ) ) {
344 } elseif ( $user->pingLimiter(
'renderfile' ) ) {
349 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
351 if ( strlen( $thumbProxyUrl ) ) {
367 $errorMsg = $errorMsg ?: $msg->rawParams(
'File::transform() returned false' )->escaped();
369 $errorMsg->
getKey() ===
'thumbnail_image-failure-limit'
373 } elseif ( $thumb->isError() ) {
374 $errorMsg = $thumb->getHtmlMsg();
375 $errorCode = $thumb->getHttpStatusCode();
376 } elseif ( !$thumb->hasFile() ) {
377 $errorMsg = $msg->rawParams(
'No path supplied in thumbnail object' )->escaped();
378 } elseif ( $thumb->fileIsSource() ) {
380 ->rawParams(
'Image was not scaled, is the requested width bigger than the source?' )
385 if ( $errorMsg !==
false ) {
386 wfThumbError( $errorCode, $errorMsg,
null, [
'file' => $thumbName,
'path' => $thumbPath ] );
389 $status = $thumb->streamFileWithStatus( $headers );
390 if ( !$status->isOK() ) {
392 'file' => $thumbName,
'path' => $thumbPath,
393 'error' => $status->getWikiText(
false,
false,
'en' ) ] );
405 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
408 $thumbProxiedUrl = $thumbProxyUrl . $img->getThumbRel( $thumbName );
411 $secret = $img->getRepo()->getThumbProxySecret();
414 if ( strlen( $secret ) ) {
415 $req->setHeader(
'X-Swift-Secret', $secret );
419 $status = $req->execute();
424 header(
'HTTP/1.1 ' . $req->getStatus() );
426 $headers = $req->getResponseHeaders();
428 foreach ( $headers as $key => $values ) {
429 foreach ( $values as $value ) {
430 header( $key .
': ' . $value,
false );
434 echo $req->getContent();
453 $file->getRepo()->getName(),
459 if (
$cache->get( $key ) >= 4 ) {
460 return [
false,
wfMessage(
'thumbnail_image-failure-limit', 4 ) ];
465 register_shutdown_function(
function () use (
$cache, &$done, $key ) {
468 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
478 if (
$file->isExpensiveToThumbnail() ) {
479 $poolCounterType =
'FileRenderExpensive';
481 $poolCounterType =
'FileRender';
488 'doWork' => function () use (
$file, $params ) {
491 'doCachedWork' =>
function () use (
$file, $params, $thumbPath ) {
494 return $file->getRepo()->fileExists( $thumbPath )
498 'error' =>
function (
Status $status ) {
499 return wfMessage(
'generic-pool-error' )->parse() .
'<hr>' . $status->getHTML();
506 } elseif ( is_string( $result ) ) {
507 $errorHtml = $result;
509 }
catch ( Exception $e ) {
516 if ( !$thumb || $thumb->isError() ) {
518 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
521 return [ $thumb, $errorHtml ];
546 $hashDirReg = $subdirReg =
'';
547 $hashLevels = $repo->getHashLevels();
548 for ( $i = 0; $i < $hashLevels; $i++ ) {
549 $subdirReg .=
'[0-9a-f]';
550 $hashDirReg .=
"$subdirReg/";
554 if ( preg_match(
"!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
555 list( , $rel, $archOrTemp, $filename, $thumbname ) = $m;
557 } elseif ( preg_match(
"!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
558 list( , $archOrTemp, $rel, $filename, $thumbname ) = $m;
563 $params = [
'f' => $filename,
'rel404' => $rel ];
564 if ( $archOrTemp ===
'archive/' ) {
565 $params[
'archived'] = 1;
566 } elseif ( $archOrTemp ===
'temp/' ) {
570 $params[
'thumbName'] = $thumbname;
583 if ( !isset( $params[
'thumbName'] ) ) {
584 throw new InvalidArgumentException(
"No thumbnail name passed to wfExtractThumbParams" );
587 $thumbname = $params[
'thumbName'];
588 unset( $params[
'thumbName'] );
595 $handler =
$file->getHandler();
598 $fileNamePos = strrpos( $thumbname, $params[
'f'] );
599 if ( $fileNamePos ===
false ) {
601 $fileNamePos = strrpos( $thumbname,
'thumbnail' );
604 if ( $handler && $fileNamePos !==
false ) {
605 $paramString = substr( $thumbname, 0, $fileNamePos - 1 );
606 $extraParams = $handler->parseParamString( $paramString );
607 if ( $extraParams !==
false ) {
608 return $params + $extraParams;
613 if ( preg_match(
'!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname,
$matches ) ) {
614 list( , , $pagenum, $size ) =
$matches;
615 $params[
'width'] = $size;
617 $params[
'page'] = $pagenum;
632 wfThumbError( $status, htmlspecialchars( $msgText, ENT_NOQUOTES ) );
650 header(
'Cache-Control: no-cache' );
651 header(
'Content-Type: text/html; charset=utf-8' );
652 if ( $status == 400 || $status == 404 || $status == 429 ) {
654 } elseif ( $status == 403 ) {
656 header(
'Vary: Cookie' );
658 LoggerFactory::getInstance(
'thumb' )->error( $msgText ?: $msgHtml,
$context );
662 header(
'X-MW-Thumbnail-Renderer: ' .
wfHostname() );
663 $url = htmlspecialchars(
664 $_SERVER[
'REQUEST_URI'] ??
'',
667 $hostname = htmlspecialchars(
wfHostname(), ENT_NOQUOTES );
668 $debug =
"<!-- $url -->\n<!-- $hostname -->\n";
675 <meta charset=
"UTF-8" />
676 <title>Error generating thumbnail</title>
679 <h1>Error generating thumbnail</h1>
688 header(
'Content-Length: ' . strlen(
$content ) );