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

问题描述 投票:0回答:7

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

我了解到,

php.ini
中有两个设置会影响文件上传的最大大小:
upload_max_filesize
post_max_size

如果文件大小超过

upload_max_filesize
,PHP 将文件大小返回为 0。没关系;我可以检查一下。

但是如果超过

post_max_size
,我的脚本会默默失败并返回到空白表单。

有什么办法可以捕获这个错误吗?

php upload
7个回答
59
投票

来自文档

如果发布数据的大小较大 比 post_max_size 、 $_POST 和 $_FILES 超全局变量为空。这 可以通过多种方式进行跟踪,例如 通过将 $_GET 变量传递给 处理数据的脚本,即

和 然后检查 $_GET['processed'] 是否为 设置。

所以不幸的是,PHP 看起来并没有发送错误。由于它发送的是空的 $_POST 数组,这就是为什么你的脚本会返回到空白表单 - 它不认为它是一个 POST。 (恕我直言,这是一个相当糟糕的设计决策)

这位评论者也有一个有趣的想法。

看来更优雅的方式是 post_max_size 和之间的比较 $_SERVER['CONTENT_LENGTH']。请 请注意,后者不仅包括 上传文件的大小加上发布数据 还有多部分序列。


47
投票

有一种方法可以捕获/处理超过最大帖子大小的文件,这是我的首选,因为它告诉最终用户发生了什么以及谁有错;)

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...
}

7
投票

我们遇到了 SOAP 请求的问题,其中 $_POST 和 $_FILES 是否为空的检查不起作用,因为它们在有效请求上也是空的。

因此我们实施了一项检查,比较 CONTENT_LENGTH 和 post_max_size。抛出的异常随后由我们注册的异常处理程序转换为 XML-SOAP-FAULT。

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;
}

4
投票

基于 @Matt McCormick 和 @AbdullahAJM 的答案,这里是一个 PHP 测试用例,它检查测试中使用的变量是否设置,然后检查 $_SERVER['CONTENT_LENGTH'] 是否超过 php_max_filesize 设置:

            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>';
                }
            }

2
投票

这是解决此问题的简单方法:

只需在代码开头调用“checkPostSizeExceeded”即可

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.'
                    );
        }
    }

记得复制这个辅助功能:

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;
}

1
投票

我遇到了同样的问题,并结合了本页面上已发布的一些解决方案(由 @Doblas、@Lance Cleveland 和 @AbdullahAJM)。

此外,我的解决方案尝试发送 413 Payload Too Large 错误(而不是 200 OK),这当然只有在 php.ini 未配置为显示警告时才有可能。

// 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);
}

0
投票

我的2美分。同样的想法,比其他答案轻得多。 只需拨打

checkPostSize();
即可。如果超过,它将以正确的 HTTP 代码结束,并记录错误。

function checkPostSize() {
  preg_match('~(\d+)([KMG])*~', ini_get('post_max_size'), $matches);
  list(, $number, $unit) = $matches;
  $postMaxSize = $number * ($unit ? pow(1024, ['K' => 1, 'M' => 2, 'G' => 3][$unit]) : 1);

  if ((int)$_SERVER['CONTENT_LENGTH'] > $postMaxSize) {
    error_log('post_max_size of ' . ($postMaxSize / pow(1024, 2)) . 'M exceeded: ' . ~~($_SERVER['CONTENT_LENGTH'] / pow(1024, 2)) . 'M received.');
    http_response_code(413);
    exit;
  }
}

另请注意,当您的有效负载足够大时,Chrome 会逐出检查器的响应,您将看不到任何内容(这并不意味着您的 PHP 已损坏)。因此,为此目的记录错误是个好主意。

© www.soinside.com 2019 - 2024. All rights reserved.