PHP ZIP文件下载

3
以下是一个从imap服务器下载附件文件的代码。几乎所有类型的文件(pdf、doc、xls等)都能正确下载,但有些zip文件会出现以下错误:
"The archive is either in unknown format or damaged"
代码:
//data from imap server
$name = "xyz 123.zip";
$type = "APPLICATION";
$subtype = "ZIP";
$encoding = "BASE64";
$body = imap_base64($data);

header('Content-Description: File Transfer');
header('Content-Type: '. $type .'/'. $subtype);
header('Content-Disposition: attachment; filename='.$name);
header('Content-Transfer-Encoding: '.$encoding);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
ob_clean();
flush();
echo $body;

如果我使用以下方法将数据打印并转换为文件:

http://www.motobit.com/util/base64-decoder-encoder.asp

该文件可以被正确下载。因此,从服务器获取文件没有问题。我哪里做错了?


我认为你的压缩文件没有完全下载,所以解压程序会显示那种错误。 - jondinham
我该如何验证?我已经尝试多次下载它。而且文件大小与服务器上的相同。如果我打印编码文件并手动解码,它也可以正常工作。 - victor_golf
您提供的代码似乎与解压错误无关。请检查执行下载以获取 $data 的代码部分。 - jondinham
检查文件大小(以字节为单位)和校验和。如果下载文件的字节大小与服务器上文件的字节大小相同,则检查校验和(MD5或其他)。如果连校验和也相同,则我认为服务器上的原始文件已损坏。 - jondinham
1
base64_decode就能解决问题。非常感谢@Paul。 - victor_golf
显示剩余4条评论
7个回答

5

用于下载 zip 格式文件的脚本:zip.php

<?php

/**
* Zip file creation class.
* Makes zip files.
*
* Last Modification and Extension By :
*
*  Hasin Hayder
*  HomePage : www.hasinme.info
*  Email : countdraculla@gmail.com
*  IDE : PHP Designer 2005
*
*
* Originally Based on :
*
*  http://www.zend.com/codex.php?id=535&single=1
*  By Eric Mueller <eric@themepark.com>
*
*  http://www.zend.com/codex.php?id=470&single=1
*  by Denis125 <webmaster@atlant.ru>
*
*  a patch from Peter Listiak <mlady@users.sourceforge.net> for last modified
*  date and time of the compressed file
*
* Official ZIP file format: http://www.pkware.com/appnote.txt
*
* @access  public
*/
class zipfile
{
    /**
     * Array to store compressed data
     *
     * @var  array    $datasec
     */
    var $datasec      = array();

    /**
     * Central directory
     *
     * @var  array    $ctrl_dir
     */
    var $ctrl_dir     = array();

    /**
     * End of central directory record
     *
     * @var  string   $eof_ctrl_dir
     */
    var $eof_ctrl_dir = "\x50\x4b\x05\x06\x00\x00\x00\x00";

    /**
     * Last offset position
     *
     * @var  integer  $old_offset
     */
    var $old_offset   = 0;


    /**
     * Converts an Unix timestamp to a four byte DOS date and time format (date
     * in high two bytes, time in low two bytes allowing magnitude comparison).
     *
     * @param  integer  the current Unix timestamp
     *
     * @return integer  the current date in a four byte DOS format
     *
     * @access private
     */
    function unix2DosTime($unixtime = 0) {
        $timearray = ($unixtime == 0) ? getdate() : getdate($unixtime);

        if ($timearray['year'] < 1980) {
            $timearray['year']    = 1980;
            $timearray['mon']     = 1;
            $timearray['mday']    = 1;
            $timearray['hours']   = 0;
            $timearray['minutes'] = 0;
            $timearray['seconds'] = 0;
        } // end if

        return (($timearray['year'] - 1980) << 25) | ($timearray['mon'] << 21) | ($timearray['mday'] << 16) |
                ($timearray['hours'] << 11) | ($timearray['minutes'] << 5) | ($timearray['seconds'] >> 1);
    } // end of the 'unix2DosTime()' method

    /**
     *
     * Function to force the download of the archive as soon as it is created
     *
     * @param archiveName string - name of the created archive file
     * @access public
     * @return ZipFile via Header
     */
    public function forceDownload($archiveName) {
        if(ini_get('zlib.output_compression')) {
            ini_set('zlib.output_compression', 'Off');
        }

        // Security checks
        if( $archiveName == "" ) {
            echo "<html><title>Public Photo Directory - Download </title><body><BR><B>ERROR:</B> The download file was NOT SPECIFIED.</body></html>";
            exit;
        }
        elseif ( ! file_exists( $archiveName ) ) {
            echo "<html><title>Public Photo Directory - Download </title><body><BR><B>ERROR:</B> File not found.</body></html>";
            exit;
        }

        header("Pragma: public");
        header("Expires: 0");
        header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
        header("Cache-Control: private",false);
        header("Content-Type: application/zip");
        header("Content-Disposition: attachment; filename=".basename($archiveName).";" );
        header("Content-Transfer-Encoding: binary");
        header("Content-Length: ".filesize($archiveName));
        readfile("$archiveName");
    }

    /**
     * Adds "file" to archive
     *
     * @param  string   file contents
     * @param  string   name of the file in the archive (may contains the path)
     * @param  integer  the current timestamp
     *
     * @access public
     */
    function addFile($data, $name, $time = 0)
    {
        $name     = str_replace('\\', '/', $name);

        $dtime    = dechex($this->unix2DosTime($time));
        $hexdtime = '\x' . $dtime[6] . $dtime[7]
                  . '\x' . $dtime[4] . $dtime[5]
                  . '\x' . $dtime[2] . $dtime[3]
                  . '\x' . $dtime[0] . $dtime[1];
        eval('$hexdtime = "' . $hexdtime . '";');

        $fr   = "\x50\x4b\x03\x04";
        $fr   .= "\x14\x00";            // ver needed to extract
        $fr   .= "\x00\x00";            // gen purpose bit flag
        $fr   .= "\x08\x00";            // compression method
        $fr   .= $hexdtime;             // last mod time and date

        // "local file header" segment
        $unc_len = strlen($data);
        $crc     = crc32($data);
        $zdata   = gzcompress($data);
        $zdata   = substr(substr($zdata, 0, strlen($zdata) - 4), 2); // fix crc bug
        $c_len   = strlen($zdata);
        $fr      .= pack('V', $crc);             // crc32
        $fr      .= pack('V', $c_len);           // compressed filesize
        $fr      .= pack('V', $unc_len);         // uncompressed filesize
        $fr      .= pack('v', strlen($name));    // length of filename
        $fr      .= pack('v', 0);                // extra field length
        $fr      .= $name;

        // "file data" segment
        $fr .= $zdata;

        // "data descriptor" segment (optional but necessary if archive is not
        // served as file)
        $fr .= pack('V', $crc);                 // crc32
        $fr .= pack('V', $c_len);               // compressed filesize
        $fr .= pack('V', $unc_len);             // uncompressed filesize

        // add this entry to array
        $this -> datasec[] = $fr;

        // now add to central directory record
        $cdrec = "\x50\x4b\x01\x02";
        $cdrec .= "\x00\x00";                // version made by
        $cdrec .= "\x14\x00";                // version needed to extract
        $cdrec .= "\x00\x00";                // gen purpose bit flag
        $cdrec .= "\x08\x00";                // compression method
        $cdrec .= $hexdtime;                 // last mod time & date
        $cdrec .= pack('V', $crc);           // crc32
        $cdrec .= pack('V', $c_len);         // compressed filesize
        $cdrec .= pack('V', $unc_len);       // uncompressed filesize
        $cdrec .= pack('v', strlen($name) ); // length of filename
        $cdrec .= pack('v', 0 );             // extra field length
        $cdrec .= pack('v', 0 );             // file comment length
        $cdrec .= pack('v', 0 );             // disk number start
        $cdrec .= pack('v', 0 );             // internal file attributes
        $cdrec .= pack('V', 32 );            // external file attributes - 'archive' bit set

        $cdrec .= pack('V', $this -> old_offset ); // relative offset of local header
        $this -> old_offset += strlen($fr);

        $cdrec .= $name;

        // optional extra field, file comment goes here
        // save to central directory
        $this -> ctrl_dir[] = $cdrec;
    } // end of the 'addFile()' method


    /**
     * Dumps out file
     *
     * @return  string  the zipped file
     *
     * @access public
     */
    function file()
    {
        $data    = implode('', $this -> datasec);
        $ctrldir = implode('', $this -> ctrl_dir);

        return
            $data .
            $ctrldir .
            $this -> eof_ctrl_dir .
            pack('v', sizeof($this -> ctrl_dir)) .  // total # of entries "on this disk"
            pack('v', sizeof($this -> ctrl_dir)) .  // total # of entries overall
            pack('V', strlen($ctrldir)) .           // size of central dir
            pack('V', strlen($data)) .              // offset to start of central dir
            "\x00\x00";                             // .zip file comment length
    } // end of the 'file()' method


    /**
     * A Wrapper of original addFile Function
     *
     * Created By Hasin Hayder at 29th Jan, 1:29 AM
     *
     * @param array An Array of files with relative/absolute path to be added in Zip File
     *
     * @access public
     */
    function addFiles($files /*Only Pass Array*/)
    {
        foreach($files as $file)
        {
        if (is_file($file)) //directory check
        {
            $data = implode("",file($file));
                    $new_file = $this->prefix_name.end(explode('/', $file));
                    $this->addFile($data, $new_file);
                }
        }
    }

    /**
     * A Wrapper of original file Function
     *
     * Created By Hasin Hayder at 29th Jan, 1:29 AM
     *
     * @param string Output file name
     *
     * @access public
     */
    function output($file)
    {
        $fp=fopen($file,"w");
        fwrite($fp,$this->file());
        fclose($fp);
    }



} // end of the 'zipfile' class
?>

使用示例:

<?php

include_once('zip.php');

$zip_file = 'my_files.zip'; // name for downloaded zip file

$ziper = new zipfile();
$ziper->prefix_name = 'folder/'; // here you create folder which will contain downloaded files
$ziper->addFiles($files_to_zip);  // array of files
$ziper->output($zip_file); 
$ziper->forceDownload($zip_file);
@unlink($zip_file);

?>

我觉得你误解了问题。我不想将多个文件下载为一个zip文件,而是在服务器上有一个zip文件需要下载。因此,你文件中的forceDownload函数和我的代码是相同的。 - victor_golf
只需移除 'forceDownload' 调用即可。 - Rasmus Søborg
回答 CroiOS 的问题,应该在 zip.php 文件的第 241 行更改,通过变量来查找数组的最后一个元素 $tempName = explode('/', $file); $new_file = $this->prefix_name.end($tempName); $this->addFile($data, $new_file); } 以避免出现“只能传递变量”的错误。 - D M Patel
对我来说,不像预期那样工作;如今创建压缩文件,我建议使用ZipArchive对象(请参见https://gist.github.com/somatonic/6427247)。 - YakovL
或者这是我的更新版本:https://gist.github.com/YakovL/a5425e7f4e116aee87fb121a3ab0b26d - YakovL

1

我可能有点晚,但我也遇到了同样的问题,这些代码对我非常有效

while (ob_get_level()) {
  ob_end_clean();
}

0

我还没有看到有人提到这一点,但是尝试在下载ZIP文件时删除Content-Length头。我遇到了完全相同的问题,这个方法对我起作用了。


0
罪魁祸首是imap_base64。它没有正确解码数据。我将其替换为base64_decode,代码就正常工作了。
$body = base64_encode($data);

0
It Worked For Me. It Download My .Zip File. But, you have to provide the full path of File, where it is located.
For ex: My File is inside assets/upload Folder. Attach name of that file with this location. You will download it without any problem.

$FilePaths='assets/Upload/'.$FileName; //Give proper file path with file name.

download_file($FilePaths);

function download_file( $fullPath )
{
  if( headers_sent() )
    die('Headers Sent');


  if(ini_get('zlib.output_compression'))
    ini_set('zlib.output_compression', 'Off');


  if( file_exists($fullPath) )
  {

    $fsize = filesize($fullPath);
    $path_parts = pathinfo($fullPath);
    $ext = strtolower($path_parts["extension"]);

    switch ($ext) 
    {
      case "pdf": $ctype="application/pdf"; break;
      case "exe": $ctype="application/octet-stream"; break;
      case "zip": $ctype="application/zip"; break;
      case "doc": $ctype="application/msword"; break;
      case "xls": $ctype="application/vnd.ms-excel"; break;
      case "ppt": $ctype="application/vnd.ms-powerpoint"; break;
      case "gif": $ctype="image/gif"; break;
      case "png": $ctype="image/png"; break;
      case "jpeg":
      case "jpg": $ctype="image/jpg"; break;
      default: $ctype="application/force-download";
    }

    header("Pragma: public"); 
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: private",false); 
    header("Content-Type: $ctype");
    header("Content-Disposition: attachment; filename=\"".basename($fullPath)."\";" );
    header("Content-Transfer-Encoding: binary");
    header("Content-Length: ".$fsize);
    ob_clean();
    flush();
    readfile( $fullPath );

  } 
  else
    die('File Not Found');

}

更多信息,请查看文件下载


0

你离解决方案非常接近了!我发现解决这个问题的方法是完全删除Content-Type头,让浏览器自己判断这是一个.zip文件。删除Content-Type即可满足需求。

// Remove this line 
header('Content-Type: '. $type .'/'. $subtype);

在我的控制台(Chrome)中,我收到了奇怪的信息:

资源作为文档解释,但传输的MIME类型是application/octet-stream:

资源作为文档解释,但传输的MIME类型是application/zip:

当我删除了MIME类型(Content-type header)时,.zip文件不再出错,正常工作。

使.zip文件正常工作的最基本的下载脚本如下所示:(注意:您仍然可以添加更多用于缓存等的标题)

<?php
    header("Content-Disposition: attachment; filename=\"".$Filename."\"");
    header("Content-Length: ".filesize($Filename));
    readfile($Filename);
?>

编辑

如果浏览器读取到额外的空格,您的.zip文件也会遇到问题。您可以通过添加exit();来防止readfile()末尾的额外空格,但是您需要确保文件开头没有任何空格。这在处理框架(特别是您自己的框架)时特别常见。一个小技巧:如果在缓冲区开始输出HTML之前包含所有函数、配置和类,请查看源代码。在开头会有一个点之前的空格。


-1

对于大文件下载来说,这真的很好。用户将立即收到文件。也许以这种方式压缩文件会更正确。

    if(file_exists($filename) && is_readable($filename) && file_exists($filename)){
             header("Content-Disposition: attachment; filename=".basename(str_replace(' ', '_', $filename)));
             header("Content-Type: application/force-download");
             header("Content-Type: application/octet-stream");
             header("Content-Type: application/download");
             header("Content-Description: File Transfer");
             header("Content-Length: " . filesize($filename));
             flush(); // this doesn't really matter.

             $fp = fopen($filename, "r");
             while (!feof($fp))
             {
                 echo fread($fp, 65536);
                 flush(); // this is essential for large downloads
             }
             fclose($fp);
         exit;
    }

多种内容类型。 - Relm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接