File Uploads in PHP

File Uploads in PHP allow users to upload both text and binary files. With PHP’s authentication and file manipulation functions, you have full control over who is allowed to upload and what is to be done with the file once it has been uploaded.

First of all, you’ll need to check that your server’s file_uploads configuration value is enabled. This will usually be present in the php.ini configuration file.

file_uploads = on

The HTML

A simple HTML form to upload a single file may look like the following:

<form action="upload.php" method="post" enctype="multipart/form-data">
    Select file to upload:
    <input type="file" name="userfile" id="userfile">
    <input type="submit" value="Upload Image" name="submit">
</form>

There are a few important things that we need to have in the HTML form for file uploads to work:

  • Make sure that the form uses method=”post”
  • The form also needs the following attribute: enctype=”multipart/form-data”. It specifies which content-type to use when submitting the form
  • There should be at least one <input type=”file” /> input field. This will show a “Choose File” button that will allow the user to browse their local machine to upload their file

The PHP

The global $_FILES will contain all the uploaded file information. Its contents from the example form is as follows.

$_FILES['userfile']['filename'] // The original name of the file on the client machine.

Note that this assumes the use of the file upload name “userfile”, as used in the form example above. This can be any name. Other file information about the file can be obtained by the following:

$_FILES['userfile']['type'] // The mime type of the file, if the browser provided this information. An example would be "image/gif". This mime type is however not checked on the PHP side

$_FILES['userfile']['size'] // The size, in bytes, of the uploaded file.

$_FILES['userfile']['tmp_name'] // The temporary filename of the file in which the uploaded file was stored on the server.

$_FILES['userfile']['error'] // The error code associated with this file upload.

Files will, by default be stored in the server’s default temporary directory, unless another location has been given with the upload_tmp_dir directive in php.ini. The directive value may look like the following:

upload_tmp_dir = /tmp/php

Others useful file-related configuration values can be found below.

  • upload_max_filesize – The maximum size of an uploaded file.When an integer is used, the value is measured in bytes. Shorthand notation, as described in this FAQ, may also be used.
  • max_file_uploads – The maximum number of files allowed to be uploaded simultaneously. Starting with PHP 5.3.4, upload fields left blank on submission do not count towards this limit.

File Upload Validation

Using the example form above, the upload.php file specified in the form action is responsible for handling the upload of the file, including some validation on the file sizes and the saving of the file itself in a particular location on the server.

// Undefined | Multiple Files | $_FILES Corruption Attack
// If this request falls under any of them, treat it invalid.
if (
    !isset($_FILES['upfile']['error']) ||
    is_array($_FILES['upfile']['error'])
) {
    throw new RuntimeException('Invalid parameters.');
}

Check for file upload errors:

// Check $_FILES['upfile']['error'] value.
switch ($_FILES['upfile']['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.');
}

A check for a maximum filesize should also be made:

// You should also check filesize here.
if ($_FILES['upfile']['size'] > 1000000) {
    throw new RuntimeException('Exceeded filesize limit.');
}

File mime types can be faked as they’re not checked PHP-side. So it’s important that a list of allowed mime types should be specified:

// DO NOT TRUST $_FILES['upfile']['mime'] VALUE !!
// Check MIME Type by yourself.
$finfo = new finfo(FILEINFO_MIME_TYPE);
if (false === $ext = array_search(
    $finfo->file($_FILES['upfile']['tmp_name']),
    array(
        'jpg' => 'image/jpeg',
        'png' => 'image/png',
        'gif' => 'image/gif',
    ),
    true
)) {
    throw new RuntimeException('Invalid file format.');
}

It’s also a good idea to rename the uploaded file to a safe unique name.

if (!move_uploaded_file(
    $_FILES['upfile']['tmp_name'],
    sprintf('./uploads/%s.%s',
        sha1_file($_FILES['upfile']['tmp_name']),
        $ext
    )
)) {
    throw new RuntimeException('Failed to move uploaded file.');
}

Note: This article is based on PHP version 5.5.