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.
Related
I'm having problems with serving CSS files from PHP. For test I'm just loading content from existing CSS file into PHP variable and than echo it. I want to set headers to allow caching of file until it was modified.
PHP code
$css_file_path = "path-to-existing-css-file";
$file_content = file_get_contents ($css_file_path);
$fmtime = date ("r", filemtime ($css_file_path));
header ("Content-type: text/css");
header ("X-Content-Type-Options: nosniff");
header ("Last-Modified: " . $fmtime);
die ($file_contents);
This is done by simple PHP code as shown above. For some reason it's never cached (tested in latest Firefox only).
I have tried to put this line before die() function to test it.
echo date ("r", time());
And it gets updated all the time. I'm such a caching noob, I admit it, so all I want to do is to make file being cached until new modification arrives.
So far, I have read tones of different posts here and web-wide and mostly found nothing or very poor information on this subject.
What am I missing and is it possible to achieve at all?
To start with
I want to do is to make file being cached until new modification arrives
The only way a browser can know there is a new modification, is by asking the server whether their cached version is still valid.
This is done as followed:
1. Browser requests /style.css
GET /style.css
2. Server sends to browser
HTTP/1.1 200 OK
Last-Modified: Wed 2 Aug 2017 21:28:00 GMT
Cache-Control: must-revalidate, max-age=31536000
... file-contents ...
// 31536000 is about 1 year
3. Next time browser wants that file it sends
GET /style.css
If-Modified-Since: Wed 2 Aug 2017 21:28:00 GMT
4a. Your server can read that header, and verify if the file isn't modified after
the given date. If it isn't, you can reply with a single:
HTTP/1.1 304 Not Modified
... without sending the contents again
4b. If your file was hower modified after Aug 2, you should sent a response simalar
as in step 2
So in code, step 2, add the Cache-Control-header:
header('Cache-Control: must-revalidate, max-age=31536000');
And step 4a, act to the If-Modified-Since request-header:
$css_file_path = "path-to-existing-css-file";
$fmtimestamp = filemtime ($css_file_path);
// Check header set by browser
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $fmtimestamp <= strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified');
die(); // We're done here
}
// Otherwise continue as ussualy
$file_content = file_get_contents ($css_file_path);
Alternative solution, without using the If-Modified-Since, but it depends on the situation if this is usable for you:
// Somewhere in your HTML
<link rel="stylesheet" href="/style.css?version=<?php echo filemtime($pathToStyle.css) ?>" />
When your file changes, the link changes and the browser would see it as a new file. In that case you can leave the must-revalidate-part out of the Cache-Control-header and the browser won't reload the style.css unless the max-age expires or cache is cleaned up.
I have a PHP file serving image files. I looks like this:
$dir = 'directory/containing/files';
$file = getFileInfoFromDatabase(filter_input(INPUT_GET, 'article'));
header("Content-Type: $file->type; name=\"$file->filename\"");
header("Name: \"$file->filename\"");
header("Content-Disposition: inline; filename=\"$file->filename\"");
header("Content-Length: $file->size");
die(readfile($dir.'/'.$file->id));
An url calling this file looks like this: www.example.com/article/image.php?article=4. 4 is the id of the article, which is sent to the database to get information stored there about the image file associated with that article.
This works well. When loading the url, I get the image and some response headers looking like this:
Cache-Control:private, max-age=10800, pre-check=10800
Connection:Keep-Alive
Content-Disposition:inline; filename="filename.jpg"
Content-Length:37308
Content-Type:image/jpeg; name="filename.jpg"
Date:Wed, 30 Sep 2015 08:42:26 GMT
Keep-Alive:timeout=5, max=75
Last-Modified:Wed, 30 Sep 2015 08:32:19 GMT
Name:"filename.jpg"
Server:Apache
The next time I try loading the url, the request contains the following header:
If-Modified-Since:Wed, 30 Sep 2015 08:32:19 GMT
And I get a 304 Not Modified response, which is fine.
However, if I update the database to point to a different file, I get the same 304 Not Modified response! I have to modify the php file serving the image file to get the new version of the image file.
How can I solve this? Do I have to turn off caching for this file? Or can I change the Last-Modified header to be that of the image file instead of the php file in some way?
If you know the modification time of the changed file, you can change the last_modified header if the request If-Modified-Since is older.
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.
The Problem:
TCPDF & mPDF error: Some data has already been output to browser, can't send PDF file
I gave up on trying to fix the error with TCPDF and installed mPDF only to get the same error when attempting to render the document to the browser. I can save the document just fine and have it displayed in the browser upon retrieval.
Additionally, this error only presented itself after switching from my dev server to my host server. Works fine on DEV server (DEV server = WAMPSERVER, PROD server = Hostgator Linux).
Troubleshooting:
I've read the many volumes of other discussions around the internet regarding this problem and I can find no white space related issue. I have condensed the request to the following:
<?php
ob_start();
$html = "Hello World";
include("../mpdf.php");
$mpdf=new mPDF();
$mpdf->WriteHTML($html);
$mpdf->Output();
ob_end_clean();
?>
Tried the same concept with TCPDF using ob_clean() method before writeHtml. Same error in all cases (I can assure everyone this is no white space related issue - I even viewed the file in hex to make sure there were no odd characters being inserted by the editor).
Possible Clue:
I was finally able to get a clue as to what's going on when I moved the entire mPDF library and classes and folders to the public_html folder, rather than from inside my application folder (a symfony project). Under this scenario, when I pointed my browser to the example pages, it rendered just fine with no errors at all (and it was super fast btw). So, I know it works, and I know there is no white-space related issue, or any other related issue, regarding the code or installation (on the mPDF/TCPDF side of things). Which leads me to believe either symfony is inserting headers of some sort (which I tried removing using: clearHttpHeaders() ), or there is a PHP INI or CONFIG setting I am missing somewhere on the PROD server.
Does anyone have ANY idea of what's going on here??
Update: stream dump:
Request URL:http://www.example.com/mpdf
Request Method:GET
Status Code:200 OK
Request Headers
GET /mpdf HTTP/1.1
Host: www.example.com
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Cookie: __utma=44708724.1463191694.1383759419.1383759419.1383765151.2; __utmz=44708724.1383759419.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); PHPSESSID=9c7c802200b9d8eefe718447755add5f; __utma=1.813547483.1383767260.1385127878.1385130071.38; __utmb=1.7.10.1385130071; __utmc=1; __utmz=1.1383767260.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)
Response Headers
Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection:Keep-Alive
Content-Type:text/html
Date:Fri, 22 Nov 2013 14:59:52 GMT
Expires:Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive:timeout=5, max=75
Pragma:no-cache
Server:Apache
Transfer-Encoding:chunked
Nothing is jumping out at me... any other thoughts?
Most probably it's BOM marker, use your IDE to remove it, other hot fix can be:
<?php
$html = "Hello World";
include("../mpdf.php");
ob_clean(); // cleaning the buffer before Output()
$mpdf=new mPDF();
$mpdf->WriteHTML($html);
$mpdf->Output();
?>
I have got the same error.
Solve this using op_start(); and ob_end_clean();
PHP is an interpreted language thus each statement is executed one after another, therefore PHP tends to send HTML to browsers in chunks thus reducing performance. Using output buffering the generated HTML gets stored in a buffer or a string variable and is sent to the buffer to render after the execution of the last statement in the PHP script.
But Output Buffering is not enabled by default. In order to enable the Output Buffering one must use the ob_start() function before any echoing any HTML content in a script.
[reference credit][1]
[PHP | ob_start() Function][2]
public function gen_pdf($html, $user_id, $paper = 'A4') {
ob_start();//Enables Output Buffering
$mpdf = new mPDF('UTF-8', $paper, '', '', 15, 15, 30, 20, 15, 5);
$mpdf->mirrorMargins = 1; // Use different Odd/Even headers and footers and mirror margins
$header = '';
$footer = '';
$mpdf->SetHTMLHeader($header);
$mpdf->SetHTMLFooter($footer);
$mpdf->SetWatermarkText('Watermark', 0.1);
$mpdf->showWatermarkText = true;
$mpdf->WriteHTML($html);
$fileName = date('Y_m_d_H_i_s');
ob_end_clean();//End Output Buffering
$mpdf->Output('Example_' . $fileName . '.pdf', 'I');
}
So that it will clear all buffered output before processing mPDF.
Best Luck...
[1]: https://www.geeksforgeeks.org/php-ob_start-function/
[2]: https://www.php.net/manual/en/function.ob-start.php
It could be some warning issued from PHP before the pdf->output. The warning text is sent to the client's browser and thus the file cannot be sent.
If your warning level is not the same for DEV and PROD, that could explain the difference of behavior.
In my case, with TCPDF, I had many warnings such as "date() it is not safe to rely on the system's timezone settings...", then the error "Some data has already been output to browser, can't send PDF".
Adding the function date_default_timezone_set() in my php source code solved the warnings and the error.
I have the same issue, and add this line before $pdf->output():
error_reporting(E_ALL);
An then I found that I have BOM on some files.
And I see a Warning message sent to the browser.
Best Luck !!
Regards
May be it occurs because of in your result of HTML code have some error to output to create the TCPDF ...
OR
If above is not work try set the Charset as UTF-8 in class file of TCPDF may it solve your issue...
Because this type of error was happening in my project before one week ago ...
Remove any file you would have included at the start of the page. In my case it was a file that was connecting with database. It worked for me. (Tip from #Nicolas400 )
Try using ob_clean(); before include("../mpdf.php");.
I have got the same error.
Data has already been sent to output, unable to output PDF file
This means before creating pdf with mPDF some data is stored in the buffer which is sended to the browser. Therefore it is unable to create PDF.
Just do this..
Add this below php built-in function at the first line of your page were you are preparing data for pdf.
op_start();
And add this below php built-in function before mPDF code (before where you are calling mpdf)
ob_end_flush();
require_once __DIR__ . '/vendor/autoload.php';
$mpdf = new \Mpdf\Mpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
So that it will clear all buffer output before processing mPDF.
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.