Excel exported as garbled PK zip file to browser - php

This is driving me crazy. I'm using a PHP function to download an Excel file. This works fine in my local environment and testing server, but on the production server, instead of downloading the spreadsheet, it prints a lot of garbled text to the browser window. It looks like the "PK" code for a zip file. The result is inconsistent. I reduce the number of columns and it works. Then I added one more column and it breaks. Then the next day it works. Then I add another column and it breaks. The same function works in other areas of the app, it's just when I try to export this one file.
This is a Symfony 4.4 app running on PHP 8.1. All environments should be identical.
$filePath = $path . $fileName;
$response = new Response();
$response->headers->set('Content-Type', "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
$response->headers->set('Content-Disposition', "attachment;filename={$fileName}");
$response->headers->set('Cache-Control', "no-cache");
$response->headers->set('Expires', "0");
$response->headers->set('Content-Transfer-Encoding', "binary");
$response->headers->set('Content-Length', filesize($filePath));
$request = Request::createFromGlobals();
$response->prepare($request);
$response->setContent(readfile($filePath));
$response->sendContent();
unlink($filePath);
return $response;

I got it. Instead of trying to set all those headers manually I just used the ResponseHeaderBag. Works now.
return $this->file("export/".$fileName, $fileName, ResponseHeaderBag::DISPOSITION_INLINE);

Right off the bat, readfile() does not return the contents of the file, but rather outputs it directly so:
$response->setContent(readfile($filePath));
is definitely wrong.
Instead use file_get_contents:
$response->setContent(file_get_contents($filePath));
I would refactor the code to read the file contents and set the Content-Length according to how many bytes $fileContents is:
$fileContents = file_get_contents($filePath);
$response->headers->set('Content-Length', strlen($fileContents));
$request = Request::createFromGlobals();
$response->prepare($request);
$response->setContent($fileContents);
(strlen() always returns the number of bytes in a string, not characters).

Related

Return an in memory file in Symfony

I'm trying to return a BinaryFileResponse response in a Symfony Controller but it asks for a path or a File object. The thing is that I'm getting the file over a SOAP service, and it creates an object like this: filename + base64 encoded content + mime + extra stuff.
I don't really want to save it to disk then create the response...
Maybe I'm blinded at the moment, but is there any way to send it without creating a file in disk?
This is the action:
public function downloadAction($hash){
$document = $this->get('soap_services.document_service')->findDocument($hash);
return $this->file($SymfonyFileObjectOrPath, $fileName);
}
The $document object offers the following relevant methods: getMime, getName, getContent.
That's what I want to use to make a response without creating a File object (and the physical file that it implies).
Hello maybe you should consider to just give a Response and force the download "manually".
// Generate response
$response = new Response();
$filename = 'yourFileName.txt';
// Set headers
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', $yourMimeType );
$response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '";');
$response->headers->set('Content-length', strlen($yourContentString));
// Send headers before outputting anything
$response->sendHeaders();
$response->setContent( $yourContentString );
return $response;
Maybe something like this will do the trick.
Can you try this, maybe change it a little bit?

CODEIGNITER: Return a PDF file from controller using readfile()

In one of my CODEIGNITER projects I have following code that is working nicely:
$this->output
->set_content_type('application/pdf')
->set_output(file_get_contents($file));
To make code memory friendly I want to use the php function readfile() in place of file_get_contents() but its not working properly.
I have noted that readfile() works if I return an image but does not work with PDF.
How I can achieve this?
The documentation on readfile clearly states that it just outputs the file to the browser. It just echoes the file to stdout and returns the number of bytes read from the file. If you want to use the function, you'll have to NOT use the CodeIgniter output functions. You'll need to use the more raw, basic PHP header functions instead. Something like this:
$filepath = "/path/to/file.pdf";
// EDIT: I added some permission/file checking.
if (!file_exists($filepath)) {
throw new Exception("File $filepath does not exist");
}
if (!is_readable($filepath)) {
throw new Exception("File $filepath is not readable");
}
http_response_code(200);
header('Content-Length: '.filesize($filepath));
header("Content-Type: application/pdf");
header('Content-Disposition: attachment; filename="downloaded.pdf"'); // feel free to change the suggested filename
readfile($filepath);
exit; // this is important so that CodeIgniter doesn't parse any more output to ruin your file download
NOTE: If the permissions on $filepath are not readable to the user executing this code (your user, apache, www-data, httpd, etc.) then you might get a file is not readable error. You can also get a file does not exist error if the file itself is readable but the directory in which it exists is not. Check the permissions on the file itself and the directory in which that file resides.
Try below code if you are sure about mimetype of file
$contents = read_file($file);
$this->output
->set_status_header(200)
->set_content_type('application/pdf')
->set_output($contents)
->_display();
exit;
If you are not sure about the mimetype of file then
$this->load->helper('file');
$file = '/path/to/pdf/file';
$contents = read_file($file);
$this->output
->set_status_header(200)
->set_content_type(get_mime_by_extension($file_path))
->set_output($contents)
->_display();
Reference:- https://codeigniter.com/user_guide/helpers/file_helper.html

Symfony2 - Force file download

I'm trying to download a file when a user clicks on download link.
In Controller:
$response = new Response();
$response->headers->set('Content-type', 'application/octect-stream');
$response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename));
$response->headers->set('Content-Length', filesize($filename));
return $response;
This is opening the dialog box to save the file, but it says the file is 0 bytes.
And changing it to:
$response = new Response();
$response->headers->set('Content-type', 'application/octect-stream');
$response->headers->set('Content-Disposition', sprintf('attachment; filename="%s"', $filename));
$response->headers->set('Content-Length', filesize($filename));
$response->headers->set('Content-Transfer-Encoding', 'binary');
$response->setContent(readfile($filename));
return $response;
I get a bunch of weird characters instead of the file download dialog box.
Finally, switching the "setContent" line to:
$response->setContent(file_get_contents($filename));
It returns a PHP error:
Fatal error: Allowed memory size...
Any clues on how to achieve this?
I've done it before in PHP (wihtout MVC), but I don't know what can be missing to do it through Symfony2...
Maybe the solution is setting the memory_limit in PHP.INI, but I guess it´s not the best practice...
The most comfortable solution is
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$response = new BinaryFileResponse($file);
$response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);
return $response;
I finally solved this without X-SendFile (which is probably the best practice). Anyway, for those who can't get X-Sendfile apache module to work (shared hosting), here's a solution:
// Generate response
$response = new Response();
// Set headers
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', mime_content_type($filename));
$response->headers->set('Content-Disposition', 'attachment; filename="' . basename($filename) . '";');
$response->headers->set('Content-length', filesize($filename));
// Send headers before outputting anything
$response->sendHeaders();
$response->setContent(file_get_contents($filename));
return $response;
You shouldn't use PHP for downloading files because it's a task for an Apache or Nginx server. Best option is to use X-Accel-Redirect (in case of Nginx) / X-Sendfile (in case of Apache) headers for file downloading.
Following action snippet can be used with configured Nginx to download files from Symfony2:
return new Response('', 200, array('X-Accel-Redirect' => $filename));
UPD1: Code for apache with configured mod_xsendfile:
return new Response('', 200, array(
'X-Sendfile' => $filename,
'Content-type' => 'application/octet-stream',
'Content-Disposition' => sprintf('attachment; filename="%s"', $filename))
);
As of Symfony 3.2 you can use the file() controller helper which is a shortcut for creating a BinaryFileResponse as mentioned in a previous answer:
public function fileAction()
{
// send the file contents and force the browser to download it
return $this->file('/path/to/some_file.pdf');
}
Don't know if it can help but it's application/octet-stream not application/octect-stream
+1 for alexander response.
But if you can't use X-Sendfile, you should use the BinaryFileResponse added in the 2.2: http://symfony.com/doc/current/components/http_foundation/introduction.html#serving-files
In my project the result is
$response = new \Symfony\Component\HttpFoundation\BinaryFileResponse($dir .DIRECTORY_SEPARATOR. $zipName);
$d = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$zipName
);
$response->headers->set('Content-Disposition', $d);
return $response;
For those who don't have the option of setting headers:
The download attribute may help depending on which browsers you need to support:
<a href="{file url}" download>
or
<a href="{file url}" download="{a different file name}">
This is not supported in all legacy browsers. See this page for browser support:
https://www.w3schools.com/tags/att_a_download.asp

symfony download a file

i have a project with symfony 1.4, in an action a export a mysql table to a .csv file and next download it from the server, i'm using Wampserver for local developement, and everything is working ok, but, when i uploaded to the production server, now, if you try to export the data, the .csv file is automatically open in the browser without asking if you want to saved or open it, the code i use after creating the .csv is this
$this->setLayout(false);
sfConfig::set('sf_web_debug', false);
$this->getResponse()->setHttpHeader('Content-Type', 'text/csv');
$this->getResponse()->setHttpHeader('Content-Disposition', 'attachment; filename="file.csv"');
$this->getResponse()->setHttpHeader('Pragma', 'no-cache');
$this->getResponse()->setHttpHeader('Expires', '0');
readfile(sfConfig::get('sf_upload_dir') . "/export/file.csv");
return sfView::NONE;
i repeat, in my local server this work ok, the browser give me the options of open or save, now, is this a problem of the code, a setting in the server or anything else??
the file is saved with utf-8 encode, is this the problem??
thanks
Thanks to the comments, i could get it work, what i did was change the content-type as #j0k suggested, and redirect to the file instead of using the readfile function
$this->getResponse()->setHttpHeader('Content-Type', 'application/csv');
$this->redirect('/uploads/export/file.csv');
now it is working
thanks
Another way of doing it without redirecting is as follows:
$this->getResponse()->clearHttpHeaders();
$this->getResponse()->setHttpHeader('Content-Description','File Transfer');
$this->getResponse()->setHttpHeader('Content-Type', 'application/csv');
$this->getResponse()->setHttpHeader('Content-Disposition','attachment;filename='.$file_name);
$this->getResponse()->setHttpHeader('Pragma', '');
$this->getResponse()->setHttpHeader('Cache-Control', '');
$this->getResponse()->sendHttpHeaders();
$this->renderText($fwrite('php://output',$stuff));
Where you save the file or write the file to 'php://output'.

Creating a temp .htm file in PHP and send it to the user?

Is there a simple way to create an empty temp .htm file on the server and send it to the user as download? I don't need to really create the file with fopen(), it's enough to just create it temporarily and send it to the user.
I need that for a website authentication. I found there is tmpfile() function within PHP, but it doesn't quite work for me yet. I am working on a Symfony project, maybe there are also a header function I don't know of.
All I need is:
$token = '12345';
$filename = $token . '.htm';
createatmpfile($filename);
header(send .htm file as download to the user);
Not sure how that works, but maybe my pseudocode above explains what I am looking for ..
EDITED:
That's how it works for Symfony2 (thanks Grad):
use Symfony\Component\HttpFoundation\Response;
Controller method:
public function generateAction() {
$response = new Response();
$response->headers->set('Content-Disposition', 'attachment; filename="'.$token.'.htm"');
return $response;
}
In plain PHP you could use to following line to force the user to download the response
header('Content-Disposition: attachment; filename="filename.htm"');
In Symfony you can use:
$this->getResponse()->setHttpHeader('Content-Disposition', 'attachment; filename="filename.htm"');
With this header set you can either just execute your controller (and let the view fill in the response), or use $contents = file_get_contents($path) together with return $this->renderText($contents).
But depending on what you're trying to achieve: you don't have to create a tempfile to force the to download it.

Categories