/** * @dataProvider provideThumbParams */ public function testIsStandard($message, $expected, $params) { $this->assertSame($expected, wfThumbIsStandard(new FakeDimensionFile(array(2000, 1800)), $params), $message); }
/** * Stream a thumbnail specified by parameters * * @param array $params List of thumbnailing parameters. In addition to parameters * passed to the MediaHandler, this may also includes the keys: * f (for filename), archived (if archived file), temp (if temp file), * w (alias for width), p (alias for page), r (ignored; historical), * rel404 (path for render on 404 to verify hash path correct), * thumbName (thumbnail name to potentially extract more parameters from * e.g. 'lossy-page1-120px-Foo.tiff' would add page, lossy and width * to the parameters) * @return void */ function wfStreamThumb(array $params) { global $wgVaryOnXFP; $headers = array(); // HTTP headers to send $fileName = isset($params['f']) ? $params['f'] : ''; // Backwards compatibility parameters if (isset($params['w'])) { $params['width'] = $params['w']; unset($params['w']); } if (isset($params['width']) && substr($params['width'], -2) == 'px') { // strip the px (pixel) suffix, if found $params['width'] = substr($params['width'], 0, -2); } if (isset($params['p'])) { $params['page'] = $params['p']; } // Is this a thumb of an archived file? $isOld = isset($params['archived']) && $params['archived']; unset($params['archived']); // handlers don't care // Is this a thumb of a temp file? $isTemp = isset($params['temp']) && $params['temp']; unset($params['temp']); // handlers don't care // Some basic input validation $fileName = strtr($fileName, '\\/', '__'); // Actually fetch the image. Method depends on whether it is archived or not. if ($isTemp) { $repo = RepoGroup::singleton()->getLocalRepo()->getTempRepo(); $img = new UnregisteredLocalFile(null, $repo, $repo->getZonePath('public') . '/' . $repo->getTempHashPath($fileName) . $fileName); } elseif ($isOld) { // Format is <timestamp>!<name> $bits = explode('!', $fileName, 2); if (count($bits) != 2) { wfThumbError(404, wfMessage('badtitletext')->parse()); return; } $title = Title::makeTitleSafe(NS_FILE, $bits[1]); if (!$title) { wfThumbError(404, wfMessage('badtitletext')->parse()); return; } $img = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName($title, $fileName); } else { $img = wfLocalFile($fileName); } // Check the source file title if (!$img) { wfThumbError(404, wfMessage('badtitletext')->parse()); return; } // Check permissions if there are read restrictions $varyHeader = array(); if (!in_array('read', User::getGroupPermissions(array('*')), true)) { if (!$img->getTitle() || !$img->getTitle()->userCan('read')) { wfThumbError(403, 'Access denied. You do not have permission to access ' . 'the source file.'); return; } $headers[] = 'Cache-Control: private'; $varyHeader[] = 'Cookie'; } // Check if the file is hidden if ($img->isDeleted(File::DELETED_FILE)) { wfThumbErrorText(404, "The source file '{$fileName}' does not exist."); return; } // Do rendering parameters extraction from thumbnail name. if (isset($params['thumbName'])) { $params = wfExtractThumbParams($img, $params); } if ($params == null) { wfThumbError(400, 'The specified thumbnail parameters are not recognized.'); return; } // Check the source file storage path if (!$img->exists()) { $redirectedLocation = false; if (!$isTemp) { // Check for file redirect // Since redirects are associated with pages, not versions of files, // we look for the most current version to see if its a redirect. $possRedirFile = RepoGroup::singleton()->getLocalRepo()->findFile($img->getName()); if ($possRedirFile && !is_null($possRedirFile->getRedirected())) { $redirTarget = $possRedirFile->getName(); $targetFile = wfLocalFile(Title::makeTitleSafe(NS_FILE, $redirTarget)); if ($targetFile->exists()) { $newThumbName = $targetFile->thumbName($params); if ($isOld) { /** @var array $bits */ $newThumbUrl = $targetFile->getArchiveThumbUrl($bits[0] . '!' . $targetFile->getName(), $newThumbName); } else { $newThumbUrl = $targetFile->getThumbUrl($newThumbName); } $redirectedLocation = wfExpandUrl($newThumbUrl, PROTO_CURRENT); } } } if ($redirectedLocation) { // File has been moved. Give redirect. $response = RequestContext::getMain()->getRequest()->response(); $response->statusHeader(302); $response->header('Location: ' . $redirectedLocation); $response->header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 12 * 3600) . ' GMT'); if ($wgVaryOnXFP) { $varyHeader[] = 'X-Forwarded-Proto'; } if (count($varyHeader)) { $response->header('Vary: ' . implode(', ', $varyHeader)); } $response->header('Content-Length: 0'); return; } // If its not a redirect that has a target as a local file, give 404. wfThumbErrorText(404, "The source file '{$fileName}' does not exist."); return; } elseif ($img->getPath() === false) { wfThumbErrorText(400, "The source file '{$fileName}' is not locally accessible."); return; } // Check IMS against the source file // This means that clients can keep a cached copy even after it has been deleted on the server if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { // Fix IE brokenness $imsString = preg_replace('/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"]); // Calculate time MediaWiki\suppressWarnings(); $imsUnix = strtotime($imsString); MediaWiki\restoreWarnings(); if (wfTimestamp(TS_UNIX, $img->getTimestamp()) <= $imsUnix) { HttpStatus::header(304); return; } } $rel404 = isset($params['rel404']) ? $params['rel404'] : null; unset($params['r']); // ignore 'r' because we unconditionally pass File::RENDER unset($params['f']); // We're done with 'f' parameter. unset($params['rel404']); // moved to $rel404 // Get the normalized thumbnail name from the parameters... try { $thumbName = $img->thumbName($params); if (!strlen($thumbName)) { // invalid params? throw new MediaTransformInvalidParametersException('Empty return from File::thumbName'); } $thumbName2 = $img->thumbName($params, File::THUMB_FULL_NAME); // b/c; "long" style } catch (MediaTransformInvalidParametersException $e) { wfThumbError(400, 'The specified thumbnail parameters are not valid: ' . $e->getMessage()); return; } catch (MWException $e) { wfThumbError(500, $e->getHTML()); return; } // For 404 handled thumbnails, we only use the base name of the URI // for the thumb params and the parent directory for the source file name. // Check that the zone relative path matches up so squid caches won't pick // up thumbs that would not be purged on source file deletion (bug 34231). if ($rel404 !== null) { // thumbnail was handled via 404 if (rawurldecode($rel404) === $img->getThumbRel($thumbName)) { // Request for the canonical thumbnail name } elseif (rawurldecode($rel404) === $img->getThumbRel($thumbName2)) { // Request for the "long" thumbnail name; redirect to canonical name $response = RequestContext::getMain()->getRequest()->response(); $response->statusHeader(301); $response->header('Location: ' . wfExpandUrl($img->getThumbUrl($thumbName), PROTO_CURRENT)); $response->header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 7 * 86400) . ' GMT'); if ($wgVaryOnXFP) { $varyHeader[] = 'X-Forwarded-Proto'; } if (count($varyHeader)) { $response->header('Vary: ' . implode(', ', $varyHeader)); } return; } else { wfThumbErrorText(404, "The given path of the specified thumbnail is incorrect;\n\t\t\t\texpected '" . $img->getThumbRel($thumbName) . "' but got '" . rawurldecode($rel404) . "'."); return; } } $dispositionType = isset($params['download']) ? 'attachment' : 'inline'; // Suggest a good name for users downloading this thumbnail $headers[] = "Content-Disposition: {$img->getThumbDisposition($thumbName, $dispositionType)}"; if (count($varyHeader)) { $headers[] = 'Vary: ' . implode(', ', $varyHeader); } // Stream the file if it exists already... $thumbPath = $img->getThumbPath($thumbName); if ($img->getRepo()->fileExists($thumbPath)) { $starttime = microtime(true); $success = $img->getRepo()->streamFile($thumbPath, $headers); $streamtime = microtime(true) - $starttime; if (!$success) { wfThumbError(500, 'Could not stream the file'); } else { RequestContext::getMain()->getStats()->timing('media.thumbnail.stream', $streamtime); } return; } $user = RequestContext::getMain()->getUser(); if (!wfThumbIsStandard($img, $params) && $user->pingLimiter('renderfile-nonstandard')) { wfThumbError(429, wfMessage('actionthrottledtext')->parse()); return; } elseif ($user->pingLimiter('renderfile')) { wfThumbError(429, wfMessage('actionthrottledtext')->parse()); return; } list($thumb, $errorMsg) = wfGenerateThumbnail($img, $params, $thumbName, $thumbPath); /** @var MediaTransformOutput|MediaTransformError|bool $thumb */ // Check for thumbnail generation errors... $msg = wfMessage('thumbnail_error'); $errorCode = 500; if (!$thumb) { $errorMsg = $errorMsg ?: $msg->rawParams('File::transform() returned false')->escaped(); if ($errorMsg instanceof MessageSpecifier && $errorMsg->getKey() === 'thumbnail_image-failure-limit') { $errorCode = 429; } } elseif ($thumb->isError()) { $errorMsg = $thumb->getHtmlMsg(); } elseif (!$thumb->hasFile()) { $errorMsg = $msg->rawParams('No path supplied in thumbnail object')->escaped(); } elseif ($thumb->fileIsSource()) { $errorMsg = $msg->rawParams('Image was not scaled, is the requested width bigger than the source?')->escaped(); $errorCode = 400; } if ($errorMsg !== false) { wfThumbError($errorCode, $errorMsg); } else { // Stream the file if there were no errors $success = $thumb->streamFile($headers); if (!$success) { wfThumbError(500, 'Could not stream the file'); } } }
/** * @dataProvider provideThumbParams */ public function testIsStandard($message, $expected, $params) { $this->setService('MediaHandlerFactory', new MockMediaHandlerFactory()); $this->assertSame($expected, wfThumbIsStandard(new FakeDimensionFile([2000, 1800]), $params), $message); }