0

I have a PHP code which produces a zip file and make it downloadable from browser. The download part looks like:

download.php

// force client download
if (headers_sent()) {
    echo 'HTTP header already sent';
} else {
    if (!is_file($zipFile)) {
    header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
    echo $zipFile . ' not found';
    } else if (!is_readable($zipFile)) {
        header($_SERVER['SERVER_PROTOCOL'].' 403 Forbidden');
        echo $zipFile . ' not readable';
    } else {
        ob_start();
        // http headers for zip downloads
        header('Content-Description: File Transfer');
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $zipName . '"');
        header('Content-Transfer-Encoding: binary');
        header('Connection: Keep-Alive');
        header('Expires: 0');
        header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
        header('Pragma: public');

        set_time_limit(0);
        ob_flush();
        ob_clean();
        readfile($zipFile);
    }
}

When called from the browser directly like localhost/download.php it works and makes me download the zipped file properly.

However, I need to call it from my JS web application.

The way I am invoking it is via a POST AJAX request that look like this:

var xhr;
if (window.XMLHttpRequest) xhr = new XMLHttpRequest(); // all browsers
else xhr = new ActiveXObject("Microsoft.XMLHTTP"); // for IE

var php_url = '/localhost/download.php' // ?wfs_url=' + url + 'format=' + format_list[0];
// (https://stackoverflow.com/a/53982364/1979665)
var formData = new FormData();
formData.append('wfs_url', url);
formData.append('format', format_list[0]);
xhr.open('POST', php_url);
xhr.onreadystatechange = function () {
    if (xhr.readyState===4 && xhr.status===200) {
        alert('Server reply: ' + xhr.responseText);
    }
}
xhr.send(formData);

return false;

The code is triggered when I click on a button.

Apparently it's doing "something". The PHP intermediate outputs (some folders) are correctly created, but the download does not begin.

The alert('Server reply: ' + xhr.responseText); part shows a strange message with some messy symbols, which I guess derives from the files created and read as text somehow.

Here is a screenshot of the message:

enter image description here

umbe1987
  • 2,894
  • 6
  • 35
  • 63
  • 4
    Yeah, the response is the file you're trying to download. I'm not sure why you need JS to be a middleman here. Just direct the browser to the URL and assume the headers are correct it will download. – Jonnix Dec 31 '18 at 15:58
  • Do you need to send data to `download.php` in order to generate the zip file? Or `download.php` does _only_ what you've posted? – Gabriel Dec 31 '18 at 16:03
  • @JonStirling Hi and thanks for the fast feedback. I need JS because I am passing some variables created in my JS application to the PHP. What do you mean by "just direct the browser to the URL and assume the headers are correct it will download."? If you mean doing it without the JS then I cannot, I have to pass for it for the reason I just mentioned. – umbe1987 Dec 31 '18 at 16:04
  • Can they be sent as GET parameters? Or does it have to be POST? – Jonnix Dec 31 '18 at 16:04
  • @Gabriel I need to pass two string variables (using the [new FormData()](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData) method) that are dynamically generated in my JS application and may varies. – umbe1987 Dec 31 '18 at 16:07
  • @JonStirling Well, actually I just switched to POST because I was having trouble encoding one of the variables i am passing to the PHP (which is a URL with OPTIONS and some & symbols, see also my [last question about this](https://stackoverflow.com/questions/53982282/passing-url-variable-with-from-js-to-php-result-in-omission)). I guess, if this is a problem, i can try to switch back, although I'd rather avoid it. – umbe1987 Dec 31 '18 at 16:11

2 Answers2

2

The PHP script is sending the actual binary source of the zip file, while the AJAX request is trying to display it as text, that's the garbage you get.

If you really want to keep the AJAX (it would be more simple to just make a <form> that is sent to download.php), you could do one two things:

Gabriel
  • 2,170
  • 1
  • 17
  • 20
  • I am not using a form because the way I am sending the request is binding it with an onclik event to a button. Will try what you suggest and come back with my finding . Thanks! – umbe1987 Dec 31 '18 at 16:19
  • I think I'm gonna try the second option although it seems a bit complicated. And, I hope I don't really need to know the name of the file in advance, because it is randomly generated by the PHP itself. – umbe1987 Dec 31 '18 at 16:26
  • If you create the file in JS, the original file name is irrelevant, you already have the data. – Gabriel Dec 31 '18 at 16:29
  • Sorry for keep commenting, but it's just to clarify as I am not that expert and something obvious may not be obvious for me: I am creating the file (the zip) via PHP, the JS is just sending to it only two string variables in order to provide two different parameters to a PHP exec() call. Anyway, I think you gave me the right suggestions and i will try to move on with those. – umbe1987 Dec 31 '18 at 16:33
  • 1
    You're creating a temporary zip file in PHP. Then its _contents_ are being sent to JS. Then, in JS, you have to tell the browser to save the data received as a (different, of course) zip file in the user's machine. Sidenote: Remember to use `escapeshellarg()` if you're using `exec()`. – Gabriel Dec 31 '18 at 16:42
  • Thank you @Gabriel. Indeed, the second option was my succesful way to go. Will post my updated working code as soon as I will use my laptop (now typing from mobile). Also, thank you so much for always clarifying via your precious comments, really appreciate it! – umbe1987 Jan 01 '19 at 20:32
0

After following option 2 in @Gabriel accepted answer I was able to make my code working. Here is the modified version of the JS (I didn't make any change in the PHP code):

var php_url = '/localhost/download.php' // ?wfs_url=' + url + 'format=' + format_list[0];
// (https://stackoverflow.com/a/53982364/1979665)
var formData = new FormData();
formData.append('wfs_url', url);
formData.append('format', format_list[0]);
xhr.open('POST', php_url);
xhr.onreadystatechange = function () {
    if (xhr.readyState===4 && xhr.status===200) {
        var blob = new Blob([xhr.response], { // SOLUTION
            type: "application/zip", // SOLUTION
        }); // SOLUTION
        var filename = "test.zip"; // SOLUTION
        if (navigator.msSaveOrOpenBlob) { // SOLUTION
            navigator.msSaveOrOpenBlob(blob, filename); // SOLUTION
        } else { // SOLUTION
            var a = document.createElement("a"); // SOLUTION
            document.body.appendChild(a); // SOLUTION
            a.style = "display:none"; // SOLUTION
            var url = window.URL.createObjectURL(blob); // SOLUTION
            a.href = url; // SOLUTION
            a.download = filename; // SOLUTION
            a.click(); // SOLUTION
            window.URL.revokeObjectURL(url); // SOLUTION
            a.remove(); // SOLUTION
         } // SOLUTION
    }
}
xhr.responseType = "arraybuffer"; // SOLUTION
xhr.send(formData);
umbe1987
  • 2,894
  • 6
  • 35
  • 63