149

I want to require a file to be downloaded upon the user visiting a web page with PHP. I think it has something to do with file_get_contents, but am not sure how to execute it.

$url = "http://example.com/go.exe";

After downloading a file with header(location) it is not redirecting to another page. It just stops.

3

12 Answers 12

285

Read the docs about built-in PHP function readfile

$file_url = 'http://www.myremoteserver.com/file.exe';
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary"); 
header("Content-disposition: attachment; filename=\"" . basename($file_url) . "\""); 
readfile($file_url); 

Also make sure to add proper content type based on your file application/zip, application/pdf etc. - but only if you do not want to trigger the save-as dialog.

5
  • I mean if its other than .exe file than only .Else this should work fine.
    – Pit Digger
    Aug 31, 2011 at 22:02
  • 13
    Dont forget to flush ;-) ob_clean(); flush(); /* before */ readfile($file_url);
    – Ash
    May 1, 2015 at 6:05
  • 1
    So if you have a 10GB big file php tries to load that whole file?
    – GDY
    Aug 3, 2018 at 8:41
  • exit() should be called at the end to avoid any potential problems (speaking from experience :-) Aug 20, 2019 at 10:51
  • 1
    i am using an image http path as source of path but file is displayed in binary code instead of downloading by using your code. Not worked for me. Thanks.
    – Kamlesh
    Mar 7, 2020 at 12:57
59
<?php
$file = "http://example.com/go.exe"; 

header("Content-Description: File Transfer"); 
header("Content-Type: application/octet-stream"); 
header("Content-Disposition: attachment; filename=\"". basename($file) ."\""); 

readfile ($file);
exit(); 
?>

Or, when the file is not openable with the browser, you can just use the Location header:

<?php header("Location: http://example.com/go.exe"); ?>
6
  • 1
    filename in your header shouldn't be $file (which contains the http part) but a valid filename.
    – Fabio
    Aug 31, 2011 at 21:56
  • 2
    There is no application/force-download media type; use application/octet-stream instead.
    – Gumbo
    Aug 31, 2011 at 21:58
  • works very nice ! But adds ' to the stored filename. Please use: filename=" . basename($file));
    – harry4516
    Jan 1, 2019 at 22:38
  • @harry4516 modified according to your finding Jan 3, 2019 at 6:34
  • Not working dear in case of png image for me. Thanks.
    – Kamlesh
    Mar 7, 2020 at 12:58
42
header("Content-Type: application/octet-stream");
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"file.exe\""); 
echo readfile($url);

is correct

or better one for exe type of files

header("Location: $url");
4
  • @Fabio: Will add more detials
    – genesis
    Aug 31, 2011 at 21:59
  • That was super easy. It worked. What is file-Get_contents for then? Just curious. Thanks.
    – john
    Aug 31, 2011 at 22:00
  • to download it to your server
    – genesis
    Aug 31, 2011 at 22:01
  • 10
    Just remove 'echo' prior to 'readfile()' since the return value is specified as "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.". So you'll end up with the content of the file + integer number at the end of the content.
    – Mladen B.
    Jun 28, 2013 at 8:08
29

Display your file first and set its value into url.

index.php

<a href="download.php?download='.$row['file'].'" title="Download File">

download.php

<?php
/*db connectors*/
include('dbconfig.php');

/*function to set your files*/
function output_file($file, $name, $mime_type='')
{
    if(!is_readable($file)) die('File not found or inaccessible!');
    $size = filesize($file);
    $name = rawurldecode($name);
    $known_mime_types=array(
        "htm" => "text/html",
        "exe" => "application/octet-stream",
        "zip" => "application/zip",
        "doc" => "application/msword",
        "jpg" => "image/jpg",
        "php" => "text/plain",
        "xls" => "application/vnd.ms-excel",
        "ppt" => "application/vnd.ms-powerpoint",
        "gif" => "image/gif",
        "pdf" => "application/pdf",
        "txt" => "text/plain",
        "html"=> "text/html",
        "png" => "image/png",
        "jpeg"=> "image/jpg"
    );

    if($mime_type==''){
        $file_extension = strtolower(substr(strrchr($file,"."),1));
        if(array_key_exists($file_extension, $known_mime_types)){
            $mime_type=$known_mime_types[$file_extension];
        } else {
            $mime_type="application/force-download";
        };
    };
    @ob_end_clean();
    if(ini_get('zlib.output_compression'))
    ini_set('zlib.output_compression', 'Off');
    header('Content-Type: ' . $mime_type);
    header('Content-Disposition: attachment; filename="'.$name.'"');
    header("Content-Transfer-Encoding: binary");
    header('Accept-Ranges: bytes');

    if(isset($_SERVER['HTTP_RANGE']))
    {
        list($a, $range) = explode("=",$_SERVER['HTTP_RANGE'],2);
        list($range) = explode(",",$range,2);
        list($range, $range_end) = explode("-", $range);
        $range=intval($range);
        if(!$range_end) {
            $range_end=$size-1;
        } else {
            $range_end=intval($range_end);
        }

        $new_length = $range_end-$range+1;
        header("HTTP/1.1 206 Partial Content");
        header("Content-Length: $new_length");
        header("Content-Range: bytes $range-$range_end/$size");
    } else {
        $new_length=$size;
        header("Content-Length: ".$size);
    }

    $chunksize = 1*(1024*1024);
    $bytes_send = 0;
    if ($file = fopen($file, 'r'))
    {
        if(isset($_SERVER['HTTP_RANGE']))
        fseek($file, $range);

        while(!feof($file) &&
            (!connection_aborted()) &&
            ($bytes_send<$new_length)
        )
        {
            $buffer = fread($file, $chunksize);
            echo($buffer);
            flush();
            $bytes_send += strlen($buffer);
        }
        fclose($file);
    } else
        die('Error - can not open file.');
    die();
}
set_time_limit(0);

/*set your folder*/
$file_path='uploads/'."your file";

/*output must be folder/yourfile*/

output_file($file_path, ''."your file".'', $row['type']);

/*back to index.php while downloading*/
header('Location:index.php');
?>
3
  • 2
    Where did you get this from? It seems powerful May 7, 2016 at 4:31
  • @jundell-agbo interesting and for a crt file?
    – fralbo
    Oct 11, 2016 at 17:40
  • This is working for a very big file also. Great solution. But` $chunksize = 1*(1024*1024);` is slow. I tried with multiple values and noticed that ` $chunksize = 8*(1024*1024);` is using all the available bandwidth.
    – Linga
    Jun 19, 2018 at 12:20
20

In case you have to download a file with a size larger than the allowed memory limit (memory_limit ini setting), which would cause the PHP Fatal error: Allowed memory size of 5242880 bytes exhausted error, you can do this:

// File to download.
$file = '/path/to/file';

// Maximum size of chunks (in bytes).
$maxRead = 1 * 1024 * 1024; // 1MB

// Give a nice name to your download.
$fileName = 'download_file.txt';

// Open a file in read mode.
$fh = fopen($file, 'r');

// These headers will force download on browser,
// and set the custom file name for the download, respectively.
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $fileName . '"');

// Run this until we have read the whole file.
// feof (eof means "end of file") returns `true` when the handler
// has reached the end of file.
while (!feof($fh)) {
    // Read and output the next chunk.
    echo fread($fh, $maxRead);

    // Flush the output buffer to free memory.
    ob_flush();
}

// Exit to make sure not to output anything else.
exit;
6
  • I just tried this and it killed my server. Wrote a massive number of errors to my logs. Aug 9, 2017 at 2:21
  • It would be useful to know what the logs were about.
    – Parziphal
    Aug 9, 2017 at 4:13
  • Yeah sorry, the log file got so big I had to just nuke it, so I didn't get to see. Aug 9, 2017 at 4:14
  • I was having to set up something like this on a server that I had very limited access to. My download script kept redirecting to the home page and I couldn't figure out why. Now I know the issue was a memory error and this code solved it.
    – Gavin
    Feb 21, 2018 at 13:57
  • If you just use ob_flush() there might be the error: ob_flush(): failed to flush buffer. No buffer to flush. wrap around it with if (ob_get_level() > 0) {ob_flush();} (Reference stackoverflow.com/a/9182133/128761 )
    – vee
    Dec 18, 2018 at 3:08
11

A modification of the accepted answer above, which also detects the MIME type in runtime:

$finfo = finfo_open(FILEINFO_MIME_TYPE);
header('Content-Type: '.finfo_file($finfo, $path));

$finfo = finfo_open(FILEINFO_MIME_ENCODING);
header('Content-Transfer-Encoding: '.finfo_file($finfo, $path)); 

header('Content-disposition: attachment; filename="'.basename($path).'"'); 
readfile($path); // do the double-download-dance (dirty but worky)
6

The answers above me works. But, I'd like to contribute a method on how to perform it using GET

on your html/php page

$File = 'some/dir/file.jpg';
<a href="<?php echo '../sumdir/download.php?f='.$File; ?>" target="_blank">Download</a>

and download.php contains

$file = $_GET['f']; 

header("Expires: 0");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

$ext = pathinfo($file, PATHINFO_EXTENSION);
$basename = pathinfo($file, PATHINFO_BASENAME);

header("Content-type: application/".$ext);
header('Content-length: '.filesize($file));
header("Content-Disposition: attachment; filename=\"$basename\"");
ob_clean(); 
flush();
readfile($file);
exit;

this should work on any file types. this is not tested using POST, but it could work.

6

You can stream download too which will consume significantly less resource. example:

$readableStream = fopen('test.zip', 'rb');
$writableStream = fopen('php://output', 'wb');

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="test.zip"');
stream_copy_to_stream($readableStream, $writableStream);
ob_flush();
flush();

In the above example, I am downloading a test.zip (which was actually the android studio zip on my local machine). php://output is a write-only stream (generally used by echo or print). after that, you just need to set the required headers and call stream_copy_to_stream(source, destination). stream_copy_to_stream() method acts as a pipe which takes the input from the source stream (read stream) and pipes it to the destination stream (write stream) and it also avoids the issue of allowed memory exhausted so you can actually download files that are bigger than your PHP memory_limit.

2
  • What do you mean by the download is streamed? Jun 21, 2019 at 23:06
  • 1
    @ParsaYazdani It means the downloaded data will be chunked. Please look onto the concept of streaming for more details. Note: this is not the only to stream (chunk) download the file in PHP. But it surely is one of the easiest methods. Jun 22, 2019 at 12:03
5

you can use download attribute to force download a file:

<a href="https://test.com/aaa.exe" download>click here to download</a>

2
  • The question asked for a PHP solution. This is an HTML solution. Dec 15, 2021 at 11:11
  • From this whole page, this HTML solution was the only one that worked for me. Maybe I have some pre-requisites unmet? Maybe something on php.ini?
    – DrBeco
    Jun 4, 2023 at 0:51
4

The following code is a correct way of implementing a download service in php as explained in the following tutorial

header('Content-Type: application/zip');
header("Content-Disposition: attachment; filename=\"$file_name\"");
set_time_limit(0);
$file = @fopen($filePath, "rb");
while(!feof($file)) {
    print(@fread($file, 1024*8));
    ob_flush();
    flush();
}
1
  • 2
    You have two variables in this code: file_name and filePath. Not very good coding practice. Especially without explanation. Linking to external tutorial not helpful.
    – Khom Nazid
    May 12, 2019 at 13:19
2

try this:

header('Content-type: audio/mp3'); 
header('Content-disposition: attachment; 
filename=“'.$trackname'”');                             
readfile('folder name /'.$trackname);          
exit();
2

http://php.net/manual/en/function.readfile.php

That's all you need. "Monkey.gif" change to your file name. If you need to download from other server, "monkey.gif" change to "http://www.exsample.com/go.exe"

1
  • 2
    Welcome to StackOverflow. It's always advisable to include the code directly instead of adding a link to avoid the link issues.
    – Stranger
    Nov 26, 2017 at 11:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.