27 define(
'MW_NO_OUTPUT_COMPRESSION', 1 );
28 define(
'MW_ENTRY_POINT',
'thumb' );
29 require __DIR__ .
'/includes/WebStart.php';
34 if ( defined(
'THUMB_HANDLER' ) ) {
55 # Set action base paths so that WebRequest::getPathInfo()
56 # recognizes the "X" as the 'title' in ../thumb_handler.php/X urls.
57 # Note: If Custom per-extension repo paths are set, this may break.
67 wfThumbError( 404,
'Could not determine the name of the requested thumbnail.' );
72 if ( $params ==
null ) {
73 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
95 $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
99 $fileName = $params[
'f'] ??
'';
102 if ( isset( $params[
'w'] ) ) {
103 $params[
'width'] = $params[
'w'];
104 unset( $params[
'w'] );
106 if ( isset( $params[
'width'] ) && substr( $params[
'width'], -2 ) ==
'px' ) {
108 $params[
'width'] = substr( $params[
'width'], 0, -2 );
110 if ( isset( $params[
'p'] ) ) {
111 $params[
'page'] = $params[
'p'];
115 $isOld = ( isset( $params[
'archived'] ) && $params[
'archived'] );
116 unset( $params[
'archived'] );
119 $isTemp = ( isset( $params[
'temp'] ) && $params[
'temp'] );
120 unset( $params[
'temp'] );
123 $fileName = strtr( $fileName,
'\\/',
'__' );
129 # Temp files are hashed based on the name without the timestamp.
130 # The thumbnails will be hashed based on the entire name however.
131 # @todo fix
this convention to actually be reasonable.
132 $repo->getZonePath(
'public' ) .
'/' . $repo->getTempHashPath( $fileName ) . $fileName
134 } elseif ( $isOld ) {
136 $bits = explode(
'!', $fileName, 2 );
137 if ( count( $bits ) != 2 ) {
159 if ( !in_array(
'read', $permissionManager->getGroupPermissions( [
'*' ] ),
true ) ) {
161 $imgTitle = $img->getTitle();
163 if ( !$imgTitle || !$permissionManager->userCan(
'read', $user, $imgTitle ) ) {
164 wfThumbError( 403,
'Access denied. You do not have permission to access ' .
165 'the source file.' );
168 $headers[] =
'Cache-Control: private';
169 $varyHeader[] =
'Cookie';
179 if ( isset( $params[
'thumbName'] ) ) {
182 if ( $params ==
null ) {
183 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
188 if ( !$img->exists() ) {
189 $redirectedLocation =
false;
195 if ( $possRedirFile && !is_null( $possRedirFile->getRedirected() ) ) {
196 $redirTarget = $possRedirFile->getName();
198 if ( $targetFile->exists() ) {
199 $newThumbName = $targetFile->thumbName( $params );
202 $newThumbUrl = $targetFile->getArchiveThumbUrl(
203 $bits[0] .
'!' . $targetFile->getName(), $newThumbName );
205 $newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
212 if ( $redirectedLocation ) {
216 $response->header(
'Location: ' . $redirectedLocation );
218 gmdate(
'D, d M Y H:i:s', time() + 12 * 3600 ) .
' GMT' );
220 $varyHeader[] =
'X-Forwarded-Proto';
222 if ( count( $varyHeader ) ) {
223 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
225 $response->header(
'Content-Length: 0' );
232 } elseif ( $img->getPath() === false ) {
233 wfThumbErrorText( 400,
"The source file '$fileName' is not locally accessible." );
239 if ( !empty( $_SERVER[
'HTTP_IF_MODIFIED_SINCE'] ) ) {
241 $imsString = preg_replace(
'/;.*$/',
'', $_SERVER[
"HTTP_IF_MODIFIED_SINCE"] );
243 Wikimedia\suppressWarnings();
244 $imsUnix = strtotime( $imsString );
245 Wikimedia\restoreWarnings();
246 if (
wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
252 $rel404 = $params[
'rel404'] ??
null;
253 unset( $params[
'r'] );
254 unset( $params[
'f'] );
255 unset( $params[
'rel404'] );
259 $thumbName = $img->thumbName( $params );
260 if ( !strlen( $thumbName ) ) {
262 'Empty return from File::thumbName'
269 'The specified thumbnail parameters are not valid: ' . $e->getMessage()
274 [
'exception' => $e ] );
282 if ( $rel404 !==
null ) {
283 if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
285 } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
292 gmdate(
'D, d M Y H:i:s', time() + 7 * 86400 ) .
' GMT' );
294 $varyHeader[] =
'X-Forwarded-Proto';
296 if ( count( $varyHeader ) ) {
297 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
301 wfThumbErrorText( 404,
"The given path of the specified thumbnail is incorrect;
302 expected '" . $img->getThumbRel( $thumbName ) .
"' but got '" .
303 rawurldecode( $rel404 ) .
"'." );
308 $dispositionType = isset( $params[
'download'] ) ?
'attachment' :
'inline';
312 "Content-Disposition: {$img->getThumbDisposition( $thumbName, $dispositionType )}";
314 if ( count( $varyHeader ) ) {
315 $headers[] =
'Vary: ' . implode(
', ', $varyHeader );
319 $thumbPath = $img->getThumbPath( $thumbName );
320 if ( $img->getRepo()->fileExists( $thumbPath ) ) {
322 $status = $img->getRepo()->streamFileWithStatus( $thumbPath, $headers );
326 MediaWikiServices::getInstance()->getStatsdDataFactory()->timing(
327 'media.thumbnail.stream', $streamtime
330 wfThumbError( 500,
'Could not stream the file',
null, [
'file' => $thumbName,
331 'path' => $thumbPath,
'error' =>
$status->getWikiText(
false,
false,
'en' ) ] );
337 if ( !
wfThumbIsStandard( $img, $params ) && $user->pingLimiter(
'renderfile-nonstandard' ) ) {
340 } elseif ( $user->pingLimiter(
'renderfile' ) ) {
345 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
347 if ( strlen( $thumbProxyUrl ) ) {
363 $errorMsg = $errorMsg ?: $msg->rawParams(
'File::transform() returned false' )->escaped();
365 $errorMsg->
getKey() ===
'thumbnail_image-failure-limit'
369 } elseif ( $thumb->isError() ) {
370 $errorMsg = $thumb->getHtmlMsg();
371 $errorCode = $thumb->getHttpStatusCode();
372 } elseif ( !$thumb->hasFile() ) {
373 $errorMsg = $msg->rawParams(
'No path supplied in thumbnail object' )->escaped();
374 } elseif ( $thumb->fileIsSource() ) {
376 ->rawParams(
'Image was not scaled, is the requested width bigger than the source?' )
381 if ( $errorMsg !==
false ) {
382 wfThumbError( $errorCode, $errorMsg,
null, [
'file' => $thumbName,
'path' => $thumbPath ] );
385 $status = $thumb->streamFileWithStatus( $headers );
388 'file' => $thumbName,
'path' => $thumbPath,
389 'error' =>
$status->getWikiText(
false,
false,
'en' ) ] );
401 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
404 $thumbProxiedUrl = $thumbProxyUrl . $img->getThumbRel( $thumbName );
407 $secret = $img->getRepo()->getThumbProxySecret();
410 if ( strlen( $secret ) ) {
411 $req->setHeader(
'X-Swift-Secret', $secret );
420 header(
'HTTP/1.1 ' . $req->getStatus() );
422 $headers = $req->getResponseHeaders();
424 foreach ( $headers as $key => $values ) {
425 foreach ( $values as $value ) {
426 header( $key .
': ' . $value,
false );
430 echo $req->getContent();
449 $file->getRepo()->getName(),
455 if (
$cache->get( $key ) >= 4 ) {
456 return [
false,
wfMessage(
'thumbnail_image-failure-limit', 4 ) ];
461 register_shutdown_function(
function () use (
$cache, &$done, $key ) {
464 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
474 if (
$file->isExpensiveToThumbnail() ) {
475 $poolCounterType =
'FileRenderExpensive';
477 $poolCounterType =
'FileRender';
484 'doWork' =>
function () use (
$file, $params ) {
487 'doCachedWork' =>
function () use (
$file, $params, $thumbPath ) {
490 return $file->getRepo()->fileExists( $thumbPath )
495 return wfMessage(
'generic-pool-error' )->parse() .
'<hr>' .
$status->getHTML();
502 } elseif ( is_string( $result ) ) {
503 $errorHtml = $result;
505 }
catch ( Exception $e ) {
512 if ( !$thumb || $thumb->isError() ) {
514 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
517 return [ $thumb, $errorHtml ];
542 $hashDirReg = $subdirReg =
'';
543 $hashLevels = $repo->getHashLevels();
544 for ( $i = 0; $i < $hashLevels; $i++ ) {
545 $subdirReg .=
'[0-9a-f]';
546 $hashDirReg .=
"$subdirReg/";
550 if ( preg_match(
"!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
551 list( , $rel, $archOrTemp, $filename, $thumbname ) = $m;
553 } elseif ( preg_match(
"!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
554 list( , $archOrTemp, $rel, $filename, $thumbname ) = $m;
559 $params = [
'f' => $filename,
'rel404' => $rel ];
560 if ( $archOrTemp ===
'archive/' ) {
561 $params[
'archived'] = 1;
562 } elseif ( $archOrTemp ===
'temp/' ) {
566 $params[
'thumbName'] = $thumbname;
579 if ( !isset( $params[
'thumbName'] ) ) {
580 throw new InvalidArgumentException(
"No thumbnail name passed to wfExtractThumbParams" );
583 $thumbname = $params[
'thumbName'];
584 unset( $params[
'thumbName'] );
591 $handler =
$file->getHandler();
594 $fileNamePos = strrpos( $thumbname, $params[
'f'] );
595 if ( $fileNamePos ===
false ) {
597 $fileNamePos = strrpos( $thumbname,
'thumbnail' );
600 if ( $handler && $fileNamePos !==
false ) {
601 $paramString = substr( $thumbname, 0, $fileNamePos - 1 );
602 $extraParams = $handler->parseParamString( $paramString );
603 if ( $extraParams !==
false ) {
604 return $params + $extraParams;
609 if ( preg_match(
'!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname,
$matches ) ) {
610 list( , , $pagenum, $size ) =
$matches;
611 $params[
'width'] = $size;
613 $params[
'page'] = $pagenum;
646 header(
'Cache-Control: no-cache' );
647 header(
'Content-Type: text/html; charset=utf-8' );
652 header(
'Vary: Cookie' );
654 LoggerFactory::getInstance(
'thumb' )->error( $msgText ?: $msgHtml,
$context );
658 header(
'X-MW-Thumbnail-Renderer: ' .
wfHostname() );
659 $url = htmlspecialchars(
660 $_SERVER[
'REQUEST_URI'] ??
'',
663 $hostname = htmlspecialchars(
wfHostname(), ENT_NOQUOTES );
664 $debug =
"<!-- $url -->\n<!-- $hostname -->\n";
671 <meta charset=
"UTF-8" />
672 <title>Error generating thumbnail</title>
675 <h1>Error generating thumbnail</h1>
684 header(
'Content-Length: ' . strlen(
$content ) );