Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2890973
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
9 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/applications/files/controller/PhabricatorFileDataController.php b/src/applications/files/controller/PhabricatorFileDataController.php
index 8189b30a21..0cc12a85ec 100644
--- a/src/applications/files/controller/PhabricatorFileDataController.php
+++ b/src/applications/files/controller/PhabricatorFileDataController.php
@@ -1,278 +1,278 @@
<?php
final class PhabricatorFileDataController extends PhabricatorFileController {
private $phid;
private $key;
private $file;
public function shouldRequireLogin() {
return false;
}
public function shouldAllowPartialSessions() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$this->phid = $request->getURIData('phid');
$this->key = $request->getURIData('key');
$alt = PhabricatorEnv::getEnvConfig('security.alternate-file-domain');
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
$alt_uri = new PhutilURI($alt);
$alt_domain = $alt_uri->getDomain();
$req_domain = $request->getHost();
$main_domain = id(new PhutilURI($base_uri))->getDomain();
$request_kind = $request->getURIData('kind');
$is_download = ($request_kind === 'download');
if (!strlen($alt) || $main_domain == $alt_domain) {
// No alternate domain.
$should_redirect = false;
$is_alternate_domain = false;
} else if ($req_domain != $alt_domain) {
// Alternate domain, but this request is on the main domain.
$should_redirect = true;
$is_alternate_domain = false;
} else {
// Alternate domain, and on the alternate domain.
$should_redirect = false;
$is_alternate_domain = true;
}
$response = $this->loadFile();
if ($response) {
return $response;
}
$file = $this->getFile();
if ($should_redirect) {
return id(new AphrontRedirectResponse())
->setIsExternal(true)
->setURI($file->getCDNURI($request_kind));
}
$response = new AphrontFileResponse();
$response->setCacheDurationInSeconds(60 * 60 * 24 * 30);
$response->setCanCDN($file->getCanCDN());
$begin = null;
$end = null;
// NOTE: It's important to accept "Range" requests when playing audio.
// If we don't, Safari has difficulty figuring out how long sounds are
// and glitches when trying to loop them. In particular, Safari sends
// an initial request for bytes 0-1 of the audio file, and things go south
// if we can't respond with a 206 Partial Content.
$range = $request->getHTTPHeader('range');
- if (strlen($range)) {
+ if (phutil_nonempty_string($range)) {
list($begin, $end) = $response->parseHTTPRange($range);
}
if (!$file->isViewableInBrowser()) {
$is_download = true;
}
$request_type = $request->getHTTPHeader('X-Phabricator-Request-Type');
$is_lfs = ($request_type == 'git-lfs');
if (!$is_download) {
$response->setMimeType($file->getViewableMimeType());
} else {
$is_post = $request->isHTTPPost();
$is_public = !$viewer->isLoggedIn();
// NOTE: Require POST to download files from the primary domain. If the
// request is not a POST request but arrives on the primary domain, we
// render a confirmation dialog. For discussion, see T13094.
// There are two exceptions to this rule:
// Git LFS requests can download with GET. This is safe (Git LFS won't
// execute files it downloads) and necessary to support Git LFS.
// Requests with no credentials may also download with GET. This
// primarily supports downloading files with `arc download` or other
// API clients. This is only "mostly" safe: if you aren't logged in, you
// are likely immune to XSS and CSRF. However, an attacker may still be
// able to set cookies on this domain (for example, to fixate your
// session). For now, we accept these risks because users running
// Phabricator in this mode are knowingly accepting a security risk
// against setup advice, and there's significant value in having
// API development against test and production installs work the same
// way.
$is_safe = ($is_alternate_domain || $is_post || $is_lfs || $is_public);
if (!$is_safe) {
return $this->newDialog()
->setSubmitURI($file->getDownloadURI())
->setTitle(pht('Download File'))
->appendParagraph(
pht(
'Download file %s (%s)?',
phutil_tag('strong', array(), $file->getName()),
phutil_format_bytes($file->getByteSize())))
->addCancelButton($file->getURI())
->addSubmitButton(pht('Download File'));
}
$response->setMimeType($file->getMimeType());
$response->setDownload($file->getName());
}
$iterator = $file->getFileDataIterator($begin, $end);
$response->setContentLength($file->getByteSize());
$response->setContentIterator($iterator);
// In Chrome, we must permit this domain in "object-src" CSP when serving a
// PDF or the browser will refuse to render it.
if (!$is_download && $file->isPDF()) {
$request_uri = id(clone $request->getAbsoluteRequestURI())
->setPath(null)
->setFragment(null)
->removeAllQueryParams();
$response->addContentSecurityPolicyURI(
'object-src',
(string)$request_uri);
}
if ($this->shouldCompressFileDataResponse($file)) {
$response->setCompressResponse(true);
}
return $response;
}
private function loadFile() {
// Access to files is provided by knowledge of a per-file secret key in
// the URI. Knowledge of this secret is sufficient to retrieve the file.
// For some requests, we also have a valid viewer. However, for many
// requests (like alternate domain requests or Git LFS requests) we will
// not. Even if we do have a valid viewer, use the omnipotent viewer to
// make this logic simpler and more consistent.
// Beyond making the policy check itself more consistent, this also makes
// sure we're consistent about returning HTTP 404 on bad requests instead
// of serving HTTP 200 with a login page, which can mislead some clients.
$viewer = PhabricatorUser::getOmnipotentUser();
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($this->phid))
->withIsDeleted(false)
->executeOne();
if (!$file) {
return new Aphront404Response();
}
// We may be on the CDN domain, so we need to use a fully-qualified URI
// here to make sure we end up back on the main domain.
$info_uri = PhabricatorEnv::getURI($file->getInfoURI());
if (!$file->validateSecretKey($this->key)) {
$dialog = $this->newDialog()
->setTitle(pht('Invalid Authorization'))
->appendParagraph(
pht(
'The link you followed to access this file is no longer '.
'valid. The visibility of the file may have changed after '.
'the link was generated.'))
->appendParagraph(
pht(
'You can continue to the file detail page to get more '.
'information and attempt to access the file.'))
->addCancelButton($info_uri, pht('Continue'));
return id(new AphrontDialogResponse())
->setDialog($dialog)
->setHTTPResponseCode(404);
}
if ($file->getIsPartial()) {
$dialog = $this->newDialog()
->setTitle(pht('Partial Upload'))
->appendParagraph(
pht(
'This file has only been partially uploaded. It must be '.
'uploaded completely before you can download it.'))
->appendParagraph(
pht(
'You can continue to the file detail page to monitor the '.
'upload progress of the file.'))
->addCancelButton($info_uri, pht('Continue'));
return id(new AphrontDialogResponse())
->setDialog($dialog)
->setHTTPResponseCode(404);
}
$this->file = $file;
return null;
}
private function getFile() {
if (!$this->file) {
throw new PhutilInvalidStateException('loadFile');
}
return $this->file;
}
private function shouldCompressFileDataResponse(PhabricatorFile $file) {
// If the client sends "Accept-Encoding: gzip", we have the option of
// compressing the response.
// We generally expect this to be a good idea if the file compresses well,
// but maybe not such a great idea if the file is already compressed (like
// an image or video) or compresses poorly: the CPU cost of compressing and
// decompressing the stream may exceed the bandwidth savings during
// transfer.
// Ideally, we'd probably make this decision by compressing files when
// they are uploaded, storing the compressed size, and then doing a test
// here using the compression savings and estimated transfer speed.
// For now, just guess that we shouldn't compress images or videos or
// files that look like they are already compressed, and should compress
// everything else.
if ($file->isViewableImage()) {
return false;
}
if ($file->isAudio()) {
return false;
}
if ($file->isVideo()) {
return false;
}
$compressed_types = array(
'application/x-gzip',
'application/x-compress',
'application/x-compressed',
'application/x-zip-compressed',
'application/zip',
);
$compressed_types = array_fuse($compressed_types);
$mime_type = $file->getMimeType();
if (isset($compressed_types[$mime_type])) {
return false;
}
return true;
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 14:25 (3 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1125483
Default Alt Text
(9 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment