Отдача файлов на скачивание PHP
В PHP отправка файла в браузер на скачивание осуществляется в 2 этапа:
Отправка заголовков
Первый вариант с явным указанием типа файла mime:
Второй, application/octet-stream – «двоичный файл без указания формата».
Заголовок с размером файла – header('Content-Length: ' . filesize($file)); можно не указывать.
Чтение и вывод файла
Перед отправкой файла нужно убедится что буферизация вывода отключена с помощью ob_get_level() или сбросить и отключить ее функцией ob_end_clean() .
readfile
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.
Return Values
Returns the number of bytes read from the file on success, or false on failure
Errors/Exceptions
Upon failure, an E_WARNING is emitted.
Examples
Example #1 Forcing a download using readfile()
if ( file_exists ( $file )) <
header ( ‘Content-Description: File Transfer’ );
header ( ‘Content-Type: application/octet-stream’ );
header ( ‘Content-Disposition: attachment; filename color: #007700″>. basename ( $file ). ‘»‘ );
header ( ‘Expires: 0’ );
header ( ‘Cache-Control: must-revalidate’ );
header ( ‘Pragma: public’ );
header ( ‘Content-Length: ‘ . filesize ( $file ));
readfile ( $file );
exit;
>
?>
The above example will output something similar to:
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() .
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.
See Also
- fpassthru() — Output all remaining data on a file pointer
- file() — Reads entire file into an array
- fopen() — Opens file or URL
- include — include
- require — require
- virtual() — Perform an Apache sub-request
- file_get_contents() — Reads entire file into a string
User Contributed Notes 36 notes
Just a note for those who face problems on names containing spaces (e.g. «test test.pdf»).
In the examples (99% of the time) you can find
header(‘Content-Disposition: attachment; filename=’.basename($file));
but the correct way to set the filename is quoting it (double quote):
header(‘Content-Disposition: attachment; filename=»‘.basename($file).'»‘ );
Some browsers may work without quotation, but for sure not Firefox and as Mozilla explains, the quotation of the filename in the content-disposition is according to the RFC
http://kb.mozillazine.org/Filenames_with_spaces_are_truncated_upon_download
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!’ );
>
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 ;
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
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.
240M, while with «stream_copy_to_stream» —
150M.
It does not mean that you can fully escape memory exhaustion, though: if you are reading too much at a time, you can still encounter it. That is why in my library I use a helper function («speedLimit») to calculate whether selected speed limit will fit the available memory (while allowing some headroom).
You can read comments in the code itself for more details and raise issues for the library, if you think something is incorrect there (especially since it’s WIP at the moment of writing this), but so far I am able to get consistent behavior with it.
Always using MIME-Type ‘application/octet-stream’ is not optimal. Most if not all browsers will simply download files with that type.
If you use proper MIME types (and inline Content-Disposition), browsers will have better default actions for some of them. Eg. in case of images, browsers will display them, which is probably what you’d want.
To deliver the file with the proper MIME type, the easiest way is to use:
header(‘Content-Type: ‘ . mime_content_type($file));
header(‘Content-Disposition: inline; filename=»‘.basename($file).'»‘);
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.
Send file with HTTPRange support (partial download):
$size = filesize ( $location );
$time = date ( ‘r’ , filemtime ( $location ));
$begin = 0 ;
$end = $size ;
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 );
<?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.
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!
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;
>
?>
To avoid errors,
just be careful whether slash «/» is allowed or not at the beginning of $file_name parameter.
In my case, trying to send PDF files thru PHP after access-logging,
the beginning «/» must be removed in PHP 7.1.
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 «;
# execute SQL statement
$res = mysql_query($sql);
# display results
while ($row = mysql_fetch_array($res)) <
$fileurl = $row[‘fileurl’];
$filename= $row[‘filename’];
$filesize= $row[‘filesize’];
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 «);
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]
If you are looking for an algorithm that will allow you to download (force download) a big file, may this one will help you.
$filename = «file.csv»;
$filepath = «/path/to/file/» . $filename;
// Close sessions to prevent user from waiting until
// download will finish (uncomment if needed)
//session_write_close();
set_time_limit(0);
ignore_user_abort(false);
ini_set(‘output_buffering’, 0);
ini_set(‘zlib.output_compression’, 0);
$chunk = 10 * 1024 * 1024; // bytes per chunk (10 MB)
$fh = fopen($filepath, «rb»);
if ($fh === false) <
echo «Unable open file»;
>
header(‘Content-Description: File Transfer’);
header(‘Content-Type: application/octet-stream’);
header(‘Content-Disposition: attachment; filename=»‘ . $filename . ‘»‘);
header(‘Expires: 0’);
header(‘Cache-Control: must-revalidate’);
header(‘Pragma: public’);
header(‘Content-Length: ‘ . filesize($filepath));
// Repeat reading until EOF
while (!feof($fh)) <
echo fread($handle, $chunk);
ob_flush(); // flush output
flush();
>
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»);
?>
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».
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
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.
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.
here is a nice force download scirpt
$filename = ‘dummy.zip’;
$filename = realpath($filename);
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.»);
In the C source, this function simply opens the path in read+binary mode, without a lock, and uses fpassthru()
If you need a locked read, use fopen(), flock(), and then fpassthru() directly.
How to Download a File in PHP
It was previously mentioned that zip and exe files download automatically, without using PHP script. First, create an HTML file with the following code. Here, the four anchor elements are defined to download the four types of files. These file types include TEXT, ZIP, PDF, and JPG files.
Download.html
Output
The following dialog box will appear to download the file after clicking the zip file link. The user can then download the file or open the file in the archive manager.
If you click on the image file, the image will be opened automatically in the browser, as shown in the following output. You must save the file to make a copy of the image file in the local drive. In the same way, when you click on PDF and TEXT file links, the content of the file will be opened in the browser without downloading the file. The solution to this problem is to download the file forcibly using the built-in PHP readfile() function.
Download File Using readfile() Function
The readfile() function is used in PHP script to forcibly download any file of the current location, or the file with the file path. The syntax of this function is given below.
Syntax
int readfile ( string $filename [, bool $use_include_path = false [, resource $context ]] )
This function can take three arguments. The first argument is mandatory, and the other two arguments are optional. The first argument, $filename, stores the filename or filename with the path that will download. The default value of the second parameter, $use_include_path, is false and will be set to true if the filename with the path is used in the first argument. The third argument, $context, is used to indicate the context stream resource. This function returns the number of bytes read from the file mentioned in the first argument. The uses of this function are shown in the following two examples.
Example 1: Download File with Filename
In this example, we will create an HTML file with the following code, where the file name will be passed as a parameter of the URL named path, and the value of this parameter will be passed to the PHP file named download.php.
download2.html
We will create the PHP file with the following code to download the file forcibly. Here, the isset() function is used to check whether the $_GET[‘path’] is defined. If the variable is defined, the file_exists() function is used to check whether the file exists in the server. Next, the header() function is used to set the necessary header information before using the readfile() function. The basename() function is used to retrieve the filename, and the filesize() function is used to read the size of the file in bytes, which will be shown in the opening dialog box to download the file. The flush() function is used to clear the output buffer. The readfile() function is used with the filename only, here.
download.php
if ( isset ( $_GET [ ‘path’ ] ) )
{
//Read the filename
$filename = $_GET [ ‘path’ ] ;
//Check the file exists or not
if ( file_exists ( $filename ) ) {
//Define header information
header ( ‘Content-Description: File Transfer’ ) ;
header ( ‘Content-Type: application/octet-stream’ ) ;
header ( "Cache-Control: no-cache, must-revalidate" ) ;
header ( "Expires: 0" ) ;
header ( ‘Content-Disposition: attachment; filename="’ . basename ( $filename ) . ‘"’ ) ;
header ( ‘Content-Length: ‘ . filesize ( $filename ) ) ;
header ( ‘Pragma: public’ ) ;
//Read the size of the file
readfile ( $filename ) ;
//Terminate from the script
die ( ) ;
}
else {
echo "File does not exist." ;
}
}
else
echo "Filename is not defined."
?>
Output
The following output will appear after clicking the download link of the image file. The file size of the rose.jpg image is 27.2 KB, as shown in the dialog box. You can download the file by selecting the Save File radio button and pressing the OK button.
Example 2: Download File with File Path
If the file exists at the given file location, the file path will be required to mention in the URL. In this example, we will create an HTML file with the following code, which will pass the filename with the file path:
download3.html
We will create a PHP file with the following code to download a file from the file path. The PHP code in the previous example will be slightly modified to download the file from the given path. The clearstatecache() function is used to clear the cache that was previously stored. Two arguments are used in the readfile() function.
download2.php
//Check the file path exists or not
if ( file_exists ( $url ) ) {
//Define header information
header ( ‘Content-Description: File Transfer’ ) ;
header ( ‘Content-Type: application/octet-stream’ ) ;
header ( ‘Content-Disposition: attachment; filename="’ . basename ( $url ) . ‘"’ ) ;
header ( ‘Content-Length: ‘ . filesize ( $url ) ) ;
header ( ‘Pragma: public’ ) ;
//Read the size of the file
readfile ( $url , true ) ;
//Terminate from the script
die ( ) ;
}
else {
echo "File path does not exist." ;
}
}
echo "File path is not defined."
Output
After the download link of the PDF file is clicked, the following output will appear.
Video Tutorial
Conclusion
This article provided a simple way to forcibly download any file using the PHP script, to help readers to add the download feature in their script.
About the author
Fahmida Yesmin
I am a trainer of web programming courses. I like to write article or tutorial on various IT topics. I have a YouTube channel where many types of tutorials based on Ubuntu, Windows, Word, Excel, WordPress, Magento, Laravel etc. are published: Tutorials4u Help.
Как отдать файл на скачивание php
In this article, we will see how to download & save the file from the URL in PHP, & will also understand the different ways to implement it through the examples. There are many approaches to download a file from a URL, some of them are discussed below:
Using file_get_contents() function: The file_get_contents() function is used to read a file into a string. This function uses memory mapping techniques that are supported by the server and thus enhances the performance making it a preferred way of reading the contents of a file.
Syntax:
Example 1: This example illustrates the use of file_get_contents() function to read the file into a string.