Detecting file size overflow in PHP

09 Jun 2010

Archive Notice: This post is an archive post from my older blog. Comments are disabled and the information contained in it may no longer be accurate.

One of the things that separates a good web application from a great one is how gracefully they handle failures. One of the often overlooked cases is when a user attempts to upload a file that exceeds the set PHP upload file size. This article shows how to detect when the user tries to upload a file that is too large and display an appropriate message.

This article assumes that you have already set upload_max_filesize, post_max_size, and memory_limit in your php.ini file to appropriate values. It also assumes that you already have a working file upload form. There are plenty of tutorials out there already to get you started.

If you can, you may want to set post_max_size to a low value (say 1M) to make testing easier.

First test to see how your script behaves. Try uploading a file that is larger than post_max_size. If you do you will get a message like this in your error log:

[09-Jun-2010 19:28:01] PHP Warning:  POST Content-Length of 30980857 bytes exceeds the limit of 2097152 bytes in Unknown on line 0

If you’re not careful this can lead to unexpected behavior in your application. The end result can range from silent failure all the way to lost customers.

Solving the problem

The PHP documentation provides a hack to solve this problem:

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.

Source: PHP manual

To be clear, it is suggesting that you pass a value in the query string along with your form. If the value is in the $GET superglobal and both $FILE and $_POST are empty then the maximum upload size is exceeded. There are two problems with this approach: it adds extra complexity on the front-end and it can potential give a false positive.

Extra complexity on the front-end means extra documentation and more room for mistakes. And if there is a mistake it may not be caught for a long time (does your QA team routinely upload large files?). In this case we already have all the data that we need to determine if the maximum file size was exceeded without adding extra complexity and headache for developers.

We know what type of request is being processed, we have the $POST and $FILES arrays, and we have the content length as it was passed to the HTTP server from the client. From that we get this code:

<?php

if ( $_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST) &&
     empty($_FILES) && $_SERVER['CONTENT_LENGTH'] > 0 )
{      
  $displayMaxSize = ini_get('post_max_size');
 
  switch ( substr($displayMaxSize,-1) )
  {
    case 'G':
      $displayMaxSize = $displayMaxSize * 1024;
    case 'M':
      $displayMaxSize = $displayMaxSize * 1024;
    case 'K':
       $displayMaxSize = $displayMaxSize * 1024;
  }
 
  $error = 'Posted data is too large. '.
           $_SERVER[CONTENT_LENGTH].
           ' bytes exceeds the maximum size of '.
           $displayMaxSize.' bytes.";
}

?>

The important thing to notice is the “if” statement on lines one and two. The example code just sets an error string. Production code might display a message to the user, execute some Javascript (for asynchronous uploads), or pass back a XML or Json object for Flash clients.

I’ve tested this code with Apache as both a module and as CGI. As far as I know it should work fine with IIS as well.