mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-17 18:21:11 +01:00
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
This commit is contained in:
parent
74ef98e76e
commit
9b2ededd48
11 changed files with 255 additions and 3 deletions
|
@ -720,6 +720,20 @@ return array(
|
||||||
// fits within configured limits.
|
// fits within configured limits.
|
||||||
'storage.engine-selector' => 'PhabricatorDefaultFileStorageEngineSelector',
|
'storage.engine-selector' => 'PhabricatorDefaultFileStorageEngineSelector',
|
||||||
|
|
||||||
|
// Set the size of the largest file a user may upload. This is used to render
|
||||||
|
// text like "Maximum file size: 10MB" on interfaces where users can upload
|
||||||
|
// files, and files larger than this size will be rejected.
|
||||||
|
//
|
||||||
|
// Specify this limit in bytes, or using a "K", "M", or "G" suffix.
|
||||||
|
//
|
||||||
|
// NOTE: Setting this to a large size is NOT sufficient to allow users to
|
||||||
|
// upload large files. You must also configure a number of other settings. To
|
||||||
|
// configure file upload limits, consult the article "Configuring File Upload
|
||||||
|
// Limits" in the documentation. Once you've configured some limit across all
|
||||||
|
// levels of the server, you can set this limit to an appropriate value and
|
||||||
|
// the UI will then reflect the actual configured limit.
|
||||||
|
'storage.upload-size-limit' => null,
|
||||||
|
|
||||||
// Phabricator puts databases in a namespace, which defualts to "phabricator"
|
// Phabricator puts databases in a namespace, which defualts to "phabricator"
|
||||||
// -- for instance, the Differential database is named
|
// -- for instance, the Differential database is named
|
||||||
// "phabricator_differential" by default. You can change this namespace if you
|
// "phabricator_differential" by default. You can change this namespace if you
|
||||||
|
|
|
@ -934,6 +934,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter',
|
'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter',
|
||||||
'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager',
|
'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager',
|
||||||
'PhabricatorUITooltipExample' => 'applications/uiexample/examples/tooltip',
|
'PhabricatorUITooltipExample' => 'applications/uiexample/examples/tooltip',
|
||||||
|
'PhabricatorUnitsTestCase' => 'view/utils/__tests__',
|
||||||
'PhabricatorUser' => 'applications/people/storage/user',
|
'PhabricatorUser' => 'applications/people/storage/user',
|
||||||
'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
|
'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
|
||||||
'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
|
'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
|
||||||
|
@ -1022,10 +1023,12 @@ phutil_register_library_map(array(
|
||||||
'javelin_render_tag' => 'infrastructure/javelin/markup',
|
'javelin_render_tag' => 'infrastructure/javelin/markup',
|
||||||
'phabricator_date' => 'view/utils',
|
'phabricator_date' => 'view/utils',
|
||||||
'phabricator_datetime' => 'view/utils',
|
'phabricator_datetime' => 'view/utils',
|
||||||
|
'phabricator_format_bytes' => 'view/utils',
|
||||||
'phabricator_format_local_time' => 'view/utils',
|
'phabricator_format_local_time' => 'view/utils',
|
||||||
'phabricator_format_relative_time' => 'view/utils',
|
'phabricator_format_relative_time' => 'view/utils',
|
||||||
'phabricator_format_units_generic' => 'view/utils',
|
'phabricator_format_units_generic' => 'view/utils',
|
||||||
'phabricator_on_relative_date' => 'view/utils',
|
'phabricator_on_relative_date' => 'view/utils',
|
||||||
|
'phabricator_parse_bytes' => 'view/utils',
|
||||||
'phabricator_relative_date' => 'view/utils',
|
'phabricator_relative_date' => 'view/utils',
|
||||||
'phabricator_render_form' => 'infrastructure/javelin/markup',
|
'phabricator_render_form' => 'infrastructure/javelin/markup',
|
||||||
'phabricator_time' => 'view/utils',
|
'phabricator_time' => 'view/utils',
|
||||||
|
@ -1796,6 +1799,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorUIListFilterExample' => 'PhabricatorUIExample',
|
'PhabricatorUIListFilterExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorUIPagerExample' => 'PhabricatorUIExample',
|
'PhabricatorUIPagerExample' => 'PhabricatorUIExample',
|
||||||
'PhabricatorUITooltipExample' => 'PhabricatorUIExample',
|
'PhabricatorUITooltipExample' => 'PhabricatorUIExample',
|
||||||
|
'PhabricatorUnitsTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorUser' => 'PhabricatorUserDAO',
|
'PhabricatorUser' => 'PhabricatorUserDAO',
|
||||||
'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||||
'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
|
||||||
|
|
|
@ -306,6 +306,8 @@ final class PhabricatorFileListController extends PhabricatorFileController {
|
||||||
$request = $this->getRequest();
|
$request = $this->getRequest();
|
||||||
$user = $request->getUser();
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$limit_text = PhabricatorFileUploadView::renderUploadLimit();
|
||||||
|
|
||||||
if ($this->useBasicUploader()) {
|
if ($this->useBasicUploader()) {
|
||||||
|
|
||||||
$upload_panel = new PhabricatorFileUploadView();
|
$upload_panel = new PhabricatorFileUploadView();
|
||||||
|
@ -319,6 +321,7 @@ final class PhabricatorFileListController extends PhabricatorFileController {
|
||||||
|
|
||||||
$upload_panel = new AphrontPanelView();
|
$upload_panel = new AphrontPanelView();
|
||||||
$upload_panel->setHeader('Upload Files');
|
$upload_panel->setHeader('Upload Files');
|
||||||
|
$upload_panel->setCaption($limit_text);
|
||||||
$upload_panel->setCreateButton('Basic Uploader',
|
$upload_panel->setCreateButton('Basic Uploader',
|
||||||
$request->getRequestURI()->setQueryParam('basic_uploader', true)
|
$request->getRequestURI()->setQueryParam('basic_uploader', true)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2011 Facebook, Inc.
|
* Copyright 2012 Facebook, Inc.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -44,7 +44,8 @@ final class PhabricatorFileUploadView extends AphrontView {
|
||||||
id(new AphrontFormFileControl())
|
id(new AphrontFormFileControl())
|
||||||
->setLabel('File')
|
->setLabel('File')
|
||||||
->setName('file')
|
->setName('file')
|
||||||
->setError(true))
|
->setError(true)
|
||||||
|
->setCaption(self::renderUploadLimit()))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('Name')
|
->setLabel('Name')
|
||||||
|
@ -63,5 +64,26 @@ final class PhabricatorFileUploadView extends AphrontView {
|
||||||
|
|
||||||
return $panel->render();
|
return $panel->render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function renderUploadLimit() {
|
||||||
|
$limit = PhabricatorEnv::getEnvConfig('storage.upload-size-limit');
|
||||||
|
$limit = phabricator_parse_bytes($limit);
|
||||||
|
if ($limit) {
|
||||||
|
$formatted = phabricator_format_bytes($limit);
|
||||||
|
return 'Maximum file size: '.phutil_escape_html($formatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
$doc_href = PhabricatorEnv::getDocLink(
|
||||||
|
'articles/Configuring_File_Upload_Limits.html');
|
||||||
|
$doc_link = phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'href' => $doc_href,
|
||||||
|
'target' => '_blank',
|
||||||
|
),
|
||||||
|
'Configuring File Upload Limits');
|
||||||
|
|
||||||
|
return 'Upload limit is not configured, see '.$doc_link.'.';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,16 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'view/base');
|
phutil_require_module('phabricator', 'view/base');
|
||||||
phutil_require_module('phabricator', 'view/form/base');
|
phutil_require_module('phabricator', 'view/form/base');
|
||||||
phutil_require_module('phabricator', 'view/form/control/file');
|
phutil_require_module('phabricator', 'view/form/control/file');
|
||||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||||
phutil_require_module('phabricator', 'view/form/control/text');
|
phutil_require_module('phabricator', 'view/form/control/text');
|
||||||
phutil_require_module('phabricator', 'view/layout/panel');
|
phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
|
phutil_require_module('phabricator', 'view/utils');
|
||||||
|
|
||||||
|
phutil_require_module('phutil', 'markup');
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -85,4 +85,6 @@ application (##/file/##) and uploading files.
|
||||||
|
|
||||||
Continue by:
|
Continue by:
|
||||||
|
|
||||||
|
- configuring file size upload limits with
|
||||||
|
@{article:Configuring File Upload Limits}; or
|
||||||
- returning to the @{article:Configuration Guide}.
|
- returning to the @{article:Configuration Guide}.
|
|
@ -0,0 +1,78 @@
|
||||||
|
@title Configuring File Upload Limits
|
||||||
|
@group config
|
||||||
|
|
||||||
|
Explains limits on file upload sizes.
|
||||||
|
|
||||||
|
= Overview =
|
||||||
|
|
||||||
|
File uploads are limited by a large number of pieces of configuration, at
|
||||||
|
multiple layers of the application. Generally, the minimum value of all the
|
||||||
|
limits is the effective one. To upload large files, you need to increase all
|
||||||
|
the limits above the maximum file size you want to support. The settings which
|
||||||
|
limit uploads are:
|
||||||
|
|
||||||
|
- **HTTP Server**: The HTTP server may set a limit on the maximum request
|
||||||
|
size. If you exceed this limit, you'll see a default server page with an
|
||||||
|
HTTP error. These directives limit the total size of the request body,
|
||||||
|
so they must be somewhat larger than the desired maximum filesize.
|
||||||
|
- **Apache**: Apache limits requests with the Apache `LimitRequestBody`
|
||||||
|
directive.
|
||||||
|
- **nginx**: nginx limits requests with the nginx `client_max_body_size`
|
||||||
|
directive. This often defaults to `1M`.
|
||||||
|
- **lighttpd**: lighttpd limits requests with the lighttpd
|
||||||
|
`server.max-request-size` directive.
|
||||||
|
- **PHP**: PHP has several directives which limit uploads. These directives
|
||||||
|
are found in `php.ini`.
|
||||||
|
- **upload_max_filesize**: Maximum file size PHP will accept in a file
|
||||||
|
upload. If you exceed this, Phabricator will give you a useful error. This
|
||||||
|
often defaults to `2M`.
|
||||||
|
- **post_max_size**: Maximum POST request size PHP will accept. If you
|
||||||
|
exceed this, Phabricator will give you a useful error. This often defaults
|
||||||
|
to `8M`.
|
||||||
|
- **memory_limit**: For some uploads, file data will be read into memory
|
||||||
|
before Phabricator can adjust the memory limit. If you exceed this, PHP
|
||||||
|
may give you a useful error, depending on your configuration.
|
||||||
|
- **max_input_vars**: When files are uploaded via HTML5 drag and drop file
|
||||||
|
upload APIs, PHP parses the file body as though it contained normal POST
|
||||||
|
parameters, and may trigger `max_input_vars` if a file has a lot of
|
||||||
|
brackets in it. You may need to set it to some astronomically high value.
|
||||||
|
- **Storage Engines**: Some storage engines can be configured not to accept
|
||||||
|
files over a certain size. To upload a file, you must have at least one
|
||||||
|
configured storage engine which can accept it. Phabricator should give you
|
||||||
|
useful errors if any of these fail.
|
||||||
|
- **MySQL Engine**: Upload size is limited by the Phabricator setting
|
||||||
|
`storage.mysql-engine.max-size`, which is in turn limited by the MySQL
|
||||||
|
setting `max_allowed_packet`. This often defaults to `1M`.
|
||||||
|
- **Amazon S3**: Upload size is limited by Phabricator's implementation to
|
||||||
|
`5G`.
|
||||||
|
- **Local Disk**: Upload size is limited only by free disk space.
|
||||||
|
- **Resource Constraints**: File uploads are limited by resource constraints
|
||||||
|
on the application server. In particular, some uploaded files are written
|
||||||
|
to disk in their entirety before being moved to storage engines, and all
|
||||||
|
uploaded files are read into memory before being moved. These hard limits
|
||||||
|
should be large for most servers, but will fundamentally prevent Phabricator
|
||||||
|
from processing truly enormous files (GB/TB scale). Phabricator is probably
|
||||||
|
not the best application for this in any case.
|
||||||
|
- **Phabricator Master Limit**: The master limit, `storage.upload-size-limit`,
|
||||||
|
is used to show upload limits in the UI.
|
||||||
|
|
||||||
|
Phabricator can't read some of these settings, so it can't figure out what the
|
||||||
|
current limit is or be much help at all in configuring it. Thus, you need to
|
||||||
|
manually configure all of these limits and then tell Phabricator what you set
|
||||||
|
them to. Follow these steps:
|
||||||
|
|
||||||
|
- Pick some limit you want to set, like `100M`.
|
||||||
|
- Configure all of the settings mentioned above to be a bit bigger than the
|
||||||
|
limit you want to enforce (**note that there are some security implications
|
||||||
|
to raising these limits**; principally, your server may become easier to
|
||||||
|
attack with a denial-of-service).
|
||||||
|
- Set `storage.upload-size-limit` to the limit you want.
|
||||||
|
- The UI should now show your limit.
|
||||||
|
- Upload a big file to make sure it works.
|
||||||
|
|
||||||
|
= Next Steps =
|
||||||
|
|
||||||
|
Continue by:
|
||||||
|
|
||||||
|
- configuring file storage with @{article:Configuring File Storage}; or
|
||||||
|
- retuning to the @{article:Configuration Guide}.
|
65
src/view/utils/__tests__/PhabricatorUnitsTestCase.php
Normal file
65
src/view/utils/__tests__/PhabricatorUnitsTestCase.php
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2012 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
final class PhabricatorUnitsTestCase extends PhabricatorTestCase {
|
||||||
|
|
||||||
|
public function testByteFormatting() {
|
||||||
|
$tests = array(
|
||||||
|
1 => '1 B',
|
||||||
|
1000 => '1 KB',
|
||||||
|
1000000 => '1 MB',
|
||||||
|
10000000 => '10 MB',
|
||||||
|
100000000 => '100 MB',
|
||||||
|
1000000000 => '1 GB',
|
||||||
|
1000000000000 => '1 TB',
|
||||||
|
999 => '999 B',
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($tests as $input => $expect) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$expect,
|
||||||
|
phabricator_format_bytes($input),
|
||||||
|
'phabricator_format_bytes('.$input.')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testByteParsing() {
|
||||||
|
$tests = array(
|
||||||
|
'1' => 1,
|
||||||
|
'1k' => 1000,
|
||||||
|
'1K' => 1000,
|
||||||
|
'1kB' => 1000,
|
||||||
|
'1Kb' => 1000,
|
||||||
|
'1KB' => 1000,
|
||||||
|
'1MB' => 1000000,
|
||||||
|
'1GB' => 1000000000,
|
||||||
|
'1TB' => 1000000000000,
|
||||||
|
'1.5M' => 1500000,
|
||||||
|
'1 000' => 1000,
|
||||||
|
'1,234.56 KB' => 1234560,
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($tests as $input => $expect) {
|
||||||
|
$this->assertEqual(
|
||||||
|
$expect,
|
||||||
|
phabricator_parse_bytes($input),
|
||||||
|
'phabricator_parse_bytes('.$input.')');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,3 +12,4 @@ phutil_require_module('phabricator', 'view/utils');
|
||||||
|
|
||||||
|
|
||||||
phutil_require_source('PhabricatorLocalTimeTestCase.php');
|
phutil_require_source('PhabricatorLocalTimeTestCase.php');
|
||||||
|
phutil_require_source('PhabricatorUnitsTestCase.php');
|
||||||
|
|
|
@ -126,6 +126,60 @@ function phabricator_format_relative_time($duration) {
|
||||||
$precision = 0);
|
$precision = 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a byte count for human consumption, e.g. "10MB" instead of
|
||||||
|
* "10000000".
|
||||||
|
*
|
||||||
|
* @param int Number of bytes.
|
||||||
|
* @return string Human-readable description.
|
||||||
|
*/
|
||||||
|
function phabricator_format_bytes($bytes) {
|
||||||
|
return phabricator_format_units_generic(
|
||||||
|
$bytes,
|
||||||
|
// NOTE: Using the SI version of these units rather than the 1024 version.
|
||||||
|
array(1000, 1000, 1000, 1000, 1000),
|
||||||
|
array('B', 'KB', 'MB', 'GB', 'TB', 'PB'),
|
||||||
|
$precision = 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a human-readable byte description (like "6MB") into an integer.
|
||||||
|
*
|
||||||
|
* @param string Human-readable description.
|
||||||
|
* @return int Number of represented bytes.
|
||||||
|
*/
|
||||||
|
function phabricator_parse_bytes($input) {
|
||||||
|
$bytes = trim($input);
|
||||||
|
if (!strlen($bytes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Assumes US-centric numeral notation.
|
||||||
|
$bytes = preg_replace('/[ ,]/', '', $bytes);
|
||||||
|
|
||||||
|
$matches = null;
|
||||||
|
if (!preg_match('/^(?:\d+(?:[.]\d+)?)([kmgtp]?)b?$/i', $bytes, $matches)) {
|
||||||
|
throw new Exception("Unable to parse byte size '{$input}'!");
|
||||||
|
}
|
||||||
|
|
||||||
|
$scale = array(
|
||||||
|
'k' => 1000,
|
||||||
|
'm' => 1000 * 1000,
|
||||||
|
'g' => 1000 * 1000 * 1000,
|
||||||
|
't' => 1000 * 1000 * 1000 * 1000,
|
||||||
|
);
|
||||||
|
|
||||||
|
$bytes = (float)$bytes;
|
||||||
|
if ($matches[1]) {
|
||||||
|
$bytes *= $scale[strtolower($matches[1])];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)$bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function phabricator_format_units_generic(
|
function phabricator_format_units_generic(
|
||||||
$n,
|
$n,
|
||||||
array $scales,
|
array $scales,
|
||||||
|
@ -144,7 +198,7 @@ function phabricator_format_units_generic(
|
||||||
|
|
||||||
$scale = array_shift($scales);
|
$scale = array_shift($scales);
|
||||||
$label = array_shift($labels);
|
$label = array_shift($labels);
|
||||||
while ($n > $scale && count($labels)) {
|
while ($n >= $scale && count($labels)) {
|
||||||
$remainder += ($n % $scale) * $accum;
|
$remainder += ($n % $scale) * $accum;
|
||||||
$n /= $scale;
|
$n /= $scale;
|
||||||
$accum *= $scale;
|
$accum *= $scale;
|
||||||
|
|
|
@ -20,6 +20,12 @@ $__start__ = microtime(true);
|
||||||
|
|
||||||
error_reporting(E_ALL | E_STRICT);
|
error_reporting(E_ALL | E_STRICT);
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'POST' && !$_POST) {
|
||||||
|
$size = ini_get('post_max_size');
|
||||||
|
phabricator_fatal(
|
||||||
|
"Request size exceeds PHP 'post_max_size' ('{$size}').");
|
||||||
|
}
|
||||||
|
|
||||||
$required_version = '5.2.3';
|
$required_version = '5.2.3';
|
||||||
if (version_compare(PHP_VERSION, $required_version) < 0) {
|
if (version_compare(PHP_VERSION, $required_version) < 0) {
|
||||||
phabricator_fatal_config_error(
|
phabricator_fatal_config_error(
|
||||||
|
|
Loading…
Reference in a new issue