How to gracefully handle files that exceed PHP's `post_max_size`?

PhpUpload

Php Problem Overview


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_filesize 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?

Php Solutions


Solution 1 - Php

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.

Solution 2 - Php

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

Solution 3 - Php

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

Solution 4 - Php

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

Solution 5 - Php

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

Solution 6 - Php

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNathan LongView Question on Stackoverflow
Solution 1 - PhpMatt McCormickView Answer on Stackoverflow
Solution 2 - PhpAbdullahAJMView Answer on Stackoverflow
Solution 3 - PhpstaabmView Answer on Stackoverflow
Solution 4 - PhpLance ClevelandView Answer on Stackoverflow
Solution 5 - PhpDoglasView Answer on Stackoverflow
Solution 6 - PhpBogisWView Answer on Stackoverflow