mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-22 06:42:42 +01:00
Add a PDF document "rendering" engine
Summary: Depends on D19251. Ref T13105. This adds rendering engine support for PDFs. It doesn't actually render them, it just renders a link which you can click to view them in a new window. This is much easier than actually rendering them inline and at least 95% as good most of the time (and probably more-than-100%-as-good some of the time). This makes PDF a viewable MIME type by default and adds a narrow CSP exception for it. See also T13112. Test Plan: - Viewed PDFs in Files, got a link to view them in a new tab. - Clicked the link in Safari, Chrome, and Firefox; got inline PDFs. - Verified primary CSP is still `object-src 'none'` with `curl ...`. - Interacted with the vanilla lightbox element to check that it still works. Maniphest Tasks: T13105 Differential Revision: https://secure.phabricator.com/D19252
This commit is contained in:
parent
8b658706a8
commit
fb4ce851c4
10 changed files with 154 additions and 30 deletions
|
@ -9,7 +9,7 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => 'e68cf1fa',
|
||||
'conpherence.pkg.js' => '15191c65',
|
||||
'core.pkg.css' => 'afe29a6c',
|
||||
'core.pkg.css' => '2d73b2f3',
|
||||
'core.pkg.js' => 'b9b4a943',
|
||||
'differential.pkg.css' => '113e692c',
|
||||
'differential.pkg.js' => 'f6d809c0',
|
||||
|
@ -112,7 +112,7 @@ return array(
|
|||
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
|
||||
'rsrc/css/application/uiexample/example.css' => '528b19de',
|
||||
'rsrc/css/core/core.css' => '62fa3ace',
|
||||
'rsrc/css/core/remarkup.css' => '97dc3523',
|
||||
'rsrc/css/core/remarkup.css' => 'b375546d',
|
||||
'rsrc/css/core/syntax.css' => 'cae95e89',
|
||||
'rsrc/css/core/z-index.css' => '9d8f7c4b',
|
||||
'rsrc/css/diviner/diviner-shared.css' => '896f1d43',
|
||||
|
@ -168,7 +168,7 @@ return array(
|
|||
'rsrc/css/phui/phui-object-box.css' => '9cff003c',
|
||||
'rsrc/css/phui/phui-pager.css' => 'edcbc226',
|
||||
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
|
||||
'rsrc/css/phui/phui-property-list-view.css' => '94a14381',
|
||||
'rsrc/css/phui/phui-property-list-view.css' => '47018d3c',
|
||||
'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863',
|
||||
'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892',
|
||||
'rsrc/css/phui/phui-spacing.css' => '042804d6',
|
||||
|
@ -780,7 +780,7 @@ return array(
|
|||
'phabricator-object-selector-css' => '85ee8ce6',
|
||||
'phabricator-phtize' => 'd254d646',
|
||||
'phabricator-prefab' => '77b0ae28',
|
||||
'phabricator-remarkup-css' => '97dc3523',
|
||||
'phabricator-remarkup-css' => 'b375546d',
|
||||
'phabricator-search-results-css' => '505dd8cf',
|
||||
'phabricator-shaped-request' => '7cbe244b',
|
||||
'phabricator-slowvote-css' => 'a94b7230',
|
||||
|
@ -850,7 +850,7 @@ return array(
|
|||
'phui-oi-simple-ui-css' => 'a8beebea',
|
||||
'phui-pager-css' => 'edcbc226',
|
||||
'phui-pinboard-view-css' => '2495140e',
|
||||
'phui-property-list-view-css' => '94a14381',
|
||||
'phui-property-list-view-css' => '47018d3c',
|
||||
'phui-remarkup-preview-css' => '54a34863',
|
||||
'phui-segment-bar-view-css' => 'b1d1b892',
|
||||
'phui-spacing-css' => '042804d6',
|
||||
|
|
|
@ -3519,6 +3519,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php',
|
||||
'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php',
|
||||
'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php',
|
||||
'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php',
|
||||
'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php',
|
||||
'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php',
|
||||
'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php',
|
||||
|
@ -9169,6 +9170,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
|
||||
'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField',
|
||||
'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine',
|
||||
'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorPHID' => 'Phobject',
|
||||
'PhabricatorPHIDConstants' => 'Phobject',
|
||||
|
|
|
@ -28,6 +28,7 @@ abstract class AphrontResponse extends Phobject {
|
|||
'connect-src' => array(),
|
||||
'frame-src' => array(),
|
||||
'form-action' => array(),
|
||||
'object-src' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -163,8 +164,10 @@ abstract class AphrontResponse extends Phobject {
|
|||
$csp[] = "frame-ancestors 'none'";
|
||||
}
|
||||
|
||||
// Block relics of the old world: Flash, Java applets, and so on.
|
||||
$csp[] = "object-src 'none'";
|
||||
// Block relics of the old world: Flash, Java applets, and so on. Note
|
||||
// that Chrome prevents the user from viewing PDF documents if they are
|
||||
// served with a policy which excludes the domain they are served from.
|
||||
$csp[] = $this->newContentSecurityPolicy('object-src', "'none'");
|
||||
|
||||
// Don't allow forms to submit offsite.
|
||||
|
||||
|
|
|
@ -45,6 +45,8 @@ final class PhabricatorFilesConfigOptions
|
|||
'video/ogg' => 'video/ogg',
|
||||
'video/webm' => 'video/webm',
|
||||
'video/quicktime' => 'video/quicktime',
|
||||
|
||||
'application/pdf' => 'application/pdf',
|
||||
);
|
||||
|
||||
$image_default = array(
|
||||
|
|
|
@ -73,11 +73,14 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
|
|||
list($begin, $end) = $response->parseHTTPRange($range);
|
||||
}
|
||||
|
||||
$is_viewable = $file->isViewableInBrowser();
|
||||
if (!$file->isViewableInBrowser()) {
|
||||
$is_download = true;
|
||||
}
|
||||
|
||||
$request_type = $request->getHTTPHeader('X-Phabricator-Request-Type');
|
||||
$is_lfs = ($request_type == 'git-lfs');
|
||||
|
||||
if ($is_viewable && !$is_download) {
|
||||
if (!$is_download) {
|
||||
$response->setMimeType($file->getViewableMimeType());
|
||||
} else {
|
||||
$is_post = $request->isHTTPPost();
|
||||
|
@ -109,6 +112,19 @@ final class PhabricatorFileDataController extends PhabricatorFileController {
|
|||
$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)
|
||||
->setQueryParams(array());
|
||||
|
||||
$response->addContentSecurityPolicyURI(
|
||||
'object-src',
|
||||
(string)$request_uri);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPDFDocumentEngine
|
||||
extends PhabricatorDocumentEngine {
|
||||
|
||||
const ENGINEKEY = 'pdf';
|
||||
|
||||
public function getViewAsLabel(PhabricatorDocumentRef $ref) {
|
||||
return pht('View as PDF');
|
||||
}
|
||||
|
||||
protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) {
|
||||
return 'fa-file-pdf-o';
|
||||
}
|
||||
|
||||
protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
|
||||
// Since we just render a link to the document anyway, we don't need to
|
||||
// check anything fancy in config to see if the MIME type is actually
|
||||
// viewable.
|
||||
|
||||
return $ref->hasAnyMimeType(
|
||||
array(
|
||||
'application/pdf',
|
||||
));
|
||||
}
|
||||
|
||||
protected function newDocumentContent(PhabricatorDocumentRef $ref) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$file = $ref->getFile();
|
||||
if ($file) {
|
||||
$source_uri = $file->getViewURI();
|
||||
} else {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
$name = $ref->getName();
|
||||
$length = $ref->getByteLength();
|
||||
|
||||
$link = id(new PhabricatorFileLinkView())
|
||||
->setViewer($viewer)
|
||||
->setFileName($name)
|
||||
->setFileViewURI($source_uri)
|
||||
->setFileViewable(true)
|
||||
->setFileSize(phutil_format_bytes($length));
|
||||
|
||||
$container = phutil_tag(
|
||||
'div',
|
||||
array(
|
||||
'class' => 'document-engine-pdf',
|
||||
),
|
||||
$link);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
}
|
|
@ -930,6 +930,19 @@ final class PhabricatorFile extends PhabricatorFileDAO
|
|||
return idx($mime_map, $mime_type);
|
||||
}
|
||||
|
||||
public function isPDF() {
|
||||
if (!$this->isViewableInBrowser()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$mime_map = array(
|
||||
'application/pdf' => 'application/pdf',
|
||||
);
|
||||
|
||||
$mime_type = $this->getMimeType();
|
||||
return idx($mime_map, $mime_type);
|
||||
}
|
||||
|
||||
public function isTransformableImage() {
|
||||
// NOTE: The way the 'gd' extension works in PHP is that you can install it
|
||||
// with support for only some file types, so it might be able to handle
|
||||
|
|
|
@ -101,26 +101,39 @@ final class PhabricatorFileLinkView extends AphrontTagView {
|
|||
}
|
||||
|
||||
protected function getTagName() {
|
||||
return 'div';
|
||||
if ($this->getFileDownloadURI()) {
|
||||
return 'div';
|
||||
} else {
|
||||
return 'a';
|
||||
}
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
$mustcapture = true;
|
||||
$sigil = 'lightboxable';
|
||||
$meta = $this->getMeta();
|
||||
|
||||
$class = 'phabricator-remarkup-embed-layout-link';
|
||||
if ($this->getCustomClass()) {
|
||||
$class = $this->getCustomClass();
|
||||
}
|
||||
|
||||
return array(
|
||||
'href' => $this->getFileViewURI(),
|
||||
'class' => $class,
|
||||
'sigil' => $sigil,
|
||||
'meta' => $meta,
|
||||
'mustcapture' => $mustcapture,
|
||||
$attributes = array(
|
||||
'href' => $this->getFileViewURI(),
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
'class' => $class,
|
||||
);
|
||||
|
||||
if ($this->getFilePHID()) {
|
||||
$mustcapture = true;
|
||||
$sigil = 'lightboxable';
|
||||
$meta = $this->getMeta();
|
||||
|
||||
$attributes += array(
|
||||
'sigil' => $sigil,
|
||||
'meta' => $meta,
|
||||
'mustcapture' => $mustcapture,
|
||||
);
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
protected function getTagContent() {
|
||||
|
@ -131,16 +144,21 @@ final class PhabricatorFileLinkView extends AphrontTagView {
|
|||
->setIcon($this->getFileIcon())
|
||||
->addClass('phabricator-remarkup-embed-layout-icon');
|
||||
|
||||
$dl_icon = id(new PHUIIconView())
|
||||
->setIcon('fa-download');
|
||||
$download_link = null;
|
||||
|
||||
$download_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'phabricator-remarkup-embed-layout-download',
|
||||
'href' => $this->getFileDownloadURI(),
|
||||
),
|
||||
pht('Download'));
|
||||
$download_uri = $this->getFileDownloadURI();
|
||||
if ($download_uri) {
|
||||
$dl_icon = id(new PHUIIconView())
|
||||
->setIcon('fa-download');
|
||||
|
||||
$download_link = phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'class' => 'phabricator-remarkup-embed-layout-download',
|
||||
'href' => $download_uri,
|
||||
),
|
||||
pht('Download'));
|
||||
}
|
||||
|
||||
$info = phutil_tag(
|
||||
'span',
|
||||
|
|
|
@ -405,8 +405,9 @@ video.phabricator-media {
|
|||
color: {$blacktext};
|
||||
min-width: 256px;
|
||||
position: relative;
|
||||
/*height: 22px;*/
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.phabricator-remarkup-embed-layout-icon {
|
||||
|
@ -426,6 +427,9 @@ video.phabricator-media {
|
|||
.phabricator-remarkup-embed-layout-link:hover {
|
||||
border-color: {$violet};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.device-desktop .phabricator-remarkup-embed-layout-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -248,3 +248,12 @@ div.phui-property-list-stacked .phui-property-list-properties
|
|||
.document-engine-remarkup {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.document-engine-pdf {
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.document-engine-pdf .phabricator-remarkup-embed-layout-link {
|
||||
text-align: left;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue