diff --git a/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php b/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php --- a/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php +++ b/src/applications/auth/controller/PhabricatorAuthSSHKeyGenerateController.php @@ -28,7 +28,7 @@ $private_key, array( 'name' => $default_name.'.key', - 'ttl' => time() + (60 * 10), + 'ttl.relative' => phutil_units('10 minutes in seconds'), 'viewPolicy' => $viewer->getPHID(), )); diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -357,7 +357,7 @@ array( 'name' => $changeset->getFilename(), 'mime-type' => 'text/plain', - 'ttl' => phutil_units('24 hours in seconds'), + 'ttl.relative' => phutil_units('24 hours in seconds'), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, )); diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -893,7 +893,7 @@ $raw_diff, array( 'name' => $file_name, - 'ttl' => (60 * 60 * 24), + 'ttl.relative' => phutil_units('24 hours in seconds'), 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, )); diff --git a/src/applications/diffusion/query/DiffusionFileFutureQuery.php b/src/applications/diffusion/query/DiffusionFileFutureQuery.php --- a/src/applications/diffusion/query/DiffusionFileFutureQuery.php +++ b/src/applications/diffusion/query/DiffusionFileFutureQuery.php @@ -93,7 +93,7 @@ $drequest = $this->getRequest(); $name = basename($drequest->getPath()); - $ttl = PhabricatorTime::getNow() + phutil_units('48 hours in seconds'); + $relative_ttl = phutil_units('48 hours in seconds'); try { $threshold = PhabricatorFileStorageEngine::getChunkThreshold(); @@ -101,7 +101,7 @@ $source = id(new PhabricatorExecFutureFileUploadSource()) ->setName($name) - ->setTTL($ttl) + ->setRelativeTTL($relative_ttl) ->setViewPolicy(PhabricatorPolicies::POLICY_NOONE) ->setExecFuture($future); diff --git a/src/applications/files/PhabricatorImageTransformer.php b/src/applications/files/PhabricatorImageTransformer.php --- a/src/applications/files/PhabricatorImageTransformer.php +++ b/src/applications/files/PhabricatorImageTransformer.php @@ -15,7 +15,7 @@ $image, array( 'name' => 'meme-'.$file->getName(), - 'ttl' => time() + 60 * 60 * 24, + 'ttl.relative' => phutil_units('24 hours in seconds'), 'canCDN' => true, )); } diff --git a/src/applications/files/conduit/FileAllocateConduitAPIMethod.php b/src/applications/files/conduit/FileAllocateConduitAPIMethod.php --- a/src/applications/files/conduit/FileAllocateConduitAPIMethod.php +++ b/src/applications/files/conduit/FileAllocateConduitAPIMethod.php @@ -42,7 +42,7 @@ $ttl = $request->getValue('deleteAfterEpoch'); if ($ttl) { - $properties['ttl'] = $ttl; + $properties['ttl.absolute'] = $ttl; } $file = null; diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php --- a/src/applications/files/controller/PhabricatorFileInfoController.php +++ b/src/applications/files/controller/PhabricatorFileInfoController.php @@ -212,6 +212,18 @@ pht('Mime Type'), $file->getMimeType()); + $ttl = $file->getTtl(); + if ($ttl) { + $delta = $ttl - PhabricatorTime::getNow(); + + $finfo->addProperty( + pht('Expires'), + pht( + '%s (%s)', + phabricator_datetime($ttl, $viewer), + phutil_format_relative_time_detailed($delta))); + } + $width = $file->getImageWidth(); if ($width) { $finfo->addProperty( 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 @@ -9,10 +9,13 @@ * * | name | Human readable filename. * | authorPHID | User PHID of uploader. - * | ttl | Temporary file lifetime, in seconds. + * | ttl.absolute | Temporary file lifetime as an epoch timestamp. + * | ttl.relative | Temporary file lifetime, relative to now, in seconds. * | viewPolicy | File visibility policy. * | isExplicitUpload | Used to show users files they explicitly uploaded. * | canCDN | Allows the file to be cached and delivered over a CDN. + * | profile | Marks the file as a profile image. + * | format | Internal encoding format. * | mime-type | Optional, explicit file MIME type. * | builtin | Optional filename, identifies this as a builtin. * @@ -1110,7 +1113,7 @@ $params = array( 'name' => $builtin->getBuiltinDisplayName(), - 'ttl' => time() + (60 * 60 * 24 * 7), + 'ttl.relative' => phutil_units('7 days in seconds'), 'canCDN' => true, 'builtin' => $key, ); @@ -1280,14 +1283,68 @@ * @return this */ private function readPropertiesFromParameters(array $params) { + PhutilTypeSpec::checkMap( + $params, + array( + 'name' => 'optional string', + 'authorPHID' => 'optional string', + 'ttl.relative' => 'optional int', + 'ttl.absolute' => 'optional int', + 'viewPolicy' => 'optional string', + 'isExplicitUpload' => 'optional bool', + 'canCDN' => 'optional bool', + 'profile' => 'optional bool', + 'format' => 'optional string|PhabricatorFileStorageFormat', + 'mime-type' => 'optional string', + 'builtin' => 'optional string', + 'storageEngines' => 'optional list', + )); + $file_name = idx($params, 'name'); $this->setName($file_name); $author_phid = idx($params, 'authorPHID'); $this->setAuthorPHID($author_phid); - $file_ttl = idx($params, 'ttl'); - $this->setTtl($file_ttl); + $absolute_ttl = idx($params, 'ttl.absolute'); + $relative_ttl = idx($params, 'ttl.relative'); + if ($absolute_ttl !== null && $relative_ttl !== null) { + throw new Exception( + pht( + 'Specify an absolute TTL or a relative TTL, but not both.')); + } else if ($absolute_ttl !== null) { + if ($absolute_ttl < PhabricatorTime::getNow()) { + throw new Exception( + pht( + 'Absolute TTL must be in the present or future, but TTL "%s" '. + 'is in the past.', + $absolute_ttl)); + } + + $this->setTtl($absolute_ttl); + } else if ($relative_ttl !== null) { + if ($relative_ttl < 0) { + throw new Exception( + pht( + 'Relative TTL must be zero or more seconds, but "%s" is '. + 'negative.', + $relative_ttl)); + } + + $max_relative = phutil_units('365 days in seconds'); + if ($relative_ttl > $max_relative) { + throw new Exception( + pht( + 'Relative TTL must not be more than "%s" seconds, but TTL '. + '"%s" was specified.', + $max_relative, + $relative_ttl)); + } + + $absolute_ttl = PhabricatorTime::getNow() + $relative_ttl; + + $this->setTtl($absolute_ttl); + } $view_policy = idx($params, 'viewPolicy'); if ($view_policy) { diff --git a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php --- a/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php +++ b/src/applications/files/storage/__tests__/PhabricatorFileTestCase.php @@ -370,11 +370,11 @@ $data = Filesystem::readRandomCharacters(64); - $ttl = (time() + 60 * 60 * 24); + $ttl = (PhabricatorTime::getNow() + phutil_units('24 hours in seconds')); $params = array( 'name' => 'test.dat', - 'ttl' => ($ttl), + 'ttl.absolute' => $ttl, 'storageEngines' => array( $engine, ), diff --git a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php --- a/src/applications/files/uploadsource/PhabricatorFileUploadSource.php +++ b/src/applications/files/uploadsource/PhabricatorFileUploadSource.php @@ -4,7 +4,7 @@ extends Phobject { private $name; - private $ttl; + private $relativeTTL; private $viewPolicy; private $rope; @@ -22,13 +22,13 @@ return $this->name; } - public function setTTL($ttl) { - $this->ttl = $ttl; + public function setRelativeTTL($relative_ttl) { + $this->relativeTTL = $relative_ttl; return $this; } - public function getTTL() { - return $this->ttl; + public function getRelativeTTL() { + return $this->relativeTTL; } public function setViewPolicy($view_policy) { @@ -214,7 +214,7 @@ private function getNewFileParameters() { return array( 'name' => $this->getName(), - 'ttl' => $this->getTTL(), + 'ttl.relative' => $this->getRelativeTTL(), 'viewPolicy' => $this->getViewPolicy(), ); } diff --git a/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php b/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php --- a/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php +++ b/src/applications/phragment/conduit/PhragmentGetPatchConduitAPIMethod.php @@ -178,7 +178,7 @@ $data, array( 'name' => 'patch.dmp', - 'ttl' => time() + 60 * 60 * 24, + 'ttl.relative' => phutil_units('24 hours in seconds'), )); $patches[$key]['patchURI'] = $file->getDownloadURI(); } diff --git a/src/applications/phragment/controller/PhragmentPatchController.php b/src/applications/phragment/controller/PhragmentPatchController.php --- a/src/applications/phragment/controller/PhragmentPatchController.php +++ b/src/applications/phragment/controller/PhragmentPatchController.php @@ -83,7 +83,7 @@ array( 'name' => $name, 'mime-type' => 'text/plain', - 'ttl' => time() + 60 * 60 * 24, + 'ttl.relative' => phutil_units('24 hours in seconds'), )); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); diff --git a/src/applications/phragment/controller/PhragmentZIPController.php b/src/applications/phragment/controller/PhragmentZIPController.php --- a/src/applications/phragment/controller/PhragmentZIPController.php +++ b/src/applications/phragment/controller/PhragmentZIPController.php @@ -104,7 +104,7 @@ $data, array( 'name' => $zip_name, - 'ttl' => time() + 60 * 60 * 24, + 'ttl.relative' => phutil_units('24 hours in seconds'), )); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();