@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 filesize limits for document rendering engines and support partial/complete rendering

Summary:
Depends on D19238. Ref T13105. Give document engines some reasonable automatic support for degrading gracefully when someone tries to hexdump a 100MB file or similar.

Also, make "Video" sort above "Audio" for files which could be rendered either way.

Test Plan: Viewed audio, video, image, and other files. Adjusted limits and saw full, partial, and fallback/error rendering.

Maniphest Tasks: T13105

Differential Revision: https://secure.phabricator.com/D19239

+172 -27
+18 -18
resources/celerity/map.php
··· 9 9 'names' => array( 10 10 'conpherence.pkg.css' => 'e68cf1fa', 11 11 'conpherence.pkg.js' => '15191c65', 12 - 'core.pkg.css' => '97dc0e74', 13 - 'core.pkg.js' => '8581cd02', 12 + 'core.pkg.css' => '6da3c0e5', 13 + 'core.pkg.js' => '932d60d4', 14 14 'differential.pkg.css' => '113e692c', 15 15 'differential.pkg.js' => 'f6d809c0', 16 16 'diffusion.pkg.css' => 'a2d17c7d', ··· 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' => 'ef864066', 171 + 'rsrc/css/phui/phui-property-list-view.css' => '6ef560df', 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', ··· 392 392 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 393 393 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 394 394 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 395 - 'rsrc/js/application/files/behavior-document-engine.js' => 'f6d6f389', 395 + 'rsrc/js/application/files/behavior-document-engine.js' => '396ef112', 396 396 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 397 397 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 398 398 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', ··· 508 508 'rsrc/js/phui/behavior-phui-submenu.js' => 'a6f7a73b', 509 509 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 510 510 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 511 - 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', 511 + 'rsrc/js/phuix/PHUIXActionView.js' => 'ed18356a', 512 512 'rsrc/js/phuix/PHUIXAutocomplete.js' => '7fa5c915', 513 513 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 514 514 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', ··· 607 607 'javelin-behavior-diffusion-jump-to' => '73d09eef', 608 608 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 609 609 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 610 - 'javelin-behavior-document-engine' => 'f6d6f389', 610 + 'javelin-behavior-document-engine' => '396ef112', 611 611 'javelin-behavior-doorkeeper-tag' => '1db13e70', 612 612 'javelin-behavior-drydock-live-operation-status' => '901935ef', 613 613 'javelin-behavior-durable-column' => '2ae077e1', ··· 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' => 'ef864066', 853 + 'phui-property-list-view-css' => '6ef560df', 854 854 'phui-remarkup-preview-css' => '54a34863', 855 855 'phui-segment-bar-view-css' => 'b1d1b892', 856 856 'phui-spacing-css' => '042804d6', ··· 864 864 'phui-workcard-view-css' => 'cca5fa92', 865 865 'phui-workpanel-view-css' => 'a3a63478', 866 866 'phuix-action-list-view' => 'b5c256b8', 867 - 'phuix-action-view' => '442efd08', 867 + 'phuix-action-view' => 'ed18356a', 868 868 'phuix-autocomplete' => '7fa5c915', 869 869 'phuix-button-view' => '8a91e1ac', 870 870 'phuix-dropdown-menu' => '04b2ae03', ··· 1114 1114 'javelin-dom', 1115 1115 'javelin-vector', 1116 1116 ), 1117 + '396ef112' => array( 1118 + 'javelin-behavior', 1119 + 'javelin-dom', 1120 + 'javelin-stratcom', 1121 + ), 1117 1122 '3ab51e2c' => array( 1118 1123 'javelin-behavior', 1119 1124 'javelin-behavior-device', ··· 1183 1188 'javelin-stratcom', 1184 1189 'javelin-workflow', 1185 1190 'javelin-workboard-controller', 1186 - ), 1187 - '442efd08' => array( 1188 - 'javelin-install', 1189 - 'javelin-dom', 1190 - 'javelin-util', 1191 1191 ), 1192 1192 '44959b73' => array( 1193 1193 'javelin-util', ··· 2125 2125 'javelin-stratcom', 2126 2126 'javelin-vector', 2127 2127 ), 2128 + 'ed18356a' => array( 2129 + 'javelin-install', 2130 + 'javelin-dom', 2131 + 'javelin-util', 2132 + ), 2128 2133 'edf8a145' => array( 2129 2134 'javelin-behavior', 2130 2135 'javelin-uri', ··· 2152 2157 'javelin-reactornode', 2153 2158 'javelin-util', 2154 2159 'javelin-reactor', 2155 - ), 2156 - 'f6d6f389' => array( 2157 - 'javelin-behavior', 2158 - 'javelin-dom', 2159 - 'javelin-stratcom', 2160 2160 ), 2161 2161 'f829edb3' => array( 2162 2162 'javelin-view',
+2
src/applications/files/controller/PhabricatorFileInfoController.php
··· 423 423 } 424 424 425 425 $view_icon = $candidate_engine->getViewAsIconIcon($ref); 426 + $view_color = $candidate_engine->getViewAsIconColor($ref); 426 427 427 428 $views[] = array( 428 429 'viewKey' => $candidate_engine->getDocumentEngineKey(), 429 430 'icon' => $view_icon, 431 + 'color' => $view_color, 430 432 'name' => $label, 431 433 'engineURI' => $candidate_engine->getRenderURI($ref), 432 434 );
+4
src/applications/files/document/PhabricatorAudioDocumentEngine.php
··· 13 13 return 'fa-file-sound-o'; 14 14 } 15 15 16 + protected function getByteLengthLimit() { 17 + return null; 18 + } 19 + 16 20 protected function canRenderDocumentType(PhabricatorDocumentRef $ref) { 17 21 $file = $ref->getFile(); 18 22 if ($file) {
+71 -1
src/applications/files/document/PhabricatorDocumentEngine.php
··· 22 22 PhabricatorDocumentRef $ref); 23 23 24 24 final public function newDocument(PhabricatorDocumentRef $ref) { 25 + $can_complete = $this->canRenderCompleteDocument($ref); 26 + $can_partial = $this->canRenderPartialDocument($ref); 27 + 28 + if (!$can_complete && !$can_partial) { 29 + return $this->newMessage( 30 + pht( 31 + 'This document is too large to be rendered inline. (The document '. 32 + 'is %s bytes, the limit for this engine is %s bytes.)', 33 + new PhutilNumber($ref->getByteLength()), 34 + new PhutilNumber($this->getByteLengthLimit()))); 35 + } 36 + 25 37 return $this->newDocumentContent($ref); 26 38 } 27 39 ··· 51 63 final public function newSortVector(PhabricatorDocumentRef $ref) { 52 64 $content_score = $this->getContentScore($ref); 53 65 66 + // Prefer engines which can render the entire file over engines which 67 + // can only render a header, and engines which can render a header over 68 + // engines which can't render anything. 69 + if ($this->canRenderCompleteDocument($ref)) { 70 + $limit_score = 0; 71 + } else if ($this->canRenderPartialDocument($ref)) { 72 + $limit_score = 1; 73 + } else { 74 + $limit_score = 2; 75 + } 76 + 54 77 return id(new PhutilSortVector()) 78 + ->addInt($limit_score) 55 79 ->addInt(-$content_score); 56 80 } 57 81 58 - protected function getContentScore() { 82 + protected function getContentScore(PhabricatorDocumentRef $ref) { 59 83 return 2000; 60 84 } 61 85 62 86 abstract public function getViewAsLabel(PhabricatorDocumentRef $ref); 63 87 64 88 public function getViewAsIconIcon(PhabricatorDocumentRef $ref) { 89 + $can_complete = $this->canRenderCompleteDocument($ref); 90 + $can_partial = $this->canRenderPartialDocument($ref); 91 + 92 + if (!$can_complete && !$can_partial) { 93 + return 'fa-times'; 94 + } 95 + 65 96 return $this->getDocumentIconIcon($ref); 66 97 } 67 98 99 + public function getViewAsIconColor(PhabricatorDocumentRef $ref) { 100 + $can_complete = $this->canRenderCompleteDocument($ref); 101 + 102 + if (!$can_complete) { 103 + return 'grey'; 104 + } 105 + 106 + return null; 107 + } 108 + 68 109 public function getRenderURI(PhabricatorDocumentRef $ref) { 69 110 $file = $ref->getFile(); 70 111 if (!$file) { ··· 105 146 $vectors = msortv($vectors, 'getSelf'); 106 147 107 148 return array_select_keys($engines, array_keys($vectors)); 149 + } 150 + 151 + protected function getByteLengthLimit() { 152 + return (1024 * 1024 * 8); 153 + } 154 + 155 + protected function canRenderCompleteDocument(PhabricatorDocumentRef $ref) { 156 + $limit = $this->getByteLengthLimit(); 157 + if ($limit) { 158 + $length = $ref->getByteLength(); 159 + if ($length > $limit) { 160 + return false; 161 + } 162 + } 163 + 164 + return true; 165 + } 166 + 167 + protected function canRenderPartialDocument(PhabricatorDocumentRef $ref) { 168 + return false; 169 + } 170 + 171 + protected function newMessage($message) { 172 + return phutil_tag( 173 + 'div', 174 + array( 175 + 'class' => 'document-engine-error', 176 + ), 177 + $message); 108 178 } 109 179 110 180 }
+9 -3
src/applications/files/document/PhabricatorDocumentRef.php
··· 56 56 return $this; 57 57 } 58 58 59 - public function getLength() { 59 + public function getByteLength() { 60 60 if ($this->byteLength !== null) { 61 61 return $this->byteLength; 62 62 } ··· 68 68 return null; 69 69 } 70 70 71 - public function loadData() { 71 + public function loadData($begin = null, $end = null) { 72 72 if ($this->file) { 73 - return $this->file->loadFileData(); 73 + $iterator = $this->file->getFileDataIterator($begin, $end); 74 + 75 + $result = ''; 76 + foreach ($iterator as $chunk) { 77 + $result .= $chunk; 78 + } 79 + return $result; 74 80 } 75 81 76 82 throw new PhutilMethodNotImplementedException();
+34 -3
src/applications/files/document/PhabricatorHexdumpDocumentEngine.php
··· 13 13 return 'fa-microchip'; 14 14 } 15 15 16 - protected function getContentScore() { 16 + protected function getByteLengthLimit() { 17 + return (1024 * 1024 * 1); 18 + } 19 + 20 + protected function getContentScore(PhabricatorDocumentRef $ref) { 17 21 return 500; 18 22 } 19 23 20 24 protected function canRenderDocumentType(PhabricatorDocumentRef $ref) { 25 + return true; 26 + } 27 + 28 + protected function canRenderPartialDocument(PhabricatorDocumentRef $ref) { 21 29 return true; 22 30 } 23 31 24 32 protected function newDocumentContent(PhabricatorDocumentRef $ref) { 25 - $content = $ref->loadData(); 33 + $limit = $this->getByteLengthLimit(); 34 + $length = $ref->getByteLength(); 35 + 36 + $is_partial = false; 37 + if ($limit) { 38 + if ($length > $limit) { 39 + $is_partial = true; 40 + $length = $limit; 41 + } 42 + } 43 + 44 + $content = $ref->loadData(null, $length); 26 45 27 46 $output = array(); 28 47 $offset = 0; ··· 48 67 ), 49 68 $output); 50 69 51 - return $container; 70 + $message = null; 71 + if ($is_partial) { 72 + $message = $this->newMessage( 73 + pht( 74 + 'This document is too large to be completely rendered inline. The '. 75 + 'first %s bytes are shown.', 76 + new PhutilNumber($limit))); 77 + } 78 + 79 + return array( 80 + $message, 81 + $container, 82 + ); 52 83 } 53 84 54 85 private function renderHex($bytes) {
+4
src/applications/files/document/PhabricatorImageDocumentEngine.php
··· 13 13 return 'fa-file-image-o'; 14 14 } 15 15 16 + protected function getByteLengthLimit() { 17 + return (1024 * 1024 * 64); 18 + } 19 + 16 20 protected function canRenderDocumentType(PhabricatorDocumentRef $ref) { 17 21 $file = $ref->getFile(); 18 22 if ($file) {
+10
src/applications/files/document/PhabricatorVideoDocumentEngine.php
··· 9 9 return pht('View as Video'); 10 10 } 11 11 12 + protected function getContentScore(PhabricatorDocumentRef $ref) { 13 + // Some video documents can be rendered as either video or audio, but we 14 + // want to prefer video. 15 + return 2500; 16 + } 17 + 18 + protected function getByteLengthLimit() { 19 + return null; 20 + } 21 + 12 22 protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) { 13 23 return 'fa-film'; 14 24 }
+5 -1
src/applications/files/document/PhabricatorVoidDocumentEngine.php
··· 13 13 return 'fa-file'; 14 14 } 15 15 16 - protected function getContentScore() { 16 + protected function getContentScore(PhabricatorDocumentRef $ref) { 17 17 return 1000; 18 + } 19 + 20 + protected function getByteLengthLimit() { 21 + return null; 18 22 } 19 23 20 24 protected function canRenderDocumentType(PhabricatorDocumentRef $ref) {
+3 -1
webroot/rsrc/css/phui/phui-property-list-view.css
··· 233 233 } 234 234 235 235 .document-engine-error { 236 - margin: 20px auto; 236 + margin: 20px; 237 + padding: 12px; 237 238 text-align: center; 238 239 color: {$redtext}; 240 + background: {$sh-redbackground}; 239 241 } 240 242 241 243 .document-engine-hexdump {
+1
webroot/rsrc/js/application/files/behavior-document-engine.js
··· 27 27 view = new JX.PHUIXActionView() 28 28 .setName(spec.name) 29 29 .setIcon(spec.icon) 30 + .setIconColor(spec.color) 30 31 .setHref(spec.engineURI); 31 32 32 33 view.setHandler(JX.bind(null, function(spec, e) {
+11
webroot/rsrc/js/phuix/PHUIXActionView.js
··· 12 12 _node: null, 13 13 _name: null, 14 14 _icon: 'none', 15 + _iconColor: null, 15 16 _disabled: false, 16 17 _label: false, 17 18 _handler: null, ··· 79 80 return this; 80 81 }, 81 82 83 + setIconColor: function(color) { 84 + this._iconColor = color; 85 + this._buildIconNode(true); 86 + return this; 87 + }, 88 + 82 89 setHref: function(href) { 83 90 this._href = href; 84 91 this._buildNameNode(true); ··· 127 134 var icon_class = this._icon; 128 135 if (this._disabled) { 129 136 icon_class = icon_class + ' grey'; 137 + } 138 + 139 + if (this._iconColor) { 140 + icon_class = icon_class + ' ' + this._iconColor; 130 141 } 131 142 132 143 JX.DOM.alterClass(node, icon_class, true);