148

I am giving link of a pdf file on my web page for download, like below

<a href="myfile.pdf">Download Brochure</a>

The problem is when user clicks on this link then

  • If the user have installed Adobe Acrobat, then it opens the file in the same browser window in Adobe Reader.
  • If the Adobe Acrobat is not installed then it pop-up to the user for Downloading the file.

But I want it always pop-up to the user for download, irrespective of "Adobe acrobat" is installed or not.

Please tell me how i can do this?

Rob W
  • 341,306
  • 83
  • 791
  • 678
djmzfKnm
  • 26,679
  • 70
  • 166
  • 227

14 Answers14

259

This is a common issue but few people know there's a simple HTML 5 solution:

<a href="./directory/yourfile.pdf" download="newfilename">Download the pdf</a>

Where newfilename is the suggested filename for the user to save the file. Or it will default to the filename on the serverside if you leave it empty, like this:

<a href="./directory/yourfile.pdf" download>Download the pdf</a>

Compatibility: I tested this on Firefox 21 and Iron, both worked fine. It might not work on HTML5-incompatible or outdated browsers. The only browser I tested that didn't force download is IE...

Check compatibility here: http://caniuse.com/#feat=download

Martin Gottweis
  • 2,721
  • 13
  • 27
T_D
  • 3,241
  • 3
  • 17
  • 24
  • 9
    This is a simple solution but unfortunately not very widely supported, esp. no support by IE http://caniuse.com/#feat=download – benebun Nov 13 '13 at 15:44
  • 2
    Yep, I know right. That's why I have the side-note on compatibility. And according to your source both IE and Safari don't support this approach, or at least not yet :) Anyhow, if you want all browsers to force download I suggest checking some of the other answers instead... – T_D Nov 14 '13 at 16:23
  • 3
    works like a charm with chrome Version 39.0.2171.65 (64-bit) ! – edelans Nov 21 '14 at 20:32
  • The solution is easy but unfortunately not supported in IE and Safari. – valkirilov Jul 14 '16 at 10:25
  • says no permission to access – Abdulla Sirajudeen Aug 07 '18 at 03:54
  • Even for browsers where it's supported, whether or not the `download` attribute triggers a download prompt depends on the user's browser settings. For example, even with Firefox 98, clicking a link to a `.pdf` file by default opens it in the browser, regardless of the `download` attribute. Other than being able to suggest a filename, the `download` attribute doesn't seem to offer much benefit. – jamesdlin Apr 02 '22 at 17:50
120

Instead of linking to the .PDF file, instead do something like

<a href="pdf_server.php?file=pdffilename">Download my eBook</a>

which outputs a custom header, opens the PDF (binary safe) and prints the data to the user's browser, then they can choose to save the PDF despite their browser settings. The pdf_server.php should look like this:

header("Content-Type: application/octet-stream");

$file = $_GET["file"] .".pdf";
header("Content-Disposition: attachment; filename=" . urlencode($file));   
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");            
header("Content-Length: " . filesize($file));
flush(); // this doesn't really matter.
$fp = fopen($file, "r");
while (!feof($fp))
{
    echo fread($fp, 65536);
    flush(); // this is essential for large downloads
} 
fclose($fp); 

PS: and obviously run some sanity checks on the "file" variable to prevent people from stealing your files such as don't accept file extensions, deny slashes, add .pdf to the value

TravisO
  • 9,406
  • 4
  • 36
  • 44
  • Is it really matter if i use the following code $file = $_GET["file"] .".pdf"; AS $file = $_REQUEST["file"] .".pdf"; – djmzfKnm Dec 13 '08 at 07:41
  • 2
    I am facing another problem with this, that my file is located at /products/brochure/myfile.pdf I am giving $file variable as $file_path = $_SERVER['DOCUMENT_ROOT'].'/products/brochure/' . $file; but its downloading the file as "%2Fvar%2Fwww%2Fweb15%2Fweb%2Fproducts%2Fbrochure%2myfile.pdf" – djmzfKnm Dec 13 '08 at 10:11
  • what i can do to make it downloaded with the name "myfile.pdf" only. I changed the header("Content-Disposition: attachment; filename=" . urlencode('myfile.pdf')); also, but still its not working. – djmzfKnm Dec 13 '08 at 10:12
  • Am I the only one getting error that .pdf was damaged when I'm trying to open .pdf? – Tommz Apr 04 '13 at 22:19
  • 3
    @TravisO `"Content-type: application/force-download"` isn't listed anywhere here: http://www.iana.org/assignments/media-types/application It's a completely bogus header. Please don't make up headers and send them. Could you update your answer. Thanks. – Nicholas Wilson Aug 28 '13 at 15:37
  • After Googling about it and double checking, it's removed – TravisO Aug 29 '13 at 15:58
  • @TravisO I Got an Error, _it is either not a supported file type or because the file has been damaged_... – Edwin Thomas Oct 01 '14 at 06:17
  • 4
    Be careful when using this code verbatim, though. This introduces a serious LFI vulnerability, as you're passing GET-variables directly into `fopen`. – Joost Feb 25 '15 at 12:48
  • 4
    This code is likely dangerous in another way. If you pass HTTP links to fopen, I think it'll go retrieve the file from another server. Could be used by an attacker to attempt to scan your internal network for exposed PDF files. – Rory McCune Sep 04 '15 at 12:24
  • 2
    Not to mention how easy it would be to bypass any "sanity checks" you think you'll be doing to the "file" parameter. Path injection / directory traversal attacks are extremely likely. – AviD Sep 04 '15 at 13:18
  • 1
    If I were going to use this code, instead of doing sanity checks, I would have a database of whitelisted files on my server that could be used with it. That's what various CMS do. As for @RоryMcCune's comment, I think it depends on how your PHP is set up. Imo, php.ini should be configured to prevent fopen from opening things from remote servers. – Evan Donovan Nov 29 '16 at 16:46
  • You could also add to your code something that would check for an additional $_GET parameter that would act as a "secret key" - that way people couldn't craft arbitrary URLs since they wouldn't know the key you were using to permit the file to be served. It wouldn't be perfectly secure but it would be an improvement. – Evan Donovan Nov 29 '16 at 16:55
  • I think this answer is not valid today, when we have better alternative of HTML5 attribute `download` – divy3993 Jun 30 '20 at 13:54
50

Don't loop through every file line. Use readfile instead, its faster. This is off the php site: http://php.net/manual/en/function.readfile.php

$file = $_GET["file"];
if (file_exists($file)) {
    header('Content-Description: File Transfer');
    header('Content-Type: application/octet-stream');
    header("Content-Type: application/force-download");
    header('Content-Disposition: attachment; filename=' . urlencode(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;
}

Make sure to sanitize your get variable as someone could download some php files...

Alex V
  • 18,176
  • 5
  • 36
  • 35
24

Instead of using a PHP script, to read and flush the file, it's more neat to rewrite the header using .htaccess. This will keep a "nice" URL (myfile.pdf instead of download.php?myfile).

<FilesMatch "\.pdf$">
ForceType applicaton/octet-stream
Header set Content-Disposition attachment
</FilesMatch>
Rob W
  • 341,306
  • 83
  • 791
  • 678
15

I found a way to do it with plain old HTML and JavaScript/jQuery that degrades gracefully. Tested in IE7-10, Safari, Chrome, and FF:

HTML for download link:

<p>Thanks for downloading! If your download doesn't start shortly, 
<a id="downloadLink" href="...yourpdf.pdf" target="_blank" 
type="application/octet-stream" download="yourpdf.pdf">click here</a>.</p>

jQuery (pure JavaScript code would be more verbose) that simulates clicking on link after a small delay:

var delay = 3000;
window.setTimeout(function(){$('#downloadLink')[0].click();},delay);

To make this more robust you could add HTML5 feature detection and if it's not there then use window.open() to open a new window with the file.

Alex W
  • 37,233
  • 13
  • 109
  • 109
8

This is the key:

header("Content-Type: application/octet-stream");

Content-type application/x-pdf-document or application/pdf is sent while sending PDF file. Adobe Reader usually sets the handler for this MIME type so browser will pass the document to Adobe Reader when any of PDF MIME types is received.

Sudden Def
  • 10,031
  • 3
  • 18
  • 8
5

This can be achieved using HTML.

<a href="myfile.pdf">Download Brochure</a>

Add download attribute to it: Here the file name will be myfile.pdf

<a href="myfile.pdf" download>Download Brochure</a>

Specify a value for the download attribute:

<a href="myfile.pdf" download="Brochure">Download Brochure</a>

Here the file name will be Brochure.pdf

Okiemute Gold
  • 459
  • 3
  • 10
2

I know I am very late to answer this but I found a hack to do this in javascript.

function downloadFile(src){
    var link=document.createElement('a');
    document.body.appendChild(link);
    link.href= src;
    link.download = '';
    link.click();
}
Shivek Parmar
  • 2,865
  • 2
  • 33
  • 42
0

Try this:

<a href="pdf_server_with_path.php?file=pdffilename&path=http://myurl.com/mypath/">Download my eBook</a>

The code inside pdf_server_with_path.php is:

header("Content-Type: application/octet-stream");

$file = $_GET["file"] .".pdf";
$path = $_GET["path"];
$fullfile = $path.$file;

header("Content-Disposition: attachment; filename=" . Urlencode($file));   
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");            
header("Content-Length: " . Filesize($fullfile));
flush(); // this doesn't really matter.
$fp = fopen($fullfile, "r");
while (!feof($fp))
{
    echo fread($fp, 65536);
    flush(); // this is essential for large downloads
} 
fclose($fp);
Peter O.
  • 32,158
  • 14
  • 82
  • 96
Saill
  • 9
  • 2
0

Here's a different approach. I prefer rather than to rely on browser support, or address this at the application layer, to use web server logic.

If you are using Apache, and can put an .htaccess file in the relevant directory you could use the code below. Of course, you could put this in httpd.conf as well, if you have access to that.

<FilesMatch "\.(?i:pdf)$">
  Header set Content-Disposition attachment
</FilesMatch>

The FilesMatch directive is just a regex so it could be set as granularly as you want, or you could add in other extensions.

The Header line does the same thing as the first line in the PHP scripts above. If you need to set the Content-Type lines as well, you could do so in the same manner, but I haven't found that necessary.

Evan Donovan
  • 748
  • 9
  • 18
-1

In a Ruby on Rails application (especially with something like the Prawn gem and the Prawnto Rails plugin), you can accomplish this a little more simply than a full on script (like the previous PHP example).

In your controller:

def index

 respond_to do |format|
   format.html # Your HTML view
   format.pdf { render :layout => false }
 end
end

The render :layout => false part tells the browser to open up the "Would you like to download this file?" prompt instead of attempting to render the PDF. Then you would be able to link to the file normally: http://mysite.com/myawesomepdf.pdf

btw
  • 7,006
  • 9
  • 40
  • 40
-1

I solved mine using the whole url of the PDF file (Instead of just putting the file name or location to href): a href="domain . com/pdf/filename.pdf"

-1

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!');
}

?>

For more information click here

-1
<!DOCTYPE html>  
<html xmlns="http://www.w3.org/1999/xhtml">  
<head>  
    <title>File Uploader</title>  
    <script src="../Script/angular1.3.8.js"></script>  
    <script src="../Script/angular-route.js"></script>  
    <script src="../UserScript/MyApp.js"></script>  
    <script src="../UserScript/FileUploder.js"></script>  
    <>  
        .percent {  
            position: absolute;  
            width: 300px;  
            height: 14px;  
            z-index: 1;  
            text-align: center;  
            font-size: 0.8em;  
            color: white;  
        }  

        .progress-bar {  
            width: 300px;  
            height: 14px;  
            border-radius: 10px;  
            border: 1px solid #CCC;  
            background-image: -webkit-gradient(linear, left top, left bottom, from(#6666cc), to(#4b4b95));  
            border-image: initial;  
        }  

        .uploaded {  
            padding: 0;  
            height: 14px;  
            border-radius: 10px;  
            background-image: -webkit-gradient(linear, left top, left bottom, from(#66cc00), to(#4b9500));  
            border-image: initial;  
        }  
    </>  
</head>  
<body ng-app="MyApp" ng-controller="FileUploder">  
    <div>  
        <table ="width:100%;border:solid;">  
            <tr>  
                <td>Select File</td>  
                <td>  
                    <input type="file" ng-model-instant id="fileToUpload" onchange="angular.element(this).scope().setFiles(this)" />  
                </td>  
            </tr>  
            <tr>  
                <td>File Size</td>  
                <td>  
                    <div ng-repeat="file in files.slice(0)">  
                        <span ng-switch="file.size > 1024*1024">  
                            <span ng-switch-when="true">{{file.size / 1024 / 1024 | number:2}} MB</span>  
                            <span ng-switch-default>{{file.size / 1024 | number:2}} kB</span>  
                        </span>  
                    </div>  
                </td>  
            </tr>             
            <tr>  
                <td>  
                    File Attach Status  
                </td>  
                <td>{{AttachStatus}}</td>  
            </tr>  
            <tr>  
                <td>  
                    <input type="button" value="Upload" ng-click="fnUpload();" />  
                </td>  
                <td>  
                    <input type="button" value="DownLoad" ng-click="fnDownLoad();" />  
                </td>  
            </tr>  
        </table>  
    </div>  
</body>  
</html>   
  • 3
    You should explain what you have provided in your code. Even in short form if not possible in details or not required in details. – Suraj Kumar Feb 06 '19 at 08:41