File traffic on the web is essentially unidirectional, i.e. files are downloaded from the server to the client. Sometimes we want to upload files from the client to the server.
The HTML form input element of type file is now well supported by browsers. This requires setting the enctype attribute of the form to be "multipart/form-data"
<form action="upload.php" method="post" enctype="multipart/form-data"> <p> Your name: <input type="text" name="userName" /><br /><br /> Small image to upload: <input type="file" size="40" name="userFile" /><br /><br /> <input type="submit" value="Upload file" /> </p> </form>
The uploaded file is stored in the TEMPDIR on the server (/tmp on Unix) and automatically deleted when the script ends. To preserve the file the PHP code must copy it to a suitable location on the server.
Let us look at how PHP makes this quite simple.
<?php echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/tr/xhtml1/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-gb"> <head> <title>WAT PHP Lecture Notes - File Upload</title> <link href="php.css" rel="stylesheet" type="text/css" /> </head> <body> <h1>Uploading Files</h1><p> You submitted this file:<br /><br /> Temporary name: <?php echo $_FILES['userFile']['tmp_name'] ?><br /> Original name: <?php echo $_FILES['userFile']['name'] ?><br /> Size: <?php echo $_FILES['userFile']['size'] ?> bytes<br /> Type: <?php echo $_FILES['userFile']['type'] ?></p> <?php $fna = explode('.', $_FILES['userFile']['name']); $ext = $fna[count($fna)-1]; // check that we have an image file smaller than 4k bytes if ( !preg_match('/gif|png|jpeg/', $_FILES['userFile']['type']) ) { echo "<p><strong>Sorry, only browser compatible images allowed</strong></p>"; } else if ( !preg_match('/gif|png|jpg|jpeg/', $ext) ) { echo "<p><strong>Sorry, only browser compatible images allowed</strong></p>"; } else if ( $_FILES['userFile']['size'] > 4096 ) { echo "<p><strong>Sorry file too large</strong></p>"; // check we have a suitable name for the file } else if ( strlen($_POST['userName']) < 3 ) { echo "<p><strong>Sorry userName too short<br />min 3 characters</strong></p>"; } else { // rename and copy the file to the uploads directory $filename = $_POST['userName'] . '.' . $ext; if ( copy($_FILES['userFile']['tmp_name'], "uploads/$filename") ) { // set group rw permission to allow deleting of the upload if ( chmod("uploads/$filename", 0664) ) { echo "<p><strong>File successfully copied</strong></p>\n"; } else { echo "<p><strong>Error: failed to chmod file</strong></p>"; } } else { echo "<p><strong>Error: failed to copy file</strong></p>"; } // output a list of all uploads echo "<p><strong>List of entries</strong></p>\n<ul>"; $handle = opendir('uploads'); while ($entry = readdir($handle)) { if ( preg_match('/(\.gif|\.png|\.jpg|\.jpeg)$/', $entry) ) echo "<li><a href=\"uploads/$entry\">$entry</a></li>\n"; } echo "</ul>"; closedir($handle); } ?> </body> </html>
The input type="file" form element causes the a file to be uploaded to the server and results in a number of PHP variables for the file's original name, temporary name in TEMPDIR, it's size in bytes and it's mime type.
Temporary name: <?php echo $_FILE['userFile']['tmp_name'] ?><br /> Original name: <?php echo $_FILE['userFile']['name'] ?><br /> Size: <?php echo $_FILE['userFile']['size'] ?> bytes<br /> Type: <?php echo $_FILE['userFile']['type'] ?></p>
Whenever software interacts you must anticipate problems.
Things can go wrong when interfacing to a filesystem or a database.
Always code defensively.
Include code to handle exceptions gracefully.
Keep the user informed - but try not to disclose too much information.
There are some risks in allowing people to upload files onto your server:
A little care in scripting and server configuration can avoid most problems.
For example, the upload_max_filesize directive in the PHP configuration file php.ini protects the server from overloading.
The above example restricts file types to image/gif|png|x-png|jpeg and checks the file size after upload, before copying the file to a permanent store.
Why?
Note how the script lists the contents of the upload directory and formats an anchor tag if the file is an image.
PHP scripts (and CGI scripts) execute as the user ID that is running the webserver, i.e. the http daemon httpd.
stuweb.cms.gre.ac.uk runs as user ID httpd.
There are many ways that this can be achieved, the easiest is something like:
drwxrwxrwx 2 mk05 staff 512 Nov 24 15:36 uploads/
Although this would be better - why?
drwxrwx--- 2 httpd webmaster 512 Nov 24 15:36 uploads/
Note the use of chmod() in Upload.php to change the permissions of the uploaded files.
Many Unix commands are available as functions in PHP. Look in the manual to find out more.
These issues are less pronounced on Windoze platforms - is this a good thing?
Image files can be quite sensitive data and it will be some time before a computer is capable of reliably moderating even text, let alone images.
It is probably a good idea to keep uploaded images private until a human has inspected the contents.
The uploaded files will be owned by httpd but you probably want to claim ownsership.
This is not a problem as they are mode 644 in a directory owned by you, so you can delete, copy or move the files and the copied/moved files will be owned by you and not httpd.
Unfortunately Unix does not intrinsically allow a user to chgrp a file into a group that they are not a member of.
Fortunately the Apache webserver stuweb.cms.gre.ac.uk runs on a Solaris operating system that supports access control lists (acl).
acls can be set using setfacl and read using getfacl.
For the uploads directory the acl can be created with appropriate permissions:
> mkdir uploads > chmod 700 uploads > setfacl -s user:httpd:rwx,user::rwx,group::---,mask:rwx,other:--- uploads
The presence of an acl is indicated by a + at the end of the file permissions reported by ls.
> ls -la uploads total 6 drwx------+ 2 mkg01 student 512 Dec 2 01:29 . drwx------+ 4 mkg01 student 2048 Dec 1 17:27 .. > getfacl uploads # file: uploads/ # owner: mkg01 # group: student user::rwx user:httpd:rwx #effective:rwx group::--- #effective:--- mask:rwx other:---
More information on acl can be found by reading the man pages for setfacl and getfacl. If you are still getting to grips with Unix there are plenty of good web resources available. Google for "Unix primer" or "Unix tutorial", make use of our own Unix Guide or look at Southampton University's excellent Unix Primer.
<?php $handle = opendir('uploads'); while ($entry = readdir($handle)) { if ( preg_match('/(\.gif|\.png|\.jpg|\.jpeg)$/', $entry) ) { $filename = 'uploads/' . $entry; $imagesize = getImageSize($filename); echo "<img src=\"$filename\" $imagesize[3] alt=\"$entry\" \> \n"; } } closedir($handle); ?>
getImageSize() returns an array containing the image dimensions. The fourth array entry is a string that can be used in an img tag.