views:

476

answers:

4

I have a web page that I use to give authenticated users access to download files. The files are in the root folder (not normally HTTP accessible), and I use a PHP script and the fopen function to generate the web page that starts the download of the files. It works perfectly for three of the files (all of which are under 200KB), but the fourth file--which is 40MB--saves as an empty file.

If I move the 40MB file into an HTTP-accessible folder, a link to the file works fine, but it obviously doesn't provide the restricted access I want.

I have confirmed in the script that the file is being found by PHP, but for some reason, when the headers are sent, the 40MB file finishes downloading instantly and is 0KB.

Funny thing is, this code works fine on my local development server, but not on the production server. Both are running the same version of PHP (5.2.10) and have the same settings for memory and execution time (32M and 30secs). I tried bumping up the production memory_limit and max_execution_time values (to 64M and 60secs) but it had no effect.

Any ideas? Here is my PHP script (filenames changed to protect the ignorant):

<?php
// Download filename given in the $_GET['filename'] variable
$filepath = $_SERVER['DOCUMENT_ROOT'] . '/../';
if (isset($_REQUEST['type'])) {
 switch (strtoupper($_REQUEST['type'])) {
 case 'EVAL':
  $filename = "littlefile1.zip";
  break;
 case 'EXE':
  $filename = "bigfile.zip";
  break;
 case 'RPT':
  $filename = "littlefile2.zip";
  break;
 case 'UPD':
  $filename = "littlefile3.zip";
  break;
 default:
  die();
 }


 header("Content-Disposition: attachment; filename=" . $filename);
 header("Content-Length: " . filesize($filepath . $filename));
 header("Content-Type: application/octet-stream");
 $fp=fopen($filepath . $filename, 'rb');
 fpassthru($fp);
 ob_flush();
 fclose($fp);
}
?>

Thanks in advance!

In response to Greg's comment, the header error I get looks like this:

Date: Sun, 29 Nov 2009 19:58:16 GMT
Server: Apache/2.2.3 (Red Hat)
X-Powered-By: PHP/5.2.10
Content-Disposition: attachment; filename=bigfile.zip
Content-Encoding: gzip
Vary: Accept-Encoding
Content-Length: 44964864
Connection: close
Content-Type: application/octet-stream

500 Internal Server Error
+1  A: 

if you set the error_reporting(E_ALL); do you get any kind of error ?

The only things I see would be the script time limit and some memory problem but since you said you have checked both.

RageZ
A: 

Do you use output buffering (with ob_start())? If not, you must use flush() instead of ob_flush(), and you have to call it before sending the file. By safety, you may call ob_clean() to clean output buffer too. BTW, readfile() saves you from having to use fopen(), fpassthru() and fclose().

<?php
// Download filename given in the $_GET['filename'] variable
$filepath = $_SERVER['DOCUMENT_ROOT'] . '/../';
filenames['EVAL'] = "littlefile1.zip";
filenames['EXE'] = "bigfile.zip";
filenames['RPT'] = "littlefile2.zip";
filenames['UPD'] = "littlefile3.zip";
if (isset($_REQUEST['type'])) {
 $filename = filenames[strtoupper($_REQUEST['type'])];
 if (!empty($filename) && file_exists( $filepath . $filename)) {
   header("Content-Disposition: attachment; filename=" . $filename);
   header("Content-Length: " . filesize($filepath . $filename));
   header("Content-Type: application/octet-stream");
   flush();
   ob_clean();
   readfile($filepath . $filename);
 }
 exit;
}
Erlock
Thanks for the much more succinct code! I've tested this, and it works fine for the other files, but I get the same error as I did previously. I am going to check with my web host to see if they are doing something to prevent this download.
iopener
A: 

I'll bet you a beer that the provider has error_reporting set to 0, or errors redirected to a log file, and thus some error occurs but doesn't get reported.

  • Can you make a test output of $_REQUEST["type"]? Could it be that it's empty or not in your switch statement?

  • Can you deliberately insert something that will cause a fatal error, and see whether is stays blank?

  • As RageZ suggested, can you set error_reporting to E_ALL?

  • Maybe errors are by default not shown in the output, but redirected to an internal error log file. Can you check with your provider whether that is the case? Maybe you have a login area to view error logs in, or they are stored somewhere in your directory structure?

Pekka