diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2557,6 +2557,7 @@ 'PhabricatorFileHasObjectEdgeType' => 'applications/files/edge/PhabricatorFileHasObjectEdgeType.php', 'PhabricatorFileIconSetSelectController' => 'applications/files/controller/PhabricatorFileIconSetSelectController.php', 'PhabricatorFileImageMacro' => 'applications/macro/storage/PhabricatorFileImageMacro.php', + 'PhabricatorFileImageProxyController' => 'applications/files/controller/PhabricatorFileImageProxyController.php', 'PhabricatorFileImageTransform' => 'applications/files/transform/PhabricatorFileImageTransform.php', 'PhabricatorFileInfoController' => 'applications/files/controller/PhabricatorFileInfoController.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', @@ -7379,6 +7380,7 @@ 'PhabricatorTokenReceiverInterface', 'PhabricatorPolicyInterface', ), + 'PhabricatorFileImageProxyController' => 'PhabricatorFileController', 'PhabricatorFileImageTransform' => 'PhabricatorFileTransform', 'PhabricatorFileInfoController' => 'PhabricatorFileController', 'PhabricatorFileLinkView' => 'AphrontView', diff --git a/src/applications/files/application/PhabricatorFilesApplication.php b/src/applications/files/application/PhabricatorFilesApplication.php --- a/src/applications/files/application/PhabricatorFilesApplication.php +++ b/src/applications/files/application/PhabricatorFilesApplication.php @@ -78,7 +78,7 @@ 'delete/(?P[1-9]\d*)/' => 'PhabricatorFileDeleteController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorFileEditController', 'info/(?P[^/]+)/' => 'PhabricatorFileInfoController', - 'proxy/' => 'PhabricatorFileProxyController', + 'imageproxy/' => 'PhabricatorFileImageProxyController', 'transforms/(?P[1-9]\d*)/' => 'PhabricatorFileTransformListController', 'uploaddialog/(?Psingle/)?' diff --git a/src/applications/files/controller/PhabricatorFileImageProxyController.php b/src/applications/files/controller/PhabricatorFileImageProxyController.php new file mode 100644 --- /dev/null +++ b/src/applications/files/controller/PhabricatorFileImageProxyController.php @@ -0,0 +1,94 @@ +getRequestData(); + $img_uri = $req_data['uri']; + + // Check if we already have the specified image URI downloaded + $cache = new PhabricatorKeyValueDatabaseCache(); + $cache = new PhutilKeyValueCacheProfiler($cache); + $cache->setProfiler(PhutilServiceProfiler::getInstance()); + $cache_key = 'file.imageproxy:'.$img_uri; + $result = $cache->getKey($cache_key); + if ($result) { + return id(new AphrontRedirectResponse()) + ->setIsExternal(true) + ->setURI($result); + } + + // Cache missed so we'll need to validate and download the image + PhabricatorEnv::requireValidRemoteURIForLink($img_uri); + $uri = new PhutilURI($img_uri); + $proto = $uri->getProtocol(); + if (!in_array($proto, array('http', 'https'))) { + throw new Exception( + pht('The provided image URI must be either http or https')); + } + + $viewer = $request->getViewer(); + try { + // Rate limit outbound fetches to make this mechanism less useful for + // scanning networks and ports. + PhabricatorSystemActionEngine::willTakeAction( + array($viewer->getPHID()), + new PhabricatorFilesOutboundRequestAction(), + 1); + + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); + $file = PhabricatorFile::newFromFileDownload( + $uri, + array( + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + 'canCDN' => true, + )); + if (!$file->isViewableInBrowser()) { + $mime_type = $file->getMimeType(); + $engine = new PhabricatorDestructionEngine(); + $engine->destroyObject($file); + $file = null; + throw new Exception( + pht( + 'The URI "%s" does not correspond to a valid image file, got '. + 'a file with MIME type "%s". You must specify the URI of a '. + 'valid image file.', + $uri, + $mime_type)); + } else { + $file + ->setAuthorPHID($viewer->getPHID()) + ->save(); + } + unset($unguarded); + $cache->setKey($cache_key, $file->getViewURI()); + return id(new AphrontRedirectResponse()) + ->setIsExternal(true) + ->setURI($file->getViewURI()); + } catch (HTTPFutureHTTPResponseStatus $status) { + throw new Exception(pht( + 'The URI "%s" could not be loaded, got %s error.', + $uri, + $status->getStatusCode())); + } catch (Exception $ex) { + throw new Exception(pht( + "The URI '%s' could not be loaded due to the following error:\n %s", + $uri, + $ex->getMessage())); + } + } +}