None have worked for me so far. The only thing that works without fail is presenting a link to the file in a folder on the web path, but that defeats the purpose of keeping files off the web path to prevent script execution. The latest thing I have tried is fopen(), but I get an error: "(IE7) cannot download download.php (the download page) from (website)". It seems the page is trying to download itself instead of the given file. I won't bore you with the entire code, but suffice to say I do a lot of checking before I get to this:

if(file_exists($thefile) && is_readable($thefile))	// Make sure file exists in source
	{
	// File exists & is readable, but fails here:
	$fp = fopen($thefile, "rb");
	header("Content-type: application/octet-stream"); 
	header("Content-Transfer-Encoding: Binary"); 
	header("Content-length: ".filesize($thefile)); 
	header("Content-disposition: attachment; filename='".$filename."')"); 
	fpassthru($fp);
	exit;

I should add that I am using:
SUSE Linux
Apache 1.3
PHP 4.3
* OpenSSL .9
(I suspect SSL may be the problem)

The file itself is in the /uploads/ folder in the server root with 777 permissions and chown to "nobody:nobody". I've stored $thefile with a random hash string for a name, which is why I rename it to the original $filename in line 8.

Any suggestions would be helpful. I'm open to using other methods that won't compromise on security. If its a version problem, I'm a few weeks away from an new Linux server, so should I just wait? The current server is live and serving another vital app, so I don't want to mess with upgrading it.

    Have you tried commenting out the header()'s, ensuring that display_errors is On and error_logging set to E_ALL, and checking if PHP is generating any error messages?

      I think you're onto something. I commented out all the headers, and it opened as HTML code - in other words, it dumped the whole text file into my browser as part of download.php.

      I uncommented all the headers except the "Content-type=" line, and it asked if I wanted to download the file. If I try to download the file, I get the same error as before. If I open the file, I get the browser dump.

      Obviously, I'm missing something in my code but I've had a difficult time finding help on download scripts (you get all sorts of random garbage when you Google "php download scripts"). I tried "force-download" as the Content-type, but that didn't work either.

      Could someone point me in the right direction please?

        Also, using "text/plain" for the mime type causes the "cannot download" error. The only thing that seems to work is commenting the "Content-type" line out and letting it dump the text into the browser.

          Okay, I bypassed this issue by going with a less graceful solution. Basically, I created a temp folder within the web tree and copied the file to it. Then I present the user with a link. I don't like doing it this way, but at least I am guaranteed that it will work. Here's the relevant code:

          // Make sure file exists in source (on Linux root)
          if(file_exists($getfile) && is_readable($getfile))
             {
             // Clean up /tmp/ folder first (in web path)
             foreach(glob($putfilepath."*") as $delfile)
                {
                unlink($delfile);
                }
             // Copy file to /tmp/ folder (in web path)
             if(copy($getfile, $putfile))
                {
                // Web-path to file
                $filelink = $webpath.$filename;
                }
          
          // Code to present the file link to the user...
          echo "<p>Click to download the file: <a href='".$filelink."'><em>".$filename.".</em></a></p>\n";
          

          This method has some obvious security implications on the server, so I'll leave this issue open for a bit and see if someone can solve the original problem.

            11 days later

            I'm not finding any easy answers here. I have a new server coming and will be building a new LAMP VM on it. I think I'll have less trouble with file handling on the latest versions of apache/php.

            In the meantime, I have resolved to just store files in the URL path with a random file name. I'll store the original filename in the db and rename it before the user downloads it. The site will be employees-only at first, so hopefully that won't be a problem. I can't really limit mime types or do regular expression checks because files type will vary widely, but I will be monitoring the uploads folder closely.

            I'll post another update once I have the new system in place, but I'm still taking suggestions.

              8 days later

              Okay, so I managed to get my file download script working. I'm not sure what the issue was, but I did learn somewhere along the way that if you don't send the "content-type" header first, IE7 tends to insert its own "text" header. I don't think that was the only problem in my original code, but it was a biggie.

              Anyway, my files did end up getting stored in the URL path, but they are stored as random strings without any extension and the user never sees the random string. Both the original file name and the random string are stored in the database and I convert them as needed.

              The handler for the upload looks like this:

              	if(file_exists($_FILES["file"]["tmp_name"]))												// Is there a file to upload?
              		{
              		if($_FILES["file"]["size"] > 1000000)															// Is file over the 1 MB size limit?
              			{
              			$up_msg = "File size is over the limit. Upload cancelled.";
              			}
              		else																															// File size okay!
              			{
              			if($_FILES["file"]["type"] == "application/octet-stream")				// Is file an executable?
              				{
              				$up_msg = "Executable files not allowed.  Upload cancelled.";
              				}
              			else																														// File isn't executable!
              				{
              				if($_FILES["file"]["error"] > 0)															// Are there any other errors?
              					{
              					$up_msg = "Error: ".$_FILES["file"]["error"];
              					}
              				else																													// Passed all the tests!
              					{
              					$uploadpath = "inet/www/uploads/";																	// Set upload folder path
              					$tmpname = $_FILES["file"]["tmp_name"];											// Get temp file name (set by php)
              					$filename = $_FILES["file"]["name"];												// Get original file name
              					$hashname = make_hash($filename);														// Create encrypted file name
              					$fullpath = $uploadpath.$hashname;													// Set full file storage path
              					// Move the file from the temp folder to the upload folder
              					if(move_uploaded_file($_FILES["file"]["tmp_name"], $fullpath) > 0)
              						{
              						// If file doesn't get moved, it gets deleted from the /tmp/ folder automatically
              						$up_msg = "Some problem uploading file.";
              						}
              					}
              				}
              			}
              		}
              
              

              Here is the code for the download page:

              if(isset($_SESSION['q_filename']) && isset($_SESSION['q_hashname']))			// Make sure file is queued in session
              	{
              	// File queued in session, change to local variables
              	$filename = $_SESSION['q_hashname'];							// Get stored hash name
              	unset($_SESSION['q_hashname']);
              	$downloadname = $_SESSION['q_filename'];					// Get file name (filename presented to user)
              	unset($_SESSION['q_filename']);
              	$filepath = "/inet/www/uploads/";						// Set the base file path
              	$fullpath = $filepath.$filename;
              
              // Start the download...
              header('Content-Type: application/octet-stream'); 
              header('Content-Description: File Transfer'); 
              header('Content-Length: ' . filesize($fullpath)); 
              header("Content-Disposition: attachment; filename=\"".$downloadname."\""); 
              readfile($fullpath);
              

              I hope this can help others who are struggling with file handling. If anyone has any other input, please feel free to post it.

                I should add that make_hash() is a function that I wrote to generate a random string to replace the original file name. It basically adds a random number and the current time to a static string, then uses the md5() function to create a hash value of the string. The file is stored with this random string and no extension. Someone might be able to randomly guess the name of a file, but it is unlikely that anyone could easily guess a specific file name or execute the file.

                  Write a Reply...