How to gracefully handle files that exceed PHP's `post_max_size`?
I'm working on a PHP form that attaches a file to an email, and trying to gracefully handle cases where the uploaded file is too large.
I've learned that there are two settings in php.ini
that affect the maxiumum size of a file upload: upload_max_fi开发者_高级运维lesize
and post_max_size
.
If a file's size exceeds upload_max_filesize
, PHP returns the file's size as 0. That's fine; I can check for that.
But if it exceeds post_max_size
, my script fails silently and goes back to the blank form.
Is there any way to catch this error?
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);
}
精彩评论