readfile

(PHP 3, PHP 4, PHP 5)

readfile -- 输出一个文件

说明

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

读入一个文件并写入到输出缓冲。

返回从文件中读入的字节数。如果出错返回 FALSE 并且除非是以 @readfile() 形式调用,否则会显示错误信息。

提示: 如果“fopen wrappers”已经被激活,则在本函数中可以把 URL 作为文件名来使用。请参阅 fopen() 函数来获取怎样指定文件名的详细信息以及支持 URL 封装协议的列表:附录 L

如果也想在 include_path 中搜索文件,可以使用可选的第二个参数并将其设为 TRUE

注: 对 context 的支持是 PHP 5.0.0 添加的。有关 context 的说明请参考参考 CXLI, Stream Functions

参见 fpassthru()file()fopen()include()require()virtual()file_get_contents()附录 L


add a note add a note User Contributed Notes
nicolas at foonster dot com
02-Nov-2006 06:21
Good Morning, All my download files are corrupting because a character is being prepended to the data from the readfile.  I cannot locate it:

<?PHP
header
("Pragma: no-cache");
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Type: application/vnd.ms-excel");
header("Content-Disposition: attachment; filename=\"test.xls\"");
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize( 'store.xls' )."");
readfile( 'store.xls' );
exit;
?>

This is my code - any help would be appreciated.
mAu
11-Oct-2006 03: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.
jhodoe at gmail dot com
18-Aug-2006 03:39
Sorry is correcto that's..
For correct download force

$url="pepe.zip";
 header('Content-Description: File Transfer');
           header('Content-Type: application/force-download');
           header("Content-Disposition: attachment; filename=\"".basename($url)."\";");

           header('Content-Length: ' . filesize($url));
@readfile($url) OR die();
rhoward at progsoc dot uts dot edu dot removethis dot au
09-Aug-2006 09:39
We've just run into a problem where certain PDF files are only partially printed when readfile() is being used inside a buffer. To fix this, you need to add an @ob_flush() after the call to readfile(), eg.

<?php
  ob_start
();
  
readfile('example.file');
  
// use @ to hide 'nothing to flush' errors
  
@ob_flush();

  
$content = ob_get_contents();
 
ob_end_clean();
?>
egingell at sisna dot com
23-Jul-2006 09:20
########## QUOTE: #########
oryan at zareste dot com
26-Nov-2005 08: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.
########## /QUOTE: #########

Or you can check the file extention and make sure it's not PHP.

<?
$ext
= pathinfo($_REQUEST['v'], PATHINFO_EXTENSION);
if (
strtolower($ext) == 'php') die('You are trying to view files that you have no business viewing.');
?>

Also note: If readfile() is used on external sites, such as 'http://www.php.net', you will output what your visitors would see if they were to go to that site by typing it into their web browser's address bar. Only all of the page's relative paths, such as in the link <a href="next_page.html">Next Page</a>, will resolve to files in your file's scope *not* the original site's scope, thus if you don't have a matching "next_page.html" in the same directory, visitors will get a 404 error when clicking the "Next Page" link. Same goes with <img>, <embed>, <object>, <link>, <form>, etc.
ericlaw1979 at hotmail dot com
07-Jun-2006 03: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
30-Mar-2006 12: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.
stefan at stefandouma dot nl
27-Mar-2006 08:00
If you want to read a file from your ftp server use this:

readfile("ftp://" . $username . ":" . $password . "@domain.com/file.html");

Don't use this, it's not going to work:

readfile(ftp_get($conn_id, $local_file, $server_file, FTP_BINARY));
Paladinux<francesco at paladinux dot net>
25-Feb-2006 12:47
[2th Part of My MESSAGE]

//UNDERSTEND EXTENSION TO RESOLVE IEXPLORER BUG
//
switch ($type) {
case "exe": (($browser=='IE' || $browser=='OPERA')?
(
$ctype="application/octetstream"):(
$ctype="application/octet-stream"));
break;
case "pdf": $ctype="application/pdf";
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";
}
//EXIST FILE? YES ->FORCEDOWNLOAD
//                  NO ->DIE AND COME BACK IF IS POSSIBLE
if (file_exists($file))
   {
           header("Pragma: public");
           header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
           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="'.$filename.'.'.$type.'"');
           header("Content-Transfer-Encoding: binary");
           header('Content-Length: '.filesize($abs_item));
           set_time_limit(0);
           @readfile($abs_item) OR die("<html><body OnLoad=\"javascript: alert('Nessun file trovato');history.back();\" bgcolor=\"#F0F0F0\"></body></html>");
           exit;
   }//fine if ($file)
else die("<html><body OnLoad=\"javascript: alert('Nessun file da scaricare!');history.back();\" bgcolor=\"#F0F0F0\"></body></html>");
Paladinux<francesco at paladinux dot net>
25-Feb-2006 12:42
[1th Part of My MESSAGE]

Argh!!!!!!!!!!!!!
There is some difference from the browser
In Explorer 6, 4 example, if you read file and force download, show the mask "Do you want't save or open the file?".
If you save, it's all right, if not don't work.

This is an example to
"HOW RESOLVE EXPLOER SAVE PROBLEM IN FORCE-DOWNLOAD WRITTEN IN PHP MODE"

Have fun!

Paladinux

======================================
error_reporting(0); //Don't remove
##UNDERSTEND THE BROWSER
function id_browser() {
$browser=$GLOBALS['__SERVER']['HTTP_USER_AGENT'];
  
   if(ereg('Opera(/| )([0-9].[0-9]{1,2})', $browser)) {
       return 'OPERA';
   } else if(ereg('MSIE ([0-9].[0-9]{1,2})', $browser)) {
       return 'IE';
   } else if(ereg('OmniWeb/([0-9].[0-9]{1,2})', $browser)) {
       return 'OMNIWEB';
   } else if(ereg('(Konqueror/)(.*)', $browser)) {
       return 'KONQUEROR';
   } else if(ereg('Mozilla/([0-9].[0-9]{1,2})', $browser)) {
       return 'MOZILLA';
   } else {
       return 'OTHER';
   }
}
## OBTAIN  ID FROM GET
$id=$_GET['id'];
##OBTAIN DATA FROM DB "SELECT * FROM your_attachment_table WHERE id=$id LIMIT 1";
#    $id,$realname ,$srvname  = $nome.'.'.$type,$byte,$download 
##V.I.THINGS-> $type      = strtolower($a_row['Attachment_Real_Ext']);
   $nome      = str_pad($a_row['id'], 10, "0", STR_PAD_LEFT)    ;
#BUILDING PATH
$abs_item="DATA/".$srvname;
#BUILDING FILE DIMENSION
$size=filesize($file);
@set_time_limit(600);
   $browser=id_browser();
oryan at zareste dot com
27-Nov-2005 11: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 09: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
17-Oct-2005 02: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
21-Sep-2005 05: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.
24-Aug-2005 05: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
22-Jul-2005 11:01
readfile and fpassthru are about 55% slower than doing a loop with "feof/echo fread".
cenaculo at netcabo dot pt
19-Jul-2005 03:09
This is a good function to overcome the Animated GIF problem in PHP, sending a GIF as a normal file, just putting a header before, just to tell the browser that you are sending a GIF. This was the only way i found to send an animated GIF that really animates in the browser.

The code i reproduce here is not mine, but posted before by chrisputnam at gmail dot com, and it works just fine, i just putted the header before:

<?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;
    
flush();
     if (
$retbytes)
     {
      
$cnt += strlen($buffer);
     }
   }
  
$status = fclose($handle);
   if (
$retbytes && $status)
   {
     return
$cnt; // return num. bytes delivered like readfile() does.
  
}
   return
$status;
  }

 
$vfile = "filename.gif";
 
header("Content-type: image/gif");
 
readfile_chunked($vfile,false);
?>

The code i had before was like this:

<?php
  $vfile
= "filename.gif";
 
$im = imagecreatefromgif($vfile);
 
header("Content-type: image/gif");
 
imagegif($im);
?>

But it only shows the first frame of the Animated GIF.
chrisputnam at gmail dot com
30-Jun-2005 04: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!
Hernn Pereira
18-May-2005 02: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
11-May-2005 07: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
07-May-2005 02: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 09: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
23-Apr-2005 02: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
21-Apr-2005 12: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
22-Feb-2005 10: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
05-Jan-2005 02: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
30-Dec-2004 07: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
30-Dec-2004 12: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
16-Nov-2004 04: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 09: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 08: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 05: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
16-Oct-2003 12: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.