The snippet reads an ouput stream in a new file CSV. That part works. I can open the file from the server and it looks perfect. The problem is with the file which downloads to my hard drive through the browser. It will not open and read properly in spreadsheet software or Excel on a Windows machine:
$NewFile = fopen($FileName,"w");
fwrite($NewFile, $output);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$FileName.'"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . filesize($NewFile));
ob_clean();
flush();
readfile($NewFile);
The content of the CSV looks ��ࡱ� like when I open it from the browser download but looks perfect when I open it directly on the server or download the stored file with FTP.
The browser sees the application/octet-stream as a binary type. You need a text/plain content type:
header('Content-Type: text/plain');
// Or:
header('Content-Type: text/csv');
// Or:
header('Content-Type: application/csv');
The Content-Transfer-Encoding header should be unnecessary if you have the Content-Type correctly set, and indeed it is probably misleading the browser into thinking it has received a binary file as well:
// No need, possibly harmful. Remove it...
// header('Content-Transfer-Encoding: binary');
Update:
I see another problem. You are setting the Content-Length not to the size of the file, but rather to the file handle opened by fopen(), which mis-informs the browser of the number of bytes to expect. filesize() takes a string filename as its argument, not a file handle. You will probably need to close the handle with fclose($NewFile) before calling filesize().
// Instead of:
header('Content-Length: ' . filesize($NewFile));
// You mean to use $FileName
// close the file handle first...
fclose($NewFile);
header('Content-Length: ' . filesize($FileName));
Related
I have written some PHP code that downloads a PDF file from a MySQL database on a server. The code works great and I can view/download/open the PDF file from various browsers. However, when I download the file and try to open up via Adobe Reader I get a message that says the file could not be opened because "because it is either not a supported file type or because the file has been damaged (for example, it was sent as an email attachment and wasn't correctly decoded)"
I am sure it is something dumb that I am forgetting to do in my code. Below is the PHP code that I am using:
// Downloads files
if (isset($_GET['scenario_id'])) {
$scenario_id = $_GET['scenario_id'];
// fetch file to download from database
$sql = "SELECT * FROM market_plans_pdfs WHERE scenario_id=$scenario_id";
$result = mysqli_query($connect, $sql);
$file = mysqli_fetch_assoc($result);
$pdf_file=$file['pdf_file'];
$filename=$file['scenario_name'];
if (isset($filename)) {
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="'.basename($filename).'"');//set content-disposition as "inline" as opposed to "attachment" so that it opens first in the browser
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
#readfile("data:application/pdf;base64,$pdf_file");
}
}
Any help would be appreciated as I have tried various solutions online with no success.
When you are changing some headers you have to do it before any output, because header must be sent at the beginning, for example:
This is invalid:
echo 'Test';
header('Content-Description: File Transfer');
Thats not your case, but you do similar mistake, on the other end.
When you are printing a file data, for example, as you do in your question, there is no function to tell that "your data ends here", just whole output is taken as file.
If you can't separate your file transfer from other output there is simple solution, try it out:
if (isset($filename)) {
header('Content-Description: File Transfer');
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="'.basename($filename).'"');//set content-disposition as "inline" as opposed to "attachment" so that it opens first in the browser
header('Content-Transfer-Encoding: binary');
header('Accept-Ranges: bytes');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
echo base64_decode($pdf_file);
exit(); // nothing else will happen, no website which is shown otherwise
}
I may be missing something exceptionally obvious here, but I'm using yii2-flysystem along with Dropbox to read and write files.
I can upload and write them to Dropbox with no problem but then, when reading like this:
$file = Yii::$app->dropboxFs->read($fn);
..all that gives me is a string (/tmp/phpQkg8mJ).
How do I actually force the download of the file that I'm reading? I'm not sure what that temporary file location actually relates to.
Try function readfile().
According to example, your code should be looks something like this:
if (file_exists($file)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.basename($file).'"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile($file);
exit;
}
I'm searching for a possibility to download a file with PHP and keep its original name.
This should work for direct links to a file ("foo.bar/test.zip") and for downloads who are proxied through PHP files ("foo.bar/download.php?fileid=12345" <- this would open a save-as prompt with test.zip as given name).
Is there a possibility in PHP that I could use to do this?
I tried different possibilities (fopen, cURL, file_get_content, etc.). But all possibilities I've found required me to manually set the new filename in order to save it locally.
Even better would be if I could proxy the remote file through my script (with it's original name!) to the client who is calling the script, without saving it temporarily.
I found the following snippet which accomplishes this exact thing
$fp = fopen($_POST["url"], 'rb');
foreach (get_headers($_POST["url"]) as $header)
{
header($header);
}
fpassthru($fp);
The file I get to download is of the right size but has the wrong name (it has the name of the called PHP script, in my case downloader.php instead of e.g. test.zip).
Any idea how to accomplish this would be very appreciated.
Thanks in advance!
May be something like this will help:
$quoted = sprintf('"%s"', addcslashes(basename($file), '"\\'));
$size = filesize($file);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . $quoted);
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $size);
Just replace $file with the file you want to be downloaded.
I've got a problem that has risen many times on SO, but I can't seem to find the solution to mine! I'm trying to deliver a pdf file to the client without it opening in the browser, the file downloads but it is corrupt when I open it and is missing quite a few bytes from the original file. I've tried several such methods for downloading the file but I'll just show you the latest I've used and hopefully get some feedback.
I have also opened the downloaded PDF in a text editor and there are no php errors at the top of it that I can see!
I'm also aware that readfile() is much quicker but for testing purposes I am desperate to get anything working so I used the while(!feof()) approach!
Anyway enough rambling, heres the code (taken from why my downloaded file is alwayes damaged or corrupted?):
$file = __DIR__ . '/reports/somepdf.pdf';
$basename = basename($file);
$length = sprintf("%u", filesize($file));
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $basename . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $length);
ob_clean();
set_time_limit(0);
readfile($file);
Also to note was the difference in file size:
Original: 351,873 bytes
Downloaded: 329,163 bytes
Make sure you're not running any compression output buffering handlers, such as ob_gzhandler. I had a similar case and I had to disable output buffering for this to work properly
You are using the the ob_gzhandler on the output buffer.
It works by gzencoding chunks of output. The output then is a stream of the encoded chunks.
Each chunk needs to get some bytes to get encoded, so the output is a little bit buffered until enough bytes are available.
However at the end of your script you discard the remaining buffer instead of flushing it.
Use ob_end_flush() instead of ob_clean() and the file gets through fully and not corrupted.
You are to use the transfer encoding of ob_gzhandler with file-uploads not having any problems when you don't destroy the output-buffer before it could have done it's work.
This is also the same if any other output buffering that works chunked would have been
enabled.
Example code:
$file = __DIR__ . '/somepdf.pdf';
$basename = basename($file);
$length = sprintf("%u", filesize($file));
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $basename . '"');
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $length);
ob_end_flush(); // <--- instead of ob_clean()
set_time_limit(0);
readfile($file);
return;
(FYI: actually even the ob_end_flush(); is not necessary, the important part is not to just kick the output-buffer before it could have done it's work)
I fought with using content-disposition for pushing a PDF download for two days before finding a solution to my problem. My PDF files were also smaller in size and corrupt - however, I could open them in Windows Preview - just not Adobe. After much troubleshooting, I discovered that Adobe expects the %PDF in the first 1024 bytes of the file. I was doing all my file type checks in my php code before creating the headers. I took out the majority of code before the headers and my PDF file was fixed.
You might not be setting it up the same way I did, but it might be the same problem:
http://helpx.adobe.com/acrobat/kb/pdf-error-1015-11001-update.html
I have an excel file that i want a user to be able to download from my server. I have looked at a lot of questions on here but i cannot find a way to correctly download the file w/o corruption. I am assuming it is the headers but i haven't had a working combination of them yet. This is what i have right now and in the corrupt file that i receive i can see the column names of the spreadsheet i want but its all messed up.
$filename = '/var/www/web1/web/public/temporary/Spreadsheet.xls';
header("Content-type: application/octet-stream");
header("Content-type: application/vnd-ms-excel");
header("Content-Disposition: attachment; filename=ExcelFile.xls;");
header("Pragma: no-cache");
header("Expires: 0");
readfile($filename);
edit: Solution I forgot to add that i was using Zend and it was corrupting the files when trying to use native php methods. My finsihed code was to place a link to another action in my controller and have the files download from there
public function downloadAction(){
$file = '/var/www/web1/web/public/temporary/Spreadsheet.xls';
header('Content-Type: application/vnd.ms-excel');
header('Content-Disposition: attachment; filename="Spreadsheet.xls"');
readfile($file);
// disable the view ... and perhaps the layout
$this->view->layout()->disableLayout();
$this->_helper->viewRenderer->setNoRender(true);
}
try doing it this way
ob_get_clean();
echo file_get_contents($filename);
ob_end_flush();
For one, only specify Content-Type once. You can use the excel-specific header but the generic application/octet-stream may be a safer bet just to get it working (the real difference will be what the browser shows the user with regards to "what would you like to open this file with", but basic browsers can rely on the extension as well)
Also, make sure you specify Content-Length and dump the size (in bytes) of the file you're outputting. The browser needs to know how big the file is and how much content it's expecting to receive (so it doesn't stop in the middle or a hiccup doesn't interrupt the file download).
So, the entire file should consist of:
<?php
$filename = '/var/www/web1/web/public/temporary/Spreadsheet.xls';
header("Content-Disposition: attachment; filename=ExcelFile.xls;");
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($filename));
header("Pragma: no-cache");
header("Expires: 0");
#readfile($filename);
$file_name = "file.xlsx";
// first, get MIME information from the file
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file_name);
finfo_close($finfo);
// send header information to browser
header('Content-Type: '.$mime);
header('Content-Disposition: attachment; filename="download_file_name.xlsx"');
header('Content-Length: ' . filesize($file_name));
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
//stream file
ob_get_clean();
echo file_get_contents($file_name);
ob_end_flush();