skip to Main Content

Running PHP/Apache on localhost. Yes, very old versions (Apache/2.4.10 (Win32) PHP/5.4.34), but the code has worked for years. I had been relying on the Xsendfile Apache module, but that failed silently. Changed to use explicit calls to header() and echo contents. That causes Chrome to say "Couldn’t finish download". And Firefox says "example.zip.part could not be saved, because the source file could not be read.
Try again later, or contact the server administrator."

Chrome 117.0.5938.89 (Official Build) (64-bit), Firefox 117.0.1 (64-bit) on Windows 10.

Here is the code:

if (false && in_array('mod_xsendfile', apache_get_modules())) {
    // Despite the documentation in https://tn123.org/mod_xsendfile/beta/, simply encoding
    // the pathname with urlencode causes X-Sendfile to provoke an internal server error.
    // See http://stackoverflow.com/questions/26769144/what-encoding-does-xsendfile-expect
    $encoded = str_replace('%2F', '/', rawurlencode($pathname));
    header("Content-Type: $mime_info");
    if ($attachment === true) {
        $dispo = 'attachment';
    }
    else {
        $dispo = 'inline';
    }
    header("Content-Disposition: $dispo; filename="$basename"");
    // xsendfile makes apache generate headers and body for best download behavior.
    header("X-SendFile: $encoded");
}
else {
    // At least one post somewhere said these were good headers for manual download
    header("Pragma: public");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: private", false); // required for certain browsers
    header("Content-Type: $mime_info");
    if ($attachment === true) {
        header("Content-Disposition: attachment; filename="$basename"");
    }
    header("Content-Length: " . filesize("$pathname"));
    ob_clean();
    flush();
    $data = file_get_contents("$pathname");
    echo $data;
}

I just tossed the "false &&" into the "if" to force it not to use the Xsendfile module. And, as I said, that changed the behavior from silent failure in Chrome (nothing in the UI indicating that a download was attempted at all), to having the download start (saying 0/32.1 KB, 38 seconds left for a while), then finally "Couldn’t finish download".

I’m totally stumped! This same code has worked before on my PC downloading the same file (using the Xsendfile module). I normally pass in $attachment = true; but also tried it with $attachment = false, with no improvement.

As the comments note, the pragmas and cache-control stuff was just copied from some post somewhere. It has been working on a public site for the last 12 years or so…

EDIT:
FWIW, I’ve tried two more things I’ve seen posted regarding problems with downloads:

  1. Open an Incognito window to do the down. No change in behavior.
  2. Add header: header("Content-Transfer-Encoding: Binary"); Again, no change in behavior.

BTW, I am running ESET Internet Security 16.2.13.0, but I have disabled its firewall, and also turned off virus scanning for these tests. But again, no change in results…

*** UPDATE ***
I’ve also tried removing the Pragma:, Expires:, and Cache-control: headers, again without any effect.

The only way I’ve been able to download this small (37KB) zip file is by making it the href attribute of an element which also has the download attribute. So I guess I’ll try to cook up something that does that. The current code is nice in that I don’t have to create the zip file until a user asks to download it, and the path from the web root is not revealed to the user. With a static element, I’ll have to keep the zip file up-to-date with any changes made by the user, and the path to it will be visible to the user. Although I could maybe use javascript to create the element dynamically and trigger a click on it, allowing me to create the zip file only when requested, and hiding the path from all but highly inquisitive users. What an incredible pain!

2

Answers


  1. Chosen as BEST ANSWER

    This doesn't exactly answer the question, as I could find no real explanation for the behavior other than "something changed recently in both Chrome and Firefox". Instead, it just states the way I resolved the problem, which was blocking my development. Although this question hasn't gotten much of a reaction, I've seen enough other old questions about downloading files in PHP to believe that someone else will run into this at some point...

    Instead of an anchor with an href to an action routine on the server that would download the file using PHP, I changed the href to a javascript: protocol that called a function to dynamically generate an anchor with the url for the file, and a download attribute specifying the desired name for the downloaded file. And then triggered a click on that anchor. Interestingly, it wasn't even necessary to insert the new anchor into the DOM, I just created it and clicked it, and Chrome showed its little animation of something flying into the downloads folder - and the download had completed by the time I could check. Similarly with Firefox.

    <script type="text/javascript">
    downloadzip = function(url) {
        var anchor = document.createElement('a');
        anchor.href = url;
        anchor.target = '_blank';
        anchor.download = 'uploads.zip';
        anchor.click();
    }
    </script>
    <a href="javascript:downloadzip('<?php echo $this->download_uri;?>');">Download the zip file</a>
    

  2. Recently, I had to deal with a very similar situation.

    We have a very old long-running subsystem in the ecosystem of our applications which is running a file serving Apache/PHP/Xsendfile with versions very close to the ones you have.
    Another system (Tomcat+Liferay) is hosting a public site and redirecting to this subsystem for downloading specific files.

    So I had a ticket where Chrome users (specifically after Chrome v.110 more or less) couldn’t download specific files from the server (not all of them). I tested with Firefox & Edge and they seemed to work with no problems.
    You could click on the file, a new Chrome tab opened, I briefly saw the redirected new URL to the Apache server on the new tab, but it automatically closed with no download starting, whereas in other browsers the same behavior started a download.
    Tampering with it a little bit, I put some loggers everywhere that I could and it
    all seemed to execute with no issues between different browsers.
    So, initially I thought it was a Chrome bug that maybe would be resolved in later versions (I even downloaded Chrome Portable to test this – failed approach in general).

    Then, after tampering with it a little bit more, I stopped the new Chrome tab of closing and tried to play with it around a bit to see what I could find.
    And I saw that it was doing an http request to the download server which, as it seems, Chrome doesn’t like (after version v110 more or less) anymore as they seem to have put some more secure restrictions on downloading (again, I didn’t find any articles to support this but I think I read somewhere something…).
    So, I found the external property defined in the Tomcat/Liferay system that was redirecting to the Apache subsystem and it was something like:

    external.download.service=http://download-server…..

    and I changed it to

    external.download.service=https://download-server…..

    It was deployed to PROD last week and, after testing, downloads seem to be working fine for all browsers.
    (fortunately, that Apache was already setup for serving SSL and the F5 configurations could map correctly to https://download-server .. but that was DevOps territory).

    Hopefully, that helps but it seemed like a logical output in general if you consider all the subsystems as a whole.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search