MediaWiki master
ThumbnailEntryPoint.php
Go to the documentation of this file.
1<?php
31namespace MediaWiki\FileRepo;
32
33use Exception;
34use File;
35use InvalidArgumentException;
49use ObjectCache;
51use Wikimedia\AtEase\AtEase;
52
54
58 public function execute() {
60
63 'stream'
64 );
65
66 // Don't use fancy MIME detection, just check the file extension for jpg/gif/png.
67 // NOTE: This only works as long as to StreamFile::contentTypeFromPath
68 // get this setting from global state. When StreamFile gets refactored,
69 // we need to find a better way.
71
72 $this->handleRequest();
73 }
74
75 protected function doPrepareForOutput() {
76 // No-op.
77 // Do not call parent::doPrepareForOutput() to avoid
78 // commitMainTransaction() getting called.
79 }
80
81 protected function handleRequest() {
82 $this->streamThumb( $this->getRequest()->getQueryValuesOnly() );
83 }
84
98 protected function streamThumb( array $params ) {
99 $varyOnXFP = $this->getConfig( MainConfigNames::VaryOnXFP );
100
101 $headers = []; // HTTP headers to send
102
103 $fileName = $params['f'] ?? '';
104
105 // Backwards compatibility parameters
106 if ( isset( $params['w'] ) ) {
107 $params['width'] = $params['w'];
108 unset( $params['w'] );
109 }
110 if ( isset( $params['width'] ) && substr( $params['width'], -2 ) == 'px' ) {
111 // strip the px (pixel) suffix, if found
112 $params['width'] = substr( $params['width'], 0, -2 );
113 }
114 if ( isset( $params['p'] ) ) {
115 $params['page'] = $params['p'];
116 }
117
118 // Is this a thumb of an archived file?
119 $isOld = ( isset( $params['archived'] ) && $params['archived'] );
120 unset( $params['archived'] ); // handlers don't care
121
122 // Is this a thumb of a temp file?
123 $isTemp = ( isset( $params['temp'] ) && $params['temp'] );
124 unset( $params['temp'] ); // handlers don't care
125
126 $services = MediaWikiServices::getInstance();
127
128 // Some basic input validation
129 $fileName = strtr( $fileName, '\\/', '__' );
130 $localRepo = $services->getRepoGroup()->getLocalRepo();
131 $archiveTimestamp = null;
132
133 // Actually fetch the image. Method depends on whether it is archived or not.
134 if ( $isTemp ) {
135 $repo = $localRepo->getTempRepo();
136 $img = new UnregisteredLocalFile( false, $repo,
137 # Temp files are hashed based on the name without the timestamp.
138 # The thumbnails will be hashed based on the entire name however.
139 # @todo fix this convention to actually be reasonable.
140 $repo->getZonePath( 'public' ) . '/' . $repo->getTempHashPath( $fileName ) . $fileName
141 );
142 } elseif ( $isOld ) {
143 // Format is <timestamp>!<name>
144 $bits = explode( '!', $fileName, 2 );
145 if ( count( $bits ) != 2 ) {
146 $this->thumbError( 404, $this->getContext()->msg( 'badtitletext' )->parse() );
147 return;
148 }
149 $archiveTimestamp = $bits[0];
150 $title = Title::makeTitleSafe( NS_FILE, $bits[1] );
151 if ( !$title ) {
152 $this->thumbError( 404, $this->getContext()->msg( 'badtitletext' )->parse() );
153 return;
154 }
155 $img = $localRepo->newFromArchiveName( $title, $fileName );
156 } else {
157 $img = $localRepo->newFile( $fileName );
158 }
159
160 // Check the source file title
161 if ( !$img ) {
162 $this->thumbError( 404, $this->getContext()->msg( 'badtitletext' )->parse() );
163 return;
164 }
165
166 // Check permissions if there are read restrictions
167 $varyHeader = [];
168 if ( !$services->getGroupPermissionsLookup()->groupHasPermission( '*', 'read' ) ) {
169 $authority = $this->getContext()->getAuthority();
170 $imgTitle = $img->getTitle();
171
172 if ( !$imgTitle || !$authority->authorizeRead( 'read', $imgTitle ) ) {
173 $this->thumbErrorText( 403, 'Access denied. You do not have permission to access ' .
174 'the source file.' );
175 return;
176 }
177 $headers[] = 'Cache-Control: private';
178 $varyHeader[] = 'Cookie';
179 }
180
181 // Check if the file is hidden
182 if ( $img->isDeleted( File::DELETED_FILE ) ) {
183 $this->thumbErrorText( 404, "The source file '$fileName' does not exist." );
184 return;
185 }
186
187 // Do rendering parameters extraction from thumbnail name.
188 if ( isset( $params['thumbName'] ) ) {
189 $params = $this->extractThumbParams( $img, $params );
190 }
191 if ( $params == null ) {
192 $this->thumbErrorText( 400, 'The specified thumbnail parameters are not recognized.' );
193 return;
194 }
195
196 // Check the source file storage path
197 if ( !$img->exists() ) {
198 $redirectedLocation = false;
199 if ( !$isTemp ) {
200 // Check for file redirect
201 // Since redirects are associated with pages, not versions of files,
202 // we look for the most current version to see if its a redirect.
203 $possRedirFile = $localRepo->findFile( $img->getName() );
204 if ( $possRedirFile && $possRedirFile->getRedirected() !== null ) {
205 $redirTarget = $possRedirFile->getName();
206 $targetFile = $localRepo->newFile( Title::makeTitleSafe( NS_FILE, $redirTarget ) );
207 if ( $targetFile->exists() ) {
208 $newThumbName = $targetFile->thumbName( $params );
209 if ( $isOld ) {
210 $newThumbUrl = $targetFile->getArchiveThumbUrl(
211 $archiveTimestamp . '!' . $targetFile->getName(), $newThumbName );
212 } else {
213 $newThumbUrl = $targetFile->getThumbUrl( $newThumbName );
214 }
215 $redirectedLocation = wfExpandUrl( $newThumbUrl, PROTO_CURRENT );
216 }
217 }
218 }
219
220 if ( $redirectedLocation ) {
221 // File has been moved. Give redirect.
222 $response = $this->getResponse();
223 $response->statusHeader( 302 );
224 $response->header( 'Location: ' . $redirectedLocation );
225 $response->header( 'Expires: ' .
226 gmdate( 'D, d M Y H:i:s', time() + 12 * 3600 ) . ' GMT' );
227 if ( $varyOnXFP ) {
228 $varyHeader[] = 'X-Forwarded-Proto';
229 }
230 if ( count( $varyHeader ) ) {
231 $response->header( 'Vary: ' . implode( ', ', $varyHeader ) );
232 }
233 $response->header( 'Content-Length: 0' );
234 return;
235 }
236
237 // If it's not a redirect that has a target as a local file, give 404.
238 $this->thumbErrorText( 404, "The source file '$fileName' does not exist." );
239 return;
240 } elseif ( $img->getPath() === false ) {
241 $this->thumbErrorText( 400, "The source file '$fileName' is not locally accessible." );
242 return;
243 }
244
245 // Check IMS against the source file
246 // This means that clients can keep a cached copy even after it has been deleted on the server
247 if ( $this->getServerInfo( 'HTTP_IF_MODIFIED_SINCE', '' ) !== '' ) {
248 // Fix IE brokenness
249 $imsString = preg_replace(
250 '/;.*$/',
251 '',
252 $this->getServerInfo( 'HTTP_IF_MODIFIED_SINCE' ) ?? ''
253 );
254 // Calculate time
255 AtEase::suppressWarnings();
256 $imsUnix = strtotime( $imsString );
257 AtEase::restoreWarnings();
258 if ( wfTimestamp( TS_UNIX, $img->getTimestamp() ) <= $imsUnix ) {
259 $this->status( 304 );
260 return;
261 }
262 }
263
264 $rel404 = $params['rel404'] ?? null;
265 unset( $params['r'] ); // ignore 'r' because we unconditionally pass File::RENDER
266 unset( $params['f'] ); // We're done with 'f' parameter.
267 unset( $params['rel404'] ); // moved to $rel404
268
269 // Get the normalized thumbnail name from the parameters...
270 try {
271 $thumbName = $img->thumbName( $params );
272 if ( !strlen( $thumbName ?? '' ) ) { // invalid params?
274 'Empty return from File::thumbName'
275 );
276 }
277 $thumbName2 = $img->thumbName( $params, File::THUMB_FULL_NAME ); // b/c; "long" style
279 $this->thumbErrorText(
280 400,
281 'The specified thumbnail parameters are not valid: ' . $e->getMessage()
282 );
283 return;
284 }
285
286 // For 404 handled thumbnails, we only use the base name of the URI
287 // for the thumb params and the parent directory for the source file name.
288 // Check that the zone relative path matches up so CDN caches won't pick
289 // up thumbs that would not be purged on source file deletion (T36231).
290 if ( $rel404 !== null ) { // thumbnail was handled via 404
291 if ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName ) ) {
292 // Request for the canonical thumbnail name
293 } elseif ( rawurldecode( $rel404 ) === $img->getThumbRel( $thumbName2 ) ) {
294 // Request for the "long" thumbnail name; redirect to canonical name
295 $this->status( 301 );
296 $this->header( 'Location: ' .
297 wfExpandUrl( $img->getThumbUrl( $thumbName ), PROTO_CURRENT ) );
298 $this->header( 'Expires: ' .
299 gmdate( 'D, d M Y H:i:s', time() + 7 * 86400 ) . ' GMT' );
300 if ( $varyOnXFP ) {
301 $varyHeader[] = 'X-Forwarded-Proto';
302 }
303 if ( count( $varyHeader ) ) {
304 $this->header( 'Vary: ' . implode( ', ', $varyHeader ) );
305 }
306 return;
307 } else {
308 $this->thumbErrorText( 404, "The given path of the specified thumbnail is incorrect;
309 expected '" . $img->getThumbRel( $thumbName ) . "' but got '" .
310 rawurldecode( $rel404 ) . "'." );
311 return;
312 }
313 }
314
315 $dispositionType = isset( $params['download'] ) ? 'attachment' : 'inline';
316
317 // Suggest a good name for users downloading this thumbnail
318 $headers[] =
319 'Content-Disposition: ' . $img->getThumbDisposition( $thumbName, $dispositionType );
320
321 if ( count( $varyHeader ) ) {
322 $headers[] = 'Vary: ' . implode( ', ', $varyHeader );
323 }
324
325 // Stream the file if it exists already...
326 $thumbPath = $img->getThumbPath( $thumbName );
327 if ( $img->getRepo()->fileExists( $thumbPath ) ) {
328 $starttime = microtime( true );
329 $status = $img->getRepo()->streamFileWithStatus( $thumbPath, $headers );
330 $streamtime = microtime( true ) - $starttime;
331
332 if ( $status->isOK() ) {
333 $services->getStatsdDataFactory()->timing(
334 'media.thumbnail.stream',
335 $streamtime
336 );
337 } else {
338 $this->thumbError(
339 500,
340 'Could not stream the file',
341 $status->getWikiText( false, false, 'en' ),
342 [
343 'file' => $thumbName,
344 'path' => $thumbPath,
345 'error' => $status->getWikiText( false, false, 'en' ),
346 ]
347 );
348 }
349 return;
350 }
351
352 $authority = $this->getContext()->getAuthority();
353 $status = PermissionStatus::newEmpty();
354 if ( !wfThumbIsStandard( $img, $params )
355 && !$authority->authorizeAction( 'renderfile-nonstandard', $status )
356 ) {
357 $statusFormatter = $services->getFormatterFactory()
358 ->getStatusFormatter( $this->getContext() );
359
360 $this->thumbError( 429, $statusFormatter->getHTML( $status ) );
361 return;
362 } elseif ( !$authority->authorizeAction( 'renderfile', $status ) ) {
363 $statusFormatter = $services->getFormatterFactory()
364 ->getStatusFormatter( $this->getContext() );
365
366 $this->thumbError( 429, $statusFormatter->getHTML( $status ) );
367 return;
368 }
369
370 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
371
372 if ( strlen( $thumbProxyUrl ?? '' ) ) {
373 $this->proxyThumbnailRequest( $img, $thumbName );
374 // No local fallback when in proxy mode
375 return;
376 } else {
377 // Generate the thumbnail locally
378 [ $thumb, $errorMsg ] = $this->generateThumbnail( $img, $params, $thumbName, $thumbPath );
379 }
380
383 // Check for thumbnail generation errors...
384 $msg = $this->getContext()->msg( 'thumbnail_error' );
385 $errorCode = 500;
386
387 if ( !$thumb ) {
388 $errorMsg = $errorMsg ?: $msg->rawParams( 'File::transform() returned false' )->escaped();
389 if ( $errorMsg instanceof MessageSpecifier &&
390 $errorMsg->getKey() === 'thumbnail_image-failure-limit'
391 ) {
392 $errorCode = 429;
393 }
394 } elseif ( $thumb->isError() ) {
395 $errorMsg = $thumb->getHtmlMsg();
396 $errorCode = $thumb->getHttpStatusCode();
397 } elseif ( !$thumb->hasFile() ) {
398 $errorMsg = $msg->rawParams( 'No path supplied in thumbnail object' )->escaped();
399 } elseif ( $thumb->fileIsSource() ) {
400 $errorMsg = $msg
401 ->rawParams( 'Image was not scaled, is the requested width bigger than the source?' )
402 ->escaped();
403 $errorCode = 400;
404 }
405
406 $this->prepareForOutput();
407
408 if ( $errorMsg !== false ) {
409 $this->thumbError( $errorCode, $errorMsg, null, [ 'file' => $thumbName, 'path' => $thumbPath ] );
410 } else {
411 // Stream the file if there were no errors
412 '@phan-var MediaTransformOutput $thumb';
413 $status = $thumb->streamFileWithStatus( $headers );
414 if ( !$status->isOK() ) {
415 $this->thumbError( 500, 'Could not stream the file', $status->getWikiText( false, false, 'en' ), [
416 'file' => $thumbName, 'path' => $thumbPath,
417 'error' => $status->getWikiText( false, false, 'en' ) ] );
418 }
419 }
420 }
421
428 private function proxyThumbnailRequest( $img, $thumbName ) {
429 $thumbProxyUrl = $img->getRepo()->getThumbProxyUrl();
430
431 // Instead of generating the thumbnail ourselves, we proxy the request to another service
432 $thumbProxiedUrl = $thumbProxyUrl . $img->getThumbRel( $thumbName );
433
434 $req = MediaWikiServices::getInstance()->getHttpRequestFactory()->create( $thumbProxiedUrl );
435 $secret = $img->getRepo()->getThumbProxySecret();
436
437 // Pass a secret key shared with the proxied service if any
438 if ( strlen( $secret ?? '' ) ) {
439 $req->setHeader( 'X-Swift-Secret', $secret );
440 }
441
442 // Send request to proxied service
443 $req->execute();
444
446
447 // Simply serve the response from the proxied service as-is
448 $this->header( 'HTTP/1.1 ' . $req->getStatus() );
449
450 $headers = $req->getResponseHeaders();
451
452 foreach ( $headers as $key => $values ) {
453 foreach ( $values as $value ) {
454 $this->header( $key . ': ' . $value, false );
455 }
456 }
457
458 $this->print( $req->getContent() );
459 }
460
470 protected function generateThumbnail( File $file, array $params, $thumbName, $thumbPath ) {
471 $attemptFailureEpoch = $this->getConfig( MainConfigNames::AttemptFailureEpoch );
472
473 $cache = ObjectCache::getLocalClusterInstance();
474 $key = $cache->makeKey(
475 'attempt-failures',
476 $attemptFailureEpoch,
477 $file->getRepo()->getName(),
478 $file->getSha1(),
479 md5( $thumbName )
480 );
481
482 // Check if this file keeps failing to render
483 if ( $cache->get( $key ) >= 4 ) {
484 return [ false, $this->getContext()->msg( 'thumbnail_image-failure-limit', 4 ) ];
485 }
486
487 $done = false;
488 // Record failures on PHP fatals in addition to caching exceptions
489 register_shutdown_function( static function () use ( $cache, &$done, $key ) {
490 if ( !$done ) { // transform() gave a fatal
491 // Randomize TTL to reduce stampedes
492 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
493 }
494 } );
495
496 $thumb = false;
497 $errorHtml = false;
498
499 // guard thumbnail rendering with PoolCounter to avoid stampedes
500 // expensive files use a separate PoolCounter config so it is possible
501 // to set up a global limit on them
502 if ( $file->isExpensiveToThumbnail() ) {
503 $poolCounterType = 'FileRenderExpensive';
504 } else {
505 $poolCounterType = 'FileRender';
506 }
507
508 // Thumbnail isn't already there, so create the new thumbnail...
509 try {
510 $work = new PoolCounterWorkViaCallback( $poolCounterType, sha1( $file->getName() ),
511 [
512 'doWork' => static function () use ( $file, $params ) {
513 return $file->transform( $params, File::RENDER_NOW );
514 },
515 'doCachedWork' => static function () use ( $file, $params, $thumbPath ) {
516 // If the worker that finished made this thumbnail then use it.
517 // Otherwise, it probably made a different thumbnail for this file.
518 return $file->getRepo()->fileExists( $thumbPath )
519 ? $file->transform( $params, File::RENDER_NOW )
520 : false; // retry once more in exclusive mode
521 },
522 'error' => function ( Status $status ) {
523 return $this->getContext()->msg( 'generic-pool-error' )->parse() . '<hr>' . $status->getHTML();
524 }
525 ]
526 );
527 $result = $work->execute();
528 if ( $result instanceof MediaTransformOutput ) {
529 $thumb = $result;
530 } elseif ( is_string( $result ) ) { // error
531 $errorHtml = $result;
532 }
533 } catch ( Exception $e ) {
534 // Tried to select a page on a non-paged file?
535 }
536
538 $done = true; // no PHP fatal occurred
539
540 if ( !$thumb || $thumb->isError() ) {
541 // Randomize TTL to reduce stampedes
542 $cache->incrWithInit( $key, $cache::TTL_HOUR + mt_rand( 0, 300 ) );
543 }
544
545 return [ $thumb, $errorHtml ];
546 }
547
556 private function extractThumbParams( $file, $params ) {
557 if ( !isset( $params['thumbName'] ) ) {
558 throw new InvalidArgumentException( "No thumbnail name passed to extractThumbParams" );
559 }
560
561 $thumbname = $params['thumbName'];
562 unset( $params['thumbName'] );
563
564 // FIXME: Files in the temp zone don't set a MIME type, which means
565 // they don't have a handler. Which means we can't parse the param
566 // string. However, not a big issue as what good is a param string
567 // if you have no handler to make use of the param string and
568 // actually generate the thumbnail.
569 $handler = $file->getHandler();
570
571 // Based on UploadStash::parseKey
572 $fileNamePos = strrpos( $thumbname, $params['f'] );
573 if ( $fileNamePos === false ) {
574 // Maybe using a short filename? (see FileRepo::nameForThumb)
575 $fileNamePos = strrpos( $thumbname, 'thumbnail' );
576 }
577
578 if ( $handler && $fileNamePos !== false ) {
579 $paramString = substr( $thumbname, 0, $fileNamePos - 1 );
580 $extraParams = $handler->parseParamString( $paramString );
581 if ( $extraParams !== false ) {
582 return $params + $extraParams;
583 }
584 }
585
586 // As a last ditch fallback, use the traditional common parameters
587 if ( preg_match( '!^(page(\d*)-)*(\d*)px-[^/]*$!', $thumbname, $matches ) ) {
588 [ /* all */, /* pagefull */, $pagenum, $size ] = $matches;
589 $params['width'] = $size;
590 if ( $pagenum ) {
591 $params['page'] = $pagenum;
592 }
593 return $params; // valid thumbnail URL
594 }
595 return null;
596 }
597
605 protected function thumbErrorText( $status, $msgText ) {
606 $this->thumbError( $status, htmlspecialchars( $msgText, ENT_NOQUOTES ) );
607 }
608
619 protected function thumbError( $status, $msgHtml, $msgText = null, $context = [] ) {
620 $showHostnames = $this->getConfig( MainConfigNames::ShowHostnames );
621
623
624 if ( $this->getResponse()->headersSent() ) {
625 LoggerFactory::getInstance( 'thumbnail' )->error(
626 'Error after output had been started. Output may be corrupt or truncated. ' .
627 'Original error: ' . ( $msgText ?: $msgHtml ) . " (Status $status)",
628 $context
629 );
630 return;
631 }
632
633 $this->header( 'Cache-Control: no-cache' );
634 $this->header( 'Content-Type: text/html; charset=utf-8' );
635 if ( $status == 400 || $status == 404 || $status == 429 ) {
636 $this->status( $status );
637 } elseif ( $status == 403 ) {
638 $this->status( 403 );
639 $this->header( 'Vary: Cookie' );
640 } else {
641 LoggerFactory::getInstance( 'thumbnail' )->error( $msgText ?: $msgHtml, $context );
642 $this->status( 500 );
643 }
644 if ( $showHostnames ) {
645 $this->header( 'X-MW-Thumbnail-Renderer: ' . wfHostname() );
646 $url = htmlspecialchars(
647 $this->getServerInfo( 'REQUEST_URI' ) ?? '',
648 ENT_NOQUOTES
649 );
650 $hostname = htmlspecialchars( wfHostname(), ENT_NOQUOTES );
651 $debug = "<!-- $url -->\n<!-- $hostname -->\n";
652 } else {
653 $debug = '';
654 }
655 $content = <<<EOT
656<!DOCTYPE html>
657<html><head>
658<meta charset="UTF-8" />
659<title>Error generating thumbnail</title>
660</head>
661<body>
662<h1>Error generating thumbnail</h1>
663<p>
664$msgHtml
665</p>
666$debug
667</body>
668</html>
669
670EOT;
671 $this->header( 'Content-Length: ' . strlen( $content ) );
672 $this->print( $content );
673 }
674
675}
const NS_FILE
Definition Defines.php:70
const PROTO_CURRENT
Definition Defines.php:207
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.
array $params
The job parameters.
const MW_ENTRY_POINT
Definition api.php:35
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:73
isExpensiveToThumbnail()
True if creating thumbnails from the file is large or otherwise resource-intensive.
Definition File.php:2485
getName()
Return the name of this file.
Definition File.php:341
getRepo()
Returns the repository.
Definition File.php:2050
transform( $params, $flags=0)
Transform a media file.
Definition File.php:1177
getSha1()
Get the SHA-1 base 36 hash of the file.
Definition File.php:2331
MediaWiki exception thrown by some methods when the transform parameter array is invalid.
Base class for the output of MediaHandler::doTransform() and File::transform().
thumbErrorText( $status, $msgText)
Output a thumbnail generation error message.
generateThumbnail(File $file, array $params, $thumbName, $thumbPath)
Actually try to generate a new thumbnail.
thumbError( $status, $msgHtml, $msgText=null, $context=[])
Output a thumbnail generation error message.
doPrepareForOutput()
Prepare for sending the output.
streamThumb(array $params)
Stream a thumbnail specified by parameters.
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const VaryOnXFP
Name constant for the VaryOnXFP setting, for use with Config::get()
const AttemptFailureEpoch
Name constant for the AttemptFailureEpoch setting, for use with Config::get()
const ShowHostnames
Name constant for the ShowHostnames setting, for use with Config::get()
Base class for entry point handlers.
prepareForOutput()
Prepare for sending the output.
getServerInfo(string $key, $default=null)
header(string $header, bool $replace=true, int $status=0)
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
A StatusValue for permission errors.
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.
Class for tracking request-level classification information for profiling/stats/logging.
static warnIfHeadersSent()
Log a warning message if headers have already been sent.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Represents a title within MediaWiki.
Definition Title.php:78
File without associated database record.
$wgTrivialMimeDetection
Config variable stub for the TrivialMimeDetection setting, for use by phpdoc and IDEs.
getKey()
Returns the message key.