How do I access files placed outside of the site root? - php

I have fried my brain all day on this. Researching SO until my eyes are bleary...I need to know: How do I access files placed outside the site root?
Background: Apache 2.0 dedicated server running Linux.
Code: PHP and MySQL
Reason: I want the files to be secured against typing in the file path and filename into a browser.
This can't be that difficult...but my splitting head says otherwise. Any help would be absolutely appreciated.

Have a look at the answers to this question, which seem to be doing more or less the same thing.
A quick summary: readfile() or file_get_contents() are what you're after. This example comes from the readfile() page:
<?php
$file = 'monkey.gif';
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
?>
I don't recommend allowing the $file variable to be set using user input! Think about where the filenames are coming from before arbitrarily returning files in the response.

are you trying to access files outside of site root? Then you can look at this link in stackoverflow.
And this is the official doc in Apache.
Otherwise you don't have to do special handling to prevent others from accessing files outside site root.

Related

PHP_xlsxwriter not working on server (file_exists cannot detect)

I had been using PHPExcel but switched to PHP_xlsxwriter because of speed. I am really satisfied with the performance but it only works on local server somehow.
$filename = "Report.xls";
$writer->writeToFile($filename);
if (file_exists($filename)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($filename).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filename));
readfile($filename);
unlink($filename);
exit;
}
This is the code and it works completely fine but on server file_exists($filename) does not catch the condition. And after seeing that I don't even know how it worked on local. How did file_exists find the file in local server? It does not even have a specific path.
Do I need to specify the path on the server? Could anyone give me an example or an answer to solve this problem? Thanks!
I also had the same problem. The error message I got is like this: "Error: ZipArchive class does not exist\n, referer: http://yoursite..."
It happened because I did not have ZipArchive class installed.
See this page for installation instructions. Install it and I hope it will work.

One Time Download Links and large files

I'm using this script:
http://www.webvamp.co.uk/blog/coding/creating-one-time-download-links/
to allow users download files (one time). Everything works fine with small files. Now i'm trying to do the same but with larger file 1.2 GB. Instead of forcing user to download file, script show off the relative patch to the file! Is there any way to modify the script or its a fault of the server configuration?
Thanks for help!
Looking for the code i think it fails on large files due to memory limitation. Script reads the whole file in memory via file_get_contents() before sending it. I suspect, >1Gb files will cause the problems with memory.
Try to replace following lines in download.php script:
//get the file content
$strFile = file_get_contents($strDownload);
//set the headers to force a download
header("Content-type: application/force-download");
header("Content-Disposition: attachment;
filename=\"".str_replace(" ", "_", $arrCheck['file'])."\"");
//echo the file to the user
echo $strFile;
to:
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($strDownload));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($strDownload));
ob_clean();
flush();
readfile($strDownload);
This may be help.
Note from manual: readfile() will not present any memory issues, even when sending large files, on its own

How to securely store/serve files on a webserver via PHP that are only accessible via a secure login area?

I am currently creating a PHP website which allows administrators to upload a variety of documents (pdf,doc,docx,xls) which can then be downloaded at a later date. These can only be accessed by administrators after they have logged in. Up until this point to do this I have been storing files above the web root and then using PHP to access and serve the file via a PHP script hence preventing direct access to the files. This does work but never seems like an ideal way to do it as it's reliant on setting the correct headers via PHP for the file download which does not always give the correct results on all browsers. I can't really see any other way of doing it that would also stop the files being publically accessible if they knew where they were located.
What process would you usually use to store and serve files on a web server that should not be publically accessible?
Sample PHP:
<?php
if (TRUE === $_SESSION['logged_in']) {
}
$file = '/full/path/to/useruploads/secret.pdf';
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
?>

PHP downloading script

I'm trying to write a php script that will check parameters passed in before it initiates a download to the client. I've started by attempting to just initiate a download:
<?php
$file = '/tticon.jpg';
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename=\"".basename($file)."\"");
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
?>
When I navigate to this script it shows a blank page and nothing happens. How do I initiate a download?
Try using application/force-download as Content-Type. If you want to show the image in the brwoser you can use image/jpg.
header("Content-type: application/force-download");
Since the file's extension is ".jpg" I assume it's mime is image/jpeg.
Replace this line:
header('Content-Type: application/octet-stream');
with this:
header('Content-Type: image/jpeg');
If you really want to force the browser to download the file (which I find very unlikely, anyway):
Replace the line with this:
header("Content-Type: application/force-download");
Your $file looks suspect. Do you really have a tticon.jpg in the root directory of your server? Remember that PHP's file-functions operate on the FILESYSTEM of the server, not the WEB directories that Apache presents. PHP will not magically pre-pend your site's document root to that path. It's literally going to be looking in the root directory of the server's file system, NOT in the document root of your site.

php, file download

I am using the simple file downloading script:
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
It is working on my localserver upto 200mb.
When i try this code in my website it downloads 173KB instead of 200MB file.
I checked everything, wrote some custom code (using ob functions and fread instead of readfile) but can't download big files.
Thank you for your answers.
I am using Apache 2.2, PHP 5.3
All PHP settings to deal with big files are ok. (execution times, memory limits, ...
One issue I have with the following code is you have no control over the output stream, your letting PHP handle it without knowing exactly what is going on within the background:
What you should do is set up an output system that you can control and replicated accros servers.
For example:
if (file_exists($file))
{
if (FALSE!== ($handler = fopen($file, 'r')))
{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: chunked'); //changed to chunked
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
//header('Content-Length: ' . filesize($file)); //Remove
//Send the content in chunks
while(false !== ($chunk = fread($handler,4096)))
{
echo $chunk;
}
}
exit;
}
echo "<h1>Content error</h1><p>The file does not exist!</p>";
This is only basic but give it a go!
Also read my reply here: file_get_contents => PHP Fatal error: Allowed memory exhausted
It seems readfile can have issues with long files. As #Khez asked, it could be that the script is running for too long. A quick Googling resulted in a couple examples of chunking the file.
http://teddy.fr/blog/how-serve-big-files-through-php
http://www.php.net/manual/en/function.readfile.php#99406
One solution to certain scenarios is that you can use PHP-script to intelligently decide what file from where to download, but instead of sending the file directly from PHP, you could return a redirection to the client which then contains the direct link which is processed by the web server alone.
This could be done at least in two ways: either PHP-script copies the file into a "download zone" which for example might be cleaned from "old" files regularly by some other background/service script or you expose the real permanent location to the clients.
There are of course drawbacks as is the case with each solution. In this one is that depending on the clients (curl, wget, GUI browser) requesting the file they may not support redirection you make and in the other one, the files are very exposed to the outer world and can be at all times read without the (access) control of the PHP script.
Have you made sure your script can run long enough and has enough memory?
Do you really need output buffering ?
The real solution is to avoid using a PHP script for just sending a file to the client, it's overkill and your webserver is better suited for the task.
Presumably you have a reason for sending the files through PHP, perhaps users must authenticate first? If that is the case then you should use X-Accel-Redirect (if you're using nginx) or X-Sendfile (previously X-LIGHTTPD-send-file) on lighttpd.
If you're using Apache I've found a few references to mod_xsendfile but I've never used it personally, and I doubt it's installed for you if you have managed hosting.
If these solutions are untenable I apologise, but I really need more information on the actual problem: Why are you sending these files through PHP in the first place?

Categories