Sign in to follow this  
Mathachew

[web] Force download returns damaged zip

Recommended Posts

I've got a script that forces a user to open/save a file. I've used it in many places and in just about every scenario, no problems had popped up, until now. On one of my employer's client's site has an accounting module that generates PDFs and CSVs of the data in question. At one point, the script appeared to be working, zipping up the directory I created and the user downloaded it, opened the files, blah blah. However, it is currently returning a damaged or corrupted zip file. I can use WinRAR to fix it and everything is peachy, however, this is not a feasible solution. I need to find out why it's becoming corrupted. I have tried three different methods of reading the file so that PHP can force the download prompt (And of course, I only use one of the three methods):
// First method used
readfile($file);

// Least favorite method
$fp = fopen($file, 'r');

while(!feof($fp))
{
	$buffer = fread($fp, 2048);
	print $buffer;
}

fclose ($fp);

// My favorite and seemingly most efficient
passthru("cat $file");





I have verified that the original zip files being read are not corrupted. Any ideas? Edit: I should also note that for security purposes, we do not hard link the zip file, which is why we're using the force-download option.

Share this post


Link to post
Share on other sites
Are you transmitting any information before or after the file?

It isn't uncommon to accidentally have cookies transmitted, or to have a header/footer transmitted.

If either of these are the case, the received zip file should be slightly larger than the original, and either the beginning or ending of it should have the plain text data on it. Normally it is obvious what extra data is being sent when you open up the corrupted file.

Share this post


Link to post
Share on other sites
Nah, nothing is being done before it. The link is to the PHP file. Here's the function being called:


function DownloadZip()
{
$id = GSQL('id');
$name = GSQL('name');
$full = ROOT_PATH.base64_decode($id).'.zip';

if(file_exists($full))
{
header('Content-Description: File Transfer');
header('Content-Type: application/force-download');
header('Content-Length: '.(string)(filesize($full)));
header("Content-Disposition: attachment; filename=".base64_decode($name).".zip");

passthru("cat $full");
}
}

Share this post


Link to post
Share on other sites
I had exactly the same problem a while back, I fixed it adding readfile() at the end of the script.

A quick example

<?php

$fileName="picture.jpg"
$path="attachments/picture.jpg"

header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.$fileName.'');
readfile('attachments'.$path);

?>

This script when called will prompt the open/save as/cancel dialogue to appear.

Btw, I would use header('Content-Type: application/octet-stream') instead of header('Content-Type: application/force-download'), from experience some browsers have difficulty with it :)

Share this post


Link to post
Share on other sites
Quote:
Original post by Mathachew
Nah, nothing is being done before it. ... Here's the function being called:

Don't just guess. The only way to be certain, rather than just guessing that a particular function call might be a problem is to look.


What exactly is the corruption going on in the zip file?

You really have three options:
A) Extra stuff is being sent.
B) Some stuff isn't being received.
C) Stuff is getting modified in transit.

The first step is to compare the file sizes.

If the received file size is bigger, open it up in an editor and look at the beginning and end. Almost certainly you will be able to see something that will clearly tell you about the problem. I'm betting a cookie that you have some thing, such as a cookie or a content flag or HTML snippet, appended or prepended to your zip file.

If the received file is smaller, you can then start to suspect caching and buffering mechanisms.

If the files are the same size, you use a tool to do a binary diff to see what exactly is modified.

Share this post


Link to post
Share on other sites
frob: I wasn't guessing. The site follows and MVC structure and I know that nothing is being sent out before this function is being called. I found out the problem and possible solution, if you want to keep reading...

Sonnenblume:
We originally had readfile() but found that the small amount of memory on this system and the amount alloted to PHP was being used up since some of the files can be over 65MB in size. As a result, we changed the script the method #2, printing out the buffer. But this also proved to be unsuccessful as you can now see.

Using method two and three on this FC3 system has shown that it gives me a damaged zip file. Using readfile() provided the proper file. I'm doing a test right now to see how the system reacts. It's been a while since we've been on this system and I'm thinking they seriously need to get one of our latest web servers. A quick comparison of the old and new server:

256MB vs 1GB RAM
80GB vs 2x 250GB HD
Celeron vs Core 2 Duo

The system is also FC3 vs Ubuntu, though I wouldn't consider that the issue; the issue is the limited hardware that they are working with.

Someone else recommended that I use the octet-stream header instead of force-download. I have yet to experience any problem with force-download, but I'll play around with the two and see where it leads me.

Thanks for the help guys.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this