On a page where I offer music sample downloads, I have several <a>
tags whose href points to a PHP file. Various data included as GET vars allow the proper file to be downloaded. Normally the PHP will respond with typical download headers followed by a readfile(). (the code for that is below, FYI). This results in a clean download (or download / play dialog box on some browsers). By "clean", I mean the download is completed with no disturbance in the visitors page.
However, in the unlikely event that the requested file is unavailable, I don't know what to do. I know it should not happen, but if it does I would like the download link to simply do NOTHING. Unfortunately since it is an <a>
tag referencing a PHP file, doing nothing results in the browser clearing the page, with the URL of the PHP file in the address bar. Not a good visitor experience! So I'd like way to avoid disturbing the page and doing NOTHING if there is is an errant request. I'll use javascript to alert the visitor about what went wrong, but I can't have the errant file request clear the page!
I thought I'd had a solution by issuing a header('Location: #'); when the script detected an impossible file download. But after a few seconds the browser cleared the page and put up a message indicating the page "redirected you too many times." (indeed, my script log fills up with over 100 entries, even though i only clicked the tag once.)
So far the only solution I have that works (works in the sense of NOT disturbing the visitors page if an "unavailable" file is requested) is to point my download headers at a "dummy" file. An actual "silence.mp3" or "nosong.mp3" file. But is there a way to call a header() that does nothing to the calling page? Simply calling exit or exit() won't work (the visitor page is redirected a blank.)
Not that it matters, but this is the code I normally call in response to the d/l request...
function downloadFile($path) {
$path_parts = pathinfo($path);
$ext = strtolower($path_parts["extension"]); // don't need this.
$fsize =fileExists($path);
if ($fsize == 0)
{
header('Location: #'); // this doesn't work!!! (too many redirectcts)
exit;
}
//$dlname = $path_parts['filename'] . "." . strtolower($path_parts["extension"]);
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: filename=\"" . $path_parts["basename"]."\"");
header("Content-Type: application/x-file-to-save");
header("Content-Transfer-Encoding: binary");
if($fsize) header("Content-length: $fsize");
$bytesRead = readfile($path);
return $bytesRead;
}
If you are using HTTP/1.x with a standard anchor tag, without JavaScript or other client-side interception. An HTTP/1.0 204 No Content
status header will cause the user-agent to simply seem like nothing happened when clicking a link that returns a 204 status header.
HTTP/1.0 204 No Content
The server has fulfilled the request but there is no new information to send back. If the client is a user agent, it should not change its document view from that which caused the request to be generated. This response is primarily intended to allow input for scripts or other actions to take place without causing a change to the user agent's active document view. The response may include new metainformation in the form of entity headers, which should apply to the document currently in the user agent's active view.
Source: https://www.w3.org/Protocols/HTTP/1.0/spec.html#Code204
This is also compatible with the HTTP/1.1
protocol.
I recommend using output buffering to ensure no other content is being sent by your application by mistake. Additionally there should be no need to send a Content-Length
header.
function downloadFile($path) {
if (!is_file($path) || !($fsize = filesize($path))) {
header('HTTP/1.0 204 No Content');
exit;
}
$path_parts = pathinfo($path);
header('Cache-Control: public');
header('Content-Description: File Transfer');
header('Content-Disposition: filename="' . $path_parts['basename'] . '"');
header('Content-Type: application/x-file-to-save');
header('Content-Transfer-Encoding: binary');
header('Content-length: ' . $fsize); //fsize already validated above.
return readfile($path);
}
Performing the file checks before creating the links is the simplest way to do this.
If I understand your request correctly you have files that you wish to allow a client to download, and links to PHP scripts that download certain files.
The problem with your implementation is that when the file is empty, the PHP script still must load and change the content of the clients page(from the action of loading the script), which is the incorrect behavior (correct being no action at all).
Since you are using tags on the main download page, really the only way to not change the content of the page in the case of a missing file is to compute the content of the tags in advance. With a simple PHP function you could check the contents of a list of files and their directories, and then generate links for the ones that exist, and blank links for the ones that do not.
Overall, I think separating the functionality of checking whether a file exists and actually downloading the file to a client is the only way to allow the functionality you desire.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With