@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

Document configuration of file upload limits

Summary: I have a patch which makes uploads all fancy and adds progress bars, but document the landscape first since it's quite complicated.

Test Plan: Generated, read docs. Configured `storage.upload-size-limit` to various values.

Reviewers: btrahan, vrana

Reviewed By: vrana

CC: aran

Maniphest Tasks: T875

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

+255 -3
+14
conf/default.conf.php
··· 720 720 // fits within configured limits. 721 721 'storage.engine-selector' => 'PhabricatorDefaultFileStorageEngineSelector', 722 722 723 + // Set the size of the largest file a user may upload. This is used to render 724 + // text like "Maximum file size: 10MB" on interfaces where users can upload 725 + // files, and files larger than this size will be rejected. 726 + // 727 + // Specify this limit in bytes, or using a "K", "M", or "G" suffix. 728 + // 729 + // NOTE: Setting this to a large size is NOT sufficient to allow users to 730 + // upload large files. You must also configure a number of other settings. To 731 + // configure file upload limits, consult the article "Configuring File Upload 732 + // Limits" in the documentation. Once you've configured some limit across all 733 + // levels of the server, you can set this limit to an appropriate value and 734 + // the UI will then reflect the actual configured limit. 735 + 'storage.upload-size-limit' => null, 736 + 723 737 // Phabricator puts databases in a namespace, which defualts to "phabricator" 724 738 // -- for instance, the Differential database is named 725 739 // "phabricator_differential" by default. You can change this namespace if you
+4
src/__phutil_library_map__.php
··· 934 934 'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter', 935 935 'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager', 936 936 'PhabricatorUITooltipExample' => 'applications/uiexample/examples/tooltip', 937 + 'PhabricatorUnitsTestCase' => 'view/utils/__tests__', 937 938 'PhabricatorUser' => 'applications/people/storage/user', 938 939 'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account', 939 940 'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit', ··· 1022 1023 'javelin_render_tag' => 'infrastructure/javelin/markup', 1023 1024 'phabricator_date' => 'view/utils', 1024 1025 'phabricator_datetime' => 'view/utils', 1026 + 'phabricator_format_bytes' => 'view/utils', 1025 1027 'phabricator_format_local_time' => 'view/utils', 1026 1028 'phabricator_format_relative_time' => 'view/utils', 1027 1029 'phabricator_format_units_generic' => 'view/utils', 1028 1030 'phabricator_on_relative_date' => 'view/utils', 1031 + 'phabricator_parse_bytes' => 'view/utils', 1029 1032 'phabricator_relative_date' => 'view/utils', 1030 1033 'phabricator_render_form' => 'infrastructure/javelin/markup', 1031 1034 'phabricator_time' => 'view/utils', ··· 1796 1799 'PhabricatorUIListFilterExample' => 'PhabricatorUIExample', 1797 1800 'PhabricatorUIPagerExample' => 'PhabricatorUIExample', 1798 1801 'PhabricatorUITooltipExample' => 'PhabricatorUIExample', 1802 + 'PhabricatorUnitsTestCase' => 'PhabricatorTestCase', 1799 1803 'PhabricatorUser' => 'PhabricatorUserDAO', 1800 1804 'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController', 1801 1805 'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
+3
src/applications/files/controller/list/PhabricatorFileListController.php
··· 306 306 $request = $this->getRequest(); 307 307 $user = $request->getUser(); 308 308 309 + $limit_text = PhabricatorFileUploadView::renderUploadLimit(); 310 + 309 311 if ($this->useBasicUploader()) { 310 312 311 313 $upload_panel = new PhabricatorFileUploadView(); ··· 319 321 320 322 $upload_panel = new AphrontPanelView(); 321 323 $upload_panel->setHeader('Upload Files'); 324 + $upload_panel->setCaption($limit_text); 322 325 $upload_panel->setCreateButton('Basic Uploader', 323 326 $request->getRequestURI()->setQueryParam('basic_uploader', true) 324 327 );
+24 -2
src/applications/files/view/upload/PhabricatorFileUploadView.php
··· 1 1 <?php 2 2 3 3 /* 4 - * Copyright 2011 Facebook, Inc. 4 + * Copyright 2012 Facebook, Inc. 5 5 * 6 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 7 * you may not use this file except in compliance with the License. ··· 44 44 id(new AphrontFormFileControl()) 45 45 ->setLabel('File') 46 46 ->setName('file') 47 - ->setError(true)) 47 + ->setError(true) 48 + ->setCaption(self::renderUploadLimit())) 48 49 ->appendChild( 49 50 id(new AphrontFormTextControl()) 50 51 ->setLabel('Name') ··· 62 63 $panel->setWidth(AphrontPanelView::WIDTH_FULL); 63 64 64 65 return $panel->render(); 66 + } 67 + 68 + public static function renderUploadLimit() { 69 + $limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit'); 70 + $limit = phabricator_parse_bytes($limit); 71 + if ($limit) { 72 + $formatted = phabricator_format_bytes($limit); 73 + return 'Maximum file size: '.phutil_escape_html($formatted); 74 + } 75 + 76 + $doc_href = PhabricatorEnv::getDocLink( 77 + 'articles/Configuring_File_Upload_Limits.html'); 78 + $doc_link = phutil_render_tag( 79 + 'a', 80 + array( 81 + 'href' => $doc_href, 82 + 'target' => '_blank', 83 + ), 84 + 'Configuring File Upload Limits'); 85 + 86 + return 'Upload limit is not configured, see '.$doc_link.'.'; 65 87 } 66 88 } 67 89
+3
src/applications/files/view/upload/__init__.php
··· 6 6 7 7 8 8 9 + phutil_require_module('phabricator', 'infrastructure/env'); 9 10 phutil_require_module('phabricator', 'view/base'); 10 11 phutil_require_module('phabricator', 'view/form/base'); 11 12 phutil_require_module('phabricator', 'view/form/control/file'); 12 13 phutil_require_module('phabricator', 'view/form/control/submit'); 13 14 phutil_require_module('phabricator', 'view/form/control/text'); 14 15 phutil_require_module('phabricator', 'view/layout/panel'); 16 + phutil_require_module('phabricator', 'view/utils'); 15 17 18 + phutil_require_module('phutil', 'markup'); 16 19 phutil_require_module('phutil', 'utils'); 17 20 18 21
+2
src/docs/configuration/configuring_file_storage.diviner
··· 85 85 86 86 Continue by: 87 87 88 + - configuring file size upload limits with 89 + @{article:Configuring File Upload Limits}; or 88 90 - returning to the @{article:Configuration Guide}.
+78
src/docs/configuration/configuring_file_upload_limits.diviner
··· 1 + @title Configuring File Upload Limits 2 + @group config 3 + 4 + Explains limits on file upload sizes. 5 + 6 + = Overview = 7 + 8 + File uploads are limited by a large number of pieces of configuration, at 9 + multiple layers of the application. Generally, the minimum value of all the 10 + limits is the effective one. To upload large files, you need to increase all 11 + the limits above the maximum file size you want to support. The settings which 12 + limit uploads are: 13 + 14 + - **HTTP Server**: The HTTP server may set a limit on the maximum request 15 + size. If you exceed this limit, you'll see a default server page with an 16 + HTTP error. These directives limit the total size of the request body, 17 + so they must be somewhat larger than the desired maximum filesize. 18 + - **Apache**: Apache limits requests with the Apache `LimitRequestBody` 19 + directive. 20 + - **nginx**: nginx limits requests with the nginx `client_max_body_size` 21 + directive. This often defaults to `1M`. 22 + - **lighttpd**: lighttpd limits requests with the lighttpd 23 + `server.max-request-size` directive. 24 + - **PHP**: PHP has several directives which limit uploads. These directives 25 + are found in `php.ini`. 26 + - **upload_max_filesize**: Maximum file size PHP will accept in a file 27 + upload. If you exceed this, Phabricator will give you a useful error. This 28 + often defaults to `2M`. 29 + - **post_max_size**: Maximum POST request size PHP will accept. If you 30 + exceed this, Phabricator will give you a useful error. This often defaults 31 + to `8M`. 32 + - **memory_limit**: For some uploads, file data will be read into memory 33 + before Phabricator can adjust the memory limit. If you exceed this, PHP 34 + may give you a useful error, depending on your configuration. 35 + - **max_input_vars**: When files are uploaded via HTML5 drag and drop file 36 + upload APIs, PHP parses the file body as though it contained normal POST 37 + parameters, and may trigger `max_input_vars` if a file has a lot of 38 + brackets in it. You may need to set it to some astronomically high value. 39 + - **Storage Engines**: Some storage engines can be configured not to accept 40 + files over a certain size. To upload a file, you must have at least one 41 + configured storage engine which can accept it. Phabricator should give you 42 + useful errors if any of these fail. 43 + - **MySQL Engine**: Upload size is limited by the Phabricator setting 44 + `storage.mysql-engine.max-size`, which is in turn limited by the MySQL 45 + setting `max_allowed_packet`. This often defaults to `1M`. 46 + - **Amazon S3**: Upload size is limited by Phabricator's implementation to 47 + `5G`. 48 + - **Local Disk**: Upload size is limited only by free disk space. 49 + - **Resource Constraints**: File uploads are limited by resource constraints 50 + on the application server. In particular, some uploaded files are written 51 + to disk in their entirety before being moved to storage engines, and all 52 + uploaded files are read into memory before being moved. These hard limits 53 + should be large for most servers, but will fundamentally prevent Phabricator 54 + from processing truly enormous files (GB/TB scale). Phabricator is probably 55 + not the best application for this in any case. 56 + - **Phabricator Master Limit**: The master limit, `storage.upload-size-limit`, 57 + is used to show upload limits in the UI. 58 + 59 + Phabricator can't read some of these settings, so it can't figure out what the 60 + current limit is or be much help at all in configuring it. Thus, you need to 61 + manually configure all of these limits and then tell Phabricator what you set 62 + them to. Follow these steps: 63 + 64 + - Pick some limit you want to set, like `100M`. 65 + - Configure all of the settings mentioned above to be a bit bigger than the 66 + limit you want to enforce (**note that there are some security implications 67 + to raising these limits**; principally, your server may become easier to 68 + attack with a denial-of-service). 69 + - Set `storage.upload-size-limit` to the limit you want. 70 + - The UI should now show your limit. 71 + - Upload a big file to make sure it works. 72 + 73 + = Next Steps = 74 + 75 + Continue by: 76 + 77 + - configuring file storage with @{article:Configuring File Storage}; or 78 + - retuning to the @{article:Configuration Guide}.
+65
src/view/utils/__tests__/PhabricatorUnitsTestCase.php
··· 1 + <?php 2 + 3 + /* 4 + * Copyright 2012 Facebook, Inc. 5 + * 6 + * Licensed under the Apache License, Version 2.0 (the "License"); 7 + * you may not use this file except in compliance with the License. 8 + * You may obtain a copy of the License at 9 + * 10 + * http://www.apache.org/licenses/LICENSE-2.0 11 + * 12 + * Unless required by applicable law or agreed to in writing, software 13 + * distributed under the License is distributed on an "AS IS" BASIS, 14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 + * See the License for the specific language governing permissions and 16 + * limitations under the License. 17 + */ 18 + 19 + final class PhabricatorUnitsTestCase extends PhabricatorTestCase { 20 + 21 + public function testByteFormatting() { 22 + $tests = array( 23 + 1 => '1 B', 24 + 1000 => '1 KB', 25 + 1000000 => '1 MB', 26 + 10000000 => '10 MB', 27 + 100000000 => '100 MB', 28 + 1000000000 => '1 GB', 29 + 1000000000000 => '1 TB', 30 + 999 => '999 B', 31 + ); 32 + 33 + foreach ($tests as $input => $expect) { 34 + $this->assertEqual( 35 + $expect, 36 + phabricator_format_bytes($input), 37 + 'phabricator_format_bytes('.$input.')'); 38 + } 39 + } 40 + 41 + public function testByteParsing() { 42 + $tests = array( 43 + '1' => 1, 44 + '1k' => 1000, 45 + '1K' => 1000, 46 + '1kB' => 1000, 47 + '1Kb' => 1000, 48 + '1KB' => 1000, 49 + '1MB' => 1000000, 50 + '1GB' => 1000000000, 51 + '1TB' => 1000000000000, 52 + '1.5M' => 1500000, 53 + '1 000' => 1000, 54 + '1,234.56 KB' => 1234560, 55 + ); 56 + 57 + foreach ($tests as $input => $expect) { 58 + $this->assertEqual( 59 + $expect, 60 + phabricator_parse_bytes($input), 61 + 'phabricator_parse_bytes('.$input.')'); 62 + } 63 + } 64 + 65 + }
+1
src/view/utils/__tests__/__init__.php
··· 12 12 13 13 14 14 phutil_require_source('PhabricatorLocalTimeTestCase.php'); 15 + phutil_require_source('PhabricatorUnitsTestCase.php');
+55 -1
src/view/utils/viewutils.php
··· 126 126 $precision = 0); 127 127 } 128 128 129 + 130 + /** 131 + * Format a byte count for human consumption, e.g. "10MB" instead of 132 + * "10000000". 133 + * 134 + * @param int Number of bytes. 135 + * @return string Human-readable description. 136 + */ 137 + function phabricator_format_bytes($bytes) { 138 + return phabricator_format_units_generic( 139 + $bytes, 140 + // NOTE: Using the SI version of these units rather than the 1024 version. 141 + array(1000, 1000, 1000, 1000, 1000), 142 + array('B', 'KB', 'MB', 'GB', 'TB', 'PB'), 143 + $precision = 0); 144 + } 145 + 146 + 147 + /** 148 + * Parse a human-readable byte description (like "6MB") into an integer. 149 + * 150 + * @param string Human-readable description. 151 + * @return int Number of represented bytes. 152 + */ 153 + function phabricator_parse_bytes($input) { 154 + $bytes = trim($input); 155 + if (!strlen($bytes)) { 156 + return null; 157 + } 158 + 159 + // NOTE: Assumes US-centric numeral notation. 160 + $bytes = preg_replace('/[ ,]/', '', $bytes); 161 + 162 + $matches = null; 163 + if (!preg_match('/^(?:\d+(?:[.]\d+)?)([kmgtp]?)b?$/i', $bytes, $matches)) { 164 + throw new Exception("Unable to parse byte size '{$input}'!"); 165 + } 166 + 167 + $scale = array( 168 + 'k' => 1000, 169 + 'm' => 1000 * 1000, 170 + 'g' => 1000 * 1000 * 1000, 171 + 't' => 1000 * 1000 * 1000 * 1000, 172 + ); 173 + 174 + $bytes = (float)$bytes; 175 + if ($matches[1]) { 176 + $bytes *= $scale[strtolower($matches[1])]; 177 + } 178 + 179 + return (int)$bytes; 180 + } 181 + 182 + 129 183 function phabricator_format_units_generic( 130 184 $n, 131 185 array $scales, ··· 144 198 145 199 $scale = array_shift($scales); 146 200 $label = array_shift($labels); 147 - while ($n > $scale && count($labels)) { 201 + while ($n >= $scale && count($labels)) { 148 202 $remainder += ($n % $scale) * $accum; 149 203 $n /= $scale; 150 204 $accum *= $scale;
+6
webroot/index.php
··· 20 20 21 21 error_reporting(E_ALL | E_STRICT); 22 22 23 + if ($_SERVER['REQUEST_METHOD'] == 'POST' && !$_POST) { 24 + $size = ini_get('post_max_size'); 25 + phabricator_fatal( 26 + "Request size exceeds PHP 'post_max_size' ('{$size}')."); 27 + } 28 + 23 29 $required_version = '5.2.3'; 24 30 if (version_compare(PHP_VERSION, $required_version) < 0) { 25 31 phabricator_fatal_config_error(