Tuesday, July 14, 2009

Beginning PHP and Oracle From Novice to Professional by W. Jason Gilmore and Bob Bryla Chapter 15

While most people tend to equate the Web with Web pages only, HTTP actually facilitates the transfer of any kind of file, such as Microsoft Office documents, PDFs, executables, MPEGs, zip files,
and a wide range of other file types. Although FTP historically has been the standard means for uploading files to a server, such file transfers are becoming increasingly prevalent via a Web-based interface. In this chapter, you’ll learn all about PHP’s file-upload handling capabilities, in particular, the following:

• PHP’s file-upload configuration directives

• PHP’s $_FILES superglobal array, used to handle file-upload data

• PHP’s built-in file-upload functions: is_uploaded_file() and move_uploaded_file()

• A review of possible error messages returned from an upload script

• An overview of the HTTP_Upload PEAR package

As always, numerous real-world examples are offered throughout this chapter, providing you with applicable insight into this topic.

Uploading Files via HTTP

The way files are uploaded via a Web browser was officially formalized in November 1995, when Ernesto Nebel and Larry Masinter of the Xerox Corporation proposed a standardized methodology for doing so within RFC 1867, “Form-Based File Upload in HTML” (http://www.ietf.org/rfc/ rfc1867.txt). This memo, which formulated the groundwork for making the additions necessary to HTML to allow for file uploads (subsequently incorporated into HTML 3.0), also offered the specifi- cation for a new Internet media type, multipart/form-data. This new media type was desired because the standard type used to encode “normal” form values, application/x-www-form-urlencoded, was considered too inefficient to handle large quantities of binary data such as that which might be uploaded via such a form interface. An example of a file uploading form follows, and a screenshot of the corre- sponding output is shown in Figure 15-1:

<form action="uploadmanager.html" enctype="multipart/form-data" method="post"> Name:<br /> <input type="text" name="name" value="" /><br />
Email:<br /> <input type="text" name="email" value="" /><br />
Class notes:<br /> <input type="file" name="homework" value="" /><br />
<p><input type="submit" name="submit" value="Submit Homework" /></p>
</form>

277

Figure 15-1. HTML form incorporating the file input type tag

Understand that this form offers only part of the desired result; whereas the file input type and other upload-related attributes standardize the way files are sent to the server via an HTML page, no capabilities are offered for determining what happens once that file gets there. The reception and subsequent handling of the uploaded files is a function of an upload handler, created using some server process, or capable server-side language such as Perl, Java, or PHP. The remainder of this chapter is devoted to this aspect of the upload process.

Uploading Files with PHP

Successfully managing file uploads via PHP is the result of cooperation between various configura- tion directives, the $_FILES superglobal, and a properly coded Web form. In the following sections, all three topics are introduced, concluding with a number of examples.

PHP’s File Upload/Resource Directives

Several configuration directives are available for fine-tuning PHP’s file-upload capabilities. These directives determine whether PHP’s file-upload support is enabled, as well as the maximum allowable uploadable file size, the maximum allowable script memory allocation, and various other important resource benchmarks. These directives are introduced next.

file_uploads = On | Off

Scope: PHP_INI_SYSTEM; Default value: 1
The file_uploads directive determines whether PHP scripts on the server can accept file uploads.

max_execution_time = integer

Scope: PHP_INI_ALL; Default value: 30
The max_execution_time directive determines the maximum amount of time, in seconds, that a
PHP script will execute before registering a fatal error.

memory_limit = integerM

Scope: PHP_INI_ALL; Default value: 8M
The memory_limit directive sets a maximum allowable amount of memory, in megabytes, that a script can allocate. Note that the integer value must be followed by M for this setting to work properly. This prevents runaway scripts from monopolizing server memory and even crashing the server in certain situ- ations. This directive takes effect only if the --enable-memory-limit flag is set at compile time.

upload_max_filesize = integerM

Scope: PHP_INI_SYSTEM; Default value: 2M
The upload_max_filesize directive determines the maximum size, in megabytes, of an uploaded file. This directive should be smaller than post_max_size (introduced in the section following the next section) because it applies only to information passed via the file input type and not to all information passed via the POST instance. Like memory_limit, note that M must follow the integer value.

upload_tmp_dir = string

Scope: PHP_INI_SYSTEM; Default value: NULL
Because an uploaded file must be successfully transferred to the server before subsequent processing on that file can begin, a staging area of sorts must be designated for such files as the loca- tion where they can be temporarily placed until they are moved to their final location. This location is specified using the upload_tmp_dir directive. For example, suppose you want to temporarily store uploaded files in the /tmp/phpuploads/ directory. You would use the following:

upload_tmp_dir = "/tmp/phpuploads/"

Keep in mind that this directory must be writable by the user owning the server process. Therefore, if user nobody owns the Apache process, user nobody should be made either owner of the temporary upload directory or a member of the group owning that directory. If this is not done, user nobody will be unable to write the file to the directory, unless world write permissions are assigned to the directory.

post_max_size = integerM

Scope: PHP_INI_SYSTEM; Default value: 8M
The post_max_size directive determines the maximum allowable size, in megabytes, of infor- mation that can be accepted via the POST method. As a rule of thumb, this directive setting should be larger than upload_max_filesize, to account for any other form fields that may be passed in addition to the uploaded file. Like memory_limit and upload_max_filesize, M must follow the integer value.

The $_FILES Array

The $_FILES superglobal is special in that it is the only one of the predefined EGCPFS (environment, get, cookie, put, files, server) superglobal arrays that is two-dimensional. Its purpose is to store a variety of information pertinent to a file (or files) uploaded to the server via a PHP script. In total, five items are available in this array, each of which is introduced here:

■Note Each of the items introduced in this section makes reference to userfile. This is simply a placeholder
for the name assigned to the file-upload form element. Therefore, this value will likely change in accordance to your chosen name assignment.

• $_FILES['userfile']['error']: This array value offers important information pertinent to the outcome of the upload attempt. In total, five return values are possible, one signifying a successful outcome, and four others denoting specific errors that arise from the attempt. The name and meaning of each return value is introduced in the later section “Upload Error Messages.”
• $_FILES['userfile']['name']: This variable specifies the original name of the file, including the extension, as declared on the client machine. Therefore, if you browse to a file named vacation.png and upload it via the form, this variable will be assigned the value vacation.png.

• $_FILES['userfile']['size']: This variable specifies the size, in bytes, of the file uploaded from the client machine. Therefore, in the case of the vacation.png file, this variable could plausibly be assigned a value such as 5253, or roughly 5KB.
• $_FILES['userfile']['tmp_name']: This variable specifies the temporary name assigned to the file once it has been uploaded to the server. This is the name of the file assigned to it while stored in the temporary directory (specified by the PHP directive upload_tmp_dir).
• $_FILES['userfile']['type']: This variable specifies the MIME type of the file uploaded from the client machine. Therefore, in the case of the vacation.png image file, this variable would be assigned the value image/png. If a PDF were uploaded, the value application/pdf would be assigned. Because this variable sometimes produces unexpected results, you should explicitly verify it yourself from within the script.

PHP’s File-Upload Functions

In addition to the host of file-handling functions made available via PHP’s file system library (see Chapter 10 for more information), PHP offers two functions specifically intended to aid in the file- upload process, is_uploaded_file() and move_uploaded_file(). This section introduces each function.

Determining Whether a File Was Uploaded

The is_uploaded_file() function determines whether a file specified by the input parameter
filename is uploaded using the POST method. Its prototype follows:

boolean is_uploaded_file(string filename)

This function is intended to prevent a potential attacker from manipulating files not intended for interaction via the script in question. For example, consider a scenario in which uploaded files are made immediately available for viewing via a public site repository. Say an attacker wants to make
a file somewhat juicier than the boring old class notes available for his perusal, say /etc/passwd. So rather than navigate to a class notes file as would be expected, the attacker instead types /etc/passwd directly into the form’s file-upload field.
Now consider the following uploadmanager.php script:

<?php
copy($_FILES['classnotes']['tmp_name'], "/www/htdocs/classnotes/".basename($classnotes));
?>

The result in this poorly written example would be that the /etc/passwd file is copied to a
publicly accessible directory. (Go ahead, try it. Scary, isn’t it?) To avoid such a problem, use the is_uploaded_file() function to ensure that the file denoted by the form field, in this case classnotes, is indeed a file that has been uploaded via the form. Here’s an improved and revised version of the uploadmanager.php code:
<?php
if (is_uploaded_file($_FILES['classnotes']['tmp_name'])) {
copy($_FILES['classnotes']['tmp_name'], "/www/htdocs/classnotes/".$_FILES['classnotes']['name']);
} else {
echo "<p>Potential script abuse attempt detected.</p>";
}
?>

In the revised script, is_uploaded_file() checks whether the file denoted by
$_FILES['classnotes']['tmp_name'] has indeed been uploaded. If the answer is yes, the file is copied to the desired destination. Otherwise, an appropriate error message is displayed.

Moving an Uploaded File

The move_uploaded_file() function was introduced in version 4.0.3 as a convenient means for moving an uploaded file from the temporary directory to a final location. Its prototype follows:

boolean move_uploaded_file(string filename, string destination)

Although copy() works equally well, move_uploaded_file() offers one additional feature that this function does not. It will check to ensure that the file denoted by the filename input parameter was in fact uploaded via PHP’s HTTP POST upload mechanism. If the file has not been uploaded, the move will fail and a FALSE value will be returned. Because of this, you can forgo using is_uploaded_file() as a precursor condition to using move_uploaded_file().
Using move_uploaded_file() is simple. Consider a scenario in which you want to move the uploaded class notes file to the directory /www/htdocs/classnotes/, while also preserving the file name as specified on the client:

move_uploaded_file($_FILES['classnotes']['tmp_name'], "/www/htdocs/classnotes/".$_FILES['classnotes']['name']);
Of course, you could rename the file to anything you wish when it’s moved. It’s important, however, that you properly reference the file’s temporary name within the first (source) parameter.

Upload Error Messages

Like any other application component involving user interaction, you need a means to assess the outcome, successful or otherwise. How do you definitively know that the file-upload procedure was successful? And if something goes awry during the upload process, how do you know what caused the error? Thankfully, sufficient information for determining the outcome, and in the case of an error, the reason for the error, is provided in $_FILES['userfile']['error']:

• UPLOAD_ERR_OK: A value of 0 is returned if the upload is successful.

• UPLOAD_ERR_INI_SIZE: A value of 1 is returned if there is an attempt to upload a file whose size exceeds the value specified by the upload_max_filesize directive.
• UPLOAD_ERR_FORM_SIZE: A value of 2 is returned if there is an attempt to upload a file whose size exceeds the value of the max_file_size directive, which can be embedded into the HTML form.

■Note Because the MAX_FILE_SIZE directive is embedded within the HTML form, it can easily be modified
by an enterprising attacker. Therefore, always use PHP’s server-side settings (upload_max_filesize,
post_max_filesize) to ensure that such predetermined absolutes are not surpassed.

• UPLOAD_ERR_PARTIAL: A value of 3 is returned if a file is not completely uploaded. This might happen if a network error occurs that results in a disruption of the upload process.
• UPLOAD_ERR_NO_FILE: A value of 4 is returned if the user submits the form without specifying a file for upload.

A Simple Example

Listing 15-1 (uploadmanager.php) implements the class notes example referred to throughout this chapter. To formalize the scenario, suppose that a professor invites students to post class notes to his Web site, the idea being that everyone might have something to gain from such a collaborative effort. Of course, credit should nonetheless be given where credit is due, so each file upload should be renamed to the last name of the student. In addition, only PDF files are accepted.

Listing 15-1. A Simple File Upload Example

<form action="uploadmanager.php" enctype="multipart/form-data" method="post"> Last Name:<br /> <input type="text" name="name" value="" /><br />
Class Notes:<br /> <input type="file" name="classnotes" value="" /><br />
<p><input type="submit" name="submit" value="Submit Notes" /></p>
</form>

<?php
/* Set a constant */
define ("FILEREPOSITORY","/home/www/htdocs/class/classnotes/");

/* Make sure that the file was POSTed. */
if (is_uploaded_file($_FILES['classnotes']['tmp_name'])) {

/* Was the file a PDF? */
if ($_FILES['classnotes']['type'] != "application/pdf") {
echo "<p>Class notes must be uploaded in PDF format.</p>";
} else {
/* move uploaded file to final destination. */
$name = $_POST['name'];

$result = move_uploaded_file($_FILES['classnotes']['tmp_name'], FILEREPOSITORY."/$name.pdf");

if ($result == 1) echo "<p>File successfully uploaded.</p>";
else echo "<p>There was a problem uploading the file.</p>";

} #endIF

} #endIF
?>

■Caution Remember that files are both uploaded and moved under the guise of the Web server daemon owner.
Failing to assign adequate permissions to both the temporary upload directory and the final directory destination for this user will result in failure to properly execute the file-upload procedure.

While it’s quite easy to manually create your own file upload mechanism, the HTTP_Upload PEAR
package truly renders the task a trivial affair. This package is the topic of the next section.

Taking Advantage of PEAR: HTTP_Upload

While the approaches to file uploading discussed thus far work just fine, it’s always nice to hide some of the implementation details by using a class. The PEAR class HTTP_Upload satisfies this desire quite nicely. It encapsulates many of the messy aspects of file uploading, exposing the information and features you’re looking for via a convenient interface. This section introduces HTTP_Upload, showing you how to take advantage of this powerful, no-nonsense package to effectively manage your site’s upload mechanisms.

Installing HTTP_Upload

To take advantage of HTTP_Upload’s features, you need to install it from PEAR. The process for doing so follows:

%>pear install HTTP_Upload
downloading HTTP_Upload-0.9.1.tgz ...
Starting to download HTTP_Upload-0.9.1.tgz (9,460 bytes)
.....done: 9,460 bytes
install ok: channel://pear.php.net/HTTP_Upload-0.9.1

Uploading a File

Uploading a file with HTTP_Upload is simple. Just invoke the class constructor and pass the name of the file-specific form field to the getFiles() method. If it uploads correctly (verified using the isValid() method), you can then move the file to its final destination (using the moveTo() method). A sample script is presented in Listing 15-2.

Listing 15-2. Using HTTP_Upload to Move an Uploaded File

<?php require('HTTP/Upload.php');

// New HTTP_Upload object
$upload = new HTTP_Upload();
// Retrieve the classnotes file
$file = $upload->getFiles('classnotes');

// If no problems with uploaded file if ($file->isValid()) {
$file->moveTo('/home/httpd/html/uploads');
echo "File successfully uploaded!";
}
else {
echo $file->errorMsg();
}
?>

You’ll notice that the last line refers to a method named errorMsg(). The package tracks a variety
of potential errors, including matters pertinent to a nonexistent upload directory, lack of write permissions, a copy failure, or a file surpassing the maximum upload size limit. By default, these messages are in English; however, HTTP_Upload supports seven languages: Dutch (nl), English (en), French (fr), German (de), Italian (it), Portuguese (pt_BR), and Spanish (es). To change the default

error language, invoke the HTTP_Upload() constructor using the appropriate abbreviation. For example, to change the language to Spanish, invoke the constructor like so:

$upload = new HTTP_Upload('es');

Learning More About an Uploaded File

In this first example, you find out how easy it is to retrieve information about an uploaded file. Again we’ll use the form presented in Listing 15-1, this time pointing the form action to uploadprops.php, found in Listing 15-3.

Listing 15-3. Using HTTP_Upload to Retrieve File Properties (uploadprops.php)

<?php require('HTTP/Upload.php');

// New HTTP_Upload object
$upload = new HTTP_Upload();

// Retrieve the classnotes file
$file = $upload->getFiles('classnotes');

// Load the file properties to associative array
$props = $file->getProp();

// Output the properties print_r($props);
?>

Uploading a file named notes.txt and executing Listing 15-3 produces the following output:

Array (
[real] => notes.txt [name] => notes.txt [form_name] => classnotes [ext] => txt
[tmp_name] => /tmp/B723k_ka43 [size] => 22616
[type] => text/plain
[error] =>
)

The key values and their respective properties are discussed earlier in this chapter, so there’s no reason to describe them again (besides, all the names are rather self-explanatory). If you’re inter- ested in just retrieving the value of a single property, pass a key to the getProp() call. For example, suppose you want to know the size (in bytes) of the file:

echo $files->getProp('size');

This produces the following output:

22616

Uploading Multiple Files

One of the beautiful aspects of HTTP_Upload is its ability to manage multiple file uploads. To handle a form consisting of multiple files, all you have to do is invoke a new instance of the class and call getFiles() for each upload control. Suppose the aforementioned professor has gone totally mad and now demands five homework assignments daily from his students. The form might look like this:

<form action="multiplehomework.php" enctype="multipart/form-data" method="post"> Last Name:<br /> <input type="text" name="name" value="" /><br />
Homework #1:<br /> <input type="file" name="homework1" value="" /><br />
Homework #2:<br /> <input type="file" name="homework2" value="" /><br /> Homework #3:<br /> <input type="file" name="homework3" value="" /><br /> Homework #4:<br /> <input type="file" name="homework4" value="" /><br /> Homework #5:<br /> <input type="file" name="homework5" value="" /><br />
<p><input type="submit" name="submit" value="Submit Notes" /></p>
</form>

Handling this with HTTP_Upload is trivial:

$homework = new HTTP_Upload();
$hw1 = $homework->getFiles('homework1');
$hw2 = $homework->getFiles('homework2');
$hw3 = $homework->getFiles('homework3');
$hw4 = $homework->getFiles('homework4');
$hw5 = $homework->getFiles('homework5');

At this point, simply use methods such as isValid() and moveTo() to do what you will with the files.

Summary

Transferring files via the Web eliminates a great many inconveniences otherwise posed by firewalls and FTP servers and clients. It also enhances an application’s ability to easily manipulate and publish nontraditional files. In this chapter, you learned just how easy it is to add such capabilities to your PHP applications. In addition to offering a comprehensive overview of PHP’s file-upload features, several practical examples were discussed.
The next chapter introduces in great detail the highly useful Web development topic of tracking
users via session handling.

0 comments: