PageRenderTime 55ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/upload/lib/utility/zip.class.php

https://github.com/bluelovers/phpwind
PHP | 303 lines | 198 code | 24 blank | 81 comment | 36 complexity | 6cc64ce33f4dc88b192310a53549c2f6 MD5 | raw file
  1. <?php
  2. !function_exists('readover') && exit('Forbidden');
  3. define('EOF_CENTRAL_DIRECTORY', 0x06054b50); //'end of central directory record'区块的标记
  4. define('LOCAL_FILE_HEADER', 0x04034b50); //'Local file header'区块标记
  5. define('CENTRAL_DIRECTORY', 0x02014b50); //'Central directory'区块标记
  6. class Zip {
  7. var $fileHeaderAndData = array();
  8. var $centralDirectory = array();
  9. var $localFileHeaderOffset = 0;
  10. var $fileHandle = '';
  11. /**
  12. * 增加待压缩的文件
  13. * @param $data string 待压缩的字符串
  14. * @param $filename string 文件名
  15. * @param $timestamp int 时间戳
  16. * @return bool
  17. */
  18. function addFile($data, $filename, $timestamp = 0){
  19. if (!$this->_checkZlib()) return false;
  20. $filename = Pcv($filename);
  21. list($modTime, $modDate) = $this->_getDosFormatTime($timestamp);
  22. $unCompressedSize = strlen($data);
  23. $crcValue = crc32($data);
  24. $compressedData = gzcompress($data);
  25. $compressedData = substr(substr($compressedData, 0, strlen($compressedData) - 4), 2); // crc problem
  26. $compressedSize = strlen($compressedData);
  27. $filenameLength = strlen($filename);
  28. $header = pack('V', LOCAL_FILE_HEADER); // local file header signature
  29. $header .= "\x14\x00"; // version needed to extract
  30. $header .= "\x00\x00"; // general purpose bit flag
  31. $header .= "\x08\x00"; // compression method, deflated used here
  32. $header .= pack('vv', $modTime, $modDate); // last mod file time, last mod file date
  33. $header .= pack('V', $crcValue); // crc-32
  34. $header .= pack('V', $compressedSize); // compressed size
  35. $header .= pack('V', $unCompressedSize); // uncompressed size
  36. $header .= pack('v', $filenameLength); // file name length
  37. $header .= pack('v', 0); // extra field length
  38. $header .= $filename; // filename
  39. $header .= $compressedData; // file data
  40. $header .= $this->_getDataDescriptor($crcValue, $compressedSize, $unCompressedSize); // Data descriptor
  41. $this->fileHeaderAndData[] = $header;
  42. //central directory
  43. $this->centralDirectory[] = $this->_getCentralDirectory($modTime, $modDate, $crcValue, $compressedSize, $unCompressedSize, $filenameLength, strlen($header), $filename);
  44. return true;
  45. }
  46. /**
  47. * 返回压缩后的数据
  48. * @return string 压缩后的数据
  49. */
  50. function getCompressedFile(){
  51. $fileHeaderAndData = implode('', $this->fileHeaderAndData);
  52. $centralDirectory = implode('', $this->centralDirectory);
  53. $return = $fileHeaderAndData . $centralDirectory;
  54. $return .= pack('V', EOF_CENTRAL_DIRECTORY); // end of central dir signature
  55. $return .= pack('v', 0); // Number of this disk
  56. $return .= pack('v', 0); // Disk where central directory starts
  57. $return .= pack('v', count($this->centralDirectory)); // central directory on this disk
  58. $return .= pack('v', count($this->centralDirectory)); // total number of entries in the central directory
  59. $return .= pack('V', strlen($centralDirectory)); // size of central dir
  60. $return .= pack('V', strlen($fileHeaderAndData)); // offset to start of central dir
  61. $return .= "\x00\x00"; // .zip file comment length
  62. return $return;
  63. }
  64. /**
  65. * 解压缩一个文件
  66. * @param $file string 文件名
  67. * @return array 解压缩后的数据,其中包括时间、文件名、数据
  68. */
  69. function extract($file) {
  70. $extractedData = array();
  71. $file = Pcv($file);
  72. if (!$file || !is_file($file)) return false;
  73. $filesize = sprintf('%u', filesize($file));
  74. $this->fileHandle = fopen($file, 'rb');
  75. $fileData = fread($this->fileHandle, $filesize);
  76. $EofCentralDirData = $this->_findEOFCentralDirectoryRecord($filesize); //获取'End of central directory record'区块的数据
  77. if (!is_array($EofCentralDirData)) return false;
  78. $centralDirectoryHeaderOffset = $EofCentralDirData['centraldiroffset'];
  79. for ($i = 0; $i < $EofCentralDirData['totalentries']; $i++) {
  80. rewind($this->fileHandle);
  81. fseek($this->fileHandle, $centralDirectoryHeaderOffset);
  82. $centralDirectoryData = $this->_readCentralDirectoryData(); // 获取'Central directory' 区块数据
  83. $centralDirectoryHeaderOffset += 46 + $centralDirectoryData['filenamelength'] + $centralDirectoryData['extrafieldlength'] + $centralDirectoryData['commentlength'];
  84. if (!is_array($centralDirectoryData) || substr($centralDirectoryData['filename'], -1) == '/') continue;
  85. $data = $this->_readLocalFileHeaderAndData($centralDirectoryData); // 获取压缩的数据
  86. if (!$data) continue;
  87. $extractedData[$i] = array(
  88. 'filename' => $centralDirectoryData['filename'],
  89. 'timestamp' => $centralDirectoryData['time'],
  90. 'data' => $data,
  91. );
  92. }
  93. fclose($this->fileHandle);
  94. return $extractedData;
  95. }
  96. /**
  97. * 初始化
  98. */
  99. function init() {
  100. $this->fileHeaderAndData = $this->centralDirectory = array();
  101. $this->localFileHeaderOffset = 0;
  102. return true;
  103. }
  104. /**
  105. * 取得压缩数据中的'Local file header'区块跟压缩的数据
  106. * @param $centralDirectoryData array 'Central directory' 区块数据
  107. * @return array
  108. */
  109. function _readLocalFileHeaderAndData($centralDirectoryData) {
  110. fseek($this->fileHandle, $centralDirectoryData['localheaderoffset']);
  111. $localFileHeaderSignature = unpack('Vsignature', fread($this->fileHandle, 4)); // 'Local file header' 区块的标记
  112. if ($localFileHeaderSignature['signature'] != 0x04034b50) return false;
  113. $localFileHeaderData = fread($this->fileHandle, 26); // 'Local file header' 除标记, file name, extra field 外的数据
  114. $localFileHeaderData = unpack('vextractversion/vflag/vcompressmethod/vmodtime/vmoddate/Vcrc/Vcompressedsize/Vuncompressedsize/vfilenamelength/vextrafieldlength', $localFileHeaderData);
  115. $localFileHeaderData['filenamelength'] && $localFileHeaderData['filename'] = fread($this->fileHandle, $localFileHeaderData['filenamelength']); //读取文件名
  116. $localFileHeaderData['extrafieldlength'] && $localFileHeaderData['extrafield'] = fread($this->fileHandle, $localFileHeaderData['extrafieldlength']); //读取extra field
  117. if (!$this->_checkLocalFileHeaderAndCentralDir($localFileHeaderData, $centralDirectoryData)) return false;
  118. if ($localFileHeaderData['flag'] & 1) return false; //文件加密过
  119. $compressedData = fread($this->fileHandle, $localFileHeaderData['compressedsize']);
  120. $data = $this->_unCompressData($compressedData, $localFileHeaderData['compressmethod']);
  121. if (crc32($data) != $localFileHeaderData['crc'] || strlen($data) != $localFileHeaderData['uncompressedsize']) return false; //crc32 校验不一致或长度不一致
  122. return $data;
  123. }
  124. /**
  125. * 解压被压缩的数据
  126. * @param $data string 被压缩的数据
  127. * @param $compressMethod int 压缩的方式
  128. * @return string 解压后的数据
  129. */
  130. function _unCompressData($data, $compressMethod) { // 根据具体的压缩方式解压缩,目前仅支持deflate 压缩方式有deflate, deflate64, bzip2 等
  131. if (!$compressMethod) return $data;
  132. switch ($compressMethod) {
  133. case 8 : // compressed by deflate
  134. $data = gzinflate($data);
  135. break;
  136. default :
  137. return false;
  138. break;
  139. }
  140. return $data;
  141. }
  142. /**
  143. * 校验 'Local file header' 跟 'Central directory'
  144. * @param unknown_type $localFileHeaderData
  145. * @param unknown_type $centralDirectoryData
  146. * @return bool
  147. */
  148. function _checkLocalFileHeaderAndCentralDir($localFileHeaderData, $centralDirectoryData) {
  149. return true; //暂时不验证,有需要时可扩展
  150. }
  151. /**
  152. * 读取'Central directory' 区块数据
  153. * @return string
  154. */
  155. function _readCentralDirectoryData() {
  156. $centralDirectorySignature = unpack('Vsignature', fread($this->fileHandle, 4)); // 'Central directory' 区块的标记
  157. if ($centralDirectorySignature['signature'] != 0x02014b50) return false;
  158. $centralDirectoryData = fread($this->fileHandle, 42); // 'Central directory' 区块除标记, file name, extra field, file comment 外的数据
  159. $centralDirectoryData = unpack('vmadeversion/vextractversion/vflag/vcompressmethod/vmodtime/vmoddate/Vcrc/Vcompressedsize/Vuncompressedsize/vfilenamelength/vextrafieldlength/vcommentlength/vdiskstart/vinternal/Vexternal/Vlocalheaderoffset', $centralDirectoryData);
  160. $centralDirectoryData['filenamelength'] && $centralDirectoryData['filename'] = fread($this->fileHandle, $centralDirectoryData['filenamelength']); //读取文件名
  161. $centralDirectoryData['extrafieldlength'] && $centralDirectoryData['extrafield'] = fread($this->fileHandle, $centralDirectoryData['extrafieldlength']); //读取extra field
  162. $centralDirectoryData['commentlength'] && $centralDirectoryData['comment'] = fread($this->fileHandle, $centralDirectoryData['commentlength']); //读取 file comment
  163. $centralDirectoryData['time'] = $this->_recoverFromDosFormatTime($centralDirectoryData['modtime'], $centralDirectoryData['moddate']); //读取时间信息
  164. return $centralDirectoryData;
  165. }
  166. /**
  167. * 读取'end of central directory record'区块数据
  168. * @param $filesize int 文件大小
  169. * @return string
  170. */
  171. function _findEOFCentralDirectoryRecord($filesize) {
  172. fseek($this->fileHandle, $filesize - 22); // 'End of central directory record' 一般在没有注释的情况下位于该位置
  173. $EofCentralDirSignature = unpack('Vsignature', fread($this->fileHandle, 4));
  174. if ($EofCentralDirSignature['signature'] != 0x06054b50) { // 'End of central directory record' 不在末尾22个字节的位置,即有注释的情况
  175. $maxLength = 65535 + 22; //'End of central directory record' 区块最大可能的长度,因为保存注释长度的区块的长度为2字节,2个字节最大可保存的长度是65535,即0xFFFF。22为'End of central directory record' 除去注释后的长度
  176. $maxLength > $filesize && $maxLength = $filesize; //最大不能超多整个文件的大小
  177. fseek($this->fileHandle, $filesize - $maxLength);
  178. $searchPos = ftell($this->fileHandle);
  179. while ($searchPos < $filesize) {
  180. fseek($this->fileHandle, $searchPos);
  181. $sigData = unpack('Vsignature', fread($this->fileHandle, 4));
  182. if ($sigData['signature'] == 0x06054b50) {
  183. break;
  184. }
  185. $searchPos++;
  186. }
  187. }
  188. $EofCentralDirData = unpack('vdisknum/vdiskstart/vcentraldirnum/vtotalentries/Vcentraldirsize/Vcentraldiroffset/vcommentlength', fread($this->fileHandle, 18)); // 'End of central directory record'区块除signature跟注释外的数据
  189. $EofCentralDirData['commentlength'] && $EofCentralDirData['comment'] = fread($this->fileHandle, $EofCentralDirData['commentlength']);
  190. return $EofCentralDirData;
  191. }
  192. /**
  193. * 检查PHP zlib扩展有没有载入
  194. * @return bool
  195. */
  196. function _checkZlib() {
  197. return (extension_loaded('zlib') && function_exists('gzcompress')) ? true : false;
  198. }
  199. /**
  200. * 组装 'Central directory' 区块数据
  201. * @param $modTime
  202. * @param $modDate
  203. * @param $crc
  204. * @param $compressedSize
  205. * @param $unCompressedSize
  206. * @param $filenameLength
  207. * @param $fileHeaderLength
  208. * @param $filename
  209. * @return string
  210. */
  211. function _getCentralDirectory($modTime, $modDate, $crc, $compressedSize, $unCompressedSize, $filenameLength, $fileHeaderLength, $filename) {
  212. $centralDirectory = pack('V', CENTRAL_DIRECTORY); // central file header signature
  213. $centralDirectory .= "\x00\x00"; // version made by
  214. $centralDirectory .= "\x14\x00"; // version needed to extract
  215. $centralDirectory .= "\x00\x00"; // general purpose bit flag
  216. $centralDirectory .= "\x08\x00"; // compression method
  217. $centralDirectory .= pack('vv', $modTime, $modDate); // last mod file time, last mod file date
  218. $centralDirectory .= pack('V', $crc); // crc-32
  219. $centralDirectory .= pack('V', $compressedSize); // compressed size
  220. $centralDirectory .= pack('V', $unCompressedSize); // uncompressed size
  221. $centralDirectory .= pack('v', $filenameLength); // file name length
  222. $centralDirectory .= pack('v', 0 ); // extra field length
  223. $centralDirectory .= pack('v', 0 ); // file comment length
  224. $centralDirectory .= pack('v', 0 ); // disk number start
  225. $centralDirectory .= pack('v', 0 ); // internal file attributes
  226. $centralDirectory .= pack('V', 32 ); // external file attributes - 'archive' bit set
  227. $centralDirectory .= pack('V', $this->localFileHeaderOffset); // relative offset of local header
  228. $this->localFileHeaderOffset += $fileHeaderLength;
  229. $centralDirectory .= $filename; // file name
  230. return $centralDirectory;
  231. }
  232. /**
  233. * 组装 'Data descriptor' 区块数据
  234. * @param $crc
  235. * @param $compressedSize
  236. * @param $unCompressedSize
  237. * @return string
  238. */
  239. function _getDataDescriptor($crc, $compressedSize, $unCompressedSize) {
  240. return ''; // return string only when bit 3 of the general purpose bit flag is set
  241. //return pack('VVV', $crc, $compressedSize, $unCompressedSize);
  242. }
  243. /**
  244. * 格式化时间为DOS格式
  245. * @param $timestamp
  246. * @return array
  247. */
  248. function _getDosFormatTime($timestamp = 0) {
  249. $timestamp = (int) $timestamp;
  250. $time = $timestamp === 0 ? getdate() : getdate($timestamp);
  251. if ($time['year'] < 1980) {
  252. $time['year'] = 1980;
  253. $time['mon'] = 1;
  254. $time['mday'] = 1;
  255. $time['hours'] = 0;
  256. $time['minutes'] = 0;
  257. $time['seconds'] = 0;
  258. }
  259. $modTime = ($time['hours'] << 11) + ($time['minutes'] << 5) + $time['seconds'] / 2;
  260. $modDate = (($time['year'] - 1980) << 9) + ($time['mon'] << 5) + $time['mday'];
  261. return array($modTime, $modDate);
  262. }
  263. /**
  264. * 还原DOS格式的时间为时间戳
  265. * @param $time
  266. * @param $date
  267. * @return int
  268. */
  269. function _recoverFromDosFormatTime($time, $date) {
  270. $year = (($date & 0xFE00) >> 9) + 1980;
  271. $month = ($date & 0x01E0) >> 5;
  272. $day = $date & 0x001F;
  273. $hour = ($time & 0xF800) >> 11;
  274. $minutes = ($time & 0x07E0) >> 5;
  275. $seconds = ($time & 0x001F)*2;
  276. return mktime($hour, $minutes, $seconds, $month, $day, $year);
  277. }
  278. }
  279. ?>