62

Let's say I have download links for files on my site.

When clicked these links send an AJAX request to the server which returns the URL with the location of the file.

What I want to do is direct the browser to download the file when the response gets back. Is there a portable way to do this?

Baptiste L
  • 7
  • 1
  • 6
Vasil
  • 36,468
  • 26
  • 90
  • 114

12 Answers12

25

We do it that way: First add this script.

<script type="text/javascript">
function populateIframe(id,path) 
{
    var ifrm = document.getElementById(id);
    ifrm.src = "download.php?path="+path;
}
</script>

Place this where you want the download button(here we use just a link):

<iframe id="frame1" style="display:none"></iframe>
<a href="javascript:populateIframe('frame1','<?php echo $path; ?>')">download</a>

The file 'download.php' (needs to be put on your server) simply contains:

<?php 
   header("Content-Type: application/octet-stream");
   header("Content-Disposition: attachment; filename=".$_GET['path']);
   readfile($_GET['path']);
?>

So when you click the link, the hidden iframe then gets/opens the sourcefile 'download.php'. With the path as get parameter. We think this is the best solution!

It should be noted that the PHP part of this solution is a simple demonstration and potentially very, very insecure. It allows the user to download any file, not just a pre-defined set. That means they could download parts of the source code of the site itself, possibly containing API credentials etc.

Tom Busby
  • 1,319
  • 2
  • 12
  • 25
roulio
  • 371
  • 3
  • 3
  • 6
    @Fabio: `if (!file_exists($path)) { header("HTTP/1.1 404 Not Found"); exit; }` should do the trick – S B Aug 25 '11 at 14:28
  • is there any way for javascript to know the download progress (or simply whether or not it's complete)? – lakenen Jan 24 '12 at 15:31
  • @camupod Not this way I don't think. If you want download progress you'll need to use XmlHttpRequest Level 2. http://www.w3.org/TR/XMLHttpRequest/#event-xhr-progress – devios1 Jul 31 '12 at 17:37
  • 52
    What's stopping a user viewing the source, getting the url for download.php and then browsing to http://www.yoursite.com/download.php?path=config.php to try and get their hands on your passwords? To put it another way: Please, *please* don't do it like this. – Grim... Aug 13 '12 at 15:00
  • @Grim... Just off the top of my head a possible solution, you could use a relative path from your download.php file, something like readfile("../public/".$_GET['path']); and then all you have to do before hand is check $_GET['path'] does not have ".." in it anywhere. Could this work? Admittedly a bit of a hack but a quick little solution? – Nick M Dec 05 '13 at 05:07
  • A relative path can be something like '/common/config.php'. You could make sure the first character isn't '/', I guess, but I bet there are many more ways to get there. – Grim... Dec 11 '13 at 11:11
  • Thanks a lot- without sent headers I couldn't get it to work in IE nor Chrome. So this solution works perfectly! – jazkat Jan 30 '14 at 14:21
  • 5
    Following up with @Grim...'s comment, he is definitely correct, do NOT do it this way. Make the iframe src link directly to the file and use an .htaccess to set the header information for that specific file type. – Matt K Mar 14 '14 at 18:20
  • @Grim... Whats the secure alternative? Why don't you answer or edit the answer..? – hitautodestruct Mar 18 '14 at 09:53
  • 1
    Some secure alternatives: 1) Check the requested path up against a whitelist, 2) Create an id=>filepath map and use the id instead. – Svish Sep 29 '14 at 07:38
18

I have created an open source jQuery File Download plugin (Demo with examples) (GitHub) which could also help with your situation. It works pretty similarly with an iframe but has some cool features that I have found quite handy:

  • User never leaves the same page they initiated a file download from. This feature is becoming crucial for modern web applications
  • Tested cross browser support (including mobile!)
  • It supports POST and GET requests in a manner similar to jQuery's AJAX API
  • successCallback and failCallback functions allow for you to be explicit about what the user sees in either situation
  • In conjunction with jQuery UI a developer can easily show a modal telling the user that a file download is occurring, disband the modal after the download starts or even inform the user in a friendly manner that an error has occurred. See the Demo for an example of this.
John Culviner
  • 22,235
  • 6
  • 55
  • 51
  • Hi. Is there a way to prevent Internet Explorer 8 to block the download? Internet Explorer Information Bar asks confirmation for download which causes the page to be reloaded and no donwload, with the result that the user has to click again on the download link. Thanks in advance – Pierpaolo May 27 '13 at 11:19
14

Just call window.location.href = new_url from your javascript and it will redirect the browser to that URL as it the user had typed that into the address bar

Gareth
  • 133,157
  • 36
  • 148
  • 157
13

Reading the answers - including the accepted one I'd like to point out the security implications of passing a path directly to readfile via GET.

It may seem obvious to some but some may simply copy/paste this code:

<?php 
   header("Content-Type: application/octet-stream");
   header("Content-Disposition: attachment; filename=".$_GET['path']);
   readfile($_GET['path']);
?>

So what happens if I pass something like '/path/to/fileWithSecrets' to this script? The given script will happily send any file the webserver-user has access to.

Please refer to this discussion for information how to prevent this: How do I make sure a file path is within a given subdirectory?

Community
  • 1
  • 1
DanielKhan
  • 1,190
  • 1
  • 9
  • 18
10

If this is your own server application then i suggest using the following header

Content-disposition: attachment; filename=fname.ext

This will force any browser to download the file and not render it in the browser window.

fasih.rana
  • 1,645
  • 1
  • 14
  • 27
5

Try this lib https://github.com/PixelsCommander/Download-File-JS it`s more modern than all solutions described before because uses "download" attribute and combination of methods to bring best possible experience.

Explained here - http://pixelscommander.com/en/javascript/javascript-file-downliading-ignore-content-type/

Seems to be ideal piece of code for starting downloading in JavaScript.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
PixelCommander
  • 134
  • 1
  • 4
3

To get around the security flaw in the top-voted answer, you can set the iframe src directly to the file you want (instead of an intermediate php file) and set the header information in an .htaccess file:

<Files *.apk>
     ForceType application/force-download
     Header set Content-Disposition attachment
     Header set Content-Type application/vnd.android.package-archive
     Header set Content-Transfer-Encoding binary
</Files>
Matt K
  • 6,620
  • 3
  • 38
  • 60
3

A agree with the methods mentioned by maxnk, however you may want to reconsider trying to automatically force the browser to download the URL. It may work fine for binary files but for other types of files (text, PDF, images, video), the browser may want to render it in the window (or IFRAME) rather than saving to disk.

If you really do need to make an Ajax call to get the final download links, what about using DHTML to dynamically write out the download link (from the ajax response) into the page? That way the user could either click on it to download (if binary) or view in their browser - or select "Save As" on the link to save to disk. It's an extra click, but the user has more control.

Marc Novakowski
  • 44,628
  • 11
  • 58
  • 63
  • Oh, yes, I've forgot about non-binary, thank you. As an alternative may be it will be good solution to make a wrapper around filestream from the server and setting the "Content-Disposition: attachment" header. But its suitable for small files only. – maxnk Dec 13 '08 at 22:53
  • Or you could try tricking the browser into thinking the downloadable file is binary by having the server send the "Content-Type" header as "application/octet-stream", no matter what the actual file type – Marc Novakowski Dec 14 '08 at 01:24
  • 1
    Internet Explorer 8 blocks file downloading using all mentioned ways (iframe, window.location.href, or even "http-equiv=REFRESH CONTENT"). "To help protect your security, IE blocked this site from downloading files to your computer...". Do you know a way to suppress this warning and start the download as, say, Firefox do? I've checked several sites: Skype, Google (with Picasa) and Download.com causes the warning when you download (because they're showing a sort of "thank you" page). Thanks for your thoughts! – Mar Nov 20 '09 at 10:55
  • Set the headers for that file type in an htaccess. – Matt K Mar 14 '14 at 18:22
2

In relation to the top answer I have a possible solution to the security risk.

<?php
     if(isset($_GET['path'])){
         if(in_array($_GET['path'], glob("*/*.*"))){
             header("Content-Type: application/octet-stream");
             header("Content-Disposition: attachment; filename=".$_GET['path']);
             readfile($_GET['path']);
         }
     }
?>

Using the glob() function (I tested the download file in a path one folder up from the file to be downloaded) I was able to make a quick array of files that are "allowed" to be downloaded and checked the passed path against it. Not only does this insure that the file being grabbed isn't something sensitive but also checks on the files existence at the same time.

~Note: Javascript / HTML~

HTML:

<iframe id="download" style="display:none"></iframe>

and

<input type="submit" value="Download" onclick="ChangeSource('document_path');return false;">

JavaScript:

<script type="text/javascript">
    <!--
        function ChangeSource(path){
            document.getElementByID('download').src = 'path_to_php?path=' + document_path;
        }
    -->
</script>
2

I suggest to make an invisible iframe on the page and set it's src to url that you've received from the server - download will start without page reloading.

Or you can just set the current document.location.href to received url address. But that's can cause for user to see an error if the requested document actually does not exists.

maxnk
  • 5,755
  • 2
  • 27
  • 20
1

Why are you making server side stuff when all you need is to redirect browser to different window.location.href?

Here is code that parses ?file= QueryString (taken from this question) and redirects user to that address in 1 second (works for me even on Android browsers):

<script type="text/javascript">
    var urlParams;
    (window.onpopstate = function () {
        var match,
        pl = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
        query = window.location.search.substring(1);

        urlParams = {};
        while (match = search.exec(query))
            urlParams[decode(match[1])] = decode(match[2]);
    })();

    (window.onload = function() {
        var path = urlParams["file"];
        setTimeout(function() { document.location.href = path; }, 1000);
    });
</script>

If you have jQuery in your project definitely remove those window.onpopstate & window.onload handlers and do everything in $(document).ready(function () { } );

Community
  • 1
  • 1
nikib3ro
  • 20,366
  • 24
  • 120
  • 181
1

I'd suggest window.open() to open a popup window. If it's a download, there will be no window and you will get your file. If there is a 404 or something, the user will see it in a new window (hence, their work will not be bothered, but they will still get an error message).

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • 1
    that works fine if you are dealing with a user clicked link (but then why have it even be js?) it will still run into the IE security violation. – Laith Mar 28 '11 at 20:18