diff --git a/src/applications/files/config/PhabricatorFilesConfigOptions.php b/src/applications/files/config/PhabricatorFilesConfigOptions.php --- a/src/applications/files/config/PhabricatorFilesConfigOptions.php +++ b/src/applications/files/config/PhabricatorFilesConfigOptions.php @@ -134,6 +134,19 @@ "Set this to a valid Amazon S3 bucket to store files there. You ". "must also configure S3 access keys in the 'Amazon Web Services' ". "group.")), + $this->newOption('storage.enable-public-uri', 'bool', false) + ->setSummary(pht('Configure usage of Amazon S3 public URI.')) + ->setDescription( + pht( + "Change this to allow certain applications to return file URLs ". + "that directly point to files stored on S3. This allows you ". + "to offload requests directly to Amazon and reduce load on ". + "the Phabricator server, at the cost of configuring Amazon S3 ". + "for public access.\n\n". + "NOTE: This currently only affects files which reside on the ". + "Amazon S3 storage engine.\n\n". + "NOTE: Currently Phragment's Conduit APIs are the only ". + "area that uses this functionality.")), $this->newOption( 'storage.engine-selector', 'class', diff --git a/src/applications/files/engine/PhabricatorFileStorageEngine.php b/src/applications/files/engine/PhabricatorFileStorageEngine.php --- a/src/applications/files/engine/PhabricatorFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorFileStorageEngine.php @@ -81,6 +81,25 @@ /** + * Retrieve an absolute URI to which a download can be performed. This + * can be used to direct downloads to Amazon S3 for non-user + * applications (such as Conduit APIs). + * + * This URL should not be provided to end users, as the underlying storage + * engine will most likely not provide the correct semantics for + * browser-based downloads. + * + * Return null to indicate that no public URI is available. + * + * @param string The handle returned from @{method:writeFile} when the + * file was written. + * @return string An absolute URI from which a download can be performed. + * @task file + */ + abstract public function retrieveFileURI($handle); + + + /** * Delete the data for a file previously written by @{method:writeFile}. * * @param string The handle returned from @{method:writeFile} when the diff --git a/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php b/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php --- a/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php @@ -66,6 +66,15 @@ /** + * Return null because there are no direct download URI. + * @task impl + */ + public function retrieveFileURI($handle) { + return null; + } + + + /** * Deletes the file from local disk, if it exists. * @task impl */ diff --git a/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php b/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php --- a/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorMySQLFileStorageEngine.php @@ -51,6 +51,15 @@ /** + * Return null because there are no direct download URI. + * @task impl + */ + public function retrieveFileURI($handle) { + return null; + } + + + /** * Delete a blob from MySQL. * @task impl */ diff --git a/src/applications/files/engine/PhabricatorS3FileStorageEngine.php b/src/applications/files/engine/PhabricatorS3FileStorageEngine.php --- a/src/applications/files/engine/PhabricatorS3FileStorageEngine.php +++ b/src/applications/files/engine/PhabricatorS3FileStorageEngine.php @@ -69,6 +69,19 @@ /** + * Return absolute public URI for redirected download. + * @task impl + */ + public function retrieveFileURI($handle) { + $base_uri = 'https://'.$this->getBucketName().'.s3.amazonaws.com/'; + if ($base_uri === null) { + return null; + } + return $base_uri.$handle; + } + + + /** * Delete a blob from Amazon S3. */ public function deleteFile($handle) { diff --git a/src/applications/files/engine/PhabricatorTestStorageEngine.php b/src/applications/files/engine/PhabricatorTestStorageEngine.php --- a/src/applications/files/engine/PhabricatorTestStorageEngine.php +++ b/src/applications/files/engine/PhabricatorTestStorageEngine.php @@ -26,6 +26,10 @@ throw new Exception("No such file with handle '{$handle}'!"); } + public function retrieveFileURI($handle) { + return null; + } + public function deleteFile($handle) { AphrontWriteGuard::willWrite(); unset(self::$storage[$handle]); diff --git a/src/applications/files/storage/PhabricatorFile.php b/src/applications/files/storage/PhabricatorFile.php --- a/src/applications/files/storage/PhabricatorFile.php +++ b/src/applications/files/storage/PhabricatorFile.php @@ -506,6 +506,22 @@ return PhabricatorEnv::getCDNURI($path); } + public function getMostDirectURI() { + if (PhabricatorEnv::getEnvConfig('storage.enable-public-uri')) { + // Redirect to the public URI if it's present. This means Conduit + // methods such as phragment.getstate will return URLs directly + // pointing to Amazon for downloading files. + $engine = $this->instantiateStorageEngine(); + $uri = $engine->retrieveFileURI($this->getStorageHandle()); + if ($uri === null) { + return $this->getDownloadURI(); + } + return $uri; + } else { + return $this->getDownloadURI(); + } + } + public function getInfoURI() { return '/file/info/'.$this->getPHID().'/'; } diff --git a/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php b/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php --- a/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php +++ b/src/applications/phragment/conduit/PhragmentQueryFragmentsConduitAPIMethod.php @@ -74,7 +74,7 @@ 'path' => $cpath, 'hash' => $file->getContentHash(), 'version' => $child->getLatestVersion()->getSequence(), - 'uri' => $file->getViewURI()); + 'uri' => $file->getMostDirectURI()); } $results[$path] = $result; }