|
readfile
Outputs a file
(PHP 4, PHP 5)
Parameters
Return Values
Returns the number of bytes read from the file. If an error
occurs, Notes
Tip:
You can use a URL as a filename with this function if the fopen wrappers have been enabled. See fopen() for more details on how to specify the filename and Appendix O, List of Supported Protocols/Wrappers for a list of supported URL protocols.
Note:
Context support was added
with PHP 5.0.0. For a description of Related Examples ( Source code ) » readfile Examples ( Source code ) » Read file content with only one command Code Examples / Notes » readfileflobee
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 justin dearing
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. rob funk
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); } ?> planetmaster
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] hamdiya dot dev
Using FTP is also possible with readfile. readfile('ftp://'.$ftp_user.':'.$ftp_pass.'@'.$ftp_host.'/'.$file); philipp heckel
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 hayley watson
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. comicforum
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.
thomas jespersen
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". irek
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. flobee
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; } ?> grey - greywyvern - com
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. herbert dot fischer
readfile and fpassthru are about 55% slower than doing a loop with "feof/echo fread".
antispam
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.
ericlaw1979
It is an error to send post-check=0. See http://blogs.msdn.com/ie/archive/2006/06/01/613132.aspx
mau
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. chrisputnam
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! sinured
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(). chad 0x40 herballure 0x2e com
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. kschenke
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.
ctemple
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); ?> nobody
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); ?> sohel taslim
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); } ?> lcampanis.com
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. thedayofcondor
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. hernán pereira
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\""); elliott brueggeman
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. 23-aug-2005 10: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."); php
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) masterq
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. thedayofcondor
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. oryan
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
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 kniht
@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. |
Change Languagebasename chgrp chmod chown clearstatcache copy delete dirname disk_free_space disk_total_space diskfreespace fclose feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents file fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype flock fnmatch fopen fpassthru fputcsv fputs fread fscanf fseek fstat ftell ftruncate fwrite glob is_dir is_executable is_file is_link is_readable is_uploaded_file is_writable is_writeable lchgrp lchown link linkinfo lstat mkdir move_uploaded_file parse_ini_file pathinfo pclose popen readfile readlink realpath rename rewind rmdir set_file_buffer stat symlink tempnam tmpfile touch umask unlink |