I have a htaccess password protected folder with several files in it. Users are not allowed to access all files, but are allowed to download their own.
Since i can't direct link the file and since copying / removing isn't a real solution, i thought i'd just open the file using file_get_contents and echo it back into the page using the right header. But.. i don't get it working.. Here is my code. The error i am getting is that when opening the file i get a "file is damaged" error from Acrobat.
<?php
$file = "cms/docs/5641-1.pdf";
header('Content-Description: File Transfer');
header('Content-type: application/pdf');
header('Content-Disposition: attachment; filename='.basename("exoticfilename.pdf"));
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));
if (file_exists($file))
{
echo file_get_contents($file);
}
?>
Also, in this example I am just using a PDF file, but there are several types of files. Therefore i should probably change the header depending on the file type. Is there a solution for that, or should i just use a very long if / else statement?
If there is another, better way, I am open for that.
UPDATE
The above works, but not with all files. Older PDF's (Acrobat 6) don't work, but Acrobat X files do. Same counts for the docx files. Some work, others don't. Very weird, since I am able to open all directly on my PC. I assume it has something to do with the application/pdf line (or application/vnd.openxmlformats-officedocument.wordprocessingml.document' for docx). All others, like images, work.
Since you are using htaccess/htpasswd to protect the directory from hot-linking leeches. You are inadvertanly blocking access to the files from an outside source such as a browser from the client side. Since the directory requires authentication to access the files within it, you need to script around it. In a sense authenticating through the script. I have seen it done before, and you can find one of many references on the subject here
http://koivi.com/php-http-auth/
but bottom line is htaccess and htpasswd over run your scripts even if on the same host machine, as they are in a lack of better description server level, ran before even php starts its process on a page load.
Related
I have some images and pdf file on my server which I use in my website
The path to the images is
<img src ='/import/folder/image.jpg'>
Every image is associated with a pdf which resides with the image like the pdf for the above image will be at /import/folder/pdffile.pdf
the image source is visible to users
when some one view the source of page and copy the image source and paste in url after my base url
let suppose my base url is localhost.com
if some one manually write localhost.com/import/folder/image.jpg he can access my whole images and pdf file even my whole file system
How can I prevent users from accessing my file structure ?
I am using php and codeigniter
Thanks in advance
he can access my whole images and pdf file
this is how the web works.
even my whole file system
not whole of course but only files that you put into public access folder
How can I prevent users from accessing my file structure
they don't have an access to your file structure but to the public folder only.
you can't prevent users from accessing public folder because your site will stop working.
you have to ask more certain question, why and which files you want to secure.
In this case its difficult to prevent that people download your images. When you use "/import/folder/" its a public folder on your webspace. You can save the path with .htaccess.
For Your PDF files you could deliver the PDF File over php.
Get PDF 123
In the script you can check if the user has the rights to download the file and wheather the file exists and return the PDF as application/pdf output.
header('Expires: Thu, 19 Nov 1981 08:52:00 GMT');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: no-cache');
header("Content-Type : application/pdf");
header("Content-Disposition: attachment; filename=".$filename.".pdf");
Then the people can download the file. But in this case you have to save PDF is theirs.
Edit:
Then put the .htaccess to the folder with
deny from all
<FilesMatch "\.pdf$">
Order Allow,Deny
Deny from all
</FilesMatch>
Depending on what your server capacity is and how big the files are, you could do the following:
Stream both - the JPEG and the PDF file using what I call a "data-proxy" - a PHP script that reads the file content and streams it back to the browser, like (be careful to set the correct content type) (similar to what Stony proposed, although he left the readfile() part out):
$file_path = $download;
header('Content-Description: File Transfer');
header('Content-Type: audio/mpeg');
header('Content-Disposition: attachment; filename="'.$_GET['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_path));
ob_clean();
ob_end_flush();
readfile($file_path);
exit;
Obfuscate the files. Make the filenames something like md5($filename.$salt) and remove the file extension. If you have the files in different folders (like /images and /pdf)) you don't need the extension for the streaming as you only read the content of the file. You could also place them outside the accessible web space (I think you need open_base_dir for this), thus no one except you would be able to access them. Use .htacces to further restrict access to the files as described in other answers.
Employ a session for the above script so only logged in users get the streaming.
Encrypt the files - you could encrypt the whole content of the files. So even if someone would get the file content, it would be encrypted. You can decrypt them just before streaming. If you employ a secure encryption algorithm this should be quite secure. However, this depends to the file sizes and the server capacity to a large extent as I suppose encrypting the whole file could be a problem if it's a large one.
Make the PDFs password protected. Although not really secure as it can be easily removed, it makes basic users run against the wall... You can do that on the server side too with an automated script.
Put Options -Indexes in a .htaccess file you place in localhost.com/import/folder/ (or higher up the document tree). This will disable access to the file structure in localhost.com/import/folder
Preventing users from accessing your files is something different, like Stony suggested, you can stream the files using php.
Edit: I saw your comment about people "guessing" the url of an image. You could store all the images with an encrypted filename. Something like an md5 hash of the filename and the uploadtime combined. Then store those encrypted names in a database table...
You can use this in the .htaccess
RewriteEngine On
RewriteCond %{HTTP_REFERER} !^http://your_site_url/.*$ [NC]
RewriteRule \.(jpg)$ - [F]
This will prevent the direct access of the images, but the images will be vissible in your website
Hi i have a problem with making a safe upload folder in a project.
The thing is that we have a file upload that everyone should be able to upload files to, but only the site administrator the site should be able to view the files later.
Is it possible making a folder non readable, but accessible from a php page?
The server is a linux inviroment
There's actually several ways to do this.
Apache configuration (you may restrict access to certain directory by IP security, or HTTP authorization), see: allow,deny and apache authentification
Save files to directory which is not accessible via website and write your own php directory listing and file download, via readfile
Upload file to directory which will be accessible only via "secret" ftp/sftp.
The simple answer to this is to place the files in a directory outside your web root, and built a page to view the directory that requires an administrator auth to access.
If the files are outside your web root, they cannot be directly accessed with a /path/to/file.ext type URL.
In cases like this, I would locate the folder outside the document root, or restrict it's access via Apache directives.
Then, using the PHP and checking access credentials, output the file using readfile()
Here is an example from the manual
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');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
?>
Lets say I have a plain text file example.txt and I have a PHP script on my web-server readfile.php.
What I want to be able to do is to prevent users from typing http://www.example.com/example.txt and looking at the text file directly but I still want people to be able to load http://www.example.com/readfile.php which reads from the file example.txt and does something with it (possibly displays the contents of the file).
Also, if this can be done, what is the best way to do it?
Yes, this is easy to do.
There are two main ways to stop users from accessing example.txt. The first is to put it in a folder outside your web folder (Usually called www or public_html), the second is to put a .htaccess file in the folder with your example.txt script which blocks access to the file altogether. The .htaccess would look like
<files "example.txt">
deny from all
</files>
But you could change example.txt to something like *.txt if you wanted to block all .txt files in the folder.
Then you can use file_get_contents() in your readfile.php to get the contents of the text file, or if you just want to output the file you can use readfile
Just store the files you don't want publicly accessible outside the webroot.
/home
example.txt
/www
readfile.php
If /home/www/ is your public webroot folder, any file above it is not accessible through the web server. readfile.php can still access the file perfectly fine at ../example.txt though.
If you need to store the files in the webroot, then put the files in a folder and deny access to that folder. If you are using apache, make a .htaccess file in the folder and type in deny from all
I've done something similar where the files contain extremely sensitive information and I only want validated users to be able to retrieve the file through an HTTPS connection.
What I did was this:
I put the files in a directory path that is outside the scope of what the web server (Apache, for me) can see. Therefore, there are no possible URLs that will result in the file being served up directly by the web server. Then I created a script that allows users to login, click on the file they want, and then the PHP script reads the file, puts the appropriate headers, and then streams the file to the user's computer.
Of course, the script that shows the user the list of files and the script that streams the file out to the user must have at least read access to the files in the path where they are being stored.
Good luck!!
You can put the file "example.txt" outside of the public folder and read from readfile.php like $content = file_get_contents("../example.txt");
You can call a file like this and the people don't see the filename:
<?php
$file = 'example.txt';
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;
}
?>
(source: php.net)
Would that work for you?
Quick hack - rename your file to ".cannotReadFileWithDOT". he server will close reading files with a dot at the beginning of the name, but your scripts will be able to read them. The plus is that the apache and nginx servers out of the box are configured to prohibit reading files with a dot at the beginning of the name.
Firstly: I'm a lowly web designer who knows just enough PHP to be dangerous and just enough about server administration to be, well, nothing. I probably won't understand you unless you're very clear!
The setup: I've set up a website where the client uploads files to a specific directory, and those files are made available, through php, for download by users. The files are generally executable files over 50MB. The client does not want them zipped, as they feel their users aren't savvy enough to unzip them. I'm using the php below to force a download dialogue box and hide the directory where the files are located.
It's Linux server, if that makes a difference.
The problem: There is a certain file that becomes corrupt after the user tries to download it. It is an executable file, but when it's clicked on, a blank DOS window opens up. The original file, prior to download opens perfectly. There are several other similar files that go through the same exact download procedure, and all of those work just fine.
Things I've tried: I've tried uploading the file zipped, then unzipping it on the server to make sure it wasn't becoming corrupt during upload, and no luck.
I've also compared the binary code of the original file to the downloaded file that doesn't work, and they're exactly the same (so the php isn't accidentally inserting anything extra into the file).
Could it be an issue with the headers in my downloadFile function? I really am not sure how to troubleshoot this one…
This is the download php, if it's relevant ($filenamereplace is defined elsewhere):
downloadFile("../DIRECTORY/files/$filenamereplace","$filenamereplace");
function downloadFile($file,$filename){
if(file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$filename.'"');
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));
# flush();
readfile($file);
exit;
}
}
ETA Additonal Info:
- Tests for working/non-working files have been done on the same machine
- If it makes any difference, the original file has a custom icon. After download, the file has a generic blank document icon.
Additonal Info: I THINK THIS ONE'S IMPORTANT!
I just tried downloading the file directly (to bypass the download link that triggers the download function above). If I download the file by just going to its url and downloading it that way, the downloaded file WORKS. So I'm thinking it must have something to do with the download function. But what??
3/17 MAJOR CORRECTION —AND RESOLVED—
So I woke up this morning and it dawned on me that maybe I was comparing the files wrong. (I had re-saved them as binary text, and then compared them. I didn't realize the comparison program would take and compare actual exe files). This morning I tried comparing the actual exe files and there is a difference. There was one line of php code that was being injected into the first line of the file. I adjusted the php, and the problem was fixed. (It was from the if/else statement that defined teh $filenamereplace variable in the code I'd cited). Thanks again for all your help, and sorry for misleading you in insisting that the files' contents were identical!
"I've also compared the binary code of the original file to the downloaded file that doesn't work, and their exactly the same (so the php isn't accidentally inserting anything extra into the file)."
If that's really true, then the problem must be in how the exe is started after it has been downloaded. It should certainly not be a problem with your PHP code.
Perhaps they were corrupted on upload. This can happen if you transfer them via FTP in ASCII mode instead of BINARY.
I'm trying to use the following code to create a zip file from a directory and serve it to the user via an http download:
// write the file
file_put_contents($path . "/index.html", $output);
// zip up the contents
chdir($path);
exec("zip -r {$course->name} ./");
$filename = "{$course->name}.zip";
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' .urlencode($filename));
header('Content-Transfer-Encoding: binary');
readfile($filename);
I am able to create the zip file, but downloading it over http is not working. If I download the zip file that's created using an ftp client then Mac's Stuffit Expander unzips the files just fine, but if I download it over http, the mac unzipper creates an endless loop. What I mean by this is say the file I download is called course.zip, then unzipping the file gives course.zip.cpgz and unzipping that file gives course.zip again..and on and on.
Anyone have any ideas?
Thanks!
I had this problem and it turned out the downloaded zip file had a new line inserted at the very beginning.
Solved by using ob_clean and flush functions
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=".basename($archive_file_name));
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($archive_file_name));
ob_clean();
flush();
echo readfile("$archive_file_name");
Re-zipping it every time it is requested is not a good idea. Try doing that only if the ZIP file does not exist already.
If is a volatile file or just a single small file you want to transfer compressed, try using ob_start('ob_gzhandler') instead, simplier, smaller, cleaner. The file is transfered compressed, but it is saved in its original format by the client-side.
Specifying the Content-Length header is needed to allow the downloader to know the end of the file, allowing progress control, detection of corruption of the file and avoiding the hang of the HTTP session (if Connection is in Keep-Alive mode), maybe the lack of this header is the root of the problem.
As suggested by karim79, I'll put my comment as an answer: what happens if you change the MIME type from application/octet-stream to application/zip?
Also, I see you're using a command line zip program, but you don't check for success of the zip, and also don't check if the file exists before attempting to send it out to the end users browser. Try hard coding a file name, manually using zip to guarantee a properly formed zip file, and then see if your code will spit it to your browser properly.
What you're seeing is that the archive utility is not recognizing the zip file as a zip file, and tried to zip up the zip archive itself. The second operation simply unzips the first file created, so never actually opening the file at all. This is due to the zip being corrupted.
It is possible that the browser somehow mangled the zip file (newline conversions anyone?) during the download process. As mentioned, check the mime type and use the php header() to set the correct MIME type (application/zip).