I've a strange behavior with a simple PHP code. When I try to force the download or print out the image using the correct content-type, the output file is corrupted.
Seems that the webserver (apache) adds two bytes (0x20 and 0x0A) at the begin of the file.
This is the code:
$file = "image.png";
$image = file_get_contents($file);
// Test
file_put_contents("test.png", $image);
// Download
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
echo $image;
I use the same code on other websites hosted on the same server without problems.
The problem is only on download only because test.png works properly. The MD5 checksum of text.png and the original image are equals.
This is the hex code of test.png.
And this is the hex code of the corrupted file after download:
As you can see, there are 2 extra bytes at the begin. If I remove them, the file returns to work properly.
I attach the screen of Wireshark (as you can see is not a browser issue):
How can I fix it?
The server is Ubuntu 16.04 with PHP-5.6 (yes I done the downgrade from 7.0 to 5.6 for compatibility issues with roundcube)
UPDATE 1: I'm trying to find if somewhere in the file there is a space + newline
UPDATE 2:
First of all: thanks.
The code is part of a Wordpress plugin and the download is called using the AJAX system. I wrote a simple plugin test:
<?php
/*
Plugin Name: Test
Plugin URI: http://www.google.com
Description: Test
Author: Anon
Version: 4.0
*/
function downlod_test() {
echo "test";
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=prova.html');
die();
}
function iopman_shared_download_doc_ajax() {
downlod_test();
}
add_action('wp_ajax_frontend_download_doc', 'iopman_shared_download_doc_ajax');
//downlod_test();
?>
If I call downlod_test with /wp-admin/admin-ajax.php?action=frontend_download_doc it adds the 2 extra bytes. If I call it directly (by removing the comments), it works.
So the problem now is: how to strip out these bytes that wordpress adds?
$file = "image.png";
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename=' . basename($file));
header("Content-Encoding: gzip");
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Length: " . filesize($file));
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
ob_get_clean();
readfile($file);
exit;
To help you find that unwanted whitespace you can track loaded files with get_included_files(). Additionally, a backtrace could also shred some light on what your script does.
In many cases, it'll come from closing PHP tags at the end of the file. Since they're optional it's recommended to just not use them.
Once you locate the file where that white space is, you only need to load in your favourite text editor and remove them (you might need to enable your editor's Show hidden chars feature).
P.S. I understand that's probably simplified code to illustrate the issue but you may want to give readfile() a try.
Related
I have a file download code using php and my code at download page is follows.
if (file_exists($strDownload)) {
//get the file content
$strFile = file_get_contents($strDownload);
//set the headers to force a download
header("Content-type: application/force-download");
header("Content-Disposition: attachment; filename=\"" . str_replace(" ", "_", $arrCheck['file_name']) . "\"");
//echo the file to the user
echo $strFile;
//update the DB to say this file has been downloaded
mysql_query("xxxxxxxx");
exit;
}
Where the function file_exists() passed with valid check and my $strDownload variable will be something like /home/public_html/uploads/myfile.zip which is located in server folder. But when I trying to download the file instead of downloading, the page displays the full encrypted source of the file. How can I make it downloadable?
EDIT: for the information, myself trying to use this bit of code inside the wordpress system and my file path will be something like http://example.com/wp-content/uploads/2016/02/myfile.zip. Also in the above mentioned code myself checking the file_exists() condition for the server path which is already mentioned above and it returns 1 as desired.
Try this
if (file_exists($file))
{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
exit;
}
It is solved by using the above bit of codes at beginning of php page. Ie, before declaring the famous wordpress tag get_header();
If we use the above code after get_header(); tag of wordpress, it results in the opening of page first and hence it writes the source of the file in the page instead of downloading since the meta tags are already set.
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
Here is my problem. I am trying to download a file using header. Here is my code:
$content_type = mime_content_type('uploads/MyBBIntegrator_v1.3.1.zip');
$file = 'uploads/MyBBIntegrator_v1.3.1.zip';
header("Cache-Control: public");
header('Content-type: application/octet-stream');
header("Content-Description: File Transfer");
header('Content-Disposition: attachment; filename="MyBBIntegrator_v1.3.1.zip');
header("Content-Transfer-Encoding: binary");
header('Expires: 0');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
readfile('uploads/MyBBIntegrator_v1.3.1.zip');
However, the only result of this is that the page displays the contents of the file (it is a text file) or a string of strange symbols if the file is image/zip/exe etc
What should I do to solve this problem?
First of all, mime_content_type() is deprecated, you should try another method to fetch the MIME value.
I have checked your code and it works fine on my server, and it works fine for me. You should check for INI directives which might block the download. Try a fresh install server.
Also, there should be no output generated by the script before the snippet you put into your question.
When I use this code to download this image (only used for testing purposes), I open the downloaded image, and all it gives me is an error. i tried it in chrome. opening it with windows photo viewer, it says that it can't display the picture because it is empty???
here is the code:
<?PHP
// Define the path to file
$file = 'http://www.media.lonelyplanet.com/lpi/12553/12553-11/469x264.jpg';
if(!file)
{
// File doesn't exist, output error
die('file not found');
}
else
{
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($file));
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($file));
ob_clean();
flush();
readfile($file);
exit;
}
?>
I've had a chance to work it out. Your problem is two-fold.
First, remove the www. from the url.
Second, remove the call to filesize($file) which is throwing an error because PHP doesn't know the size of the file before it downloads the file. (really, just remove the whole line)
Removing these two things, I was successful.
Replace ob_clean() with ob_end_clean()
You're still buffering, so none of the image contents get to the browser.
If your intention is just the download the file from a third party on click of a link, you could use the new property download in the anchor tag.
The code will look something like
<a download href="path/to/the/download/file"> Clicking on this link will force download the file</a>
It works on firefox and chrome latest version. Should I mention that I didn't check it in IE? :P
Replace:
ob_clean();
flush();
readfile($file);
With:
echo file_get_contents($file);
This one should be easy, I think. I have a paginated image gallery, and under each image is a small link that says "Download Comp". This should allow people to quickly download the .jpg file (with a PHP generated watermark) to their computer.
Now, I know I can just link straight to the .jpg file, but that requires the user to have the image open in a new window, right click, Save As..., etc. Instead, I want the "Download Comp" link to initiate the download of the file immediately.
PHP.net seemed to suggest using readfile(), so each "Download Comp" link is being echoed as "?download=true&g={$gallery}&i={$image}".
Then at the top of the page I catch to see if the $_GET['download'] var isset, and if so, I run the following code:
if(isset($_GET['download'])) {
$gallery = $_GET['g'];
$image = $_GET['i'];
$file = "../watermark.php?src={$gallery}/images/{$image}";
header('Content-Description: File Transfer');
header('Content-Type: application/jpeg');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: public');
header('Pragma: public');
header('Content-Length: ' . filesize($file));
ob_clean();
flush();
readfile($file);
}
The link takes a lonnnnnnnnng time, and then it brings up a dialog prompt asking you to Open or Save the file, but once you Save and try to open it, it says the file is corrupt and can't be opened.
Any ideas?
Don't set $file to a relative url. The readfile function will try to access the php file on the server. That is not what you want. In your case it looks like the watermark.php file will send the contents you want, so you could possibly just set up the environment it needs and include it.
<?php
if(isset($_GET['download'])) {
$gallery = $_GET['g'];
$image = $_GET['i'];
$_GET['src'] = "{$gallery}/images/{$image}";
header('Content-Description: File Transfer');
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename='.basename($image));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: public');
header('Pragma: public');
ob_clean();
include('../watermark.php');
exit;
}
Another (simpler) way is to modify watermark.php. Add a query parameter to make it send the proper headers to force a download and link to that
...
watermark.php:
<?php
if (isset($_GET['download']) && $_GET['download'] == 'true') {
header('Content-Description: File Transfer');
header('Content-Type: image/jpeg');
header('Content-Disposition: attachment; filename='.basename($src));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: public');
header('Pragma: public');
}
// continue with the rest of the file as-is
Also, you don't need the call to flush(). There should not be any output to send at that point, so it is not necessary.
header('Content-Type: image/jpeg');
Perhaps?
I think you might need to follow the call to readfile() with a call to exit() to make sure nothing else gets written to the output buffer.
This seems like a security issue.
What if someone enters:
$g = '../../../../../../';
$i = '../../sensitive file at root';
How about making .htaccess (if you are using apache) i for the gallery directory serve jpegs up as a download rather than normal.
Also, try file_get_contents() instead of readfile(). I find it works under more circumstances. I would also recommend you use ob_flush() after you output the image data. I've never needed to use ob_clean() or flush() to get this kind of thing to work.
And as Eric said, you may also want to put a call to exit() in there as well for good measure if it still isn't working just in case you are getting some junk data stuck at the end.