Uploading Files Using CGI and Perl Article

Share this article

Creating the File Upload Script
Handling the data that the browser sends when it uploads a file is quite a complex process. Fortunately, the Perl CGI library, CGI.pm, does most of the dirty work for us! Using two methods of the CGI query object, param and upload, we can retrieve the uploaded file’s filename and file handle, respectively. Using the file handle, we can read the contents of the file, and save it to a new file in our file upload area on the server.
1. First Things First At the top of our script, we need to create the shebang line. We then put the Perl interpreter into strict mode to make our script as safe as possible, and include the Perl CGI and File::Basename modules for use in the script. We’ll also use the CGI::Carp module to display errors in the web page, rather than displaying a generic “500 Server Error” message (it’s a good idea to comment out this line in a production environment): #!/usr/bin/perl -wT use strict; use CGI; use CGI::Carp qw ( fatalsToBrowser ); use File::Basename; Note the use of the -w switch to make Perl warn us of any potential dangers in our code. It’s nearly always a good idea to put the -w in! In addition, the -T switch turns on taint checking. This ensures that any untrusted input to the script, such as the uploaded file’s filename, is marked as tainted; we then need to explicitly “clean” this data before using it. (If you try to use tainted data, Perl throws an error.) More on this in a moment. 2. Setting Safety Limits In order to prevent the server being overloaded by huge file uploads, we’ll limit the allowable size of an uploaded file to 5MB; this should be big enough to handle most digital photos: $CGI::POST_MAX = 1024 * 5000; We’ll also create a list of “safe” characters for filenames. Some characters, such as slashes (/), are dangerous in filenames, as they might allow attackers to upload files to any directory they wanted. Generally speaking, letters, digits, underscores, periods, and hyphens are safe bets: my $safe_filename_characters = "a-zA-Z0-9_.-"; 3. The Upload Directory We need to create a location on our server where we can store the uploaded files. We want these files (the photos) to be visible on our web site, so we should store them in a directory under our document root, for example: my $upload_dir = "/home/mywebsite/htdocs/upload"; You’ll need to create a directory called “upload” on your web site’s document root, then set $upload_dir to the absolute path to that directory, as I’ve done above. Make sure your directory can be read and written to by your script; on a shared UNIX server, this usually means setting the mode to 777 (for example, by issuing the chmod 777 upload command at the command line). Check with your web hosting provider if you’re not sure what you need to do. 4. Reading the Form Variables
The next step is to create a CGI object (we assign it to $query below); this allows us to access methods in the CGI.pm library. We can then read in the filename of our uploaded file, and the email address that the user entered into the form: my $query = new CGI; my $filename = $query->param("photo"); my $email_address = $query->param("email_address"); If there was a problem uploading the file — for example, the file was bigger than the $CGI::POST_MAX setting — $filename will be empty. We can test for this and report the problem to the user as follows: if ( !$filename ) { print $query->header ( ); print "There was a problem uploading your photo (try a smaller file)."; exit; } 5. Making the Filename Safe We can’t necessarily trust the filename that’s been sent by the browser; an attacker could manipulate this filename to do nasty things such as upload the file to any directory on the Web server, or attempt to run programs on the server. The first thing we’ll do is use the fileparse routine in the File::Basename module to split the filename into its leading path (if any), the filename itself, and the file extension. We can then safely ignore the leading path. Not only does this help thwart attempts to save the file anywhere on the web server, but some browsers send the whole path to the file on the user’s hard drive, which is obviously no use to us: my ( $name, $path, $extension ) = fileparse ( $filename, '..*' ); $filename = $name . $extension; The above code splits the full filename, as passed by the browser, into the name portion ($name), the leading path to the file ($path), and the filename’s extension ($extension). To locate the extension, we pass in the regular expression '..*' — in other words, a literal period (.) followed by zero or more characters. We then join the extension back onto the name to reconstruct the filename without any leading path. The next stage in our quest to clean up the filename is to remove any characters that aren’t in our safe character list ($safe_filename_characters). We’ll use Perl’s substitution operator (s///) to do this. While we’re at it, we’ll convert any spaces in the filename to underscores, as underscores are easier to deal within URLs: $filename =~ tr/ /_/; $filename =~ s/[^$safe_filename_characters]//g; Finally, to make doubly sure that our filename is now safe, we’ll match it against our $safe_filename_characters regular expression, and extract the characters that match (which should be all of them). We also need to do this to untaint the $filename variable. This variable is tainted because it contains potentially unsafe data passed by the browser. The only way to untaint a tainted variable is to use regular expression matching to extract the safe characters: if ( $filename =~ /^([$safe_filename_characters]+)$/ ) { $filename = $1; } else { die "Filename contains invalid characters"; }
(Note that the above die function should never be executed, because we’ve already removed our dodgy characters using the earlier substitution. However, it doesn’t hurt to be cautious!) 6. Getting the File Handle As I mentioned above, we can use the upload method to grab the file handle of the uploaded file (which actually points to a temporary file created by CGI.pm). We do this like so: my $upload_filehandle = $query->upload("photo"); 7. Saving the File Now that we have a handle to our uploaded file, we can read its contents and save it out to a new file in our file upload area. We’ll use the uploaded file’s filename — now fully sanitised — as the name of our new file: open ( UPLOADFILE, ">$upload_dir/$filename" ) or die "$!"; binmode UPLOADFILE; while ( <$upload_filehandle> ) { print UPLOADFILE; } close UPLOADFILE; Notice the die function at the end of the first line above; if there’s an error writing the file, this function stops the script running and reports the error message (stored in the special variable $!). Meanwhile, the binmode function tells Perl to write the file in binary mode, rather than in text mode. This prevents the uploaded file from being corrupted on non-UNIX servers (such as Windows machines). 8. Thanking the User We’ve now uploaded our file! The last step is to display a quick thank-you note to the users, and to show them their uploaded photo and email address: print $query->header ( ); print <<END_HTML; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Thanks!</title> <style type="text/css"> img {border: none;} </style> </head> <body> <p>Thanks for uploading your photo!</p> <p>Your email address: $email_address</p> <p>Your photo:</p> <p><img src="/upload/$filename" alt="Photo" /></p> </body> </html> END_HTML
The Finished Script
Your finished CGI script should look something like this: #!/usr/bin/perl -wT use strict; use CGI; use CGI::Carp qw ( fatalsToBrowser ); use File::Basename; $CGI::POST_MAX = 1024 * 5000; my $safe_filename_characters = "a-zA-Z0-9_.-"; my $upload_dir = "/home/mywebsite/htdocs/upload"; my $query = new CGI; my $filename = $query->param("photo"); my $email_address = $query->param("email_address"); if ( !$filename ) { print $query->header ( ); print "There was a problem uploading your photo (try a smaller file)."; exit; } my ( $name, $path, $extension ) = fileparse ( $filename, '..*' ); $filename = $name . $extension; $filename =~ tr/ /_/; $filename =~ s/[^$safe_filename_characters]//g; if ( $filename =~ /^([$safe_filename_characters]+)$/ ) { $filename = $1; } else { die "Filename contains invalid characters"; } my $upload_filehandle = $query->upload("photo"); open ( UPLOADFILE, ">$upload_dir/$filename" ) or die "$!"; binmode UPLOADFILE; while ( <$upload_filehandle> ) { print UPLOADFILE; } close UPLOADFILE; print $query->header ( ); print <<END_HTML; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="https://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Thanks!</title> <style type="text/css"> img {border: none;} </style> </head> <body> <p>Thanks for uploading your photo!</p> <p>Your email address: $email_address</p> <p>Your photo:</p> <p><img src="/upload/$filename" alt="Photo" /></p> </body> </html> END_HTML Save this file on your hard drive, and call it upload.cgi. Now we’ve created our server-side script, we can place both the script and the form on our server and test the file upload.
Go to page: 1 | 2 | 3

Frequently Asked Questions (FAQs) about Uploading Files with CGI and Perl

How Can I Secure My File Uploads in Perl?

Security is a crucial aspect when dealing with file uploads. To secure your file uploads in Perl, you should validate the file type and size before uploading. You can use the ‘file’ command in Unix systems to check the file type. Additionally, you can use the ‘-s’ operator to check the file size. Always ensure to use a secure directory for file uploads, preferably outside the webroot, to prevent direct access to the uploaded files.

How Can I Handle Multiple File Uploads in Perl?

Handling multiple file uploads in Perl involves looping through the file input fields. You can use the CGI module’s ‘param’ function to get the list of uploaded files. Then, loop through the list and handle each file individually. Remember to validate and sanitize each file before saving it to the server.

How Can I Display Progress While Uploading Files in Perl?

Displaying progress while uploading files can enhance user experience. However, Perl does not natively support this feature. You can use JavaScript along with Perl to achieve this. The JavaScript code will send periodic AJAX requests to the server to check the upload progress, and then update the progress bar accordingly.

How Can I Limit the File Size for Uploads in Perl?

Limiting the file size for uploads can prevent server overload. You can use the CGI module’s ‘POST_MAX’ directive to set the maximum allowable file size for uploads. If a user tries to upload a file larger than the specified size, the server will reject the upload and return an error.

How Can I Rename Uploaded Files in Perl?

Renaming uploaded files can help prevent file name conflicts. You can use the ‘move’ function from the File::Copy module to rename and move the uploaded file to its destination. The ‘move’ function takes two arguments: the source file and the destination file.

How Can I Handle File Upload Errors in Perl?

Handling file upload errors is essential for debugging and user feedback. You can use the CGI module’s ‘cgi_error’ function to check if an error occurred during file upload. If an error occurs, the ‘cgi_error’ function will return a string describing the error.

How Can I Upload Files to a Specific Directory in Perl?

Uploading files to a specific directory involves specifying the directory path in the ‘move’ function. The ‘move’ function from the File::Copy module takes the destination file as its second argument. You can include the directory path in this argument to upload the file to the specific directory.

How Can I Upload Files Without Using the CGI Module in Perl?

While the CGI module simplifies file uploads in Perl, it is not the only way. You can use the HTTP::Request::Common module to handle file uploads. This module provides the ‘POST’ function, which you can use to send a POST request with the file data to the server.

How Can I Test File Uploads in Perl?

Testing file uploads involves creating a test script that mimics the file upload process. You can use the Test::More and Test::MockObject modules to create the test script. These modules provide functions for simulating user input and checking the output.

How Can I Upload Files Asynchronously in Perl?

Asynchronous file uploads can improve user experience by allowing the user to continue using the application while the file is uploading. You can use JavaScript along with Perl to achieve this. The JavaScript code will send the file data to the server using an AJAX request, allowing the file to upload in the background.

Matt DoyleMatt Doyle
View Author

Matt works for Elated.com, which offers great Webmaster resources, from free templates and stock images through to tutorials and forums.

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