Creating a file manager with PHP
(Page 2 out of 3)Streaming Files
Once a file has been uploaded, you might want to be able to download it as well. The easiest way to do this is to simply redirect the user to the location of the file, but what if your upload directory is off limits to the general public? To solve this problem, you need to write a script that reads the file, and then sends it to the user.
PLEASE NOTE: Do NOT have your upload directory in a public place, as this leads to huge security problems. Make sure your upload directory cannot be accessed by visitors.
The first thing our streaming script will do is some standard validation, like so:
// Get file
if (!isset($_GET['file'])) { die('Invalid File'); }
$file = $_GET['file'];
// Create file path
$filepath = $path . $file;
// Now check if there isn't any funny business going on
if ($filepath != realpath($filepath)) {
die('Security error! Please go back and try again.');
}
This code basically makes sure that the passed filename is in the upload directory, and not somewhere else, which is extremely important.
The next step is to try and determine what kind of file it is. We do this by looking at the extension, and then guessing the type. Although this is far from foolproof, it doesn't really matter, as the file will still be downloaded anyway. This does what we want:
$ext = explode('.', $file);
$extension = $ext[count($ext)-1];
// Try and find appropriate type
switch(strtolower($extension)) {
case 'txt': $type = 'text/plain'; break;
case "pdf": $type = 'application/pdf'; break;
case "exe": $type = 'application/octet-stream'; break;
case "zip": $type = 'application/zip'; break;
case "doc": $type = 'application/msword'; break;
case "xls": $type = 'application/vnd.ms-excel'; break;
case "ppt": $type = 'application/vnd.ms-powerpoint'; break;
case "gif": $type = 'image/gif'; break;
case "png": $type = 'image/png'; break;
case "jpg": $type = 'image/jpg'; break;
case "jpeg": $type = 'image/jpg'; break;
case "html": $type = 'text/html'; break;
default: $type = 'application/force-download';
}
Now that everything's been checked, the script can start sending the appropriate headers to the browser to let it know that it's about to send a file. The first thing the script has to send is a few generic caching headers:
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Transfer-Encoding: binary");
These are all very general, and are used to make sure the browser doesn't do anything wrong (like cache the file, or try and display the file instead of downloading it). The next step is to send the file type:
header("Content-Type: " . $type);
After that the script sends a header with the total length of the file. This is not a necessary header, but it's certainly recommended. If you don't include this header, the browser won't know how big the file is, and won't display a progress bar.
header("Content-Length: " . filesize($filepath));
Finally, you must also send a header with the name of the file, like so:
header("Content-Disposition: attachment; filename=\"" . $file . "\";" );
Now that all the headers have been sent, all that's left is to send the actual file, by reading the file, and printing the data:
readfile($filepath);
And that's the whole file streaming script in a nutshell.
Editing Files
Our file manager should also let certain files be edited online, using a simple text editor. Obviously only certain types of files can be edited, like text files, HTML files, PHP files, etc, so the first thing our editing script must do is check whether the file can be edited.
To check what type the file is, we use the same technique as in the streaming script. The script will look at the extension, and then determine whether the file is editable or not. It's possible that the file will have a wrong extension (i.e. a text file with an .exe extension), but there's nothing we can do about that.
The following code will determine if the file is editable:
// Get file
if (!isset($_GET['file'])) { die('Invalid File'); }
$file = $_GET['file'];
// Create file path
$filepath = $path . $file;
// Now check if there isn't any funny business going on
if ($filepath != realpath($filepath)) {
die('Security error! Please go back and try again.');
}
// Get file extension
$ext = explode('.', $file);
$extension = $ext[count($ext)-1];
// Is this file editable or not?
// Check if extension matches an invalid one
$invalid = array('exe', 'doc', 'jpg', 'gif');
if (in_array(strtolower($file['extension']), $invalid)) {
die('Can\'t edit this file. Not suitable for editing. Please go back.');
}
As you can see the script uses a 'bad extensions' array that contains a list of all extensions that can't be edited and any other extension that doesn't match this list can be edited.
The next step of the script is to determine whether an editing form should be shown, or if the form has already been submitted, like this:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// ... update file ...
} else {
// ... show the edit form ...
}
The edit form is a fairly simple task, and all that needs to be done is reading the file, and displaying the form:
$f = fopen($filepath, 'r');
$data = fread($f, filesize($filepath));
fclose($f);
?>
Edit File: echo htmlentities($file); ?>
Updating the file is also quite an easy task. First the script should check if any new data has been submitted, and after that it should open the file, and write the new data. This does exactly what we want:
if (!isset($_POST['newfile'])) {
die('Please enter some new data for this file.');
}
// Write new data
$f = fopen($filepath, 'w');
fwrite($f, $_POST['newfile']);
fclose($f);
And that finishes the editing script. Let's have a look at the final feature: a deleting script.
January 23rd, 2006 at 1:31 pm
This is a really bad tutorial in a few ways:
* All files are uploaded in a directory accessible by the webserver, I can enter /upload on the live example and run my own php scripts.
* Magic quotes is probably enabled. So in the files ‘ will be converted to \’ . This is not really usefull for most files
January 23rd, 2006 at 2:47 pm
Now think about a file manager web 2.0. Using set of class like prototype.js … now that would be something wouldn’t it?.
January 23rd, 2006 at 4:20 pm
Evert: you are absolutely right, and I’ve fixed the problem, though this problem only exists in the demo. Normally, you would place the upload directory above the web root anyway.
As for the magic quotes problem; it’s a very easy fix.
Bocse: That would be pretty neat, and something to consider writing. Imagine what you could create with prototype.js, ajax, and more.
January 23rd, 2006 at 9:55 pm
[…] The second article is Creating a file manager with PHP. It treats the basics of uploading files and working with files in PHP (opening, editing, deleting files) […]
January 23rd, 2006 at 11:26 pm
Interesting article Dennis. I don’t agree with Evert, it’s not a bad tutorial. But it’s good you added the lines about security. The difficulty with tutorials like these is that you want to focus on one problem. Here, uploading/managing files. If you would add an explanation of every weak point of every script, each tutorial would be at least 20 pages. But nonetheless, a few warnings, even just a few lines is never bad. Looking forward to your next articles.
January 25th, 2006 at 9:19 pm
[…] var site=”s20phpit” « Previous Creating a file manager with PHP […]
January 26th, 2006 at 7:37 pm
[…] In a previous article I have posted a link to a PHPIt article: Creating a file manager with PHP. Now, PHPIt created the second tutorial in this series: Creating a SECURE file manager with PHP. It has all the information you need to secure your file manager made using the previous article. […]
June 22nd, 2006 at 6:05 pm
This is a great tutorial, however why does it not include the ability to access different folders in a directory? Do I need to change something in the code? Maybe I am just overlooking something. If someone could help me I would greatly apreciate it.
August 5th, 2006 at 11:24 pm
This is a great tutorial. You can add some AJAX to show the Progress bar to the user. Keep it up..
August 16th, 2006 at 4:22 am
I have problem in viewing encryted image file to view in IE6 SP1. just the images doesnt render properly while the other type (like pdf) doesnt have any error.
is it IE setting? IE really sucks at all time.