Tools Blog Learn Quizzes Smile API Log In / Sign Up
Tools Blog Learn Quizzes Smile API Log In / Sign Up
« Return to the tutorials list
We have updated the website and our policies to make sure your privacy rights and security are respected.
Click here to learn more about the way our website handles your data.

Remove this message.

Handling image uploads securely in PHP

Difficulty: 30 / 50 Tweet
cloud upload

Today you'll learn how to upload image files to the server with PHP. Specifically, interested web developers will go through every required validation that should be done when uploading files to the server.

First thing's first. Most modern websites allow multiple uploads which is an awesome feature. However web developers should never send multiple files to the server at once by simply using the "multiple" attribute of the files input (Wrong:<input name="files" type="file">).

Why ? Here's one reason pointed out by Neale Poole.

If you want to do multiple file uploads have a look at how the big guys do it. If you inspect GMail file uploads, you will notice that they add the files to an upload queue and then send them individually to the server. I wrote an article a while back on how you can upload files using Ajax. That script can be tweaked to allow queued file uploads by creating a recursive method and sending each file with Ajax once the previous request has finished. If you're having difficulties with that code just let me know in the comments section and I will help you out.

Now that we've established that it's not a good practice to send multiple files to the server at once we can go ahead and start writing our code. Begin with this simple upload form:

  
    <form method="POST" enctype="multipart/form-data">
      <input name="files" type="file">
      <input name="submit" value="submit" type="submit">
    </form>
  

It is a good practice to use Exceptions to handle tasks that require proper logging or which are simply more sensitive in nature so we will do just that. Inside the file that processes your form add the code below and then we will start writing a simple "imageUploader" class.

  
    <?php
    if(isset($_POST['submit']))
    {
      try {
        $isUploaded = imageUploader::upload();
      } catch (Exception $e) {
        echo '<pre>'; var_dump($e); die;
      }

      if($isUploaded===true)
        die("Image is up!");
    }
    ?>
  

For the purpose of this tutorial the "imageUploader" class will only contain a static method called upload(). You can extend "imageUploader" as you wish to turn it into a real interface that handles uploads.

From top to bottom here's what error checks and validations you need to do:

  1. If the $_FILES array doesn't have the $_FILES['file']['error'] constant set it means something is wrong so we throw a Runtime Exception
  2. If the $_FILES['file']['error'] is an array then it means multiple files are being sent which we've already established is not a good practice
  3. Then we check the UPLOAD_ERR_* constants and throw adequate exception messages for each
  4. Checking file size is also important. In my code, I check the file size against the php.ini value of upload_max_filesize
  5. Finally we also need to check if the file is actually an image. To do that properly you shouldn't rely on the "type" value in the $_FILES array because that is not checked by PHP. That value merely represents whatever was sent from the browser which is unsafe because the value can be modified remotely. In order to do proper file type checking I use the finfo functions which are a much better method of checking MIME Types and check that against a predefined array of allowed MIME Types.
  6. Finally when moving the file make sure you give it a unique name. Don't keep the original file name! In this case I use the sha1_file() function which calculates the specified hash based on the contents of the file.

Here's the code:

  
    <?php
    class imageUploader
    {
      const UPLOADS_FOLDER = '.';

      static function upload()
      {
        $imagefile = $_FILES;

        if( !isset($imagefile['files']['error']) )
          throw new RuntimeException('Invalid parameters.');

        //multiple uploads not permitted. you should queue file uploads from the client
        if(is_array($imagefile['files']['error']))
          throw new RuntimeException('Only one file allowed.');

        switch ($imagefile['files']['error']) {
          case UPLOAD_ERR_OK:
            break;
          case UPLOAD_ERR_NO_FILE:
            throw new RuntimeException('No file sent.');
          case UPLOAD_ERR_INI_SIZE:
          case UPLOAD_ERR_FORM_SIZE:
            throw new RuntimeException('Exceeded filesize limit.');
          default:
            throw new RuntimeException('Unknown errors.');
        }

        $max = ini_get('upload_max_filesize') * 1000 * 1000;
        if ($imagefile['files']['size'] > $max)
            throw new RuntimeException('Exceeded filesize limit.');

        //check the file type - but not the one sent by the browser instead use finfo
        $finfo = new finfo(FILEINFO_MIME_TYPE);
        $mime = $finfo->file($_FILES['files']['tmp_name']);
        $allowed = array('jpg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif');
        $ext = array_search($mime, $allowed, true);

        if(false === $ext)
          throw new RuntimeException('Invalid file format.');

        $path = sprintf( self::UPLOADS_FOLDER . '/%s.%s', sha1_file($imagefile['files']['tmp_name']), $ext );
        if (!move_uploaded_file( $imagefile['files']['tmp_name'],$path))
          throw new RuntimeException('Failed to move uploaded file.');
        else
          return true;
      }
    }
    ?>
  

Finally if you will use my code or not is less important than remembering to always ask yourself these questions before writing any upload code in PHP.

  1. What types of files do I want to accept ?
  2. How do I check file types ?
  3. What is the maximum size of the files I want to accept ?
  4. Do I have a naming mechanism for my files ?

Special thanks go to CertaiN who provided the initial code ideas for this article.

comments powered by Disqus