For example, I use this form submit to download a file.
if(!isset($hasError) {
//some code here.
$file_url = "http://example.com/files/download/file.pdf";
header("Expires: 0");
header("Cache-Control: no-cache, no-store, must-revalidate");
header('Cache-Control: pre-check=0, post-check=0, max-age=0', false);
header("Pragma: no-cache");
header("Content-type: {$content_type}");
header("Content-Disposition:attachment; filename={$file_new_name}");
header("Content-Type: application/force-download");
flush();
readfile("{$file_url}");
exit();
}
With the submit form above, it will return downloadable file to save. As I see on Chrome (Ctrl + J), the download link is not displayed. It shows only form link with action. But is possible for visitor to know the actual link like this: "http://example.com/files/download/file.pdf".
If they can find that link, any solution to prevent this?
Thank you so much.
You can add .htaccess
with Deny from all
in files/download/
to forbid direct access (= HTTP request) but with readfile you will can read the file.
NB : You need to use readfile function and stream your request (for big file) like that : https://www.media-division.com/the-right-way-to-handle-file-downloads-in-php/ & https://www.media-division.com/php-download-script-with-resume-option/
No, find the source link is not possible from the browser, the PHP script is trasparently sending in output the remote file content which is exactly what readfile
function does, it reads buffers of content and echo it to the browser without leaking any info.
The browser has no ways to know anything, the user can only do some timing analysis of the response speed to know if the file is local to the server or remote, but no more than that (still don't know where "remote" is).
The manual page for readfile() says (emphasis mine):
Return Values
Returns the number of bytes read from the file. If an error occurs, FALSE is returned and unless the function was called as @readfile(), an error message is printed.
The reference to @
is not even particularly correct because you can have a custom error handler that ignores it. That means that, depending on your server configuration, you can have the URL printed as part of the output. For example, running your code as-is prints this in my computer:
Warning: readfile(http://example.com/files/download/file.pdf): failed to open stream: HTTP request failed! HTTP/1.0 404 Not Found in Standard input code on line 13
This (and similar issues) can be avoided by ensuring that error messages are never shown to user. That typically involves setting display_errors
to false
in production environment and, in cases like this, ensuring that the warning is properly handled (either with @
or with a custom error handler).
In any case, protected downloads are normally handled with password-protected locations and/or temporary links. A proxy server doesn't hide the fact that there's an open internet server where the file can be freely downloaded.
Note: This answer was written under the assumption that network downloads were being used because it was a remote resource. Using HTTP to fetch a local file is the equivalent of sharing your desktop on the internet to open files with Google Chrome instead of double-clicking on the file manager. The proper way to build a protected download system is to move the files outside the public web server root and feeding readfile()
with the local file system path, e.g.:
readfile('/home/foo/private/file.pdf');
This way, accidentally revealing the local path is not a security issue. But, as said, that's a different question.