Path: blob/master/src/applications/files/controller/PhabricatorFileImageProxyController.php
12242 views
<?php12final class PhabricatorFileImageProxyController3extends PhabricatorFileController {45public function shouldAllowPublic() {6return true;7}89public function handleRequest(AphrontRequest $request) {10$viewer = $request->getViewer();11$img_uri = $request->getStr('uri');1213// Validate the URI before doing anything14PhabricatorEnv::requireValidRemoteURIForLink($img_uri);15$uri = new PhutilURI($img_uri);16$proto = $uri->getProtocol();1718$allowed_protocols = array(19'http',20'https',21);22if (!in_array($proto, $allowed_protocols)) {23throw new Exception(24pht(25'The provided image URI must use one of these protocols: %s.',26implode(', ', $allowed_protocols)));27}2829// Check if we already have the specified image URI downloaded30$cached_request = id(new PhabricatorFileExternalRequest())->loadOneWhere(31'uriIndex = %s',32PhabricatorHash::digestForIndex($img_uri));3334if ($cached_request) {35return $this->getExternalResponse($cached_request);36}3738$ttl = PhabricatorTime::getNow() + phutil_units('7 days in seconds');39$external_request = id(new PhabricatorFileExternalRequest())40->setURI($img_uri)41->setTTL($ttl);4243// Cache missed, so we'll need to validate and download the image.44$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();45$save_request = false;46try {47// Rate limit outbound fetches to make this mechanism less useful for48// scanning networks and ports.49PhabricatorSystemActionEngine::willTakeAction(50array($viewer->getPHID()),51new PhabricatorFilesOutboundRequestAction(),521);5354$file = PhabricatorFile::newFromFileDownload(55$uri,56array(57'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,58'canCDN' => true,59));6061if (!$file->isViewableImage()) {62$mime_type = $file->getMimeType();63$engine = new PhabricatorDestructionEngine();64$engine->destroyObject($file);65$file = null;66throw new Exception(67pht(68'The URI "%s" does not correspond to a valid image file (got '.69'a file with MIME type "%s"). You must specify the URI of a '.70'valid image file.',71$uri,72$mime_type));73}7475$file->save();7677$external_request78->setIsSuccessful(1)79->setFilePHID($file->getPHID());8081$save_request = true;82} catch (HTTPFutureHTTPResponseStatus $status) {83$external_request84->setIsSuccessful(0)85->setResponseMessage($status->getMessage());8687$save_request = true;88} catch (Exception $ex) {89// Not actually saving the request in this case90$external_request->setResponseMessage($ex->getMessage());91}9293if ($save_request) {94try {95$external_request->save();96} catch (AphrontDuplicateKeyQueryException $ex) {97// We may have raced against another identical request. If we did,98// just throw our result away and use the winner's result.99$external_request = $external_request->loadOneWhere(100'uriIndex = %s',101PhabricatorHash::digestForIndex($img_uri));102if (!$external_request) {103throw new Exception(104pht(105'Hit duplicate key collision when saving proxied image, but '.106'failed to load duplicate row (for URI "%s").',107$img_uri));108}109}110}111112unset($unguarded);113114115return $this->getExternalResponse($external_request);116}117118private function getExternalResponse(119PhabricatorFileExternalRequest $request) {120if (!$request->getIsSuccessful()) {121throw new Exception(122pht(123'Request to "%s" failed: %s',124$request->getURI(),125$request->getResponseMessage()));126}127128$file = id(new PhabricatorFileQuery())129->setViewer(PhabricatorUser::getOmnipotentUser())130->withPHIDs(array($request->getFilePHID()))131->executeOne();132if (!$file) {133throw new Exception(134pht(135'The underlying file does not exist, but the cached request was '.136'successful. This likely means the file record was manually '.137'deleted by an administrator.'));138}139140return id(new AphrontAjaxResponse())141->setContent(142array(143'imageURI' => $file->getViewURI(),144));145}146}147148149