9

I have to trigger a download of a zip file ( The Zip file is inside my data folder). For this i am using the code,

$file = 'D:\php7\htdocs\Project\trunk\api\data\file.zip';
header('Content-Description: File Transfer');
header('Content-type: application/zip');
header('Content-disposition: attachment; filename=' . basename($file) );
readfile($file);`

This is working in core php as i expected. But when i am using the same code in the Zend prints a content like below,

PKYsVJ)~�� study.xlsPKYsVJs�����+ tutorial-point-Export.xlsPKYsVJn��� 8��Zabc.xlsP

In between the content i can see the name of all files in the zip. But it is not getting downloaded.

After i realised that this is not working i started searching about it and Found some solution from stack over flow

Try 1: Adding different header element and ob functions in every random lines

  • 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: ' . $file_size);
  • ob_start();
  • ob_clean();
  • flush();

All these are tried from different stack overflow Question and answers and have the same result

Try 2:PHP is reading file instead of downloading . This question do not have any accepted answer (He was asking about the core php but i have the same issue with zend only) . I tried all of this but it was not working.

Try 3:Changing the .htaccess . After that i thought it was a problem with my .htaccess and found this answer for changing the .htaccess file.

<FilesMatch "\.(?i:zip)$">
        ForceType application/octet-stream
        Header set Content-Disposition attachment
</FilesMatch>

This also given me the same result.

Try 4:Using download functions in Zend . I have tried the all the zend functions in the answer of this question. But given me an empty output even the file was not read.

Try 5: Remove all the unwanted spaces before and after the php tag as per the answer

Is there any other way to trigger a download in ZF2 framework?

EDIT

Below is my exact function. This is GET(API) function,

public function getList(){
    try{
       //here i am getting the zip file name.
       $exportFile = $this->getRequest()->getQuery('exportid','');
       $file = 'D:\php7\htdocs\Project\trunk\api\data\\' . $exportFile . '.zip';
       header('Content-Description: File Transfer');
       header('Content-type: application/zip');
       header('Content-disposition: attachment; filename=' . basename($file) );
       readfile($file);
       return new JsonModel(["status"=>"Success"]);
    } catch(\Exception $e){
       return new JsonModel(["status"=>"Failed"]);
    }
}
Community
  • 1
  • 1
Prifulnath
  • 463
  • 7
  • 24
  • Not sure, if it would help, but I usually send the charset in my headers. You might want to try this. – Chris Feb 23 '17 at 11:21
  • @Chris Thanku you for your reply. I have tried the `charset=utf-8` with `Content-Type` as `header('Content-type:application/zip; charset=utf-8');` But i have the same readed file on my screen. And searched about the Charset it says that charset is normally used for `text` files. Is there any correction? – Prifulnath Feb 23 '17 at 11:32
  • If you already tried your download including the charset info, then it's probalbly something completely different. A last thing: The content-disposition header usually sends the filename in quotes: Content-Disposition: attachment; filename="filename.jpg" – Chris Feb 23 '17 at 12:54
  • @Chris i have tried that too, the result was the same :( – Prifulnath Feb 27 '17 at 06:11
  • try with error reporting enabled, and check weather header already sent? – Chetan Ameta Feb 27 '17 at 11:01
  • I thought the same in the middle and i checked that but headers were already send. Even then i used `ob_` functions as in the question(Try 1) but the code worked in the same way. – Prifulnath Feb 27 '17 at 11:53
  • Have you tried putting `exit();` after the line with `readfile($file);`? – Cave Johnson Feb 27 '17 at 19:12
  • @KodosJohnson i have tried both `exit()` and `die()` after the `readfile()` , it was also printing the file content there. – Prifulnath Feb 28 '17 at 03:49
  • Just checking, have you tried following the accepted answer here: https://stackoverflow.com/questions/7263923/how-to-force-file-download-with-php – Cave Johnson Feb 28 '17 at 05:22
  • @KodosJohnson This is what i have tried first. As now this was working fine in core PHP, But read the file in ZF2 Framework. And currently i am using JavaScript Redirect instead of PHP Code for the download (Currently Working). – Prifulnath Feb 28 '17 at 05:38
  • 1
    can you post your code of function which you exactly wrote in ZF? – Chetan Ameta Feb 28 '17 at 07:56
  • Yup, the code as posted **should** work, so there is something else going on. Post the method that's using this. – yivi Feb 28 '17 at 09:10
  • @ChetanAmeta i have edited the post with my function – Prifulnath Feb 28 '17 at 10:43
  • `header('Content-Type: application/octet-stream')` and `Content-Disposition` with capital D – ETech Mar 06 '17 at 11:46
  • @ETech tried the capitalisation too :( – Prifulnath Mar 13 '17 at 11:38

6 Answers6

1

There are two problems here:

  • your browser trying to open the file, instead of downloading it.
  • also, it is not opening the file correctly.

Both point to a Content-Type error. Verify that the Content-Type being received by the browser is correct (instead of being rewritten as, say, text/html).

If it is, change it to application/x-download. This might not work in Internet Explorer, which performs some aggressive Content-Type sniffing. You might try adding a nosniff directive.

Additionally, after a readfile (and you might be forced to return the file's contents instead of readfile()'ing - i.e., return file_get_contents($filename);), you should stop all output with return null;. ZIP file directory is at the very end, so if you attach a JSON message there, you risk the browser neither downloading the file, nor displaying it correctly.

As a last resort, you can go nuclear and do everything yourself. Extremely non-elegant, and all frameworks ought to provide an alternative, but just in case...

// Stop *all* buffering
while (ob_get_level()) {
    ob_end_clean();
}
// Set headers using PHP functions instead of Response
header('Content-Type: application/x-download');
header('X-Content-Type-Options: nosniff');
header('Content-Length: ' . filesize($filename));
header('Content-Disposition: attachment; filename="whatever.zip"');
die(readfile($filename));

It's possible that some creative use of atexit handlers or destructor hooks might mess up even this last option, but I feel it's unlikely.

Community
  • 1
  • 1
LSerni
  • 55,617
  • 10
  • 65
  • 107
  • Still the same content is printed. I can't still find why it is working in core php and why not working in zend. So, i decided to use Javascript for this :( . – Prifulnath Mar 03 '17 at 05:34
  • 1
    As per @LSerni's response above, have you verified the content type that is received in the browser (eg using dev tools in Chrome, looking at the headers of the network request for the download)? Are the headers what you are expecting? It could be that something in your setup means the zend code is trying to set headers too late (ie after the content has started). – James Fry Mar 07 '17 at 11:23
0

Based on this SO answer, you can try the following modification to your function.

public function getList(){
    try{
       //here i am getting the zip file name.
       $exportFile = $this->getRequest()->getQuery('exportid','');
       $file = 'D:\php7\htdocs\Project\trunk\api\data\\' . $exportFile . '.zip';
        if (file_exists($file)) {
            $response = new \Zend\Http\Response\Stream();
            $response->setStream(fopen($file, 'r'));
            $response->setStatusCode(200);
            $response->setStreamName(basename($file));
            $headers = new \Zend\Http\Headers();

            $headers->addHeaders(array(
                'Content-Description' => 'File Transfer',
                'Content-Disposition' => 'attachment; filename="' . basename($file) .'"',
                'Content-Type' => 'application/zip',
                'Content-Length' => filesize($file)
            ));
            $response->setHeaders($headers);
            return $response;
            //return new JsonModel(["status"=>"Success"]);
        } else {
            return new JsonModel(["status"=>"Failed. No such file in \"".$file."\""]);
        }           
    } catch(\Exception $e){
       return new JsonModel(["status"=>"Failed"]);
    }
}
Community
  • 1
  • 1
  • i am getting a Fatal error: `Uncaught Error: Call to undefined method Zend\Http\PhpEnvironment\Response::setHeader()`. I have tried this too before but because of the error i have searched for another solution. – Prifulnath Mar 01 '17 at 04:42
  • the code simply print `{"status":"Success"}` without any other action,(The file content are not printing now and the download is not triggering) – Prifulnath Mar 01 '17 at 09:18
  • instead of `return new JsonModel(["status"=>"Success"]);` write `return $response;` @Kuttoozz –  Mar 01 '17 at 12:30
  • That was a great try, but sadly the content was printing instead of downloading it :( – Prifulnath Mar 02 '17 at 04:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137013/discussion-between-kuttoozz-and-peter-darmis). – Prifulnath Mar 02 '17 at 04:16
0

This worked for me!

        ob_clean(); // Clear any previously written headers in the output buffer

        $filepath = "some_file.zip";
        $content_type = 'application/octet_stream';
        $filetype = filetype($filepath);
        $filename =$filepath;
        if($filetype=='application/zip')
        {
            if(ini_get('zlib.output_compression'))
                ini_set('zlib.output_compression', 'Off');
            $fp = @fopen($filepath, 'rb'); 
            if (strstr($_SERVER['HTTP_USER_AGENT'], "MSIE"))
            {
                header('Content-Type: '.$content_type);
                header('Content-Disposition: attachment; filename="'.$filename.'"');
                header('Expires: 0');
                header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
                header("Content-Transfer-Encoding: binary");
                header('Pragma: public');
                header("Content-Length: ".filesize(trim($filepath)));
            }
            else
            {
                header('Content-Type: '.$content_type);
                header('Content-Disposition: attachment; filename="'.$filename.'"');
                header("Content-Transfer-Encoding: binary");
                header('Expires: 0');
                header('Pragma: no-cache');
                header("Content-Length: ".filesize(trim($filepath)));
            }

            fpassthru($fp);
            fclose($fp);
        }
0

If you correct the capitalisation of the headers does it work? ie use Content-Disposition and Content-Type over Content-disposition and Content-type respectively?

Regardless, as standard debugging technique I would suggest using your browser dev tools to inspect the requests that are being made (inc headers) and comparing that to what ends up in your serverside code, and what is in the server side response and what ends up in the client. I would also validate this using a private-session (Incognito mode in Chrome etc) or a fresh profile / VM install just to eliminate anything else.

Also, why not use xsendfile and delegate the responsibility of sending the file to the web server so you aren't incurring the responsibility in your PHP code? You can do this with appropriate server configuration (sometimes through .htaccess, but in this day and age surely you have complete control anyway) and then simply setting the X-Sendfile header as per the example on the above link:

header("X-Sendfile: $path_to_somefile");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"$somefile\"");
James Fry
  • 1,133
  • 1
  • 11
  • 28
  • I have tried adding capitalisation of the headers earlir and now i tried the X-Sendfile both of them are not working for me any way thanks for your help. – Prifulnath Mar 06 '17 at 05:21
0

Because you are return JsonModel so your output will be a json with your message instead of buffering for downloading.

Edit: I notice that you was missing Content-Transfer-Encoding: Binary, tested on my os x - php5.6 env.

You should try this

public function getList(){
    try{
       //here i am getting the zip file name.
       $exportFile = $this->getRequest()->getQuery('exportid','');
       $file = 'D:\php7\htdocs\Project\trunk\api\data\\' . $exportFile . '.zip';
       header('Content-Description: File Transfer');
       header('Content-type: application/zip');
       header('Content-disposition: attachment; filename=' . basename($file));

       header("Content-Transfer-Encoding: Binary");
       header("Content-length: " . filesize($file));
       header("Pragma: no-cache"); 
       header("Expires: 0"); 
       readfile("$file");
    } catch(\Exception $e){
       return new JsonModel(["status"=>"Failed"]);
    }
}

Just remove your JSonModel on response.

Đào Minh Hạt
  • 2,742
  • 16
  • 20
-1

You can try this for downloading the file instead of readfile(); Server side - file_put_contents("file.zip", fopen("http://someurl/file.zip", 'r'));

Client side - <a href="http://someurl/file.zip"><button>download file</button></a> download file

Cool Breeze
  • 738
  • 2
  • 10
  • 26