I've come across a rather interesing (and frustrating) problem with IE6. We are serving up some server generated pdfs and then simply setting headers in PHP to force a browser download of the file. Works fine and all, except in IE6 but only if the windows user account is set to standard user (ie. not administrator).
Since this is for a corporate environment, of course all their accounts are setup this way. Weird thing is, that in the download dialog, the Content-Type is not recognized:
header( 'Pragma: public' );
header( 'Expires: 0' );
header( 'Cache-Control: must-revalidate, pre-check=0, post-check=0' );
header( 'Cache-Control: public' );
header( 'Content-Description: File Transfer' );
header( 'Content-Type: application/pdf' );
header( 'Content-Disposition: attachment; filename="xxx.pdf"' );
header( 'Content-Transfer-Encoding: binary' );
echo $content;
exit;
I also tried writing the file content to a temporary file first so I could also set the Content-Length in the header but that didn't help.
These headers are bogus!
Content-Transfer-Encoding: binary
This header is copied from e-mail headers. It doesn't apply to HTTP simply because HTTP doesn't have any other mode of transfer than binary. Setting it makes as much sense as setting X-Bits-Per-Byte: 8.
Cache-control: pre-check=0, post-check=0
These non-standard values define when IE should check whether cached content is still fresh. 0 is the default, so setting it to 0 is waste of time. These directives apply only to cacheable content, and Expires:0 and must-revalidate hint that you wanted to make it non-cacheable.
Content-Description: File Transfer
This is another e-mail copycat. By design this header doesn't affect download in any way. It's just informative free-form text. It's technically as useful as X-Hi-Mom: I'm sending you a file! header.
header( 'Cache-Control: must-revalidate, pre-check=0, post-check=0' );
header( 'Cache-Control: public' );
In PHP second line completely overwrites the first one. You seem to be stabbing in the dark.
What really makes a difference
Content-Disposition: attachment
You don't have to insert filename there (you can use mod_rewrite or index.php/fakefilename.doc trick – it gives much better support for special characters and works in browsers that ignore the optional Content-Disposition header).
In IE it makes difference whether file is in cache or not ("Open" doens't work for non-cacheable files), and whether user has plug-in that claims to support type of file that IE detects.
To disable cache you only need Cache-control:no-cache (without 20 extra fake headers), and to make file cacheable you don't have to send anything.
NB: PHP has horrible misfeature called session.cache_limiter which hopelessly screws up HTTP headers unlesss you set it to none.
ini_set('session.cache_limiter','none'); // tell PHP to stop screwing up HTTP
some versions of IE seem to take
header( 'Expires: 0' );
header( 'Cache-Control: must-revalidate, pre-check=0, post-check=0' );
way too seriously and remove the downloaded content before it's passed to the plugin to display it.
Remove these two and you should be fine.
And make sure you are not using any server-side GZIP compression when working with PDFs because some versions of Acrobat seem to struggle with this.
I know I'm vague here, but above tips are based on real-world experience I got using a web application serving dynamically built PDFs containing barcodes. I don't know what versions are affected, I only know that using the two "tricks" above made the support calls go away :p
I have had the exact same problem about a year ago, and after much googling and research, my headers (from Java code) look for IE6 & PDFs like this:
response.setHeader("Content-Type", "application/pdf "; name=" + file.getName());
response.setContentType("application/pdf");
response.setHeader("Last-Modified", getHeaderDate(file.getFile());
response.setHeader("Content-Length", file.getLength());
Drop everything else.
There is apparently something a bit whacky with IE6, caching, forced downloading and plug-ins. I hope this works for you...a small difference for me is that the request initially comes from a Flash swf file. But that should not matter.
I appreciate the time you guys spent on this post. I tried several combinations and finally got my symfony project to work. Here I post the solutions in case anyone will have the same problem:
public function download(sfResponse $response) {
$response->clearHttpHeaders();
$response->setHttpHeader('Pragma: public', true);
$response->addCacheControlHttpHeader("Cache-control","private");
$response->setContentType('application/octet-stream', true);
$response->setHttpHeader('Content-Length', filesize(sfConfig::get('sf_web_dir') . sfConfig::get('app_paths_docPdf') . $this->getFilename()), true);
$response->setHttpHeader("Content-Disposition", "attachment; filename=\"". $this->getFilename() ."\"");
$response->setHttpHeader('Content-Transfer-Encoding', 'binary', true);
$response->setHttpHeader("Content-Description","File Transfer");
$response->sendHttpHeaders();
$response->setContent(readfile(sfConfig::get('sf_web_dir') . sfConfig::get('app_paths_docPdf') . $this->getFilename()));
return sfView::NONE;
}
This works just fine for me in IE6,IE7, Chrome, Firefox.
Hope this will help someone.
As pilif already mentions, make sure to turn off the server-side gzip compression. For me this has caused problems with PDF files (among other types) and for maybe-not-so-obscure reasons also with .zip files both under Internet Explorer and FireFox.
As far as I could tell, the last bit of the zip footer would get stripped (at least by FireFox) causing a corrupted format.
In PHP you can use the following code:
ini_set("zlib.output_compression",0);
The following bit of Java code works for me (tested on Firefox 2 and 3, IE 6 and 7):
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
response.setContentType(getServletContext().getMimeType(file.getName()));
response.setContentLength(file.length());
No other headers were necessary at all.
Also, I tested this code both with gzip compression on and off (using a separate servlet filter that does the compression). Doesn't make any difference (works without any problem in the four browsers I tested it on).
Plus, this works for other filetypes as well.
You can add an additional parameter that the server won't read to the url it might help too.
http://www.mycom.com/services/pdf?action=blahblah&filename=pdf0001.pdf
I have run into cases where ie will be more likely to read the filename on the end of the url than any of the headers
I had a similar problem, but it might not be exactly related. My issue was that IE6 seems to have a problem with special characters (specifically slashes) in the file name. Removing these fixed the issue.
If you are using SSL:
Make sure you do not include any cache control (or Pragma) headers. There is a bug in IE6 which will prevent users from downloading files if cache control headers are used. They will get an error message.
I pulled my hair out over this for 2 days, so hopefully this message helps someone.
simply switch to this content type and it will work, also be sure Pragma ist set to something NOT equal "no-cache"
header( 'Content-type: application/octet-stream'); # force download, no matter what mimetype
header( 'Content-Transfer-Encoding: binary' ); # is always ok, also for plain text
Related
When generating a PDF in the browser programmatically (via PHP) the rendered PDF displays fine in both Firefox and Safari, but Chrome returns an ERR_INVALID_RESPONSE. It is a valid PDF - can be opened locally with Adobe Reader/Preview once saved from the working browsers, and will even open in Chrome once the PDF is saved from a different browser.
The PDF file is being read through file_get_contents(), is given a current timestamp and then passed to the browser. A workaround would involve saving the file to a temporary spot and redirecting the user (for Chrome, at least) but this is not ideal.
I've researched it and only been able to find bug reports dating from 2008.
I have an inkling it's a header error. After the PDF is generated, the following headers are sent to the browser (again working fine in FF, Safari and IE):
header('Content-type:application/pdf');
header("HTTP/1.1 200 OK");
I've also tried adding the following headers after searching on Stack Overflow, but to no avail:
header("Content-Transfer-Encoding: binary");
header('Accept-Ranges: bytes');
Are there missing headers that Chrome requires? Does anyone have experience with getting dynamically generated PDFs to display in Chrome?
EDIT: One of my more salient questions is what could be causing this to work fine locally in Chrome, but wouldn't work on a server environment.
In my case I had to add these 2 parameters to headers because wordpress was sending 404 code as it didn't recognize the url of my php function:
header("Content-type: application/pdf",true,200);
as stated in this answer on wordpress.stackexchange.
This forces the headers to replace (2nd param true) the 404 status code generated by wordpress as it does not recognize the custom url, and sets 200 OK (3rd param 200).
So it ended being something like this:
$pdf_name = "test.pdf";
$pdf_file = "/absolute/path/to/my/pdfs/on/my/server/{$pdf_name}";
header('Content-type: application/pdf',true,200);
header("Content-Disposition: attachment; filename={$pdf_name}");
header('Cache-Control: public');
readfile($pdf_file);
exit();
Try this
<?php
$filename = 'Physical Path to PDf file.pdf';
$content = file_get_contents($filename);
header("Content-type:application/pdf");
// It will be called downloaded.pdf
header("Content-Disposition:inline;filename='".basename($filename)."'");
header('Content-Length: '.strlen( $content ));
// The PDF source is in original.pdf
readfile($filename);
?>
<html>
<body>
...
...
...
Make sure that above header code is called before output of PHP script is
sent to browser.
I want to thank everyone for their answers.
It turns out this was not related to the headers. After attempting to change/remove headers in various ways (detecting encoding, trying with and without content-length, etc.) we decided to dig into the deeper httpd logs to see if anything was resolving differently for Chrome.
It turns out that mod_sec on our server was flagging the request (only from Chrome for some reason) as an attempt at a file injection attack and was returning a 403 forbidden response. Chrome displayed this as the ERR_INVALID_RESPONSE rather than a 403.
The hostname of the CDN was present in the request (we had ample checking at the endpoint to ensure that the file was indeed an allowed resource), and instead are building the URL out on the server instead.
My searching leads me to believe this is an issue with the headers being set for the response. This application is built using the Zend framework, and here are the headers being set (this response contains information about a file upload):
$response
->setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
->setHeader('Cache-Control', 'private, no-cache')
->setHeader('Pragma', 'no-cache')
->setHeader('Content-Disposition', 'inline; filename="files.json"')
->setHeader('X-Content-Type-Options', 'nosniff')
->setHeader('Content-type', 'application/json; charset=UTF-8');
This is the contents of the "files.json":
{"webpath":"http://www.domain.com/avatar/38b/3ef/f8b/a4c62a71.jpg","file_id":"484","height":250,"width":250}
Edit: I'm having this issue in all versions of IE, including IE9. I have also attempted to use 'text/plain' for the Content-type, with no avail. Also fixed the typo on the word "private".
When I am returning json I set the following in whateverAction():-
$this->getResponse()->setHttpResponseCode(200);
$this->getResponse()->setHeader('Content-Type', 'application/json');
$this->getResponse()->setBody($Json());
That's it, nothing else and it works across all browsers. All those I can test anyway, which includes IE 9.
If browsers ask you to download a JSON document nothing goes wrong. By default browsers do not display JSON which has a proper content type inline unless you have some extension installed (e.g. JSONView in Firefox).
If you just want to view the JSON created by your script, use such an extension or temporarily use a text content type (such as text/plain or text/javascript). In other cases leave it as-is since JSON should be sent with a proper content type.
Have you tried setting Content-Disposition to just "inline" (i.e. remove '; filename="files.json"')?
The headers tell IE to download the file. I would remove
->setHeader('Content-Disposition', 'inline; filename="files.json"')
because I think this one triggers the download.
If that's not the case, I would remove all headers at first, then review if they are all written properly (see "private") and test first with no headers and then adding them one by one to find out which one triggers your problem. Then please write which one it is to look further into it.
I have written the following PHP function but still get the prompt to download the file:
function navigateToBytes($contentType, $bytes){
header('Content-Type: ' .$contentType);
//header('Content-Transfer-Encoding: binary'); // UPDATE: as pointed out this is not needed, though it does not solve the problem
header('Content-Length: '.strlen($bytes));
ob_clean();
flush();
echo $bytes;
}
An example of calling the function:
navigateToBytes('image/jpeg', $bytes); // UPDATE: turns out this does work, using image/tiff for tiff images is when the browser does not display the image
where $bytes are the bytes as read from the file.
Apologies all - turns out I was having the problem because the images I was testing were TIFF's (with the Content-Type correctly set to image/tiff) when I used a JPEG the browser would display the image!
Ultimately it is up to the browser to decide whether it can display the Content-Type you are sending.
For the record the only headers I needed to change was
Content-Type,
I should set
Content-Length
too unless I set
Transfer-Encoding: chunked
Try the HTTP header "Content-Disposition: Inline", however some browsers may try to save the user from seeing binary data. Here is a random blog article on that HTTP header:
http://dotanything.wordpress.com/2008/05/30/content-disposition-attachment-vs-inline/
That seems like correct behavior to me. The browser is a viewport for humans to view things in. Humans, by and large, don't want to view binary data. What do you think should happen?
Random Advice: If there's a site that's doing what you want to do, use curl to sniff the headers they're sending.
curl -I http://example.com/path/to/binary/file/that/displays/in/browser
and then use the exact same headers in your own script.
As a start, get rid of things that do not exist in HTTP (Content-Transfer-Encoding).
Then get an HTTP tracing tool, such as the Live HTTP headers plugin for Firefox, and compare "your" headers with those received for a working image.
In doubt, post the HTTP trace here.
I am trying to send files by using X-Sendfile directive in lighttpd.
My php code is;
header("Content-Type: application/force-download");
header( "Content-Disposition: attachment; filename=" . 's.php');
header("Content-Length: ". filesize("/home/web/domain/htdocs/download.php"));
header( "X-Sendfile: /home/web/domain/htdocs/download.php");
(I am sending download.php file just for testing purpose)
However, I get blank file no matter what I try. If I change the filename, I get;
2010-08-30 18:01:14: (mod_fastcgi.c.2587) send-file error: couldn't get stat_cache entry for: /home/web/domain/htdocs/downloa1d.php
So, it is working, but when I send the correct file it does not give any error in the logs and the browser downloads an empty file.
What could be wrong? What should I do?
It is likely that you don't have the right line in your lighttpd.conf file.
Add this in your fastcgi.server between the options with => arrows:
"allow-x-send-file" => "enable",
All but the last option end with commas.
I assume that you have your fastcgi already configured. If you are using a different framework for connecting PHP to your server, I can't help you.
If you have more than one PHP worker process, you must add that option to all of them.
I should add that .php files are small in size and outputting them with readfile() won't make a dent.
And, as I see it, you are using some non-critical headers wrongly. There is no such thing as force-download, it will just behave like an unknown file type. Use application/octet-stream. And your Content-Disposition is also shaky. Not only in that weird string enclosing. What the client should get is:
Content-Disposition: attachment; filename="My file ♥.txt"
The file name is surrounded by double quotes and it is NOT escaped; instead, it is either in UTF-8 or in the encoding of the calling page (this is a dirty grey area where every browser acts differently). You don't even have to include the file name to be on the safe side if you are satisfied with the downloading path. Then, it will look like this:
Content-Disposition: attachment
You can then use a pseudo-directory trick to supply a less buggy file name:
www.example.com/download.php/Custom%20Name.txt
This will work more reliably than that filename= parameter, but takes more planning in the link system. It may require you to do some redirecting.
OK this is an old post, but I'm going to reply anyway 'cause I was stuck on this too for way too long and I didn't find my solution anywhere so here I go:
Yeah I edited lighttpd.conf and added
fastcgi.server += ( ".php" => (( "allow-x-send-file" => "enable" )) )
and used
Header("X-LIGHTTPD-send-file: /path/filename");
in the PHP test page. But it didn't work. It took me a while to figure out I also had a PHP definition in
etc/lighttpd/conf-enabled/15-fastcgi-php.conf
After I added the option in there it worked :)
When attempting to view a *.wav URL on our webserver, the data doesn't transfer properly.
We are using apache2 with php5.10. Among the apache rewrite rules is:
RewriteRule ^(.+)\.(wav)$ /wav.php?wav=$1.$2 [L,NC]
And the relevant code from wav.php is:
<?php
$image = getPassed("wav");
header( 'Content-Type: audio/wav');
set_include_path("/");
readfile($image, true);
exit;
?>
This is supposed to return any .wav file on the server when server.company.com/filepath is accessed via a web browser.
When attempting to listen to any *.wav file in firefox or chrome in ubuntu (haven't tested other OSes yet), the plugin errors: "Location not found." However, right-clicking and choosing "save link as" allows the user to download the .wav file.
Any thoughts?
Edit:
getPassed is a function to return variables from $_GET or $_POST
I think that you need a couple more headers for this functionality to work, not 100% sure because I have never tried to stream a file.
header( 'Content-Type: audio/wav');
header("Content-Transfer-Encoding: binary");
header("Content-Length: " . filesize($image) ."; ");
header('filename="'.$image . '"; ');
It is worth a shot to try it with those either way. You may also want to try a content-type of Content-Type: application/octet-stream;, you may also want to try "chunking" the file using readfile_chunked() (a user contributed function on php.net) and see if that possibly helps as well.
Hope it helps.