I have created a function for my Symfony application based on this answer: https://stackoverflow.com/a/13025363/1749653
The function is this:
public function getForceDownloadResponse($file_path, $file_name){
$file_info = finfo_open(FILEINFO_MIME_TYPE);
$mine_type = finfo_file($file_info, $file_path.$file_name);
finfo_close($file_info);
$response = new Response();
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', $mine_type);
$response->headers->set('Content-Disposition', 'attachment; filename="' . $file_name . '"');
$response->headers->set('Content-length', filesize($file_path.$file_name));
$response->sendHeaders();
$response->setContent(readfile($file_path.$file_name));
return $response;
}
And that all worked out quite well... until someone tried downloading a text file with it.
For some reason, all text files are downloaded with the wrong filename. They all follows this pattern:
Actual_File_Name-,attachment
so, for example: new_text.txt-,attachment
etc
After some investigation I only found one irregularity with text files as a whole. Somehow the response header fields are dublicated.
Where binary files will output something like this:
Cache-Control:private
Connection:close
Content-Disposition:attachment; filename="Jellyfish.jpg"
Content-Length:775702
Content-Type:image/jpeg
Date:Mon, 10 Nov 2014 09:14:41 GMT
Server:Apache/2.4.7 (Win32) OpenSSL/1.0.1e PHP/5.5.6
Any text file will look like this:
Cache-Control:private
Cache-Control:private
Connection:Keep-Alive
Content-Disposition:attachment; filename="full.txt"
Content-Disposition:attachment; filename="full.txt"
Content-Length:15
Content-Type:text/plain; charset=UTF-8
Date:Mon, 10 Nov 2014 09:06:00 GMT
Keep-Alive:timeout=5, max=97
Server:Apache/2.4.7 (Win32) OpenSSL/1.0.1e PHP/5.5.6
I suspect the uninteded behavior is rooted somewhere in whatever causes that Content Header behavior. But I can't for the life of me figure out how those come about.
So, if anyone knows more about how these get created, what might cause them to behave as this or even how to solve the problem outright, any help would be greatly appreciated.
Finally figured it out. Posting it here in case someone, somewhere has a similar problem and finds it useful:
So. It turns out that the problematic line was this:
$response-sendHeaders()
For binary files, this line is critical in making it work. Text files don't need it though and will in fact break like seen above. I have not found out exactly 'why' it behaves like this. But the solution was to simply check for whether or not the requested file is a text file or not. (with a build in exception for .html files, but thats more of a optional feature / quirk of the project and can thus be safely ignored)
This is what the function looks like now and its working fine:
public function getForceDownloadResponse($file_path, $file_name){
$file_info = finfo_open(FILEINFO_MIME_TYPE);
$mime_type = finfo_file($file_info, $file_path.$file_name);
$text = (substr(finfo_file($file_info, $file_path.$file_name), 0, 4) == 'text') ? 1 : 0;
finfo_close($file_info);
$response = new Response();
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', $mime_type);
$response->headers->set('Content-Disposition', 'attachment; filename="' . $file_name . '"');
$response->headers->set('Content-length', filesize($file_path.$file_name));
if(!$text || $mime_type == 'text/html'){
$response->sendHeaders();
}
$response->setContent(readfile($file_path.$file_name));
return $response;
}
Since I still don't know what actually caused the different behaviors its not as safe and solid as I'd like it to be, but it does the job just fine so far.
Related
I have followed Cakes Cookbook to send files (http://book.cakephp.org/3.0/en/controllers/request-response.html#sending-files) but I've been facing a weird problem.
PDF's, DOC's and other binaries work just fine. But when I try to download/show an image (JPG or PNG) the file corrupts itself.
The downloaded file is not recognized as an image. It has the exactly same size of the original one, but when I diff it they are completly different.
I couldn't find anything like at the internet related to cake so I hope you can help me!
The below code is my download action
public function arquivo($id) {
$file = $this->Arquivos->get( $id );
$this->response->file($file['filename'], ['download' => true]);
// Return response object to prevent controller from trying to render
// a view.
return $this->response;
}
Response headers:
Accept-Ranges:bytes
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:Keep-Alive
Content-Length:121000
Content-Type:image/jpeg
Date:Thu, 24 Nov 2016 16:17:49 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive:timeout=5, max=100
Pragma:no-cache
Server:Apache/2.4.10 (Ubuntu)
Check your file path and MIME Types is correct.
public function arquivo($id) {
$file = $this->Arquivos->get( $id );
$this->response->file(
$file['filename'], #Check $file['filename'] is full path of your download file
[
'download' => true,
'name' => 'Your_Download_File_Name_Here'
]
);
return $this->response;
}
Example:
Your $file['filename'] variable should be /path/to/your/file.jpg
Also check the correct MIME TYPES from CakePHP 3 Sending or Downloading Files
I've got the same problem with downloading jpg and xlsx files.
My code is quite simple:
public function download() {
$response = $this->response->withFile($template_file_path, ['download' => true]);
return $response;
}
Compared with 2 files (original file AND the downloaded file which is corrupted) with a binary editor, there is 1 byte difference. I don't know why but the 0x20 was added at the top of the file.
I've got the same problem and I was able to solve it. The problem was caused as soon as a recent change on my source code for another controller was made so I started searching for spaces after or before the opening php tag<?php <?. Also I deleted the closures ?> for all of my php files. I know it is hard to detect where the problem comes from but make sure to check the last files you changed. i.e controllers, behaviors, models etc.
I hope it can solve your problem.
In our app, we direct the user to download a .mobileconfig. Originally the problem was Safari just displayed the XML rather than downloaded that, but we got around this with a PHP script (below).
<?php
$file = "http://example.com/myProfile.mobileconfig";
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename=\"$file\"");
readfile ($file);
?>
However we commonly see it where the .mobileconfig is downloaded and automatically it brings up the 'do you wish to install page'. How is that done? Are we missing something in Content-Type?
Thanks,
Sam
Experienced same problem, the solution is below.
Just set the content type, works like a charm ;)
public function controllerMethod() {
// $config contains generated XML file as string.
$config = $this->generateConfig('apple', Input::all());
// Make response -> attach $config to response
$response = Response::make($config);
// Set headers
$response->headers->set('Content-Type', 'application/x-apple-aspen-config');
$response->headers->set('Content-Disposition', "attachment; filename='mail.mobileconfig'");
return $response;
}
I use Symfony2 Framework and use the following code to export an xml file:
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'application/xml');
$response->headers->set('Content-Description', 'Submissions Export');
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename .'"');
$response->headers->set('Content-Transfer-Encoding', 'binary');
$response->headers->set('Pragma', 'no-cache');
$response->headers->set('Expires', '0');
I does not matter whether I take "Content-Transfer-Encoding" or "Pragma" or all of them except "Content-Type" and "Content-Disposition" away. The result in Chrome is always: "filename-, attachment" (without the ").
In Firefox this works fine.
So for instance, if I have a file called home.xml Firefox will download home.xml, whereas Chrome will give me home.xml-, attachment.
I know this is a pretty old discussion, but if anyone gets here looking for help with this, I determined that chrome seems to want a semicolon at the end of the filename and then it's happy. As in (VBScript):
Response.AddHeader "Content-Disposition", "attachment;filename=Something.xls;"
That fixed it for me.
-Dave
I had the same problem in ZEND because I found a lot of non working stuff I wanted to add my anwser to this post. Since it took me a while to solve it thought more users and developers would wanted to know how I solved it. I add this just before my fopen call.
header('Content-Disposition: attachment; filename='.time().'.csv');
Solved the issue for me. Before I used a contextswitched what was buggy but now it works as a charm. Do not forget to remove previous content disposition headers hence you might get a double header error.
I have a question, how can I do something like this:
header("Content-Disposition: inline; filename=result.pdf");
header("Content-type: application/x-pdf");
With Zend Framework, I have tried:
$this->getResponse()
->setHeader('Content-Disposition:inline', ' filename=result.pdf')
->setHeader('Content-type', 'application/x-pdf');
But doesn't work correctly.
Your statement to set the response headers is slightly malformed:
$this->getResponse()
->setHeader('Content-Disposition', 'inline; filename=result.pdf')
->setHeader('Content-type', 'application/x-pdf');
The above should work - please note the difference in the Content-Disposition-header.
By the way... When you want to force a download box (instead of loading the document in the browser) you should use the Content-Disposition attachment.
$this->getResponse()
->setHeader('Content-Disposition', 'attachment; filename=result.pdf')
->setHeader('Content-type', 'application/x-pdf');
Depending on the browser it may be possible that you also have to set the Content-Length or change the Content-type to a combination (multiple headers) of one or more of application/force-download, application/octet-stream and/or application/download. And as I wrote in the comment sometimes caching headers may interfere with your download. Check to see which caching-headers are sent.
Late to the table, I can recommend this action helper as a simple, reusable component for sending files or in memory data to the browser.
Has options for caching, disposition and can utilise Apache Sendfile
My guess is that you're doing something like:
$this->getResponse()
->setHeader('Content-Disposition:inline', ' filename=result.pdf')
->setHeader('Content-type', 'application/x-pdf');
fpassthru($filename);
exit();
or something.
The response here will never be rendered (which renders the headers). The response is rendered during post-action printing, usually.
You will have to directly set the headers (as you noted in the non-oo code), or use $this->getResponse()->sendHeaders() directly.
I had a header set.
It was not set, but ADDED.
So I had a Content-Type of text/html and also application/pdf.
Flagging the Content-Type with TRUE made the download possible in IOS and other devices which showed only cryptic symbols after the download or an error:
->setHeader('Content-type', 'application/x-pdf', true);
setHeader($name, $value, $replace = false)
from:
https://framework.zend.com/manual/1.12/de/zend.controller.response.html
Solved
$this->getResponse()
->setHeader('Content-Disposition:inline', ';filename=result.pdf')
->setHeader('Content-Type', 'application/x-pdf');
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