PHP makes it easy to upload files to our web server by way of an HTML form.
The upload files are stored in temporary directory with file details stored in a $_FILES super-global.
Upon completion of this module, you should be able to:
$_FILES super-global.<!DOCTYPE html>
<html>
<head><title>File Upload Form</title></head>
<body>
<form method="post" enctype="multipart/form-data">
<label for="image">Image Filename:</label>
<input type="file" name="image" id="image">
<input type="submit" name="submit" value="Upload Image">
</form>
</body>
</html>
This form will allow users to browse for a single local file and upload it when the form is submitted.
Note that the form element has an extra attribute named enctype that has been set to multipart/form-data. This attribute is required in order for the upload dialog to function properly. Also required is a method attribute with a value of post.
Details about each file input in our HTML form will be accessibly by name in the $_FILES super-global.
The name attribute of our file input in the previously shown form was image, so we look in $_FILES['image'].
<?php if (isset($_FILES['image']) && $_FILES['image']['error'] > 0): ?>
<p>Error Number: <?= $_FILES['image']['error'] ?></p>
<?php elseif (isset($_FILES['image'])): ?>
<p>Client-Side Filename: <?= $_FILES['image']['name'] ?></p>
<p>Apparent Mime Type: <?= $_FILES['image']['type'] ?></p>
<p>Size in Bytes: <?= $_FILES['image']['size'] ?></p>
<p>Temporary Path: <?= $_FILES['image']['tmp_name'] ?></p>
<?php endif ?>
Tips & Gotchas:
The file metadata is not accessed through the $_POST super-global, but instead through the $_FILES super-global.
The uploaded file will be deleted from the temporary path automatically after this script finishes executing.
$_FILES["image"]["error"] is an error number:
0: No error. File upload successful.1: Maximum file-size, as specified in php.ini, exceeded.4: No file uploaded with POST. Not actually an error for forms where file uploads are optional.In order to save an uploaded file your PHP script must move it to a known location on your file-system.
<?php
function file_upload_path($original_filename, $upload_subfolder_name = 'uploads') {
$current_folder = dirname(__FILE__);
$path_segments = [$current_folder, $upload_subfolder_name, basename($original_filename)];
return join(DIRECTORY_SEPARATOR, $path_segments);
}
$image_upload_detected = isset($_FILES['image']) && ($_FILES['image']['error'] === 0);
if ($image_upload_detected) {
$image_filename = $_FILES['image']['name'];
$temporary_image_path = $_FILES['image']['tmp_name'];
$new_image_path = file_upload_path($image_filename);
move_uploaded_file($temporary_image_path, $new_image_path);
}
?>
By allowing file uploads we are giving our users write-access to our file-system. This can be a very dangerous move.
Imagine the following scenario:
p0wn.php./uploads/ folder.http://host.ca/uploads/p0wn.phpAn example p0wn.php file:
<?php system($_GET['command']) ?>
This script would allow a hacker to execute any command on your system under the same security permissions as the Apache user. Here is our hacker deleting everything in the current directory:
http://host.ca/uploads/malicious.php?command=rm%20-rf%20.
File uploads can be checked for “image-ness” by looking at the temporary file path, the potential new path, and the file mime-type.
<?php
function file_is_an_image($temporary_path, $new_path) {
$allowed_mime_types = ['image/gif', 'image/jpeg', 'image/png'];
$allowed_file_extensions = ['gif', 'jpg', 'jpeg', 'png'];
$actual_file_extension = pathinfo($new_path, PATHINFO_EXTENSION);
$actual_mime_type = getimagesize($temporary_path)['mime'];
$file_extension_is_valid = in_array($actual_file_extension, $allowed_file_extensions);
$mime_type_is_valid = in_array($actual_mime_type, $allowed_mime_types);
return $file_extension_is_valid && $mime_type_is_valid;
}
?>
If you are only accepting image uploads, then only move the uploaded file when it’s an image:
<?php
$image_upload_detected = isset($_FILES['image']) && ($_FILES['image']['error'] === 0);
if ($image_upload_detected) {
$image_filename = $_FILES['image']['name'];
$temporary_image_path = $_FILES['image']['tmp_name'];
$new_image_path = file_upload_path($image_filename);
if (file_is_an_image($temporary_image_path, $new_image_path)) {
move_uploaded_file($temporary_image_path, $new_image_path);
}
}
?>