ZIP Files get corrupted by IE - php

I am delivering a ZIP file in 64k chunks using a loop in PHP (but the problem would arise with any server side language).
When fetching the file with FF, everything goes just fine.
When fetching the file with IE7, some bits get corrupted. This leads to an error message regarding wrong CRC (a hash) and some of the unzipped files end up being corrupted.
The headers being sent are the following:
Expires: 0
Cache-Control: must-revalidate, post-check=0, pre-check=0
Pragma: public
Content-Description: File Transfer
Content-Disposition: attachment; filename="671fb8f80f5e94984c59e61c3c91bb70.zip";
Content-Transfer-Encoding: binary
Vary: Accept-Encoding
Content-Encoding: gzip
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/octet-stream
Does anyone have a clue where this corruption comes from?

Thanks to the previous answers, i managed to solve the problem:
Apache's mod_deflate encoded the responses in gzip. This had two effects when sending the file in chunks:
The Content-Length header was not sent out
The delivered files were corrupted when using IE7
The solution, in php, is to disable the encoding of the response using the following command:
apache_setenv('no-gzip', '1');

Content-Encoding: gzip
Did you intend to gzip your (already compressed) zip? I assume your web server adds this header, but if you added it yourself with PHP then maybe this could be the problem?

This MSDN article explains that IIS encodes ZIP files using gzip, but without the proper headers, it won't decode it before sending it to the unzipping program. Firefox is probably smart enough to automatically decode it. A fix is mentioned in the article, thought the article title doesn't exactly mention your issue.
I would double-check your IIS settings just in case.

Related

PHP file download are blocked by Chrome due to insecure connection (mixed-content)

I have a few projects where file downloads don't works anymore. It seems due to a recent Chrome policy change.
Downloads tries fails with this error in the console :
Mixed Content: The site at 'https://example.com/' was loaded over a secure connection, but the file at 'https://example.com/logs_view.php?export=true' was redirected through an insecure connection. This file should be served over HTTPS. This download has been blocked. See https://blog.chromium.org/2020/02/protecting-users-from-insecure.html for more details.
My files are served thru PHP :
header("Content-Type: application/xls");
header("Content-Disposition: attachment; filename=".$filename.".xls");
header("Pragma: no-cache");
header("Expires: 0");
echo utf8_decode($export);
It seems this method don't send the file with HTTPS headers... Any idea on how to fix this ?
You can find below the headers of the server response header :
HTTP/1.1 200 OK
Date: Tue, 19 Jan 2021 11:33:14 GMT
Server: Apache
Expires: 0
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Disposition: attachment; filename=base_changelog_2021-01-19_12-33-14.xls
Content-Length: 899
Keep-Alive: timeout=5, max=92
Connection: Keep-Alive
Content-Type: application/xls
Thanks a lot for your support.
Ben
There is nothing wrong in Your code.
When You make redirect to the url which returns file You are trying to do it from http while you are on https secure connection. Probably You have link with wrong protocol.
I find a way to force chrome to "upgrade" the download.
I added to my .htaccess this tweak, and it did the job!!!
<ifModule mod_headers.c>
Header always set Content-Security-Policy "upgrade-insecure-requests;"
</IfModule>

Transfer-Encoding: chunked sent twice (chunk size included in response body)

I'm using Apache 2.2 and PHP 7.0.1. I force chunked encoding with flush() like in this example:
<?php
header('HTTP/1.1 200 OK');
echo "hello";
flush();
echo "world";
die;
And I get unwanted characters at the beginning and end of the response:
HTTP/1.1 200 OK
Date: Fri, 09 Sep 2016 15:58:20 GMT
Server: Apache/2.2.15 (CentOS)
X-Powered-By: PHP/7.0.9
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
a
helloworld
0
The first one is the chunk size in hex (10 = A). I'm using Klein as PHP router and I have found that the problem comes up only when the HTTP status header is rewritten. I guess there is a problem with my Apache config, but I wasn't able to figure it out.
Edited: My problem had nothing to do with Apache but Nginx and chunked_transfer_encoding directive. Check the answer below.
This is how Transfer-Encoding: chunked works. The extra characters you're seeing are part of the encoding, rather than the body.
A client that understands the encoding will not include them in the result; a client that doesn't doesn't support HTTP/1.1, and should be considered bugged.
As #Joe pointed out before, that is the normal behavior when Chunked transfer enconding is enabled. My tests where not accurate because I was requesting Apache directly on the server. Actually, when I was experiencing the problem in Chrome I was querying a Nginx service as a proxy for Apache.
By running tcpdump I realized that Nginx was rechunking responses, but only when rewritting HTTP status header (header('HTTP/1.1 200 OK')) in PHP. The solution to sending Transfer-Encoding: chunked twice is to set chunked_transfer_encoding off in the location context of my Nginx .php handler.

Teamspeak Hostbanner doesn't work with PHP File

I'm having a PHP file that is imitating a PNG file by setting the mime-type to image/png.
Thanks to a .htaccess-file that I have, I can access my image in these ways:
/img/img
/img/img.png
/img/img.php
and all of them are working well in my browser, but they don't load on my Teamspeak server.
I'm not sure what you expect to happen, these are the headers being returned:
CF-Cache-Status
MISS
CF-RAY
21cddbcc942220ae-LAX
Cache-Control
no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection
keep-alive
Content-Encoding
gzip
Content-Type
text/html; charset=UTF-8
Date
Fri, 28 Aug 2015 06:11:24 GMT
Expires
Thu, 19 Nov 1981 08:52:00 GMT
Pragma
no-cache
Server
cloudflare-nginx
Transfer-Encoding
chunked
Vary
Accept-Encoding
X-Powered-By
PHP/5.4.41-0+deb7u1
So first off, you are returning text/html. That is not the proper mime type for a .png image.
Secondarily, your script needs to actually return .png data.
I'm assuming here you plan to create the .png in your script using GD or ImageMagick, but whatever you plan to do, nothing will appear when an image is expected, when you don't actually provide the proper mime type and the proper image data that is expected to follow that mime type.
Setting the mime type at the top of your script is as easy as:
header('Content-type: image/png');
You are also going through Cloudflare. You should have Cloudflare turned off until you've debugged things and are sure that the script is operating as expected.
Thanks to #gview's answer I found out that Cloudflare was protecting the /img/ folder, so I made a page rule to stop this. Just in case you need it, this now is my script and it sets the correct headers:
# Generate $im and Stuff
# ...
# Now Output it
http_response_code(200);
$file = "./temp.png";
imagepng($im, $file);
header("Content-Type: image/png");
header("Content-Length: ".filesize($file));
readfile($file);
exit;

Stream mp3 with PHP, on Linux+FireFox

I'm trying to stream an mp3 file with PHP and play it on the browser.
I'm using Ubuntu for both the server ( apache ) and client for testing. My code works on Chrome, but not on FireFox.
When I access the mp3 directly ( so it's served by the web server ) it works on FireFox as well, but comparing the headers that the web server generates with the headers I send in PHP I couldn't find how to fix the problem. ( I'm spying the headers using FireBug )
Here are the webserver generated headers ( That does work ):
Accept-Ranges bytes
Connection Keep-Alive
Content-Length 490265
Content-Type audio/mpeg
Date Sun, 11 Mar 2012 04:01:45 GMT
Etag "22064e-77b19-4badff4a88200"
Keep-Alive timeout=5, max=100
Last-Modified Sat, 10 Mar 2012 09:15:52 GMT
Server Apache/2.2.20 (Ubuntu)
Here are the headers that are sent to the browser from my PHP script:
Accept-Ranges bytes
Cache-Control no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Connection Keep-Alive
Content-Length 490265
Content-Type audio/mpeg
Date Sun, 11 Mar 2012 04:16:00 GMT
Expires Thu, 19 Nov 1981 08:52:00 GMT
Keep-Alive timeout=5, max=100
Pragma no-cache
Server Apache/2.2.20 (Ubuntu)
X-Powered-By PHP/5.3.6-13ubuntu3.6
This is the code I use to stream the mp3:
header('Content-length: ' . filesize($path));
header('Content-Type: audio/mpeg');
header('Accept-Ranges: bytes');
readfile($path);
exit;
I did also tried other headers which didn't help, such as:
header('Content-Disposition: inline; filename="name.mp3"');
header('Expires: '.gmdate('D, d M Y H:i:s').' GMT');
header('Pragma: no-cache');
header('Cache-Control: no-cache');
But like I said, none of these fixed the problem.
Many thanks for any help,
Oded.
EDIT:
OK this appears to be extremely strange. After much debugging, I made sure that the headers and content of the PHP version and the webserver versions are the same, and then I found out what breaks it, but I have no idea why. Here is the scenario that breaks it:
1) Store a string of a path in $_SESSION in a previous script.
2) Read this string in the script that streams the mp3.
3) Use this string as the path to load the mp3 file.
If I do that, FireFox cannot play the file, when I press on the mp3 player, it prints a "GstDecodeBin2: This appears to be a text file" message.
If I hard code the path instead of using the $_SESSION, it works. The crazy thing is that I made absolutely sure that the path in the $_SESSION is correct! Remember that the headers and content of the PHP and webserver versions are identical!
The HTTP Accept-Ranges header allows the browser to send a starting and ending point of the file to download, this allows for multi-part downloading of the same file. There are plenty of PHP implementations of this, here is one found on the PHP.net documentation page for fread().
http://www.php.net/manual/en/function.fread.php#106999
I found what the problem is using WireShark to monitor the requests. Earlier I used FireBug and HTTPFox, and they don't show all the requests!
WireShark showed me that after the initial successful request there is another request for the same URI. This second request was not caught by xdebug, and was missed by FireBug, and HTTPFox. The problem is that this request does not include the PHPSESSID! Obviously as a result the session did not work, and because it did work on the first request I was confused.
This seems to me like a bug in FireFox with its media player module.
I can work around this by manually adding the PHPSESSID to the URL as query string.

Where are these extra HTTP headers coming from?

When I simply echo something out of php file, I do not send any headers intentionally, however - there are some default headers present anyway when I look at firebug response:
response headers:
HTTP/1.1 200 OK
Server: nginx
Date: Thu, 23 Jun 2011 19:33:51 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: PHP/5.3.6-6~dotdeb.1
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Encoding: gzip
I'm curious - are these default response headers set by the server(nginx) or by PHP?
I believe it is a combination of both... You can tell that "X-Powered-By: PHP/5.3.6-6~dotdeb.1" comes from PHP and "Server: nginx" comes from NGINX.
You can alter the headers in PHP as follows:
<?php
header("HTTP/1.0 404 Not Found");
?>
The gzip header most definitely comes from NGINX as it is compressing the output (html) to the browser. PHP can "add" to the headers by calling a function like the one above. Then the server combines it with the PHP headers and serves the request.
It depends on your server whether or not the PHP headers take precedence over the server headers.
Hope this helps.
The majority are set by nginx, for example the Server, Date, Content-Encoding, and Connection. However, some other headers are set by PHP, and you can add others in PHP like this header("Name: Value");
The X-Powered-By header is controlled by the value of the expose_php directive in php.ini:
Decides whether PHP may expose the fact that it is installed on the server (e.g. by adding its signature to the Web server header). It is no security threat in any way, but it makes it possible to determine whether you use PHP on your server or not.
Most headers are sent by nginx. To list the headers (to be) sent by PHP, use the function headers_list:
<?php
echo htmlentities(print_R(headers_list(), true));
?>
PHP automatically sets some of them, like Content-Type: text/html for the hello world page. nginx sets the ones that have to do with the socket, like Connection: keep-alive.
You'll find settings for connections in nginx's configuration. Content-wise, it's PHP. You're allowed to override quite a few of them with the header() function in PHP, as well as add your own custom headers.
http://php.net/manual/en/function.header.php
For example, you could set the Content-Type to application/json if you're planning to have PHP send out a JSON string.
What's still missing in the answers is the role of PHP:
Some of the headers are indeed set by PHP itself, but the reason is not that easy to find. It's the default session cache delimiter behavior explained here: http://www.php.net/manual/en/function.session-cache-limiter.php
What's afaik not in the docs is how to turn them off completely - simply pass some undefined value to it:
session_cache_limiter(false);
You must to do this before you start your session. In case you are using the Zend Framework, you have to set this before your applications bootstrap() - otherwise it won't work.
You can also overwrite any of the default server headers using the header() function. For example, if you include in your PHP header('Server: ') this will reset the Server: header to be blank.

Categories