@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+154 -30
+5 -5
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => 'e68cf1fa', 11 11 'conpherence.pkg.js' => '15191c65', 12 - 'core.pkg.css' => 'afe29a6c', 12 + 'core.pkg.css' => '2d73b2f3', 13 13 'core.pkg.js' => 'b9b4a943', 14 14 'differential.pkg.css' => '113e692c', 15 15 'differential.pkg.js' => 'f6d809c0', ··· 112 112 'rsrc/css/application/tokens/tokens.css' => '3d0f239e', 113 113 'rsrc/css/application/uiexample/example.css' => '528b19de', 114 114 'rsrc/css/core/core.css' => '62fa3ace', 115 - 'rsrc/css/core/remarkup.css' => '97dc3523', 115 + 'rsrc/css/core/remarkup.css' => 'b375546d', 116 116 'rsrc/css/core/syntax.css' => 'cae95e89', 117 117 'rsrc/css/core/z-index.css' => '9d8f7c4b', 118 118 'rsrc/css/diviner/diviner-shared.css' => '896f1d43', ··· 168 168 'rsrc/css/phui/phui-object-box.css' => '9cff003c', 169 169 'rsrc/css/phui/phui-pager.css' => 'edcbc226', 170 170 'rsrc/css/phui/phui-pinboard-view.css' => '2495140e', 171 - 'rsrc/css/phui/phui-property-list-view.css' => '94a14381', 171 + 'rsrc/css/phui/phui-property-list-view.css' => '47018d3c', 172 172 'rsrc/css/phui/phui-remarkup-preview.css' => '54a34863', 173 173 'rsrc/css/phui/phui-segment-bar-view.css' => 'b1d1b892', 174 174 'rsrc/css/phui/phui-spacing.css' => '042804d6', ··· 780 780 'phabricator-object-selector-css' => '85ee8ce6', 781 781 'phabricator-phtize' => 'd254d646', 782 782 'phabricator-prefab' => '77b0ae28', 783 - 'phabricator-remarkup-css' => '97dc3523', 783 + 'phabricator-remarkup-css' => 'b375546d', 784 784 'phabricator-search-results-css' => '505dd8cf', 785 785 'phabricator-shaped-request' => '7cbe244b', 786 786 'phabricator-slowvote-css' => 'a94b7230', ··· 850 850 'phui-oi-simple-ui-css' => 'a8beebea', 851 851 'phui-pager-css' => 'edcbc226', 852 852 'phui-pinboard-view-css' => '2495140e', 853 - 'phui-property-list-view-css' => '94a14381', 853 + 'phui-property-list-view-css' => '47018d3c', 854 854 'phui-remarkup-preview-css' => '54a34863', 855 855 'phui-segment-bar-view-css' => 'b1d1b892', 856 856 'phui-spacing-css' => '042804d6',
+2
src/__phutil_library_map__.php
··· 3519 3519 'PhabricatorOwnersPathsSearchEngineAttachment' => 'applications/owners/engineextension/PhabricatorOwnersPathsSearchEngineAttachment.php', 3520 3520 'PhabricatorOwnersSchemaSpec' => 'applications/owners/storage/PhabricatorOwnersSchemaSpec.php', 3521 3521 'PhabricatorOwnersSearchField' => 'applications/owners/searchfield/PhabricatorOwnersSearchField.php', 3522 + 'PhabricatorPDFDocumentEngine' => 'applications/files/document/PhabricatorPDFDocumentEngine.php', 3522 3523 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 3523 3524 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 3524 3525 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', ··· 9169 9170 'PhabricatorOwnersPathsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 9170 9171 'PhabricatorOwnersSchemaSpec' => 'PhabricatorConfigSchemaSpec', 9171 9172 'PhabricatorOwnersSearchField' => 'PhabricatorSearchTokenizerField', 9173 + 'PhabricatorPDFDocumentEngine' => 'PhabricatorDocumentEngine', 9172 9174 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 9173 9175 'PhabricatorPHID' => 'Phobject', 9174 9176 'PhabricatorPHIDConstants' => 'Phobject',
+5 -2
src/aphront/response/AphrontResponse.php
··· 28 28 'connect-src' => array(), 29 29 'frame-src' => array(), 30 30 'form-action' => array(), 31 + 'object-src' => array(), 31 32 ); 32 33 } 33 34 ··· 163 164 $csp[] = "frame-ancestors 'none'"; 164 165 } 165 166 166 - // Block relics of the old world: Flash, Java applets, and so on. 167 - $csp[] = "object-src 'none'"; 167 + // Block relics of the old world: Flash, Java applets, and so on. Note 168 + // that Chrome prevents the user from viewing PDF documents if they are 169 + // served with a policy which excludes the domain they are served from. 170 + $csp[] = $this->newContentSecurityPolicy('object-src', "'none'"); 168 171 169 172 // Don't allow forms to submit offsite. 170 173
+2
src/applications/files/config/PhabricatorFilesConfigOptions.php
··· 45 45 'video/ogg' => 'video/ogg', 46 46 'video/webm' => 'video/webm', 47 47 'video/quicktime' => 'video/quicktime', 48 + 49 + 'application/pdf' => 'application/pdf', 48 50 ); 49 51 50 52 $image_default = array(
+18 -2
src/applications/files/controller/PhabricatorFileDataController.php
··· 73 73 list($begin, $end) = $response->parseHTTPRange($range); 74 74 } 75 75 76 - $is_viewable = $file->isViewableInBrowser(); 76 + if (!$file->isViewableInBrowser()) { 77 + $is_download = true; 78 + } 79 + 77 80 $request_type = $request->getHTTPHeader('X-Phabricator-Request-Type'); 78 81 $is_lfs = ($request_type == 'git-lfs'); 79 82 80 - if ($is_viewable && !$is_download) { 83 + if (!$is_download) { 81 84 $response->setMimeType($file->getViewableMimeType()); 82 85 } else { 83 86 $is_post = $request->isHTTPPost(); ··· 108 111 109 112 $response->setContentLength($file->getByteSize()); 110 113 $response->setContentIterator($iterator); 114 + 115 + // In Chrome, we must permit this domain in "object-src" CSP when serving a 116 + // PDF or the browser will refuse to render it. 117 + if (!$is_download && $file->isPDF()) { 118 + $request_uri = id(clone $request->getAbsoluteRequestURI()) 119 + ->setPath(null) 120 + ->setFragment(null) 121 + ->setQueryParams(array()); 122 + 123 + $response->addContentSecurityPolicyURI( 124 + 'object-src', 125 + (string)$request_uri); 126 + } 111 127 112 128 return $response; 113 129 }
+57
src/applications/files/document/PhabricatorPDFDocumentEngine.php
··· 1 + <?php 2 + 3 + final class PhabricatorPDFDocumentEngine 4 + extends PhabricatorDocumentEngine { 5 + 6 + const ENGINEKEY = 'pdf'; 7 + 8 + public function getViewAsLabel(PhabricatorDocumentRef $ref) { 9 + return pht('View as PDF'); 10 + } 11 + 12 + protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) { 13 + return 'fa-file-pdf-o'; 14 + } 15 + 16 + protected function canRenderDocumentType(PhabricatorDocumentRef $ref) { 17 + // Since we just render a link to the document anyway, we don't need to 18 + // check anything fancy in config to see if the MIME type is actually 19 + // viewable. 20 + 21 + return $ref->hasAnyMimeType( 22 + array( 23 + 'application/pdf', 24 + )); 25 + } 26 + 27 + protected function newDocumentContent(PhabricatorDocumentRef $ref) { 28 + $viewer = $this->getViewer(); 29 + 30 + $file = $ref->getFile(); 31 + if ($file) { 32 + $source_uri = $file->getViewURI(); 33 + } else { 34 + throw new PhutilMethodNotImplementedException(); 35 + } 36 + 37 + $name = $ref->getName(); 38 + $length = $ref->getByteLength(); 39 + 40 + $link = id(new PhabricatorFileLinkView()) 41 + ->setViewer($viewer) 42 + ->setFileName($name) 43 + ->setFileViewURI($source_uri) 44 + ->setFileViewable(true) 45 + ->setFileSize(phutil_format_bytes($length)); 46 + 47 + $container = phutil_tag( 48 + 'div', 49 + array( 50 + 'class' => 'document-engine-pdf', 51 + ), 52 + $link); 53 + 54 + return $container; 55 + } 56 + 57 + }
+13
src/applications/files/storage/PhabricatorFile.php
··· 930 930 return idx($mime_map, $mime_type); 931 931 } 932 932 933 + public function isPDF() { 934 + if (!$this->isViewableInBrowser()) { 935 + return false; 936 + } 937 + 938 + $mime_map = array( 939 + 'application/pdf' => 'application/pdf', 940 + ); 941 + 942 + $mime_type = $this->getMimeType(); 943 + return idx($mime_map, $mime_type); 944 + } 945 + 933 946 public function isTransformableImage() { 934 947 // NOTE: The way the 'gd' extension works in PHP is that you can install it 935 948 // with support for only some file types, so it might be able to handle
+38 -20
src/view/layout/PhabricatorFileLinkView.php
··· 101 101 } 102 102 103 103 protected function getTagName() { 104 - return 'div'; 104 + if ($this->getFileDownloadURI()) { 105 + return 'div'; 106 + } else { 107 + return 'a'; 108 + } 105 109 } 106 110 107 111 protected function getTagAttributes() { 108 - $mustcapture = true; 109 - $sigil = 'lightboxable'; 110 - $meta = $this->getMeta(); 111 - 112 112 $class = 'phabricator-remarkup-embed-layout-link'; 113 113 if ($this->getCustomClass()) { 114 114 $class = $this->getCustomClass(); 115 115 } 116 116 117 - return array( 118 - 'href' => $this->getFileViewURI(), 119 - 'class' => $class, 120 - 'sigil' => $sigil, 121 - 'meta' => $meta, 122 - 'mustcapture' => $mustcapture, 117 + $attributes = array( 118 + 'href' => $this->getFileViewURI(), 119 + 'target' => '_blank', 120 + 'rel' => 'noreferrer', 121 + 'class' => $class, 123 122 ); 123 + 124 + if ($this->getFilePHID()) { 125 + $mustcapture = true; 126 + $sigil = 'lightboxable'; 127 + $meta = $this->getMeta(); 128 + 129 + $attributes += array( 130 + 'sigil' => $sigil, 131 + 'meta' => $meta, 132 + 'mustcapture' => $mustcapture, 133 + ); 134 + } 135 + 136 + return $attributes; 124 137 } 125 138 126 139 protected function getTagContent() { ··· 131 144 ->setIcon($this->getFileIcon()) 132 145 ->addClass('phabricator-remarkup-embed-layout-icon'); 133 146 134 - $dl_icon = id(new PHUIIconView()) 135 - ->setIcon('fa-download'); 147 + $download_link = null; 148 + 149 + $download_uri = $this->getFileDownloadURI(); 150 + if ($download_uri) { 151 + $dl_icon = id(new PHUIIconView()) 152 + ->setIcon('fa-download'); 136 153 137 - $download_link = phutil_tag( 138 - 'a', 139 - array( 140 - 'class' => 'phabricator-remarkup-embed-layout-download', 141 - 'href' => $this->getFileDownloadURI(), 142 - ), 143 - pht('Download')); 154 + $download_link = phutil_tag( 155 + 'a', 156 + array( 157 + 'class' => 'phabricator-remarkup-embed-layout-download', 158 + 'href' => $download_uri, 159 + ), 160 + pht('Download')); 161 + } 144 162 145 163 $info = phutil_tag( 146 164 'span',
+5 -1
webroot/rsrc/css/core/remarkup.css
··· 405 405 color: {$blacktext}; 406 406 min-width: 256px; 407 407 position: relative; 408 - /*height: 22px;*/ 409 408 line-height: 20px; 409 + overflow: hidden; 410 + min-height: 38px; 410 411 } 411 412 412 413 .phabricator-remarkup-embed-layout-icon { ··· 426 427 .phabricator-remarkup-embed-layout-link:hover { 427 428 border-color: {$violet}; 428 429 cursor: pointer; 430 + } 431 + 432 + .device-desktop .phabricator-remarkup-embed-layout-link:hover { 429 433 text-decoration: none; 430 434 } 431 435
+9
webroot/rsrc/css/phui/phui-property-list-view.css
··· 248 248 .document-engine-remarkup { 249 249 margin: 20px; 250 250 } 251 + 252 + .document-engine-pdf { 253 + margin: 20px; 254 + text-align: center; 255 + } 256 + 257 + .document-engine-pdf .phabricator-remarkup-embed-layout-link { 258 + text-align: left; 259 + }