The Server Pages
:: TSP Menu
- Home
- Authors
- Glossary
- RSS Feed

:: Servers
- CPanel
- Hosting
- Linux
- Windows

:: Webmaster
- Databases
- Design
- PHP
- Search Engines

:: Scripts
- Scripts

:: Documentation
- PHP Manual

:: Other
- Errors & Solutions
- TechPunt
- Wojjie

search for in the

readlink> <popen
[edit] Last updated: Sat, 12 May 2012

view this page in

readfile

(PHP 4, PHP 5)

readfileOutputs a file

Description

int readfile ( string $filename [, bool $use_include_path = false [, resource $context ]] )

Reads a file and writes it to the output buffer.

Parameters

filename

The filename being read.

use_include_path

You can use the optional second parameter and set it to TRUE, if you want to search for the file in the include_path, too.

context

A context stream resource.

Return Values

Returns the number of bytes read from the file. If an error occurs, FALSE is returned and unless the function was called as @readfile(), an error message is printed.

Examples

Example #1 Forcing a download using readfile()

<?php
$file 
'monkey.gif';

if (
file_exists($file)) {
    
header('Content-Description: File Transfer');
    
header('Content-Type: application/octet-stream');
    
header('Content-Disposition: attachment; filename='.basename($file));
    
header('Content-Transfer-Encoding: binary');
    
header('Expires: 0');
    
header('Cache-Control: must-revalidate');
    
header('Pragma: public');
    
header('Content-Length: ' filesize($file));
    
ob_clean();
    
flush();
    
readfile($file);
    exit;
}
?>

The above example will output something similar to:

Open / Save dialogue

Notes

Note:

readfile() will not present any memory issues, even when sending large files, on its own. If you encounter an out of memory error ensure that output buffering is off with ob_get_level().

Tip

A URL can be used as a filename with this function if the fopen wrappers have been enabled. See fopen() for more details on how to specify the filename. See the Supported Protocols and Wrappers for links to information about what abilities the various wrappers have, notes on their usage, and information on any predefined variables they may provide.

Note: Context support was added with PHP 5.0.0. For a description of contexts, refer to Streams.

See Also



readlink> <popen
[edit] Last updated: Sat, 12 May 2012
 
add a note add a note User Contributed Notes readfile
stefano dot cudini at gmail dot com 30-Mar-2012 05:53
readfile with accurate limit rate, using pv linux command

<?php

$file
= $_SERVER['QUERY_STRING'];
header("Content-length: ".filesize($file));
header("Content-type: ".mime_content_type($file));
readfileLimit($file);

function
readfileLimit($file, $limit='10M')
{
   
$f = popen("pv -L $limit '$file'",'r') or return false;
    while(!
feof($f))
    {
        echo
fread($f, 1024);
       
flush();
    }
   
fclose($f);
}
?>
ben at indietorrent dot org 29-Oct-2011 04:37
An update my previous comment: The 404 errors were occurring because the PHP code was tailored to delete the temporary file after sending it to the client. The deletion works as expected with readfile(), but the same code (the only addition being the x-sendfile header) causes the source file to be deleted mid-download with mod_xsendfile.

Lesson learned: Don't attempt to delete a temporary file after a client downloads it if using mod_xsendfile; the file will be deleted immediately after the headers are sent and the client's download will be interrupted with a 404 (because Apache is no longer able to access the file).
ben at indietorrent dot org 29-Oct-2011 01:54
It should be noted that using Apache's "mod_xsendfile" module as an alternative to PHP's native readfile() function does have its drawbacks.

With mod_xsendfile, script execution continues immediately after

<?php
header
('X-Sendfile: ' . $file);
?>

As such, there is no means by which to delete the file when downloading is complete, determine how much time the download required (or whether or not the download succeeded), track memory usage related to the download, etc. When the aforementioned functionality is required, it seems that readfile() is the only option.

Further, and more importantly, failing to terminate script execution immediately after sending the headers (e.g., by returning TRUE within a function) seems to cause mod_xsendfile to fail silently and yield a 404 error.
daren -remove-me- schwenke 05-May-2011 08:52
If you are lucky enough to not be on shared hosting and have apache, look at installing mod_xsendfile.
This was the only way I found to both protect and transfer very large files with PHP (gigabytes). 
It's also proved to be much faster for basically any file.
Available directives have changed since the other note on this and XSendFileAllowAbove was replaced with XSendFilePath to allow more control over access to files outside of webroot.

Download the source.

Install with: apxs -cia mod_xsendfile.c

Add the appropriate configuration directives to your .htaccess or httpd.conf files:
# Turn it on
XSendFile on
# Whitelist a target directory.
XSendFilePath /tmp/blah

Then to use it in your script:
<?php
$file
= '/tmp/blah/foo.iso';
$download_name = basename($file);
if (
file_exists($file)) {
   
header('Content-Type: application/octet-stream');
   
header('Content-Disposition: attachment; filename='.$download_name);
   
header('X-Sendfile: '.$file);
    exit;
}
?>
cOrti 31-Mar-2011 03:59
Hello,

Here's a simple trick for browsers (FF, IE) address spaces in filenames for Content-Disposition:

eg: <?php header ("Content-Disposition:attachment; filename=\"$filename\""); ?>

The \ "before and after the file name makes the difference.
mncahill at Gmail dot com 28-Jan-2011 07:22
It should be noted that in the example:

    header('Content-Length: ' . filesize($file));

$file should really be the full path to the file.  Otherwise content length will not always be set, often resulting in the dreaded "0 byte file" problem.
chrisbloom7 at gmail dot com 15-Aug-2010 08:32
This seems to be one of those areas of programming where there is no hard and fast rule as to what works. I tried countless permutations of the tricks I found on this page and elsewhere on the web until I finally found something that works. This might be overkill, but it works so I'm leaving it as is. So far this was tested on Mac/Apache and Win32/Apache servers, using Firefox, Safari and Chrome clients. I've tested this with files up to 151MB and as small as a few Kb. As usual, YMMV.

<?php
$realpath
= "/some/absolute/path.exe";
$mtime = ($mtime = filemtime($realpath)) ? $mtime : gmtime();
$size = intval(sprintf("%u", filesize($realpath)));
// Maybe the problem is we are running into PHPs own memory limit, so:
if (intval($size + 1) > return_bytes(ini_get('memory_limit')) && intval($size * 1.5) <= 1073741824) { //Not higher than 1GB
 
ini_set('memory_limit', intval($size * 1.5));
}
// Maybe the problem is Apache is trying to compress the output, so:
@apache_setenv('no-gzip', 1);
@
ini_set('zlib.output_compression', 0);
// Maybe the client doesn't know what to do with the output so send a bunch of these headers:
header("Content-type: application/force-download");
header('Content-Type: application/octet-stream');
if (
strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != false) {
 
header("Content-Disposition: attachment; filename=" . urlencode(basename($F['FILE']['file_path'])) . '; modification-date="' . date('r', $mtime) . '";');
} else {
 
header("Content-Disposition: attachment; filename=\"" . basename($F['FILE']['file_path']) . '"; modification-date="' . date('r', $mtime) . '";');
}
// Set the length so the browser can set the download timers
header("Content-Length: " . $size);
// If it's a large file we don't want the script to timeout, so:
set_time_limit(300);
// If it's a large file, readfile might not be able to do it in one go, so:
$chunksize = 1 * (1024 * 1024); // how many bytes per chunk
if ($size > $chunksize) {
 
$handle = fopen($realpath, 'rb');
 
$buffer = '';
  while (!
feof($handle)) {
   
$buffer = fread($handle, $chunksize);
    echo
$buffer;
   
ob_flush();
   
flush();
  }
 
fclose($handle);
} else {
 
readfile($realpath);
}
// Exit successfully. We could just let the script exit
// normally at the bottom of the page, but then blank lines
// after the close of the script code would potentially cause
// problems after the file download.
exit;
?>

[EDIT BY danbrown AT php DOT net: An addendum to this note was submitted by the original author on 04-MAR-2011.  It follows.]

I started running into trouble when I had really large files being sent to clients with really slow download speeds. In those cases, the script would time out and the download would terminate with an incomplete file. I am dead-set against disabling script timeouts - any time that is the solution to a programming problem, you are doing something wrong - so I attempted to scale the timeout based on the size of the file. That ultimately failed though because it was impossible to predict the speed at which the end user would be downloading the file at, so it was really just a best guess so inevitably we still get reports of script timeouts.

Then I stumbled across a fantastic Apache module called mod_xsendfile (https://tn123.org/mod_xsendfile/ (binaries) or https://github.com/nmaier/mod_xsendfile (source)). This module basically monitors the output buffer for the presence of special headers, and when it finds them it triggers apache to send the file on its own, almost as if the user requested the file directly. PHP processing is halted at that point, so no timeout errors regardless of the size of the file or the download speed of the client. And the end client gets the full benefits of Apache sending the file, such as an accurate file size report and download status bar.

The code I finally ended up with is too long to post here, but in general is uses the mod_xsendfile module if it is present, and if not the script falls back to using the code I originally posted. You can find some example code at https://gist.github.com/854168
Mike 04-Aug-2010 07:46
Beware of using download managers.. I was trying to use readfile in IE8 and kept getting the message "failed to get data for 'type'". Eventually figured out the problem was that I had LeechGet installed and it was intercepting the download, which in turn prevented the download from taking place.
Sguiggz at yahoo dot com 28-Jun-2010 11:23
I was getting errors playing music files when downloading, using the Content-Length header as listed in the examples above. I plugged in the numeric size of the file in bytes, removing the filesize command and it worked. At a loss for other ideas, I had the filesize command populate a variable, then I added one to the variable when calling it for the filesize. It worked like a champ, but... My files are still cutting off, and I think it has something to do with my server. Back to the drawing board, I guess.

Here's the snippet for reference:

<?php
$filesize
= filesize($dir_name);
header('Content-Length: '.$filesize+1);
?>

Hopefully this will help someone more than it helped me.

Cheers.
Zambz 22-May-2010 06:33
If you are using the procedures outlined in this article to force sending a file to a user, you may find that the "Content-Length" header is not being sent on some servers.

The reason this occurs is because some servers are setup by default to enable gzip compression, which sends an additional header for such operations.  This additional header is "Transfer-Encoding: chunked" which essentially overrides the "Content-Length" header and forces a chunked download.  Of course, this is not required if you are using the intelligent versions of readfile in this article.

A missing Content-Length header implies the following:

1) Your browser will not show a progress bar on downloads because it doesn't know their length
2) If you output anything (e.g. white space) after the readfile function (by mistake), the browser will add that to the end of the download, resulting in corrupt data.

The easiest way to disable this behaviour is with the following .htaccess directive.

SetEnv no-gzip dont-vary
david at elocal dot co dot uk 08-Dec-2009 02:09
If you are trying the example given in the manual and are getting invalid files, then it is probably because your server is set to display notices and ob_clean() is outputting one at the start of the file, corrupting it.

As a starting point, you can try turning off all error reporting for this script by using error_reporting(0) at the start of your PHP file...
<?php
error_reporting
(0);

// rest of example goes here
?>
sakai at d4k dot net 23-Apr-2009 05:25
To reduce the burden on the server, you might want to output "Etag" and/or "Last-Modified" on http response header.  But there are some headers, which PHP itself outputs automatically, disturbing this.  So I wrote this function with erasing these.

If you guys know how to judge the return values of function "stat", in order to avoid using "is_file" or "is_readable" (or "is_dir"), please let me know or just write it here.

If you don't have to do anything special on 404, "header('HTTP/1.x xxx xxxxx');" can be inside of the function.

<?php

$filename
= '/foo/bar/myfeed.rss';
$http_stat_code = readfile_if_modified($filename, array('Content-Type: text/xml'));
switch(
$http_stat_code) {
case
404:
   
header('HTTP/1.0 404 Not Found');
    echo
'<html><head></head><body><a href="http://example.com/">http://example.com/<a></body></html>';
    exit;
default:
   
header('X-System-Url: http://example.com/', true, $http_stat_code);
}

function
readfile_if_modified($filename, $http_header_additionals = array()) {

    if(!
is_file($filename)) {
//      header('HTTP/1.0 404 Not Found');
       
return 404;
    }

    if(!
is_readable($filename)) {
//      header('HTTP/1.0 403 Forbidden');
       
return 403;
    }

   
$stat = @stat($filename);
   
$etag = sprintf('%x-%x-%x', $stat['ino'], $stat['size'], $stat['mtime'] * 1000000);

   
header('Expires: ');
   
header('Cache-Control: ');
   
header('Pragma: ');

    if(isset(
$_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
       
header('Etag: "' . $etag . '"');
//      header('HTTP/1.0 304 Not Modified');
       
return 304;
    } elseif(isset(
$_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $stat['mtime']) {
       
header('Last-Modified: ' . date('r', $stat['mtime']));
//      header('HTTP/1.0 304 Not Modified');
       
return 304;
    }

   
header('Last-Modified: ' . date('r', $stat['mtime']));
   
header('Etag: "' . $etag . '"');
   
header('Accept-Ranges: bytes');
   
header('Content-Length:' . $stat['size']);
    foreach(
$http_header_additionals as $header) {
       
header($header);
    }

    if(@
readfile($filename) === false) {
//      header('HTTP/1.0 500 Internal Server Error');
       
return 500;
    } else {
//      header('HTTP/1.0 200 OK');
       
return 200;
    }

}

?>
aks dot gandhi at gmail dot com 13-Apr-2009 03:16
Files were not opening in my windows vista system after I download it from my website.

File size were fine.
But I marked that dimensions and resolution information was not ther in file properties ( I basically compared same .jpg files)

Problem was: I was using readfile() fuction with few header properties set.( Most of the examples online do not use all the header properties before readfile() function)

I used all the properties stated here and now i am able to open files

:)

Following examples online took my 4 hrs to fix this small problem.
rmatakajr at gmail dot com 28-Jan-2009 12:25
Here is my version of readfile_chunked
you can make it act like file() or file_get_contents()
by using the type arg

<?php
   
function readfile_chunked ($filename,$type='array') {
     
$chunk_array=array();
     
$chunksize = 1*(1024*1024); // how many bytes per chunk
     
$buffer = '';
     
$handle = fopen($filename, 'rb');
      if (
$handle === false) {
       return
false;
      }
      while (!
feof($handle)) {
          switch(
$type)
          {
              case
'array':
             
// Returns Lines Array like file()
             
$lines[] = fgets($handle, $chunksize);
              break;
              case
'string':
             
// Returns Lines String like file_get_contents()
             
$lines = fread($handle, $chunksize);
              break;
          }
      }
      
fclose($handle);
       return
$lines;
    }
?>
Dr. Gianluigi &#34;Zane&#34; Zanettini, CEO[MLI] 29-Oct-2008 10:40
If the client shows "unknown filesize" in the dialog when downloading a file provided via readfile, you'd better check if mod_deflate, mod_gzip, mod_something-that-shrinks-http is installed on your server, and put an exception for your gateway page. Read more here: http://snipurl.com/4th3a
levhita at gmail dot com 21-Oct-2008 12:59
A note on the smartReadFile function from gaosipov:

Change the indexes on the preg_match matches to:
     
      $begin = intval($matches[1]);
      if( !empty($matches[2]) ) {
        $end = intval($matches[2]);
      }

Otherwise the $begin would be set to the entire section matched and the $end to what should be the begin.

See preg_match for more details on this.
gaosipov at gmail dot com 09-Oct-2008 02:08
Send file with HTTPRange support (partial download):

<?php
function smartReadFile($location, $filename, $mimeType='application/octet-stream')
{ if(!
file_exists($location))
  {
header ("HTTP/1.0 404 Not Found");
    return;
  }
 
 
$size=filesize($location);
 
$time=date('r',filemtime($location));
 
 
$fm=@fopen($location,'rb');
  if(!
$fm)
  {
header ("HTTP/1.0 505 Internal server error");
    return;
  }
 
 
$begin=0;
 
$end=$size;
 
  if(isset(
$_SERVER['HTTP_RANGE']))
  { if(
preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches))
    {
$begin=intval($matches[0]);
      if(!empty(
$matches[1]))
       
$end=intval($matches[1]);
    }
  }
 
  if(
$begin>0||$end<$size)
   
header('HTTP/1.0 206 Partial Content');
  else
   
header('HTTP/1.0 200 OK'); 
 
 
header("Content-Type: $mimeType");
 
header('Cache-Control: public, must-revalidate, max-age=0');
 
header('Pragma: no-cache'); 
 
header('Accept-Ranges: bytes');
 
header('Content-Length:'.($end-$begin));
 
header("Content-Range: bytes $begin-$end/$size");
 
header("Content-Disposition: inline; filename=$filename");
 
header("Content-Transfer-Encoding: binary\n");
 
header("Last-Modified: $time");
 
header('Connection: close'); 
 
 
$cur=$begin;
 
fseek($fm,$begin,0);

  while(!
feof($fm)&&$cur<$end&&(connection_status()==0))
  { print
fread($fm,min(1024*16,$end-$cur));
   
$cur+=1024*16;
  }
}
?>

Usage:

<?php
smartReadFile
("/tmp/filename","myfile.mp3","audio/mpeg")
?>

It can be slow for big files to read by fread, but this is a single way to read file in strict bounds. You can modify this and add fpassthru instead of fread and while, but it sends all data from begin --- it would be not fruitful if request is bytes from 100 to 200 from 100mb file.
davedx at gmail dot com 06-Oct-2008 02:29
There's a problem with the session mutex and file handling in PHP that can cause deadlocks and php to fail @ maximum execution time.

This includes any kind of exec flavour, and also readfile. We had a problem where if the user tried to download a file from our server that was sent with readfile, but then cancelled and retried straight away, php would freeze and time out. The solution was to end_session() before sending the file, this fixed it.
johniskew 26-Sep-2008 12:17
@marks suggestion-

That is one way to do it, however this is avoidable.  For example in Zend Framework you could do

<?php

// Action controller
public function someAction() {

   
$response = $this->_response;

   
// Disable view and layout rendering
   
$this->_helper->viewRenderer->setNoRender();
   
$this->_helper->layout()->disableLayout();

   
// Process the file
   
$file = 'whatever.zip';
   
$bits = @file_get_contents($file);
    if(
strlen($bits) == 0) {
       
$response->setBody('Sorry, we could not find requested download file.');
    }
    else {
       
$response->setHeader('Content-type', 'application/octet-stream', true);
       
$response->setBody($bits);
    }
}

?>
mark at shust dot com 17-Sep-2008 12:40
When using readfile with Zend Framework (or any other popular MVC for that matter), you must not follow the built-in httpEquiv or view pattern but rather write your own php to get readfile to work. You must also include a call to 'die' otherwise the script will continue following the MVC pattern and readfile will produce errors. This is not the fault of readfile, but the fault of the current MVC frameworks.

Here is a sample action that retrieves and displays a prompt box (open/save) for a specific pdf.

<?php
   
public function fddAction()
    {
       
// get attachment location
       
$attachment_location = $_SERVER["DOCUMENT_ROOT"] . "/pdf/fdd/sample.pdf";
       
        if (
file_exists($attachment_location)) {
           
// attachment exists
           
            // send open/save pdf dialog to user
           
header('Cache-Control: public'); // needed for i.e.
           
header('Content-Type: application/pdf');
           
header('Content-Disposition: attachment; filename="sample.pdf"');
           
readfile($attachment_location);
            die();
// stop execution of further script because we are only outputting the pdf
           
       
} else {
            die(
'Error: File not found.');
        }
    }
?>
paul dot worlton at convergint dot com 07-Aug-2008 08:57
Had a problem using PHP4.3 with IE7 not initiating the download.  Discovered that my content-type header was being replaced with "document\text".  Was able to resolve the problem by moving the content-type header to the top of the header list.
jtgrant at gmail dot com 28-Jul-2008 01:46
FIREFOX will recognize an absolute HTTP://www.site.com/path/file.pdf
OTHER BROWSERS WILL NOT. 
use relative: 'path/file.pdf'

for downloading files/pdfs:

make certain to check
if(file_exists($file)){header('Content-Disposition: attachment; filename="'.basename($file).'"');}

else{echo "$file not found";}

- This can be frustrating when your downloads are working in Firefox , but  not other browsers.  Best to check if file exists first because that will let you know if it's going to work all around.
william [aht] expostfacto [dawht] se 18-Jun-2008 04:59
Response to : antispam [at] rdx page [dot] com
20-Sep-2005 02:14 - Regarding the bod_bw apache module for limiting download speed and readfile;

If you use readfile to, say, pseduo stream mp3's or such, you can, at least with mod_bw 0.7, specify the php file that handles the readfile and limit bandwidth that way.

Example VirtualHost;

<VirtualHost *:80>
BandwidthModule On
ForceBandWidthModule On
LargeFileLimit theStreamFile.php 1 25000

...

</VirtualHost>

This would throttle all files that gets sent via theStreamFile.php to 25 kb/sek. Works like a champ. Tested on CentOS 5.
yura_imbp at mail dot ru 05-Jun-2008 09:46
if you need to limit download rate, use this code

<?php
$local_file
= 'file.zip';
$download_file = 'name.zip';

// set the download rate limit (=> 20,5 kb/s)
$download_rate = 20.5;
if(
file_exists($local_file) && is_file($local_file))
{
   
header('Cache-control: private');
   
header('Content-Type: application/octet-stream');
   
header('Content-Length: '.filesize($local_file));
   
header('Content-Disposition: filename='.$download_file);

   
flush();
   
$file = fopen($local_file, "r");
    while(!
feof($file))
    {
       
// send the current file part to the browser
       
print fread($file, round($download_rate * 1024));
       
// flush the content to the browser
       
flush();
       
// sleep one second
       
sleep(1);
    }
   
fclose($file);}
else {
    die(
'Error: The file '.$local_file.' does not exist!');
}

?>
saidketchman at gmail dot com 19-Apr-2008 05:06
Well I have a solution for anyone who may have had the same problem as I did.  Basically the problem was that when trying to use external urls in readfile, or in general trying to use the http:// prefix, only a file placeholder would download in Internet Explorer 7 and Safari (and possibly other browsers with the exception of Firefox).

So basically this is the how I solved the problem.  If you are using the code for the Content-Length:

header("Content-Length: ".filesize($filepath));

Then this is the issue.  I don't know if it is with only certain servers, but in my case Internet Explorer couldn't get a filesize so it decided to not download any of the file.

The solution was to simply remove this line and give it no filesize and then the browsers would download the whole file.
saidketchman at gmail dot com 17-Apr-2008 12:19
I'm having an issue with browers except FireFox 2.

I'm trying to use my download script to download from external URLs.  Firefox handles it fine and downloads the file but Internet Explorer 7 and Safari 2 only download a placeholder (empty) file.

Any ideas why I can't handle the external URLs?  I can swear it was working fine before but then out of no where a user emailed me saying it wasn't working anymore.

This is my code (with a bunch of headers added from posts here):

# Set Format Type
if ($audioformat == "wma")
{
    header('Content-type: audio/x-ms-wma');
}
else if ($audioformat == "mp3")
{
    header('Content-type: audio/mpeg');
}

$fullpath = $audiopath .$artist. '/' .$filename;

# header info for download
header('Content-Length: ' . filesize($fullpath));
header('Content-Disposition: attachment; filename="' . $filename. '"');
header('Content-Transfer-Encoding: binary');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');

header('Expires: 0');
header('Content-Description: Quran Download');
ob_clean();
flush();

# begin download
readfile($fullpath) or die("error!");
exit;
marro at email dot cz 19-Mar-2008 11:17
My script working correctly on IE6 and Firefox 2 with any typ e of files (I hope :))

function DownloadFile($file) { // $file = include path
        if(file_exists($file)) {
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename='.basename($file));
            header('Content-Transfer-Encoding: binary');
            header('Expires: 0');
            header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
            header('Pragma: public');
            header('Content-Length: ' . filesize($file));
            ob_clean();
            flush();
            readfile($file);
            exit;
        }

    }

Run on Apache 2 (WIN32) PHP5
uisluu at gmail dot com 19-Mar-2008 12:33
If you still encounter problems, downloading some types of binary files (.doc, .zip, .xls etc.) such as corrupted bytes (esp. NUL(0x00) bytes replaced by spaces(0x20)) with readfile(), consider the following replacement for use with download.php?file=somefile.ext

<?php
$downloadFolder
= '/download/';

if ( !
preg_match( '/^(\w)+\.(?!php|html|htm|shtml|phtml)[a-z0-9]{2,4}$/i', $_GET['file'] ) )    {
   
   
error_log( "USER REQUESTed for:\t".$_GET['file'] );
    die;
};

$filename = ROOT.$downloadFolder.$_GET['file'];

if ( !
file_exists($filename) )    {
    echo
"No file here. [<a href='javascript:self.close();'>close</a>]";
   
error_log( "USER REQUESTed for non-existing file:\t".$_GET['file'] );
    exit;
};

$string = 'Location: '.HOST.$downloadFolder.$_GET['file'];
header($string);

exit;
?>
TimB 11-Feb-2008 02:23
To anyone that's had problems with Readfile() reading large files into memory the problem is not Readfile() itself, it's because you have output buffering on. Just turn off output buffering immediately before the call to Readfile(). Use something like ob_end_flush().
lcampanis.com 31-Oct-2007 01:55
If you are trying to force a download from a script and you're having corrupted files, but the download was successful, just make sure you don't have spaces or news lines before and/or after <? script ?>

You can check this by opening your download with a text editor. If you see empty lines or spaces at the top, then that's the problem.
Hayley Watson 17-Oct-2007 09:27
To avoid the risk of choosing themselves which files to download by messing with the request and doing things like inserting "../" into the "filename", simply remember that URLs are not file paths, and there's no reason why the mapping between them has to be so literal as "download.php?file=thingy.mpg" resulting in the download of the file "thingy.mpg".

It's your script and you have full control over how it maps file requests to file names, and which requests retrieve which files.

But even then, as ever, never trust ANYTHING in the request. Basic first-day-at-school security principle, that.
Sohel Taslim 02-Aug-2007 02:44
If you use session and Secure Site(SSL- Secure Sockets Layer) to download files using PHP function readfile(), You can get an error message for Inetrnet Explorer (IE).
To avoid this problem try following function.
Hope it can help you. By, sohel62 at yahoo dot com.

<?php

session_cache_limiter
('none'); //*Use before session_start()
session_start();

$file = 'ASDFGgg.pdf';
_Download("files_dir/".$file, $file);

function
_Download($f_location,$f_name){
    
header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");
   
header('Content-Description: File Transfer');
   
header('Content-Type: application/octet-stream');
   
header('Content-Length: ' . filesize($f_location));
   
header('Content-Disposition: attachment; filename=' . basename($f_name));
   
readfile($f_location);
 }

?>
hamdiya dot dev at gmail dot com 17-Jul-2007 09:41
Using FTP is also possible with readfile.

readfile('ftp://'.$ftp_user.':'.$ftp_pass.'@'.$ftp_host.'/'.$file);
Sinured 17-Jul-2007 06:00
In response to "grey - greywyvern - com":

If you know the target _can't_ be a remote file (e.g. prefixing it with a directory), you should use include instead.
If the user manages to set the target to some kinda config-file (configuration.php in Joomla!), he will get a blank page - unless readfile() is used. Using include will just behave as a normal request (no output).
For remote files however use readfile().
Kniht 07-Jul-2007 11:27
@Elliott Brueggeman
What's the point of a user's settings if not to determine their environment? If they have it set a specific way, honor their setting.
Elliott Brueggeman 25-Jun-2007 08:47
I have noticed some unusual behavior with Internet Explorer 6 that’s worth taking note of. I have a link on my site to a script that outputs an XML file to the browser with the below code:

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
@readfile($file);

When the popular IE setting “Reuse Window for Launching Shortcuts” is unchecked (access this setting in the Tools Menu > Internet Options > Advanced Tab) this script will output the file to the browser and open it in a different window if the user clicks the open button on the IE prompt. However, if this setting is checked, and browser windows are being re-used, then it will open up on top of the page where the link was clicked to access the script.

If I instead set the html link target option to be “_blank”, the script will open up in a new window as expected if the “Reuse Window for Launching Shortcuts” is checked. But, if the setting is unchecked, the output XML file will open up in a new window and there will be another blank window also open that has the address of the script, in addition to our original window.

This is far from ideal, and there is no way of knowing whether users have this option checked or not. We are stuck with the distinct possibility of half of our visitors seeing either an annoying third blank window being opened or the script writing over their original window, depending on their “Reuse Window for Launching Shortcuts” setting.
chad 0x40 herballure 0x2e com 17-May-2007 07:53
In reply to herbert dot fischer at NOSPAM dot gmail dot com:

The streams API in PHP5 tries to make things as efficient as possible; in php-5.1.6 on Linux, fpassthru is faster than 'echo fread($fp, 8192)' in a loop, and readfile is even faster for files on disk. I didn't benchmark further, but I'd be willing to bet non-mmap'able streams still win because they can loop in C instead of PHP.
MasterQ at 127 dot 0 dot 0 dot 1 22-Apr-2007 07:40
Do not forgot to call
exit;
after readfile else you will get trouble with the file checksum of the file, because there will be added \n at the end of the script.
mAu 10-Oct-2006 12:25
Instead of using
<?php
header
('Content-Type: application/force-download');
?>
use
<?php
header
('Content-Type: application/octet-stream');
?>
Some browsers have troubles with force-download.
ericlaw1979 at hotmail dot com 07-Jun-2006 12:18
It is an error to send post-check=0.  See http://blogs.msdn.com/ie/archive/2006/06/01/613132.aspx
irek at eccomes dot de 29-Mar-2006 08:35
Related to francesco at paladinux: HOW TO RESOLVE EXPLORER SAVE PROBLEM IN FORCE-DOWNLOAD.

To use "application/octetstream" instead of "application/octet-stream" may help in some cases. But the solution of this problem in most cases is to use

header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");

See also message of ctemple below. This helps not only for pdf but also generally.
oryan at zareste dot com 26-Nov-2005 07:18
As Grey said below:  Readfile will send users un-executed PHP files, which makes it easy to exploit vulnerabilities.  It's common - and easy - to use GET variables pointing to downloadable files, like script.php?v=web/file.mov , but this lets users to change it to script.php?v=index.php and get damaging info.  Even POST variables can be exploited this way if the user's on a custom browser.

To keep secure, limit downloadable files to one directory, like 'web/', so that script.php?v=file.mov will send web/file.mov, and scan the variable for '..' and 'php' to make sure users can't go into other directories, or open php files you may have stupidly put under web/.  This should cover all the bases.
peavey at pixelpickers dot com 20-Oct-2005 06:38
A mime-type-independent forced download can also be conducted by using:

<?
(...)
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // some day in the past
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Content-type: application/x-download");
header("Content-Disposition: attachment; filename={$new_name}");
header("Content-Transfer-Encoding: binary");
?>

Cheers,

Peavey
planetmaster at planetgac dot com 16-Oct-2005 11:44
Using pieces of the forced download script, adding in MySQL database functions, and hiding the file location for security was what we needed for downloading wmv files from our members creations without prompting Media player as well as secure the file itself and use only database queries. Something to the effect below, very customizable for private access, remote files, and keeping order of your online media.

<?
   
# Protect Script against SQL-Injections
   
$fileid=intval($_GET[id]);
   
# setup SQL statement
   
$sql = " SELECT id, fileurl, filename, filesize FROM ibf_movies WHERE id=' $fileid' ";

   
# execute SQL statement
   
$res = mysql_query($sql);

       
# display results
       
while ($row = mysql_fetch_array($res)) {
       
$fileurl = $row['fileurl'];
       
$filename= $row['filename'];
       
$filesize= $row['filesize'];

          
$file_extension = strtolower(substr(strrchr($filename,"."),1));

           switch (
$file_extension) {
               case
"wmv": $ctype="video/x-ms-wmv"; break;
               default:
$ctype="application/force-download";
           }

// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');

          
header("Pragma: public");
          
header("Expires: 0");
          
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
          
header("Cache-Control: private",false);
          
header("Content-Type: video/x-ms-wmv");
          
header("Content-Type: $ctype");
          
header("Content-Disposition: attachment; filename=\"".basename($filename)."\";");
          
header("Content-Transfer-Encoding: binary");
          
header("Content-Length: ".@filesize($filename));
          
set_time_limit(0);
           @
readfile("$fileurl") or die("File not found.");

}

$donwloaded = "downloads + 1";

    if (
$_GET["hit"]) {
       
mysql_query("UPDATE ibf_movies SET downloads = $donwloaded WHERE id=' $fileid'");

}

?>

While at it I added into download.php a hit (download) counter. Of course you need to setup the DB, table, and columns. Email me for Full setup// Session marker is also a security/logging option
Used in the context of linking:
http://www.yourdomain.com/download.php?id=xx&hit=1

[Edited by sp@php.net: Added Protection against SQL-Injection]
antispam [at] rdx page [dot] com 20-Sep-2005 02:14
Just a note:  If you're using bw_mod (current version 0.6) to limit bandwidth in Apache 2, it *will not* limit bandwidth during readfile events.
23-Aug-2005 02:39
here is a nice force download scirpt

            $filename = 'dummy.zip';
            $filename = realpath($filename);

            $file_extension = strtolower(substr(strrchr($filename,"."),1));

            switch ($file_extension) {
                case "pdf": $ctype="application/pdf"; break;
                case "exe": $ctype="application/octet-stream"; break;
                case "zip": $ctype="application/zip"; break;
                case "doc": $ctype="application/msword"; break;
                case "xls": $ctype="application/vnd.ms-excel"; break;
                case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
                case "gif": $ctype="image/gif"; break;
                case "png": $ctype="image/png"; break;
                case "jpe": case "jpeg":
                case "jpg": $ctype="image/jpg"; break;
                default: $ctype="application/force-download";
            }

            if (!file_exists($filename)) {
                die("NO FILE HERE");
            }

            header("Pragma: public");
            header("Expires: 0");
            header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
            header("Cache-Control: private",false);
            header("Content-Type: $ctype");
            header("Content-Disposition: attachment; filename=\"".basename($filename)."\";");
            header("Content-Transfer-Encoding: binary");
            header("Content-Length: ".@filesize($filename));
            set_time_limit(0);
            @readfile("$filename") or die("File not found.");
herbert dot fischer at NOSPAM dot gmail dot com 21-Jul-2005 08:01
readfile and fpassthru are about 55% slower than doing a loop with "feof/echo fread".
chrisputnam at gmail dot com 29-Jun-2005 01:44
In response to flowbee@gmail.com --

When using the readfile_chunked function noted here with files larger than 10MB or so I am still having memory errors. It's because the writers have left out the all important flush() after each read. So this is the proper chunked readfile (which isn't really readfile at all, and should probably be crossposted to passthru(), fopen(), and popen() just so browsers can find this information):

<?php
function readfile_chunked($filename,$retbytes=true) {
  
$chunksize = 1*(1024*1024); // how many bytes per chunk
  
$buffer = '';
  
$cnt =0;
  
// $handle = fopen($filename, 'rb');
  
$handle = fopen($filename, 'rb');
   if (
$handle === false) {
       return
false;
   }
   while (!
feof($handle)) {
      
$buffer = fread($handle, $chunksize);
       echo
$buffer;
      
ob_flush();
      
flush();
       if (
$retbytes) {
          
$cnt += strlen($buffer);
       }
   }
      
$status = fclose($handle);
   if (
$retbytes && $status) {
       return
$cnt; // return num. bytes delivered like readfile() does.
  
}
   return
$status;

}
?>

All I've added is a flush(); after the echo line. Be sure to include this!
17-May-2005 11:21
I saw in previous contributed notes that in content-disposition the file is not a quoted-string, there is no problem if the filename have no spaces but if it has in IE it works but in Firefox not.

The RFC 2616 puts as an example this:
Content-Disposition: attachment; filename="fname.ext"

You can see http://www.faqs.org/rfcs/rfc2616.html section "19.5.1 Content-Disposition" for more details.

The correct header then is this:
header("Content-Disposition: attachment; filename=\"$filename\"");
Philipp Heckel 10-May-2005 04:58
To use readfile() it is absolutely necessary to set the mime-type before. If you are using an Apache, it's quite simple to figure out the correct mime type. Apache has a file called "mime.types" which can (in normal case) be read by all users.

Use this (or another) function to get a list of mime-types:

<?php

   
function mimeTypes($file) {
        if (!
is_file($file) || !is_readable($file)) return false;
       
$types = array();
       
$fp = fopen($file,"r");
        while (
false != ($line = fgets($fp,4096))) {
            if (!
preg_match("/^\s*(?!#)\s*(\S+)\s+(?=\S)(.+)/",$line,$match)) continue;
           
$tmp = preg_split("/\s/",trim($match[2]));
            foreach(
$tmp as $type) $types[strtolower($type)] = $match[1];
        }
       
fclose ($fp);
       
        return
$types;
    }

   
# [...]

    # read the mime-types
   
$mimes = mimeTypes('/usr/local/apache/current/conf/mime.types');

   
# use them ($ext is the extension of your file)
   
if (isset($mimes[$ext])) header("Content-Type: ".$mimes[$ext]);
   
header("Content-Length: ".@filesize($fullpath));
   
readfile($fullpath); exit;

?>

If you do not want to read from the mime.types file directly, you can of course make a copy in another folder!
Cheers Philipp Heckel
flobee at gmail dot com 06-May-2005 11:17
regarding php5:
i found out that there is already a disscussion @php-dev  about readfile() and fpassthru() where only exactly 2 MB will be delivered.

so you may use this on php5 to get lager files
<?php
function readfile_chunked($filename,$retbytes=true) {
   
$chunksize = 1*(1024*1024); // how many bytes per chunk
   
$buffer = '';
   
$cnt =0;
   
// $handle = fopen($filename, 'rb');
   
$handle = fopen($filename, 'rb');
    if (
$handle === false) {
        return
false;
    }
    while (!
feof($handle)) {
       
$buffer = fread($handle, $chunksize);
        echo
$buffer;
        if (
$retbytes) {
           
$cnt += strlen($buffer);
        }
    }
       
$status = fclose($handle);
    if (
$retbytes && $status) {
        return
$cnt; // return num. bytes delivered like readfile() does.
   
}
    return
$status;

}
?>
TheDayOfCondor 27-Apr-2005 06:24
I think that readfile suffers from the maximum script execution time. The readfile is always completed even if it exceed the default 30 seconds limit, then the script is aborted.
Be warned that you can get very odd behaviour not only on large files, but also on small files if the user has a slow connection.

The best thing to do is to use

<?
  set_time_limit
(0);
?>

just before the readfile, to disable completely the watchdog if you intend to use the readfile call to tranfer a file to the user.
comicforum at lelon dot net 22-Apr-2005 11:22
The problem with using readfile on large files isn't caused by your memory_limit setting.  Setting it to 4x the size of the file can still cause the file to be truncated.  Use the readfile_chunked found below.
TheDayOfCondor 20-Apr-2005 09:10
Beware - the chunky readfile suggested by Rob Funk can easily exceed you maximum script execution time (30 seconds by default).

I suggest you to use the set_time_limit function inside the while loop to reset the php watchdog.
php at cNhOiSpPpAlMe dot net 21-Feb-2005 06:25
For some reason, readfile seems to reset the file's modified time (filemtime). Using fopen and fpassthru avoids this.

<?
$fp
= @fopen($file,"rb");
fpassthru($fp);
fclose($fp);
?>

(PHP version: 4.3.10)
Rob Funk 04-Jan-2005 10:31
When using readfile() with very large files, it's possible to run into problems due to the memory_limit setting; apparently readfile() pulls the whole file into memory at once.

One solution is to make sure memory_limit is larger than the largest file you'll use with readfile().  A better solution is to write a chunking readfile.  Here's a simple one that doesn't exactly conform to the API, but is close enough for most purposes:

<?php
function readfile_chunked ($filename) {
 
$chunksize = 1*(1024*1024); // how many bytes per chunk
 
$buffer = '';
 
$handle = fopen($filename, 'rb');
  if (
$handle === false) {
    return
false;
  }
  while (!
feof($handle)) {
   
$buffer = fread($handle, $chunksize);
    print
$buffer;
  }
  return
fclose($handle);
}
?>
Justin Dearing 29-Dec-2004 03:38
Word for word copy of a comment on session_start() posted by Kevin. Might be relevant here.

If you're having a problem with a file download script not working with IE if you call session_start() before sending the file, then try adding a session_cache_limiter() call before session_start().

I use session_cache_limiter('none'), but 'public' and 'private' seem to fix the problem too; use whichever suits your application.
grey - greywyvern - com 29-Dec-2004 08:29
readfile() makes a handy include for what you know should be plain HTML or text.  Sloppy and/or lazy scripters can introduce security risks when using include() and require() in such a way that users can tell the script what file(s) to include.  This is because the PHP code contained in any file they refer to is executed with PHP's privledges.

With readfile(), any PHP code is passed to the output buffer as-is, without being executed.
ctemple at n-able dot com 15-Nov-2004 12:12
If you're passing files through a script, you may want to include this header:

        header ("Cache-Control: must-revalidate, post-check=0, pre-check=0");

Otherwise, some programs such as Adobe Reader may have problems opening files directly.

<?php
        header
("Cache-Control: must-revalidate, post-check=0, pre-check=0");
       
header ("Content-Type: application/octet-stream");
       
header ("Content-Length: " . filesize($theFile));
       
header ("Content-Disposition: attachment; filename=$theFileName");
       
readfile($theFile);
?>
Thomas Jespersen 25-Oct-2004 06:06
Remember if you make a "force download" script like mentioned below that you SANITIZE YOUR INPUT!

I have seen a lot of  download scripts that does not test so you are able to download anything you want on the server.

Test especially for strings like ".." which makes directory traversal possible. If possible only permit characters a-z, A-Z and 0-9 and make it possible to only download from one "download-folder".
flobee at gmail dot com 04-Oct-2004 05:33
you may try the statemants below with
header("Content-Type: application/octet-stream");
// insteat OF: header('Content-Type: application/force-download');
MAC OSX / IE: has problems to find the right mime type and do not accept spaces in filenames. then all plattforms should work
nobody at localhost 23-Sep-2004 02:03
If you want to force a download:

<?php
$file
= '/var/www/html/file-to-download.xyz';
header('Content-Description: File Transfer');
header('Content-Type: application/force-download');
header('Content-Length: ' . filesize($filename));
header('Content-Disposition: attachment; filename=' . basename($file));
readfile($file);
?>
kschenke at datafreight dot com 15-Oct-2003 09:43
If your site visitors are having problems with Internet Explorer downloading files over an SSL connection, see the manual page for the session_cache_limiter() function.

 
show source | credits | sitemap | contact | advertising | mirror sites