如何优雅地处理超过 PHP 的‘ post_max_size’的文件?

我正在开发一种将文件附加到电子邮件的 PHP 表单,并试图优雅地处理上传的文件过大的情况。

我了解到在 php.ini中有两个设置影响文件上传的最大大小: upload_max_filesizepost_max_size

如果文件的大小超过 upload_max_filesize,PHP 将返回文件的大小为0。

但是如果超过 post_max_size,我的脚本将无声地失败,并返回到空白表单。

有什么方法可以捕捉到这个错误吗?

41887 次浏览

From the documentation :

If the size of post data is greater than post_max_size, the $_POST and $_FILES superglobals are empty. This can be tracked in various ways, e.g. by passing the $_GET variable to the script processing the data, i.e. <form action="edit.php?processed=1">, and then checking if $_GET['processed'] is set.

So unfortunately, it doesn't look like PHP sends an error. And since it sends am empty $_POST array, that is why your script is going back to the blank form - it doesn't think it is a POST. (Quite a poor design decision IMHO)

This commenter also has an interesting idea.

It seems that a more elegant way is comparison between post_max_size and $_SERVER['CONTENT_LENGTH']. Please note that the latter includes not only size of uploaded file plus post data but also multipart sequences.

there is a way to catch / handle files exceeding max post size, this is my preferred on, as it tells the end user what has happened and who is at fault ;)

if (empty($_FILES) && empty($_POST) &&
isset($_SERVER['REQUEST_METHOD']) &&
strtolower($_SERVER['REQUEST_METHOD']) == 'post') {
//catch file overload error...
$postMax = ini_get('post_max_size'); //grab the size limits...
echo "<p style=\"color: #F00;\">\nPlease note files larger than {$postMax} will result in this error!<br>Please be advised this is not a limitation in the CMS, This is a limitation of the hosting server.<br>For various reasons they limit the max size of uploaded files, if you have access to the php ini file you can fix this by changing the post_max_size setting.<br> If you can't then please ask your host to increase the size limits, or use the FTP uploaded form</p>"; // echo out error and solutions...
addForm(); //bounce back to the just filled out form.
}
else {
// continue on with processing of the page...
}

We got the problem for SOAP requests where a check for emptiness of $_POST and $_FILES doesn't work, because they are also empty on valid requests.

Therefore we implemented a check, comparing CONTENT_LENGTH and post_max_size. The thrown Exception is later on transformed into a XML-SOAP-FAULT by our registered exception handler.

private function checkPostSizeExceeded() {
$maxPostSize = $this->iniGetBytes('post_max_size');


if ($_SERVER['CONTENT_LENGTH'] > $maxPostSize) {
throw new Exception(
sprintf('Max post size exceeded! Got %s bytes, but limit is %s bytes.',
$_SERVER['CONTENT_LENGTH'],
$maxPostSize
)
);
}
}


private function iniGetBytes($val)
{
$val = trim(ini_get($val));
if ($val != '') {
$last = strtolower(
$val{strlen($val) - 1}
);
} else {
$last = '';
}
switch ($last) {
// The 'G' modifier is available since PHP 5.1.0
case 'g':
$val *= 1024;
// fall through
case 'm':
$val *= 1024;
// fall through
case 'k':
$val *= 1024;
// fall through
}


return $val;
}

Building on @Matt McCormick's and @AbdullahAJM's answers, here is a PHP test case that checks the variables used in the test are set and then checks if the $_SERVER['CONTENT_LENGTH'] exceeds the php_max_filesize setting:

            if (
isset( $_SERVER['REQUEST_METHOD'] )      &&
($_SERVER['REQUEST_METHOD'] === 'POST' ) &&
isset( $_SERVER['CONTENT_LENGTH'] )      &&
( empty( $_POST ) )
) {
$max_post_size = ini_get('post_max_size');
$content_length = $_SERVER['CONTENT_LENGTH'] / 1024 / 1024;
if ($content_length > $max_post_size ) {
print "<div class='updated fade'>" .
sprintf(
__('It appears you tried to upload %d MiB of data but the PHP post_max_size is %d MiB.', 'csa-slplus'),
$content_length,
$max_post_size
) .
'<br/>' .
__( 'Try increasing the post_max_size setting in your php.ini file.' , 'csa-slplus' ) .
'</div>';
}
}

That is a simple way to fix this problem:

Just call "checkPostSizeExceeded" on begin of your code

function checkPostSizeExceeded() {
if (isset($_SERVER['REQUEST_METHOD']) and $_SERVER['REQUEST_METHOD'] == 'POST' and
isset($_SERVER['CONTENT_LENGTH']) and empty($_POST)//if is a post request and $_POST variable is empty(a symptom of "post max size error")
) {
$max = get_ini_bytes('post_max_size');//get the limit of post size
$send = $_SERVER['CONTENT_LENGTH'];//get the sent post size


if($max < $_SERVER['CONTENT_LENGTH'])//compare
throw new Exception(
'Max size exceeded! Were sent ' .
number_format($send/(1024*1024), 2) . 'MB, but ' . number_format($max/(1024*1024), 2) . 'MB is the application limit.'
);
}
}

Remember copy this auxiliar function:

function get_ini_bytes($attr){
$attr_value = trim(ini_get($attr));


if ($attr_value != '') {
$type_byte = strtolower(
$attr_value{strlen($attr_value) - 1}
);
} else
return $attr_value;


switch ($type_byte) {
case 'g': $attr_value *= 1024*1024*1024; break;
case 'm': $attr_value *= 1024*1024; break;
case 'k': $attr_value *= 1024; break;
}


return $attr_value;
}

I had the same problem, and combined some of the solutions already posted here on this page (by @Doblas, @Lance Cleveland and @AbdullahAJM).

Additionally, my solution tries to sends a 413 Payload Too Large error (instead of 200 OK), which is of course only possible, when php.ini is not configured to display warnings.

// Check for Warning: php catch Warning: Unknown: POST Content-Length of bytes exceeds the limit of bytes in Unknown on line 0
// Sending 413 only works, if Warnings are turned off in php.ini!!!


// grab the size limits...
$postMaxSize = trim(ini_get('post_max_size'));
if (strlen($postMaxSize)>0) {
$postMaxSizeValue = substr($postMaxSize, 0, -1);
$postMaxSizeUnit = strtolower(substr($postMaxSize, -1));
$postMaxSize = 0; // make it fail save
if (false !== filter_var($postMaxSizeValue, FILTER_VALIDATE_INT, array('options' => array( 'min_range' => 0)))) {
switch ($postMaxSizeUnit) {
case 'g': $postMaxSizeValue*=1024; // ... and fall through
case 'm': $postMaxSizeValue*=1024; // ... and fall through
case 'k': $postMaxSizeValue*=1024; break;
default: if ($postMaxSizeUnit>='0' && $postMaxSizeUnit<='9') {
$postMaxSizeValue = (int) $postMaxSizeValue.$postMaxSizeUnit;
} else {
$postMaxSizeValue = 0;
}
}
$postMaxSize = $postMaxSizeValue;
}
} else {
$postMaxSize = 0;
}


if (empty($_FILES) && empty($_POST) &&
isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST' &&
isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > $postMaxSize) {
// if is a post request and $_POST variable is empty(a symptom of "post max size error")
    

if (headers_sent()) {
// echo out error and solutions...
echo "<p style=\"color: #F00;\">\nPlease note that an error <b>413 Payload Too Large</b> should be sent, but the warning can't be catched, and so the client gets a <b>200 OK</b>. ".
"Please turn off warnings in php.ini in order to achieve the correct behaviour.</p>";
} else {
http_response_code(413);
}


// catch file overload error: echo out error and solutions...
echo "<p style=\"color: #F00;\">\nPlease note files larger than ".$postMaxSize." will result in this error!<br>".
"Please be advised this is not a limitation in the script, this is a limitation of the hosting server.</p>";
exit(1);
}