File Uploads
June 25, 2014



uploads

Making a script that allows for file uploads is a common problem for many web developers just starting out, and can sometimes be a little tricky for those just starting out. Another problem comes from the wide variety of different examples you will find regarding uploading files using PHP. The examples range from super simple to extremely elaborate, and often the more complex examples are hard to understand since they are just displayed with no real explanation. In this tutorial, I wanted to go over a relatively elaborate (but very secure) file upload script, but explain each individual part and what it does, so that you can use it with confidence if you are just starting out.

The Form

Before we take a look at the PHP (which will be the heart of the tutorial), we need to look at the HTML form that will deliver us the file. Creating a form with a file is extremely easy, since there is a type=”file” for HTML <input> tags. For example, if we wanted to upload a resume, the HTML form would look like:

Notice that in this example we will be posting to the same page as the form with embedded PHP at the top of the page to handle the post. While this may not be the case all the time, it happens quite a bit so I felt it was acceptable for this tutorial. It’s common to upload a file, and then stay on that same page so you can review what you uploaded and finally click a “Next” button when you are done. That’s all we need on the HTML side, so now its time to get started on the PHP script itself.

The Script

When writing your PHP script, its important to remember that a lot of different checks need to be made in order to ensure proper security. Allowing people to just throw any file they want on your server will quickly lead to security problems. This means there will be a lot of error checking throughout the script, so its best to plan ahead on how you want to handle those errors. In this case, since we are posting to our own page, I like to error log the exceptions and possibly display them to the user as well. This can be achieved quite easily with the following setup:

As you can see, we first check to make sure the form has been submitted by checking $_POST[‘submit’] to make sure it is set and that it’s value is correct. After that, we have a try { … } catch ( … ) { … } loop where all of our checks will take place. This allows us to catch the first error that occurs, right it out to the error log, and if we wanted, we could echo the $ex variable to the user below to let them know what happened as well.

Check for an Undefined File, Multiple Files, and a $_FILES Corruption Attack

Although the title of this sub section may seem complicated at first, we can actually check for all these things at the same time with a simple if ( … || … ) statement (please note that I will show each of the parts individually and finally combine them all together to show the final working page) which can be seen below:

In the above code, the if ( !isset($_FILES[‘resume’][‘error’]) ) checks that the file is not undefined, and then the if ( is_array($_FILES[‘resume’][‘error’]) ) checks to make sure that there are no multiple files (there would be an array of course if there was multiple files) and for corruption attacks.

Now seems like a good time to explain that the $_FILES variable is a 2D array. Each file has many parameters associated with it, such as error, , and . Of course there are many more, and if you want to read about them please check out the PHP Manual Page for $_FILES for more information.

Check the File’s Error Value

The next thing we want to check is the actual value of $_FILES[‘resume’][‘error’] to ensure there is no other error occurring. This is simple an integer value, but luckily there are predefined constants that we can use to make our code more readable. The code looks like:

We use a switch ( … ) statement to quickly check the value of the error, and in every case other than no error we throw the appropriate error with a small description of the error. As a reminder, these will be output to the error log and if we wish, we could echo them out to the user.

As is usually the case, if you would like to know more please check out the PHP Manual Page for Upload Errors to learn more about each error.

You will also want to check the file’s size once more if you have a different allowed size for this particular upload than the one set in your php.ini file. It’s also just good practice to do the check again just in case. The second check uses the actual [‘filesize’] attribute and looks like:

Checking the File’s MIME Type

Checking your file’s MIME type is probably the most important step of your upload script in terms of security. It’s extremely important that the user cannot upload any PHP, MySQL, etc… file types that could run and cause damage to your server or site. Since this is such an important step, its important to never rely on $_FILES[‘resume’][‘mime’] but rather to check the MIME type for yourself. The code is below:

There is a new attribute that we have not seen yet in this code, the [‘tmp_name’] attribute. This is the name given to the file that was put in the temporary file folder by PHP. This where the actual file is on your server when you post the file and before you move it to your uploads directory. We make sure we test this file for its file type because it is hard to fake, where as the $_FILES[‘resume’][‘mime’] is easy to fake.

The most complicated process of the above code is the if ( false === array_search($extension, $allowed_extensions) ) statement. The reason for using the === is that array_search does not necessarily return a boolean. If more than one instance of the needle (the thing we are searching for) is found in the haystack (the thing we are searching) then it will return the index of the first instance rather than a boolean. For this reason we have to use the === to check (read more about it on the Manual Page for array_search).

Checking if the File Already Exists

The next thing we need to do is check if a file with the same name already exists in our uploads directory. Note that in my example, this simply gives an error, but you could just as easily change the name of the file by appending something to the end and then still move it into your uploads directory. The code is below:

There is also a large group of people who believe that you should name the file uniquely rather than letting it have the same name that the user gave it. This would definitely improve security, but unfortunately the user usually gets confused if they see there file again later on and its named “ahgh131b9u1bv9u1.txt” rather than “resume.txt”, and while there are workarounds for this (such as saving the original file name in the database along side the new one, and just replacing instances of it) that isn’t really practical for this example. But please note that on a project for a client it is best to name the file uniquely for security.

Move the File

Finally we need to move the file to the uploads directory. This can be achieved by using the very handy move_uploaded_file( … ) method. This takes in the [‘tmp_name’] attribute as the first parameter and the destination as the second parameter. It also returns a boolean notifying the user if the operation completed or not. With all of this in mind, our final bit of code will look like:

As you can see, we use the $uploads_dir variable again to determine the uploads directory. Then we simply try to move the temporary file to the new location.

The Completed Script

Below is a copy of the entire PHP script start to finish:

The Complete Page

And below this section is a copy of the complete page including the HTML form below the PHP (note that this page would have to have the .php extension):

And that’s all there is to uploading. I hope that you find this tutorial to be very informative but still easy to follow. I know there is a lot of steps to do an upload, but my hope is that by breaking them down you understand each one and how they work together. If you have any questions or concerns be sure to let me know in the comments below and I’ll try my best to answer them.

As always thank you for reading and please share it around as much as you can! Please feel free to put any suggestions or ideas for future tutorials in the comments section below.


Comments