6

i want to create a downloadscript which allows Force Download of JPGs. This is my php script:

<?php
    header("Pragma: public"); // required
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Content-Description: File Transfer");
    header("Content-Type: image/jpg");
    header('Content-Disposition: attachment; filename="'.basename($GET['a']).'"');
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: ".filesize(($GET['a']));
    readfile(($GET['a']);
?>

This is a code segment of my js code:

function downloadFile(a){
    document.location = "download.php?a="+ a;
}

With this code sample nothing happens. If i append the result into a HTML-tag, it shows the content of the file.

Any ideas how to teach the browser to download this file?

EDIT: SCRIPT UPDATE

Sylnois
  • 1,589
  • 6
  • 23
  • 47

5 Answers5

22

You can't download files with ajax. So, if you have something that should happen on ajax, you should return url in response and apply it like document.location = "url"to start download process.

One note here. As I remember, browser will block file download if it is initiated not by user click. So, this will work fine:

.click(function(){
   document.location = "download url"
})

But if it is started not by user click, it will be blocked. So, code like this:

.click(function(){
       $.ajax({...,
       success:function(download_url_from_server){
           document.location = download_url_from_server;
       }});           
    })

will be blocked by browser. So, if you want to pass some data with a post, you may submit a form into hidden iframe or to blank page using <form target="...":

 function checkToken(token){
    var $form = $("#downloadForm");
    if ($form.length == 0) {
        $form = $("<form>").attr({ "target": "_blank", "id": "downloadForm", "method": "POST", "action": "script.php" }).hide();
        $("body").append($form);
    }
    $form.find("input").remove();
    var args = { a: "checkToken", b: token }
    for (var field in args) {
        $form.append($("<input>").attr({"value":args[field], "name":field}));
    }
    $form.submit();
}

And in script.php you need to execute code from download.php immediately, if token is Ok, or do a redirect to download script:

header("Location: download.php?a=" . $filename)
Viktor S.
  • 12,736
  • 1
  • 27
  • 52
  • Yeah, but i want to force download JPGs and not to show it in the browser. Is there no way to do it with PHP-headers? – Sylnois Feb 08 '13 at 14:10
  • Content disposition: attachment should do that. – Viktor S. Feb 08 '13 at 14:11
  • As you can see in the question bellow, that i added this already. – Sylnois Feb 08 '13 at 14:12
  • 1
    Yeah. But are you still trying to do that with ajax? – Viktor S. Feb 08 '13 at 14:15
  • 1
    I just checked it on my project, and content-disposition: attachment; works fine. But you must do document.location = "url to php file" – Viktor S. Feb 08 '13 at 14:17
  • give me a minute to test this – Sylnois Feb 08 '13 at 14:24
  • Look at my question bellow. I changed my script. But still doesn't work. Thanks for your help. – Sylnois Feb 08 '13 at 14:30
  • When you open link: "mysite.com/download.php?a=file.txt" directly in browser... What happens than? Also, how do you call `downloadFile`? Is it still in success function of ajax call? – Viktor S. Feb 08 '13 at 14:31
  • Server error 500. Yep, i call a ajax call to find the path with the filename. In the success function i transfer the result(filepath) to the download function which you can see above. – Sylnois Feb 08 '13 at 14:35
  • First: try to enter url with a file name directly into address bar, like: mysite.com/download.php?a=file.txt . Does it work? – Viktor S. Feb 08 '13 at 14:39
  • to header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Length: ' . filesize($fullPath)); header('Content-Disposition: attachment; filename="' . basename($_GET['a']) . '"'); readfile($_GET['a']); – Sylnois Feb 08 '13 at 14:41
  • Another Update, i change the header conte-type to image/jpg. No IE8 blocks the content and asks me if i want to download it. I permit it but nothing happens – Sylnois Feb 08 '13 at 14:44
  • Can you show an ajax call code? Not sure why nothing happens, but suppose that is because of block. – Viktor S. Feb 08 '13 at 14:48
  • `function checkToken(token){ $.ajax({ url: "script.php", type: 'POST', data: ({ a: "checkToken", b: token }), success: function(msg){ downloadFile(msg); }, error: function(xhr, err){ alert("readyState: "+xhr.readyState+"\nstatus: "+xhr.status); alert("responseText: "+xhr.responseText); } }); }` – Sylnois Feb 08 '13 at 14:51
  • See updated answer about how you can avoid your request being blocked – Viktor S. Feb 08 '13 at 14:57
  • It works! I just added a visible input for the pathfile. If checkToken is successful, it writes the path in it. And when you submit the downloadbutton, he gets the content of the hidden input and calls the download function. Thank you very much for your help! – Sylnois Feb 08 '13 at 15:09
3

Setting the mime type to image/jpeg will most probably not work. So, you need application/octet-stream instead to force the download.

Replace the content type header in your php with the following:

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

Also, One nice solution instead of using document.location is to inject an iframe. Use the following function in your success callback

function downloadFile(url)
    {
        var iframe;
        iframe = document.getElementById("download-container");
        if (iframe === null)
        {
            iframe = document.createElement('iframe');  
            iframe.id = "download-container";
            iframe.style.visibility = 'hidden';
            document.body.appendChild(iframe);
        }
        iframe.src = url;   
    }
dlock
  • 9,447
  • 9
  • 47
  • 67
  • 2
    This seems overly complicated. If you simply redirect the user to a page that downloads the file, so long as you have the appropriate headers set, it will download the file in the exact same way without navigating them away from the page. The two examples achieve the same result, but one with less complexity. – jamesmillerio Feb 08 '13 at 14:19
  • But, wouldn't it change the url in the address bar? :) – dlock Feb 08 '13 at 14:21
  • No, it will simply prompt them to save the file without navigating away. The URL should remain unchanged. – jamesmillerio Feb 08 '13 at 14:35
  • I really like this solution, far more flexible when dealing with a special situation like no jQuery and doesn't depend on being inside a click callback exclusively. Thanks! – Aditya M P Apr 25 '14 at 03:02
1

It seems you have errors in your script. First of all, correct speliing for GET variable is $_GET['a'], not $GET['a']. The second issue here is that you have extra opening parenthesis, when I copied your code, I received 500 Internal Server Error response. If we correct mistakes, it seems to work fine. Just try corrected version of your code.

<?php
    header("Pragma: public"); // required
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Content-Description: File Transfer");
    header("Content-Type: image/jpg");
    header('Content-Disposition: attachment; filename="'.basename($_GET['a']).'"');
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: ".filesize($_GET['a']));
    readfile($_GET['a']);
?>
Patlatus
  • 1,217
  • 2
  • 16
  • 28
0

You're getting it confused a bit. As FAngel pointed out, you can't download files via AJAX. What you need to do is redirect the user to another page that then has your above PHP code in it. That PHP code should then allow the user to download the file directly. What you're attempting is absolutely possible, you just need to approach it from another direction, ie not with AJAX.

jamesmillerio
  • 3,154
  • 4
  • 27
  • 32
0

You can force download file with Mouse middle event:

const url = "https://www.google.com.vn/images/branding/googlelogo/2x/googlelogo_color_120x44dp.png";

const forceDownload = url => {
 try {
    const link = document.createElement('a');
    const fileName = url.substring(url.lastIndexOf('/') + 1, url.length);
    const event = new MouseEvent( "click", { "button": 1, "which": 1 });

    link.href = url;
    link.download = fileName;
    link.dispatchEvent(event);
  } catch(e) {
     document.location = url;
  }
}

forceDownload(url);
Nghiệp
  • 4,038
  • 2
  • 23
  • 27