Uploading Files with PHP

Share this article

What do pictures in an online photo album, email attachments in a web-based mail client, and data files submitted to an online application for batch processing all have in common? They all rely on the ability to upload files across the Internet from the user’s web browser. Indeed, uploading files is an important feature of many of the sites and web-based applications we use on a daily basis. In this post, I show you how to add support for file uploads to your site using PHP.

Key Takeaways

  • Adding support for file uploads in PHP requires creating an HTML form for the user and a PHP script to handle the uploaded file on the server. The
    element must use the POST method and have an enctype attribute set to multipart/form-data.
  • Uploaded files are first stored in a temporary directory. The PHP script responsible for handling the form post verifies the file and processes it, often moving it from its temporary location to a more permanent home.
  • Information about the file upload is made available with the multidimensional $_FILES array. The move_uploaded_file() function moves an uploaded file from its temporary to permanent location, performing additional checks to ensure the file was indeed uploaded by the HTTP POST request.
  • There are security risks associated with allowing file uploads. To mitigate these, one can verify the type of the uploaded file, impose hard limits on upload traffic, and scan for viruses. It’s also important to ensure a safe filename by replacing any characters with an underscore that aren’t a letter, number, or a member of a very restricted set of punctuation.

Requirements

Handling file uploads isn’t difficult, but there are a handful of small details that must be correct or else the upload will fail. First, you need to ensure PHP is configured to allow uploads. Check your php.ini file and verify the file_uploads directive is set On.
file_uploads = On
Uploaded files are first stored in a temporary directory (don’t worry… your PHP script can move the files to a more permanent location afterward). By default, the initial location is the system’s default temporary directory. You can specify a different directory using the upload_tmp_dir directive in php.ini. Regardless, you should verify the PHP process has the proper privileges to write to whichever directory is used.
upload_tmp_dir = "/tmp"

tboronczyk@zarkov:~$ ls -l / | grep tmp
drwxrwxrwt  13 root root 40960 2011-08-31 00:50 tmp
After you’re certain the configuration allows the server to accept uploaded files, you can focus your attention on the HTML details. As with most other server-side interactions from HTML, uploading files make use of forms. It is imperative that your <form> element uses the POST method and has an enctype attribute set to multipart/form-data.
<form action="upload.php" method="post" enctype="multipart/form-data">

Scripting the Upload Process

You can probably guess the workflow file uploads go through based on your own experiences and the requirement checks I’ve just mentioned.
  • A visitor views an HTML page with a form specifically written to support file uploads
  • The visitor provides the file he wants to upload and submits the form
  • The browser encodes the file and sends it as part of the POST request it makes to the server
  • PHP receives the form submission, decodes the file and saves it in a temporary location on the server
  • The PHP script responsible for handling the form post verifies the file and processes it in some manner, often moving it from its temporary location to a more permanent home
Adding support for file uploads requires you to create an HTML form to be presented to the user and a PHP script to take care of the uploaded file on the server.

HTML

HTML forms provide the interface through which a user initiates a file upload. Remember, the <form> element must have its method attribute set to post and its enctype attribute set to multipart/form-data. A file <input> element provides the a field used to specify the file that will be upload. Like any other form element, it is important you provide a name attribute so you can reference it in the PHP script that processes the form. Here’s what markup for a basic file upload form looks like:
<form action="upload.php" method="post" enctype="multipart/form-data"> 
 <input type="file" name="myFile">
 <br>
 <input type="submit" value="Upload">
</form>
It’s worth noting that different browsers will render the file field differently. IE, Firefox, and Opera display it as a text field with a button next to it labeled “Browse” or “Choose.” Safari renders it just as button labeled “Choose File.” This isn’t a problem most of the time since users are accustomed to how the field renders in their browser of choice and know how to use it. Occasionally, however, you will be faced with a client or designer who is adamant on presenting it a certain way. The amount of CSS and JavaScript that can be applied to a file field is extremely limited because of security reasons imposed by the browsers. Styling the file field can be difficult. If appearance is important for you, I recommend you check out Peter-Paul Koch’s Styling an input type=”file”
.

PHP

Information about the file upload is made available with the multidimensional $_FILES array. This array is indexed by the names assigned to the file fields in the HTML form, just as how $_GET and $_POST are indexed. Each file’s array then contains the following indexes:
  • $_FILES["myFile"]["name"] stores the original filename from the client
  • $_FILES["myFile"]["type"] stores the file’s mime-type
  • $_FILES["myFile"]["size"] stores the file’s size (in bytes)
  • $_FILES["myFile"]["tmp_name"] stores the name of the temporary file
  • $_FILES[“myFile”][“error”] stores any error code resulting from the transfer
The move_uploaded_file() function moves an uploaded file from its temporary to permanent location. You should always use move_uploaded_file() over functions like copy() and rename() for this purpose because it performs additional checks to ensure the file was indeed uploaded by the HTTP POST request. If you plan on saving a file with the original filename provided by the user, it’s a good idea to make sure it’s safe to do so. The filename should not contain any characters that can affect the destination path, such as a slash. The name shouldn’t cause the file to overwrite an existing file with the same name, either (unless that’s what your application is designed to do). I ensure a safe filename by replacing any characters with an underscore that aren’t a letter, number, or a member of a very restricted set of punctuation, and then append an incrementing number when a file by that name already exists. Here’s what receiving and processing a file upload with PHP looks like:
<?php
define("UPLOAD_DIR", "/srv/www/uploads/");

if (!empty($_FILES["myFile"])) {
    $myFile = $_FILES["myFile"];

    if ($myFile["error"] !== UPLOAD_ERR_OK) {
        echo "<p>An error occurred.</p>";
        exit;
    }

    // ensure a safe filename
    $name = preg_replace("/[^A-Z0-9._-]/i", "_", $myFile["name"]);

    // don't overwrite an existing file
    $i = 0;
    $parts = pathinfo($name);
    while (file_exists(UPLOAD_DIR . $name)) {
        $i++;
        $name = $parts["filename"] . "-" . $i . "." . $parts["extension"];
    }

    // preserve file from temporary directory
    $success = move_uploaded_file($myFile["tmp_name"],
        UPLOAD_DIR . $name);
    if (!$success) { 
        echo "<p>Unable to save file.</p>";
        exit;
    }

    // set proper permissions on the new file
    chmod(UPLOAD_DIR . $name, 0644);
}
The code first makes sure the file uploaded without any errors. It then determines a safe filename as I just described, and then moves the file to its final directory using move_uploaded_file(). Finally, there is a call to chmod() to make sure sane access permissions are set on the new file.

Security Considerations

Most of us wouldn’t let complete strangers store random files on our personal computers, and yet that is exactly what you are doing when you allow file uploads in our application. You may intend for a user to upload a picture of himself for a profile page, but what if he tries to upload a specially-crafted, virus-laden executable instead? I’d like to share a few steps that you can take to minimize the security risks inherent in allowing file uploads. One is to verify the type of the uploaded file is what it should be. Relying on either the value of $_FILES["myFile"]["type"] or on the filename’s extension isn’t secure because both can easily be spoofed. Rather, use a function like exif_imagetype() to examine the contents of the file and determine if it is indeed a GIF, JPEG, or one of several other supported image formats. If exif_imagetype() isn’t available (the function requires the Exif extension to be enabled), then you can use getimagesize(). The array returned by getimagesize() will contain the image type if it is recognized.
<?php
// verify the file is a GIF, JPEG, or PNG
$fileType = exif_imagetype($_FILES["myFile"]["tmp_name"]);
$allowed = array(IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG);
if (!in_array($fileType, $allowed)) {
    // file type is not permitted
    ...
For non-image files, you can use exec() to invoking the unix file utility. file determines a file’s type by looking for known binary signatures in expected locations.
<?php
// verify the file is a PDF
$mime = "application/pdf; charset=binary";
exec("file -bi " . $_FILES["myFile"]["tmp_name"], $out);
if ($out[0] != $mime) {
    // file is not a PDF
    ...
Another step you can take is to impose hard limits on the total size of the POST request and the number of files that can be uploaded. To do so, specify an appropriate value for the upload_max_size
, post_max_size, and max_file_uploads directives in php.ini. The upload_max_size directive specifies the maximum size a file upload can be. In addition to the size of the upload, you can limit the size of the entire POST request with the post_max_size directive. max_file_uploads is a newer directive (added in version 5.2.12) which limits the number of file uploads. These three directives help protect your site against attacks that try to disrupt its availability by causing heavy network traffic or system load.
post_max_size = 8M
upload_max_size = 2M
max_file_uploads = 20
A third step you can take to minimize your risk is to scan uploaded files with a virus scanner. This is vitally important in this day and age of widespread viruses and malware, especially if your site later makes uploaded files available for download by other individuals, such as with attachments in a web-based email client or a (legal) file-sharing site. There is a PHP extension that provides access to ClamAV, but of course you can invoke ClamAV’s command-line utility in much the same way I demonstrated for file.
<?php
exec("clamscan --stdout " . $_FILES["myFile"]["tmp_name"], $out, $return);
if ($return) {
    // file is infected
    ...

Summary

You’ve learned how easy it is to support file uploads with your site or web-based application. For the upload to succeed, the HTML form must be submitted via a multipart/form-data encoded POST request, and PHP must permit the transfer as specified using the file_uploads directive. After the file is transferred, the script responsible for handling the upload uses the information found in the $_FILES array to move the file from the temporary directory to the desired location. I also shared some extra precautions that you can take to protect yourself and your users from some of the risks associated with allowing file uploads. You saw how you can ensure the filename is safe, verify the file type, impose hard limits on upload traffic, and scan for viruses. For those who may be interested, supplemental code for this article is available on GitHub. You can view, download, or clone the repository and play with the code to get a better understanding of how the process of uploading files works. And if you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like Jump Start PHP. Comments on this article are closed. Have a question about PHP? Why not ask it on our forums? Image via VolsKinvols / Shutterstock

Frequently Asked Questions (FAQs) about PHP File Uploads

What are the security risks associated with PHP file uploads and how can they be mitigated?

PHP file uploads can pose several security risks. For instance, an attacker might upload a malicious file to your server, which could lead to unauthorized access or data loss. To mitigate these risks, you should always validate the file type and size before uploading. You can use the $_FILES['uploadedfile']['type'] and $_FILES['uploadedfile']['size'] variables in PHP to check these parameters. Additionally, you should store uploaded files in a directory outside the webroot to prevent direct access.

How can I limit the size of files being uploaded using PHP?

You can limit the size of files being uploaded using the MAX_FILE_SIZE hidden field in your HTML form. This field accepts a value in bytes. However, this is just a guideline for the browser and can be easily bypassed. For a more secure validation, use the $_FILES['uploadedfile']['size'] variable in PHP to check the file size after it has been uploaded.

How can I handle multiple file uploads in PHP?

PHP allows you to handle multiple file uploads using the $_FILES array. When you set the name attribute of your file input field to an array (e.g., name="uploadedfiles[]"), PHP will automatically create a multidimensional array containing the details of all uploaded files. You can then loop through this array to handle each file individually.

Why am I getting an ‘Undefined index’ error when trying to access $_FILES['uploadedfile']?

This error usually occurs when you try to access $_FILES['uploadedfile'] before a file has been uploaded. Make sure your form’s enctype attribute is set to "multipart/form-data" and that you’re checking whether a file has been uploaded using isset($_FILES['uploadedfile']) before trying to access it.

How can I display a progress bar for file uploads in PHP?

Displaying a progress bar for file uploads in PHP requires a combination of PHP and JavaScript. PHP’s session.upload_progress feature allows you to track the progress of file uploads, which you can then display using JavaScript. However, this feature is only available in PHP 5.4.0 and later.

How can I upload files to a different server using PHP?

Uploading files to a different server requires a combination of PHP and FTP (File Transfer Protocol). You can use PHP’s built-in FTP functions to connect to the remote server, upload the file, and then close the connection.

How can I handle file upload errors in PHP?

PHP provides several error codes that can help you handle file upload errors. These codes are stored in the $_FILES['uploadedfile']['error'] variable. For instance, an error code of 1 means the uploaded file exceeds the upload_max_filesize directive in php.ini.

How can I upload files asynchronously using PHP and AJAX?

Asynchronous file uploads can be achieved using a combination of PHP, AJAX, and JavaScript’s FormData object. The FormData object allows you to create a set of key/value pairs representing form fields and their values, which you can then send using AJAX.

How can I rename uploaded files in PHP?

You can rename uploaded files using the move_uploaded_file() function in PHP. This function moves an uploaded file to a new location, and you can specify a new name for the file in the destination path.

How can I upload files to a database using PHP?

Uploading files to a database involves storing the file in a BLOB (Binary Large Object) field. However, this is generally not recommended as it can make the database significantly larger and slower. A better approach is to upload the file to the file system and then store the path to the file in the database.

Timothy BoronczykTimothy Boronczyk
View Author

Timothy Boronczyk is a native of Syracuse, New York, where he lives with no wife and no cats. He has a degree in Software Application Programming, is a Zend Certified Engineer, and a Certified Scrum Master. By day, Timothy works as a developer at ShoreGroup, Inc. By night, he freelances as a writer and editor. Timothy enjoys spending what little spare time he has left visiting friends, dabbling with Esperanto, and sleeping with his feet off the end of his bed.

Beginner
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week