PHP readfile() which preserves mime-type - php

I am trying to set up a PHP script which can pull files (for validated users) from a folder outside of the live webroot. The problem is that when doing this, even if I use a proper "Content-Type" header, the file is downloaded as application/octet-stream. This has security implications if the user later tries to re-upload the downloaded file as it will be detected as an application even when it is really a pdf (my application only allows certain mime-types to be uploaded).
This is what I am using to pull the file into the browser:
$secret_file = "../attach/1/test.pdf";
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: ".mime_content_type($secret_file));
header("Content-Disposition: inline; filename=\"test.pdf\";");
header("Content-Transfer-Encoding: binary");
readfile($secret_file);
Interestingly, if I download the file this way through a browser and view its properties in linux, it shows up as "application/pdf". However, if I check the same file using PHP's mime_content_type() function, it reads as application/octet-stream.
I did confirm that checking the original file this way (before it is pulled through readfile) shows it as application/pdf.
Is there any way to use readfile() without changing mime types? Or, is there perhaps a better alternative?
EDIT
Answering some questions from the comments:
Developer Tools in Firefox reports that the inline file has a Content-Type: application/pdf header.
sha256sum of downloaded file does not match the original file. But how could readfile() (or similar) be used without changing the file?

Related

Download Excel file with PHP over https

I am about to take a web application I've been working on live and am now, of course, updating code to make things that worked perfectly on my localhost work on the secure server. One of the tasks this application performs is generating and downloading files. All of the files are being generated and written to the server, but the .xls, .csv, and .json files download with no data even though the data is in the files on the server. The .xml, .zip, and .sql files do download with accurate data. An .xls or .json file that is contained within a .zip file is fine. This problem is not IE specific as I am using Chrome. Also tried Safari with the same results.
Here is what I'm doing to download the Excel:
$excel_content = file_get_contents($download_file_name);
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"".$file_name);
header("Content-Transfer-Encoding: binary");
header("Pragma: no-cache");
header("Expires: 0");
print chr(255).chr(254).mb_convert_encoding($excel_content, 'UTF-16LE', 'UTF-8');
Here is what I'm doing to download the XML:
$xml_content = file_get_contents($download_file_name);
header("Content-Type: application/xml");
header("Content-Disposition: attachment; filename=\"".$file_name);
header("Content-Transfer-Encoding: UTF-8");
header("Pragma: no-cache");
header("Expires: 0");
print $xml_content;
Can anyone see why the Excel works on my localhost, but not on my server through https protocol? If it matters, I do have openssl enabled as verified with an info.php file on the server.

How change the default download location in PHP

my code:
$zip_name="download.zip";
$ctype="application/zip";
// required for IE, otherwise Content-disposition is ignored
if(ini_get('zlib.output_compression'))
ini_set('zlib.output_compression', 'Off');
header("Pragma: public"); // required
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: $ctype");
// change, added quotes to allow spaces in filenames
header("Content-Disposition: attachment; filename=\"".basename($zip_name)."\";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($zip_name));
readfile("$zip_name");
now files download on Downloads folder.
i need download all files in D: drive.
This is not possible. A webserver cannot tell a client where to save a file. Imagine the security implications if a site could specify an exact location for a file save operation.
See RFC 2183, section 2.3:
The receiving MUA SHOULD NOT respect any directory path information
that may seem to be present in the filename parameter. The filename
should be treated as a terminal component only. Portable
specification of directory paths might possibly be done in the future
via a separate Content-Disposition parameter, but no provision is
made for it in this draft.

Copy and download file in htaccess protected folder

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.

Download .zip file problem (downloading .php file)

I want to download a .zip file, but when i go to download it, it forces me to download the .php file. I use this simple code that is all over the internet so I don't know why I can't download .zip file.
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Content-type: application/zip");
header('Content-length: '.filesize($fullpath));
header("Content-disposition: attachment; filename=".basename($fullpath));
readfile($fullpath);
If you are serving the PHP file with a .zip extension, getting the PHP source code for download is expected behaviour - ZIP files do not get parsed by the PHP interpreter by default.
You would have to register the ZIP extension to be parsed by PHP. That is pretty sub-optimal, though; in this case, you can link to the PHP file with the .php extension. The filename header will provide the correct name to the user who downloads the file.
If you are using a .php extension and the source code of your PHP file is served, your server configuration is broken.
I think you are downloading the zip file named as .php
try to open the downloaded file with winzip or winrar

Creating and serving zipped files with php

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).

Categories