2024-08-31 01:03:37 +08:00

355 lines
12 KiB
PHP
Executable File

<?php
/*
* @link http://kodcloud.com/
* @author warlee | e-mail:kodcloud@qq.com
* @copyright warlee 2014.(Shanghai)Co.,Ltd
* @license http://kodcloud.com/tools/license/license.txt
*/
/**
* zip解压缩处理类
*
*
* http://localhost/kod/kod_dev/?editor&project=/Library/WebServer/Documents/localhost/works/zip64/
* https://blog.csdn.net/a200710716/article/details/51644421
* https://github.com/brokencube/ZipStream64/blob/14087549a4914bfc441a396ca02849569145a273/src/ZipStream.php#L808
* https://pkware.cachefly.net/webdocs/APPNOTE/APPNOTE-6.2.0.txt
*/
class ZipMake{
const VERSION = '0.2.0';
const ZIP_VERSION = 0x000A;
const ZIP_VERSION_64 = 0x002D;
const METHOD_STORE = 0x00;
const FILE_HEADER_SIGNATURE = 0x04034b50; //'504b0304'
const CDR_FILE_SIGNATURE = 0x02014b50; //'504b0102'
const CDR_EOF_SIGNATURE = 0x06054b50; //'504b0506'
const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; //'504b0708'
const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; //'504b0606'
const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; //'504b0607'
public $files = array();
public $cdrOffset = 0;
public $ofs = 0;
protected $needHeaders;
protected $outputName;
public function __construct($name = null){
$this->outputStream = fopen('php://output', 'w');
$this->outputName = $name;
$this->needHeaders = true;
}
/**
* addFileFromPath
* add a file at path to the archive.
*/
public function addFile($name, $path){
$name = $this->filterFilename($name);
$zipMethod = static::METHOD_STORE;
$headerLength = $this->addFileHeader($name,$zipMethod);
$zipLength = $fileLength = filesize_64($path);
$fh = fopen($path, 'rb');
while (!feof($fh)) {
$data = fread($fh, 1048576);
$this->send($data);
}
fclose($fh);
$crc = hexdec(hash_file('crc32b', $path));
$this->addFileFooter($name,$zipMethod, $crc, $zipLength, $fileLength, $headerLength);
}
/**
* addFile_from_stream
*/
public function addFileFromStream($name, $stream){
$name = $this->filterFilename($name);
$zipMethod = static::METHOD_STORE;
$headerLength = $this->addFileHeader($name,$zipMethod);
fseek($stream, 0, SEEK_END);
$zipLength = $fileLength = ftell($stream);
rewind($stream);
$hashCtx = hash_init('crc32b');
while (!feof($stream)) {
$data = fread($stream, 1048576);
hash_update($hashCtx, $data);
$this->send($data);
}
$crc = hexdec(hash_final($hashCtx));
$this->addFileFooter($name,$zipMethod, $crc, $zipLength, $fileLength, $headerLength);
}
public function finish(){
foreach ($this->files as $file){
$this->addCdrFile($file);
}
$this->addCdr64Eof();
$this->addCdr64Locator();
$this->addCdrEof();
$this->clear();
}
/**
* Create and send zip header for this file.
*
* @param String $name
* @param Integer $zipMethod
* @return void
*/
protected function addFileHeader($name,$zipMethod){
$name = preg_replace('/^\\/+/', '', $name);
$nlen = strlen($name);
$time = $this->dosTime(time());
$fields = array(// v=2byte,V=4byte,P=8byte
array('V', static::FILE_HEADER_SIGNATURE),
array('v', static::ZIP_VERSION_64), // 压缩版本
array('v', 0b00001000), // General purpose bit flags - data descriptor flag set
array('v', $zipMethod), // Compression method
array('V', $time), // Timestamp (DOS Format) time=2byte/date/2byte
array('V', 0x00000000), // CRC32 of data (0 -> moved to data descriptor footer)
array('V', 0xFFFFFFFF), // zip64时全1
array('V', 0xFFFFFFFF), // Length of original data (Forced to 0xFFFFFFFF for 64bit extension)
array('v', $nlen), // Length of filename
array('v', 32), // Extra data (32 bytes)
);
$fields64 = array(// 32Byte;
array('v', 0x0001), // 64Bit Extension
array('v', 28), // 28bytes of data follows
array('P', 0x0000000000000000), // Length of original data (0 -> moved to data descriptor footer)
array('P', 0x0000000000000000), // Length of compressed data (0 -> moved to data descriptor footer)
array('P', 0x0000000000000000), // Relative Header Offset
array('V', 0x00000000) // Disk number
);
$header = $this->packFields($fields);
$header64 = $this->packFields($fields64);
$this->send($header . $name . $header64);
return strlen($header) + $nlen + strlen($header64);
}
/**
* Create and send data descriptor footer for this file.
*/
protected function addFileFooter($name,$zipMethod, $crc, $zipLength, $fileLength, $headerLength){
$fields = array(
array('V', static::DATA_DESCRIPTOR_SIGNATURE),
array('V', $crc), // CRC32
array('P', $zipLength), // 压缩后大小
array('P', $fileLength), // 原始大小
);
$footer = $this->packFields($fields);
$this->send($footer);
$totalLength = $headerLength + $zipLength + $flen;
$this->addToCdr($name,$zipMethod, $crc, $zipLength, $fileLength, $totalLength);
}
/**
* Save file attributes for trailing CDR record.
*
* @param String $name
* @param Integer $zipMethod
* @param string $crc
* @param Integer $zipLength
* @param Integer $len
* @param Integer $rec_len
* @return void
* @return void
*/
private function addToCdr($name,$zipMethod, $crc, $zipLength, $len, $rec_len) {
$this->files[] = array(
$name,
$zipMethod,
$crc,
$zipLength,
$len,
$this->ofs
);
$this->ofs += $rec_len;
}
/**
* Send CDR record for specified file.
*/
protected function addCdrFile($args){
list($name,$zipMethod, $crc, $zipLength, $len, $offset) = $args;
$comment = '';
$time = $this->dosTime(time());
$fields = array(
array('V', static::CDR_FILE_SIGNATURE), // Central file header signature
array('v', static::ZIP_VERSION_64), // Made by version
array('v', static::ZIP_VERSION_64), // Extract by version
array('v', 0b00001000), // General purpose bit flags - data descriptor flag set
array('v', $zipMethod), // Compression method
array('V', $time), // Timestamp (DOS Format)
array('V', $crc), // CRC32
array('V', 0xFFFFFFFF), // Compressed Data Length (Forced to 0xFFFFFFFF for 64bit Extension)
array('V', 0xFFFFFFFF), // Original Data Length (Forced to 0xFFFFFFFF for 64bit Extension)
array('v', strlen($name)), // Length of filename
array('v', 32), // Extra data len (32bytes of 64bit Extension)
array('v', strlen($comment)), // Length of comment
array('v', 0), // Disk number
array('v', 0), // Internal File Attributes
array('V', 32), // External File Attributes
array('V', 0xFFFFFFFF) // Relative offset of local header (Forced to 0xFFFFFFFF for 64bit Extension)
);
$fields64 = array(
array('v', 0x0001), // 64Bit Extension
array('v', 28), // 28bytes of data follows
array('P', $len), // Length of original data (0 -> moved to data descriptor footer)
array('P', $zipLength), // Length of compressed data (0 -> moved to data descriptor footer)
array('P', $offset), // Relative Header Offset
array('V', 0) // Disk number
);
$header = $this->packFields($fields);
$footer = $this->packFields($fields64);
$ret = $header . $name . $comment . $footer;
$this->send($ret);
$this->cdr_ofs += strlen($ret);
}
/**
* Send ZIP64 CDR EOF (Central Directory Record End-of-File) record.
*/
protected function addCdr64Eof(){
$num = count($this->files);
$cdrLength = $this->cdr_ofs;
$cdrOffset = $this->ofs;
$fields = array(
array('V', static::ZIP64_CDR_EOF_SIGNATURE), // ZIP64 end of central file header signature
array('P', 44), // Length of data below this header (length of block - 12) = 44
array('v', static::ZIP_VERSION_64), // Made by version
array('v', static::ZIP_VERSION_64), // Extract by version
array('V', 0x00), // disk number
array('V', 0x00), // no of disks
array('P', $num), // no of entries on disk
array('P', $num), // no of entries in cdr
array('P', $cdrLength), // CDR size
array('P', $cdrOffset), // CDR offset
);
$ret = $this->packFields($fields);
$this->send($ret);
}
/**
* Send ZIP64 CDR Locator (Central Directory Record Locator) record.
*/
protected function addCdr64Locator(){
$num = count($this->files);
$cdrLength = $this->cdr_ofs;
$cdrOffset = $this->ofs;
$fields = array(
array('V', static::ZIP64_CDR_LOCATOR_SIGNATURE), // ZIP64 end of central file header signature
array('V', 0x00), // Disc number containing CDR64EOF
array('P', $cdrOffset + $cdrLength), // CDR offset
array('V', 1), // Total number of disks
);
$ret = $this->packFields($fields);
$this->send($ret);
}
/**
* Send CDR EOF (Central Directory Record End-of-File) record.
*/
protected function addCdrEof(){
$num = count($this->files);
$cdrLength = $this->cdr_ofs;
$cdrOffset = $this->ofs;
$comment = '';
$fields = array(
array('V', static::CDR_EOF_SIGNATURE), // end of central file header signature
array('v', 0x00), // disk number
array('v', 0x00), // no of disks
array('v', $num), // no of entries on disk
array('v', $num), // no of entries in cdr
array('V', 0xFFFFFFFF), // CDR size (Force to 0xFFFFFFFF for Zip64)
array('V', 0xFFFFFFFF), // CDR offset (Force to 0xFFFFFFFF for Zip64)
array('v', strlen($comment)), // Zip Comment size
);
$ret = $this->packFields($fields) . $comment;
$this->send($ret);
}
/**
* Add CDR (Central Directory Record) footer.
*/
protected function addCdr(){
foreach ($this->files as $file){
$this->addCdrFile($file);
}
$this->addCdrEof();
}
protected function clear(){
$this->files = array();
$this->ofs = 0;
$this->cdr_ofs = 0;
}
protected function sendHttpHeaders(){
$disposition = 'attachment';
if ($this->outputName) {
$safeOutput = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->outputName));
$urlencoded = rawurlencode($safeOutput);
$disposition .= "; filename*=utf-8''{$urlencoded}";
}
$headers = array(
'Content-Type' => 'application/x-zip',
'Content-Disposition' => $disposition,
'Pragma' => 'public',
'Cache-Control' => 'public, must-revalidate',
'Content-Transfer-Encoding' => 'binary'
);
foreach ($headers as $key => $value) {
header($key.': '.$value);
}
}
/**
* Send string, sending HTTP headers if necessary.
*/
protected function send($str){
if ($this->needHeaders) {
$this->sendHttpHeaders();
}
$this->needHeaders = false;
fwrite($this->outputStream, $str);
}
/**
* 转换时间戳为dos时间
*/
protected final function dosTime($when){
$d = getdate($when);
if ($d['year'] < 1980) {
$d = array(
'year' => 1980,
'mon' => 1,
'mday' => 1,
'hours' => 0,
'minutes' => 0,
'seconds' => 0
);
}
$d['year'] -= 1980;
return ($d['year'] << 25) | ($d['mon'] << 21) | ($d['mday'] << 16) |
($d['hours'] << 11) | ($d['minutes'] << 5) | ($d['seconds'] >> 1);
}
protected function packFields($fields){
$fmt = '';
$args = array();
foreach ($fields as $field) {
$fmt .= $field[0];
$args[] = $field[1];
}
array_unshift($args, $fmt);
return call_user_func_array('pack', $args);
}
protected function filterFilename($filename){
return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename);
}
}