PHP

PHP Upload Script Example

In this example, we are going to see how to make file uploads with PHP, that is, how to upload a file from the client side, to the server side.

The process itself is pretty simple, but several checks have to be done to ensure that the file upload is made safely, i.e., to neutralise any hypothetical malicious misuse; and to be able to detect every possible error to act accordingly, so, this is something that must be done carefully.

For this tutorial, we will use:

  • Ubuntu (14.04) as Operating System.
  • Apache HTTP server (2.4.7).
  • PHP (5.5.9).
Tip
You may skip environment preparation and jump directly to the beginning of the example below.

1. Preparing the environment

1.1. Installation

Below, are shown the commands to install Apache and PHP:

sudo apt-get update
sudo apt-get install apache2 php5 libapache2-mod-php5
sudo service apache2 restart

1.2. PHP configuration

Is necessary to configure PHP to allow file uploads. With your favourite editor, open the php.ini file:

sudo vi /etc/php5/apache2/php.ini

And check that the file_uploads directive value is set to On:

file_uploads = On

If you want, you can change the upload_max_filesize directive, to change the maximum size allowed for upload.

Don’t forget to restart Apache after doing any change.

Note: if you want to use Windows, installing XAMPP is the fastest and easiest way to install a complete web server that meets the prerequisites.

2. Upload form

First, we need to create a form, where the file will be attached. It should be similar to this:

upload_form.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Upload form</title>
</head>
<body>
    <form action="upload.php" method="post" enctype="multipart/form-data">
        <div>
            <label for="file">Select a file to upload:</label>
            <input type="file" name="file" id="file">
        </div>
        <div>
            <input type="submit" value="Upload file!">
        </div>
    </form>
</body>
</html>

There are several things to take into account:

  • Form action must point to the PHP file that will perform the upload (this is quite obvious).
  • Submission method must be POST.
  • Form enctype attribute must be specified, with multipart/form-data value. This is not to make the form encode the characters.

2.1. Accepting only certain type of files

It is possible to restrict the uploading files to only expected file types. This can be made in several ways:

  • By file extension. For example, if we are expecting a document, we can restrict the extensions to .pdf and .doc.
  • By file type. We can directly say that we are expecting an image, an audio or/and a video.
  • By media type, specifying the MIME type. An example could be text/plain, if we are expecting plain files.

All of these can be combinated, so we could say, for example, that we are expecting a document file, and it can be accepted in pdf, doc, docx and odt and format.

Applying this last example to the form above (line 11), the result would be the following:

upload_form.html

<input type="file" name="file" id="file" accept=".pdf,.doc,.docx,.odt">

As you can see, the conditions are sepparated by comma.

Note: keep always in mind that client-side validation is never enough. Every validation we do in the client-side, it should be done also in the server – remember that here is the person in the client-side who has the control, not you!

3. PHP upload script

Once the form is submitted, is time for the server to handle the upload. The following script will do the job:

upload.php

<?php

define('UPLOAD_DIRECTORY', '/var/php_uploaded_files/');

$uploadedTempFile = $_FILES['file']['tmp_name'];
$filename = $_FILES['file']['name'];
$destFile = UPLOAD_DIRECTORY . $filename;

move_uploaded_file($uploadedTempFile, $destFile);

Easy, isn’t it? We just define the target directory where the file will be stored, we get the file PHP uploaded temporarily, original file’s name, and we move that file to the defined target directory, with its name.

But, as we said in the introduction, in this task we must focus the efforts into the security. This script does not check:

  • File size. We should set a maximum allowed size (what if an user is trying to upload a file bigger than server’s available space?)
  • File type. If we are expecting, for example, a document, receiving an executable file would be quite suspicious, wouldn’t it?
  • That the file was properly uploaded to the server, and moved to the target directory. We can’t take anything for granted. With that script, we won’t notice if an error occurs in the upload.

3.1. A secure file upload script

Let’s update our script to make a safe file upload, supposing that we expect only document-type files, as we defined 2.1 section, of pdf, doc, docx and odt format:

upload.php

<?php

define('UPLOAD_DIRECTORY', '/var/php_uploaded_files/');
define('MAXSIZE', 5242880); // 5MB in bytes.

// Before PHP 5.6, we can't define arrays as constants.
$ALLOWED_EXTENSIONS = array('pdf', 'doc', 'docx', 'odt');
$ALLOWED_MIMES = array('application/pdf', // For .pdf files.
    'application/msword', // For .doc files.
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document', // For .docx files.
    'application/vnd.oasis.opendocument.text', // For .odt files.
);

/**
 * Checks if given file's extension and MIME are defined as allowed, which are defined in
 * array $ALLOWED_EXTENSIONS and $ALLOWED_MIMES, respectively.
 *
 * @param $uploadedTempFile The file that is has been uploaded already, from where the MIME
 *     will be read.
 * @param $destFilePath The path that the dest file will have, from where the extension will
 *     be read.
 * @return True if file's extension and MIME are allowed; false if at least one of them is not.
 */
function validFileType($uploadedTempFile, $destFilePath) {
    global $ALLOWED_EXTENSIONS, $ALLOWED_MIMES;

    $fileExtension = pathinfo($destFilePath, PATHINFO_EXTENSION);
    $fileMime = mime_content_type($uploadedTempFile);

    $validFileExtension = in_array($fileExtension, $ALLOWED_EXTENSIONS);
    $validFileMime = in_array($fileMime, $ALLOWED_MIMES);

    $validFileType = $validFileExtension && $validFileMime;

    return $validFileType;
}

/**
 * Handles the file upload, first, checking if the file we are going to deal with is actually an
 * uploaded file; second, if file's size is smaller than specified; and third, if the file is
 * a valid file (extension and MIME).
 *
 * @return Response with string of the result; if it has been successful or not.
 */
function handleUpload() {
    $uploadedTempFile = $_FILES['file']['tmp_name'];
    $filename = basename($_FILES['file']['name']);
    $destFile = UPLOAD_DIRECTORY . $filename;

    $isUploadedFile = is_uploaded_file($uploadedTempFile);
    $validSize = $_FILES['file']['size'] <= MAXSIZE && $_FILES['file']['size'] >= 0;

    if ($isUploadedFile && $validSize && validFileType($uploadedTempFile, $destFile)) {
        $success = move_uploaded_file($uploadedTempFile, $destFile);

        if ($success) {
            $response = 'The file was uploaded successfully!';
        } else {
            $response = 'An unexpected error occurred; the file could not be uploaded.';
        }
    } else {
        $response = 'Error: the file you tried to upload is not a valid file. Check file type and size.';
    }

    return $response;
}

// Flow starts here.

$validFormSubmission = !empty($_FILES);

if ($validFormSubmission) {
    $error = $_FILES['file']['error'];

    switch($error) {
        case UPLOAD_ERR_OK:
            $response = handleUpload();
            break;

        case UPLOAD_ERR_INI_SIZE:
            $response = 'Error: file size is bigger than allowed.';
            break;

        case UPLOAD_ERR_PARTIAL:
            $response = 'Error: the file was only partially uploaded.';
            break;

        case UPLOAD_ERR_NO_FILE:
            $response = 'Error: no file could have been uploaded.';
            break;

        case UPLOAD_ERR_NO_TMP_DIR:
            $response = 'Error: no temp directory! Contact the administrator.';
            break;

        case UPLOAD_ERR_CANT_WRITE:
            $response = 'Error: it was not possible to write in the disk. Contact the administrator.';
            break;

        case UPLOAD_ERR_EXTENSION:
            $response = 'Error: a PHP extension stopped the upload. Contact the administrator.';
            break;

        default:
            $response = 'An unexpected error occurred; the file could not be uploaded.';
            break;
    }
} else {
    $response = 'Error: the form was not submitted correctly - did you try to access the action url directly?';
}

echo $response;

Now, let’s see the key instructions added to this script:

  • We first check the $_FILES superglobal, in line 70. If it’s empty, it would be because the access to this script has not been done through a form submission, so, we shouldn’t perform any action.
  • We get the error property of the superglobal, in line 73. Here is saved the state of the file submission, indicating if it has been done correctly, or if any error occured. The state is later evaluated in the switch, where we perform different actions depending on the state.
  • Getting the basename of the file, in line 47. This PHP built-in gives us the file name. Even if it is defined in $_FILES['file']['name'], it is always recommended to get file names using this function.
  • Checking if the file is actually an uploaded file, in line 50. This function checks that the given file is really a file uploaded through HTTP POST. This check is necessary to avoid malicious actions that may want to have access server files.
  • Checking the size of the file, in line 51. Before proceding, we ensure that the uploading file doesn’t exceed the maximum size set. Because the limit does not always have to be the one set in php.ini.
  • Checking the file type, in line 53. This is probably one of the most interesting checks. Here, we ensure that the receiving file is actually a document, in two steps: getting the file extension (line 27), and the file MIME (line 28). It is not enough checking only one of these. Consider the following scenario: we are checking only the file extension, which is expected to be one of those defined in the array. If an evil user wants to upload an executable file, it only has to change its extension to one of those. But the content type is not modified with the extension. So, we have to check both to ensure that the files are real documents.
  • Finally, we check that the file has been moved correctly to the target directory, with its original name (line 54). The difference here compared with the previous script, is that we retrieve the boolean value returned by the move_uploaded_files function, because it may fail, for any reason. As we said before, we can’t take anything for granted.

3.2. Considerations

You may noticed that the temp files created by PHP ($_FILES['file']['tmp_name']) are not being deleted. This is because we don’t have to care about it; when the script finishes it execution, PHP will delete them for us.

About the checking of allowed files, in some cases, when the upload is not oriented only to a very specific file type, it may be more interesting to have a blacklist where not allowed file types are defined, instead of having a whitelist, like in the script above. Generally, we won’t expect to receive files that can be harmful, like executable files, or even scripts.

4. Summary

With this script, files will be safely uploaded to our server, ensuring that the received file is of the type/types we are expecting (remember that the validation must reside always in the server-side), and also that doesn’t exceed the established size. And, if something goes wrong, we will be able to identify the exact error, that will allow us to take the most appropiate decision for each case.

5. Download the source code

This was an example of file uploading with PHP.

Download
You can download the full source code of this example here: PHPUploadScript
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ramin Ahmad
Ramin Ahmad
7 years ago

That’s great, thank you for sharing :-)
How to allow excel files as well (.xlsx, .xls) ?

Ramin Ahmad
Ramin Ahmad
7 years ago

That’s great, thank you for sharing :-)
How to allow excel files as well (.xlsx, .xls) ?
Also, I was getting error ( Fatal error: Call to undefined function mime_content_type() in c:\xampp\htdocs\test\upload.php on line 28)
Then i searched for that, most people are telling it’s not recommended to use
Also the PHP.net tell: Warning: Mimetype extensions has been removed from PHP 5.3.0 in favor of Fileinfo.
what to do plz ?

Back to top button