31 use Wikimedia\AtEase\AtEase;
33 define(
'MW_NO_OUTPUT_COMPRESSION', 1 );
36 if ( !defined(
'MW_ENTRY_POINT' ) ) {
37 define(
'MW_ENTRY_POINT',
'thumb' );
39 require __DIR__ .
'/includes/WebStart.php';
49 if ( defined(
'THUMB_HANDLER' ) ) {
73 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
74 $baseUrl = $repo->getZoneUrl(
'thumb' );
75 if ( substr( $baseUrl, 0, 1 ) ===
'/' ) {
78 $basePath = parse_url( $baseUrl, PHP_URL_PATH );
84 if ( $params ==
null ) {
85 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
110 $fileName = $params[
'f'] ??
'';
113 if ( isset( $params[
'w'] ) ) {
114 $params[
'width'] = $params[
'w'];
115 unset( $params[
'w'] );
117 if ( isset( $params[
'width'] ) && substr( $params[
'width'], -2 ) ==
'px' ) {
119 $params[
'width'] = substr( $params[
'width'], 0, -2 );
121 if ( isset( $params[
'p'] ) ) {
122 $params[
'page'] = $params[
'p'];
126 $isOld = ( isset( $params[
'archived'] ) && $params[
'archived'] );
127 unset( $params[
'archived'] );
130 $isTemp = ( isset( $params[
'temp'] ) && $params[
'temp'] );
131 unset( $params[
'temp'] );
133 $services = MediaWikiServices::getInstance();
136 $fileName = strtr( $fileName,
'\\/',
'__' );
137 $localRepo = $services->getRepoGroup()->getLocalRepo();
141 $repo = $localRepo->getTempRepo();
143 # Temp files are hashed based on the name without the timestamp.
144 # The thumbnails will be hashed based on the entire name however.
145 # @todo fix
this convention to actually be reasonable.
146 $repo->getZonePath(
'public' ) .
'/' . $repo->getTempHashPath( $fileName ) . $fileName
148 } elseif ( $isOld ) {
150 $bits = explode(
'!', $fileName, 2 );
151 if ( count( $bits ) != 2 ) {
160 $img = $localRepo->newFromArchiveName(
$title, $fileName );
162 $img = $localRepo->newFile( $fileName );
173 if ( !$services->getGroupPermissionsLookup()->groupHasPermission(
'*',
'read' ) ) {
175 $imgTitle = $img->getTitle();
177 if ( !$imgTitle || !$services->getPermissionManager()->userCan(
'read', $user, $imgTitle ) ) {
178 wfThumbError( 403,
'Access denied. You do not have permission to access ' .
179 'the source file.' );
182 $headers[] =
'Cache-Control: private';
183 $varyHeader[] =
'Cookie';
193 if ( isset( $params[
'thumbName'] ) ) {
196 if ( $params ==
null ) {
197 wfThumbError( 400,
'The specified thumbnail parameters are not recognized.' );
202 if ( !$img->exists() ) {
203 $redirectedLocation =
false;
208 $possRedirFile = $localRepo->findFile( $img->getName() );
209 if ( $possRedirFile && $possRedirFile->getRedirected() !==
null ) {
210 $redirTarget = $possRedirFile->getName();
212 if ( $targetFile->exists() ) {
213 $newThumbName = $targetFile->thumbName( $params );
216 $newThumbUrl = $targetFile->getArchiveThumbUrl(
217 $bits[0] .
'!' . $targetFile->getName(), $newThumbName );
219 $newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
226 if ( $redirectedLocation ) {
229 $response->statusHeader( 302 );
230 $response->header(
'Location: ' . $redirectedLocation );
231 $response->header(
'Expires: ' .
232 gmdate(
'D, d M Y H:i:s', time() + 12 * 3600 ) .
' GMT' );
234 $varyHeader[] =
'X-Forwarded-Proto';
236 if ( count( $varyHeader ) ) {
237 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
239 $response->header(
'Content-Length: 0' );
246 } elseif ( $img->getPath() ===
false ) {
247 wfThumbErrorText( 400,
"The source file '$fileName' is not locally accessible." );
253 if ( !empty( $_SERVER[
'HTTP_IF_MODIFIED_SINCE'] ) ) {
255 $imsString = preg_replace(
'/;.*$/',
'', $_SERVER[
"HTTP_IF_MODIFIED_SINCE"] );
257 AtEase::suppressWarnings();
258 $imsUnix = strtotime( $imsString );
259 AtEase::restoreWarnings();
260 if (
wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
266 $rel404 = $params[
'rel404'] ??
null;
267 unset( $params[
'r'] );
268 unset( $params[
'f'] );
269 unset( $params[
'rel404'] );
273 $thumbName = $img->thumbName( $params );
274 if ( !strlen( $thumbName ) ) {
276 'Empty return from File::thumbName'
283 'The specified thumbnail parameters are not valid: ' . $e->getMessage()
288 [
'exception' => $e ] );
296 if ( $rel404 !==
null ) {
297 if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
299 } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
302 $response->statusHeader( 301 );
303 $response->header(
'Location: ' .
305 $response->header(
'Expires: ' .
306 gmdate(
'D, d M Y H:i:s', time() + 7 * 86400 ) .
' GMT' );
308 $varyHeader[] =
'X-Forwarded-Proto';
310 if ( count( $varyHeader ) ) {
311 $response->header(
'Vary: ' . implode(
', ', $varyHeader ) );
315 wfThumbErrorText( 404,
"The given path of the specified thumbnail is incorrect;
316 expected '" . $img->getThumbRel( $thumbName ) .
"' but got '" .
317 rawurldecode( $rel404 ) .
"'." );
322 $dispositionType = isset( $params[
'download'] ) ?
'attachment' :
'inline';
326 'Content-Disposition: ' . $img->getThumbDisposition( $thumbName, $dispositionType );
328 if ( count( $varyHeader ) ) {
329 $headers[] =
'Vary: ' . implode(
', ', $varyHeader );
333 $thumbPath = $img->getThumbPath( $thumbName );
334 if ( $img->getRepo()->fileExists( $thumbPath ) ) {
335 $starttime = microtime(
true );
336 $status = $img->getRepo()->streamFileWithStatus( $thumbPath, $headers );
337 $streamtime = microtime(
true ) - $starttime;
339 if ( $status->isOK() ) {
340 $services->getStatsdDataFactory()->timing(
341 'media.thumbnail.stream', $streamtime
344 wfThumbError( 500,
'Could not stream the file',
null, [
'file' => $thumbName,
345 'path' => $thumbPath,
'error' => $status->getWikiText(
false,
false,
'en' ) ] );
351 if ( !
wfThumbIsStandard( $img, $params ) && $user->pingLimiter(
'renderfile-nonstandard' ) ) {
354 } elseif ( $user->pingLimiter(
'renderfile' ) ) {
359 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
361 if ( strlen( $thumbProxyUrl ) ) {
377 $errorMsg = $errorMsg ?: $msg->rawParams(
'File::transform() returned false' )->escaped();
379 $errorMsg->
getKey() ===
'thumbnail_image-failure-limit'
383 } elseif ( $thumb->isError() ) {
384 $errorMsg = $thumb->getHtmlMsg();
385 $errorCode = $thumb->getHttpStatusCode();
386 } elseif ( !$thumb->hasFile() ) {
387 $errorMsg = $msg->rawParams(
'No path supplied in thumbnail object' )->escaped();
388 } elseif ( $thumb->fileIsSource() ) {
390 ->rawParams(
'Image was not scaled, is the requested width bigger than the source?' )
395 if ( $errorMsg !==
false ) {
396 wfThumbError( $errorCode, $errorMsg,
null, [
'file' => $thumbName,
'path' => $thumbPath ] );
399 $status = $thumb->streamFileWithStatus( $headers );
400 if ( !$status->isOK() ) {
402 'file' => $thumbName,
'path' => $thumbPath,
403 'error' => $status->getWikiText(
false,
false,
'en' ) ] );
415 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
418 $thumbProxiedUrl = $thumbProxyUrl . $img->getThumbRel( $thumbName );
420 $req = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $thumbProxiedUrl );
421 $secret = $img->getRepo()->getThumbProxySecret();
424 if ( strlen( $secret ) ) {
425 $req->setHeader(
'X-Swift-Secret', $secret );
429 $status = $req->execute();
431 \MediaWiki\Request\HeaderCallback::warnIfHeadersSent();
434 header(
'HTTP/1.1 ' . $req->getStatus() );
436 $headers = $req->getResponseHeaders();
438 foreach ( $headers as $key => $values ) {
439 foreach ( $values as $value ) {
440 header( $key .
': ' . $value,
false );
444 echo $req->getContent();
460 $key = $cache->makeKey(
463 $file->getRepo()->getName(),
469 if ( $cache->get( $key ) >= 4 ) {
470 return [
false,
wfMessage(
'thumbnail_image-failure-limit', 4 ) ];
475 register_shutdown_function(
static function () use ( $cache, &$done, $key ) {
478 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
488 if (
$file->isExpensiveToThumbnail() ) {
489 $poolCounterType =
'FileRenderExpensive';
491 $poolCounterType =
'FileRender';
498 'doWork' =>
static function () use (
$file, $params ) {
501 'doCachedWork' =>
static function () use (
$file, $params, $thumbPath ) {
504 return $file->getRepo()->fileExists( $thumbPath )
508 'error' =>
static function (
Status $status ) {
509 return wfMessage(
'generic-pool-error' )->parse() .
'<hr>' . $status->getHTML();
516 } elseif ( is_string( $result ) ) {
517 $errorHtml = $result;
519 }
catch ( Exception $e ) {
526 if ( !$thumb || $thumb->isError() ) {
528 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
531 return [ $thumb, $errorHtml ];
554 $repo = MediaWikiServices::getInstance()->getRepoGroup()->getLocalRepo();
556 $hashDirReg = $subdirReg =
'';
557 $hashLevels = $repo->getHashLevels();
558 for ( $i = 0; $i < $hashLevels; $i++ ) {
559 $subdirReg .=
'[0-9a-f]';
560 $hashDirReg .=
"$subdirReg/";
564 if ( preg_match(
"!^((archive/)?$hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
565 [ , $rel, $archOrTemp, $filename, $thumbname ] = $m;
567 } elseif ( preg_match(
"!^(temp/)($hashDirReg([^/]*)/([^/]*))$!", $thumbRel, $m ) ) {
568 [ , $archOrTemp, $rel, $filename, $thumbname ] = $m;
573 $params = [
'f' => $filename,
'rel404' => $rel ];
574 if ( $archOrTemp ===
'archive/' ) {
575 $params[
'archived'] = 1;
576 } elseif ( $archOrTemp ===
'temp/' ) {
580 $params[
'thumbName'] = $thumbname;
593 if ( !isset( $params[
'thumbName'] ) ) {
594 throw new InvalidArgumentException(
"No thumbnail name passed to wfExtractThumbParams" );
597 $thumbname = $params[
'thumbName'];
598 unset( $params[
'thumbName'] );
605 $handler =
$file->getHandler();
608 $fileNamePos = strrpos( $thumbname, $params[
'f'] );
609 if ( $fileNamePos ===
false ) {
611 $fileNamePos = strrpos( $thumbname,
'thumbnail' );
614 if ( $handler && $fileNamePos !==
false ) {
615 $paramString = substr( $thumbname, 0, $fileNamePos - 1 );
616 $extraParams = $handler->parseParamString( $paramString );
617 if ( $extraParams !==
false ) {
618 return $params + $extraParams;
623 if ( preg_match(
'!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname,
$matches ) ) {
624 list( , , $pagenum, $size ) =
$matches;
625 $params[
'width'] = $size;
627 $params[
'page'] = $pagenum;
642 wfThumbError( $status, htmlspecialchars( $msgText, ENT_NOQUOTES ) );
655 function wfThumbError( $status, $msgHtml, $msgText =
null, $context = [] ) {
658 \MediaWiki\Request\HeaderCallback::warnIfHeadersSent();
660 if ( headers_sent() ) {
661 LoggerFactory::getInstance(
'thumbnail' )->error(
662 'Error after output had been started. Output may be corrupt or truncated. ' .
663 'Original error: ' . ( $msgText ?: $msgHtml ) .
" (Status $status)",
669 header(
'Cache-Control: no-cache' );
670 header(
'Content-Type: text/html; charset=utf-8' );
671 if ( $status == 400 || $status == 404 || $status == 429 ) {
673 } elseif ( $status == 403 ) {
675 header(
'Vary: Cookie' );
677 LoggerFactory::getInstance(
'thumbnail' )->error( $msgText ?: $msgHtml, $context );
681 header(
'X-MW-Thumbnail-Renderer: ' .
wfHostname() );
682 $url = htmlspecialchars(
683 $_SERVER[
'REQUEST_URI'] ??
'',
686 $hostname = htmlspecialchars(
wfHostname(), ENT_NOQUOTES );
687 $debug =
"<!-- $url -->\n<!-- $hostname -->\n";
694 <meta charset=
"UTF-8" />
695 <title>Error generating thumbnail</title>
698 <h1>Error generating thumbnail</h1>
707 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.
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.
getHTML()
Format an HTML message for the current exception object.
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.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
File without associated database record.
static getRequestPathSuffix( $basePath)
If the request URL matches a given base path, extract the path part of the request URL after that bas...
$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.