Chrome cannot recognize url encoded filename while downloading - php

I tried to download a file with filename GB2312%D5%D5%C6%AC.JPG from my site, everything goes well in IE9/Firefox, but not in Google Chrome.
In Google Chrome, this filename is replaced by my download page name (Maybe Chrome is failed to decode the filename).
To find out if Chrome is tring to decode filename, I tried to use another string GB2312%2C%2D%2E.txt as the filename, firefox/IE9 still work as expected, but Google Chrome will try to decode this filename (replace %2D with '-').
How to prevent Google Chrome from decoding filename? Better if I can solve this problem at my server side (PHP)
The following lines are response headers generated by my server.
**Response Headers:**
Cache-Control:must-revalidate, post-check=0, pre-check=0, private
Connection:keep-alive
Content-Description:File Transfer
Content-Disposition:attachment; filename="GB2312%D5%D5%C6%AC.JPG"
Content-Length:121969
Content-Transfer-Encoding:binary
Content-Type:application/force-download; charset=UTF-8
Date:Wed, 18 Apr 2012 03:32:30 GMT
Expires:0
Pragma:public
Server:nginx/1.1.5
X-Powered-By:PHP/5.3.8

I just had this very problem. This question was helpful.
The cause is, as you pointed out, that Chrome is trying to decode it and it's outside of ASCII. I would call it a bug, personally.
Basically the answer to get it working in Chrome is to use this:
Content-Disposition:attachment; filename*=UTF-8''GB2312%D5%D5%C6%AC.JPG
However, this will break it in IE8 and Safari. Ugh! So to get it working in those as well, try doing it like this example.
Content-Disposition:attachment; filename="GB2312%D5%D5%C6%AC.JPG"; filename*=UTF-8''GB2312%D5%D5%C6%AC.JPG
For me, I had to url encode the file name before putting it in the filename*=UTF-8 part. So my values for the first file name and the second one aren't the same. I used PHP and my code looks like this:
$encodedfilename = urlencode($downloadfilename);
header("Content-Disposition: attachment; filename=\"$downloadfilename\"; filename*=UTF-8''$encodedfilename");
So I'm not actually stopping it from encoding like you asked for, but I encode it and then pass the parameter that gets decoded by Chrome, back to what it's supposed to be.

Related

PDF Generation Results in ERR_INVALID_RESPONSE in Chrome

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.

Ogg audio served from PHP-file unseekable on Chrome

I'm developing a basic audio player using HTML5 audio element. All served files are .ogg coming from another system as a binary string through an API and printed out by a simple PHP handler (which includes some basic security checking) and there's really nothing I can do about it.
However, while everything works nicely on Firefox, Chrome has some issues regarding seeking, skipping or replaying (simply put: it can't be done). Getting audio duration from the audio element returns Infinity, and while currentTime returns correct value, it can't be manipulated.
I started fiddling with headers and response codes and nothing seems to work properly. Using response code 206 for partial content or declaring Accept-Ranges in header causes audio element to go disabled as it does when the source file doesn't exist.
Searching for proper headers didn't yield much results, since it always was about partial content and X-Content-Duration (which is a PITA to calculate because of VBR), which did absolutely nothing on Chrome.
Here are the relevant headers which work just fine on FF. Did I make a major mistake on something, or is there just some issue with Chrome?
HTTP/1.1 200 OK (changing this to 206 makes the file unplayable in Chrome)
Cache-Control: public, no-store
Content-Disposition: attachment; filename="redacted.ogg"
Content-Length: 123456
X-Content-Duration: 12 (this does nothing on Chrome, works on FF)
Content-Range: bytes 0-123455/123456
Accept-Ranges: bytes (...as does including this)
Content-Transfer-Encoding: binary
Content-Type: audio/ogg
Expires: 0
Pragma: public
Edit: some kind of behaviour on Opera, probably on all Webkit browsers. Also doesn't work in fread/file_get_contents type of situations.
Alright, dumb me, it really was due the partial content. Chrome didn't really like the idea of taking the whole file at once (and caching it like FF), so to enable seeking and whatnot it needed Accept-Ranges: bytes, a bit of incoming header parsing to get the requested byte range and couple of lines which return that aforementioned byte range with correct partial content status (206), not just "bytes 0-(filesize-1)/filesize" (whole file), even if the first request asks for it (bytes=0-).
Hope this helps someone else as well.

Streaming file to Android Client

I want to download a file with android and so i stream the file with php with the following function
function stream($fileName,$fileSize,$fName){
//header("Pragma:public");
//header("Expires:0");
//header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
//header("Cache-Control:private",false);
header("Content-Disposition:attachment;filename=\"".$fName."\";");
header("Content-Type:application/octet-stream");
header("Content-Transfer-Encoding:binary");
header("Content-Length:".$fileSize);
readfile($fileName);
}
But the download does not work on an android device. On my computer, it isn't a problem. I already found a solution on http://www.digiblog.de/2011/04/android-and-the-download-file-headers/ but this didn't helped me here.
As mentioned in my blog posting that you cite, the success of your download on Android devices depends on multiple factors, which cannot be told by reading your code:
Your $fileName must contain an uppercase extension.
The pure URL that you call to download must contain all information needed for the download to start. No POST parameters may be involved, they will not be forwarded to the Android Download Manager and thus the download will fail. GET parameters (query string) are ok.
Also you did not obey the blog posting in all details:There is a semicolon after your Content-Disposition header which is fatal.
I also recommend to start with the Content-Type and Content-Disposition headers ONLY. When you got it working on Android, you can still add any other headers that you want to send. But do not forget to re-test on Android (multiple versions if possible) when you are complete. Android is really picky about some of those headers (or should I say "bitchy"?).
If all of this does not help you will have to provide some more information about your case. What exactly happens when you try it? What URL do you call. What are the parameters passed to your function?

IE wants to download my JSON response; other browsers work as expected

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.

How to stream a media file using PHP?

I am trying to build an application in which i have to stream the media files (audio and video) to the browser. I am reading the file through php and send the data to browser. I am using the following code.
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Sat, 26 Jul 1997 05:00:00 GMT"); // Date in the past
header("Content-Type: {$file->getMimetype()}");
header("Content-Disposition: inline; filename=".$filename.";");
header("Content-Length: ".strlen($file_content));
echo $file_content;
Every thing is working fine, except when i try to forward the video or audio, (I mean suppose current play location is 0:15 and it directly go to 1:25), media stops and when i press the play button again, it starts from the beginning.
I think the problem is with the buffering, but can't figure it out. Am i doing something wrong in header or something else is required.
Thanks.
I think you need to implement the Range header, so that the client can skip to a specific position in the file. You can probably find out what goes wrong by sniffing the request the player sends.
What you want is called "Content-Range requests"
Have a look here Resumable downloads when using PHP to send the file?
I came across this recently which may help you:
http://www.jasny.net/articles/how-i-php-x-sendfile/
Rather than passing the whole file through PHP (which eats up memory), you can use x-sendfile. This is an Apache module which allows you to run a PHP program, but pass control back to the web server to handle the actual file download once your code has done what it needs to do (authentication, etc).
It means that your PHP code doesn't have to worry about how the file is served; let the web server do what it's designed for.
Hope that helps.
Here is a good tutorial for it, you only want the PHP section but still:
http://www.devshed.com/c/a/PHP/Video-Streaming-PHP-Script-Tutorial/3/

Categories