9

If I make the url for a zip file the href of a link and click the link, my zip file gets downloaded and opening it gets the contents as I expect.

Here's that HTML:

<a href="http://mysite.com/uploads/my-archive.zip">download zip</a>

The problem is I'd like the link to point to my application such that I could determine whether the user is authorized to access this zip file.

so I'd like my HTML to be this:

 <a href="/canDownload">download zip</a> 

and my PHP for the /canDownload page:

//business logic to determine if user can download

if($yesCanDownload){

$archive='https://mysite.com/uploads/my-archive.zip';
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=".basename($archive));
header("Content-Length: ".filesize($archive));
ob_clean();
flush();
echo readfile("$archive");
}   

So, I think the problem has to do with the header() code but i've tried a bunch of things related to that based on various google and other SO suggestions and none work.

If you answer my question, it is likely you can answer this question too: Zipped file with PHP results in cpgz file after extraction

Community
  • 1
  • 1
tim peterson
  • 23,653
  • 59
  • 177
  • 299
  • For the sake of simplicity I referred to it as a cpgz file, but its actually a `xxx.zip.cpgz` file. From google, I think it's a zipping of the zip file. It seems I'm downloading a file whose contents can't be read perhaps because the header() instructions were already determined by `/canDownload`? – tim peterson Jul 15 '12 at 19:58
  • Try changing the extension to .txt and see if you have plain text in there. – jprofitt Jul 15 '12 at 19:59
  • so you mean `https://mysite.com/uploads/my-archive.txt`? – tim peterson Jul 15 '12 at 20:04
  • I mean after you download the file, change its extension to .txt and open it up. – jprofitt Jul 15 '12 at 20:06
  • thanks @jprofitt i think you are on to the problem, when i opened the zip file as a txt, i got the following error message: `Cannot modify header information - headers already sent by ` – tim peterson Jul 15 '12 at 20:15
  • That means there is some other code in your program that is sending output (or headers) before you are calling `header()` here. There is probably some error or some other output before the code you posted. – cegfault Jul 15 '12 at 20:26
  • yes you are correct, the first error is `filesize() [function.filesize]: stat failed for https://mysite.com/uploads/my-archive.php<`. It looks like this `header() and filesize()` problems are secondary to real issue which is how to call the zip file from my s3 bucket, still looking through the AWS PHP SDK... – tim peterson Jul 15 '12 at 20:30
  • 1
    The SDK is the way to go; you may want to just copy the archive temporarily to the local server and get this issue closed out (ie, eliminate all errors besides AWS), and then try adding AWS code in to make the remote call. If you have issues there, you should open another question – cegfault Jul 16 '12 at 00:31

6 Answers6

13

The answer in my case was that there was an empty line being output before readfile().

So i added:

ob_end_clean();

readfile($filename);

But you should probably search for the place where this line is being output in your code.

carlosvini
  • 1,742
  • 19
  • 18
4

The PHP documentation for readfile says that it will output the contents of a file and return an int.

So your code, echo readfile("$archive");, will echo $archive (btw, the double quotes are meaningless here; you should remove them), and THEN output the int that is being returned. That is, your line should be: readfile($archive);

Also, you should be using a local path (not an http:// link) to the archive.

Altogether:

if($yesCanDownload){
    $archive='/path/to/my-archive.zip';
    header("Content-Type: application/zip");
    header("Content-Disposition: attachment; filename=".basename($archive));
    header("Content-Length: ".filesize($archive));
    ob_clean();
    flush();
    readfile($archive);
}

Lastly, if that does not work, make sure filesize($archive) is returning the accurate length of the file.

cegfault
  • 6,442
  • 3
  • 27
  • 49
  • thanks @cegfault for your help, I need to use an `http://` because the zip file is actually on a remote server. So, yeah now that i'm realizing, readfile() makes no sense in this context. Have any other suggestions? – tim peterson Jul 15 '12 at 20:14
  • how are you loading the remote file? that's probably where the problem is. I saw you might be using an S3 bucket. I had a problem on Rackspace CloudFiles where the file was also reading the headers, and there were some extra CRLF's ("\r\n") in the file. make sure you are calling the file correctly - try it first with a .txt file or something. If you are still having problems, post your actual S3 code (remove the username/apikey/etc), because that might be where the problem is at. – cegfault Jul 15 '12 at 20:17
  • hi @cegfault, I currently don't have any S3 code in my `/canDownload` php file. What I have is exactly what you see. I'm just learning the S3 SDK. I see there is a [getObject](http://docs.amazonwebservices.com/AWSSDKforPHP/latest/#m=AmazonS3/get_object) call but not sure how to adapt it to my situation. I'm not a total noob as I can upload to my S3, but still somewhat confused, might you be able to advise? – tim peterson Jul 15 '12 at 20:22
2

Ok, I answered my own question.

The main problem, which I originally didn't make clear, was that the file was not located on my application server. It was in a Amazon AWS s3 bucket. That is why I had used a full url in my question, http://mysite... and not just a file path on the server. As it turns out fopen() can open urls (all s3 bucket "objects", a.k.a. files, have urls) so that is what I did.

Here's my final code:

$zip= "http://mysite.com/uploads/my-archive.zip"; // my Amazon AWS s3 url
header("Content-Type: archive/zip"); // works with "application/zip" too
header("Content-Disposition: attachment; filename='my-archive.zip"); // what you want to call the downloaded zip file, can be different from what is in the s3 bucket   
$zip = fopen($zip,"r"); // open the zip file
echo fpassthru($zip); // deliver the zip file
exit(); //non-essential
tim peterson
  • 23,653
  • 59
  • 177
  • 299
2

Another possible answer, I found After much searching, I found that the two possible reasons for a *.zip "unzipping" to a *.zip.cpgz are:

  1. the *.zip file is corrupted
  2. the "unzip" tool being used can't handle >2GB files

Being a Mac user, the second reason was the cause for my problem unzipping the file: the standard Mac OS tool is Archive Utility, and it apparently can't handle >2GB files. (The file in question for me was a zipped 4GB raspbian disk image.)

What I ended up doing was to use a Debian virtual machine, already existing in Virtual Box on my Mac. unzip 6.0 on Debian 8.2 had no problem unzipping the archive.

roens
  • 333
  • 2
  • 9
1

You're passing the URL to readfile() like:

$archive = 'https://mysite.com/uploads/my-archive.zip';

While you should pass the path on the server, for example:

$archive = '/uploads/my-archive.zip';

Assuming the file is located in the upload folder.

Additionally try the following headers:

header("Content-type: application/octet-stream"); 
header("Content-disposition: attachment; filename=file.zip");  
Anne
  • 26,765
  • 9
  • 65
  • 71
  • The zip is actually in an S3 bucket and not my app. My question seemed complicated enough so that is why i needed to include the full URL. – tim peterson Jul 15 '12 at 20:02
0

In my case, I was trying to create the file in a directory above public_html and the rules of the hosting didn't allow it.

chispitaos
  • 767
  • 9
  • 14