1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26:
27: class Horde_Compress_Zip extends Horde_Compress_Base
28: {
29:
30: const ZIP_LIST = 1;
31: const ZIP_DATA = 2;
32:
33:
34: const = "\x50\x4b\x01\x02";
35:
36:
37: const CTRL_DIR_END = "\x50\x4b\x05\x06\x00\x00\x00\x00";
38:
39:
40: const = "\x50\x4b\x03\x04";
41:
42: 43:
44: public $canCompress = true;
45:
46: 47:
48: public $canDecompress = true;
49:
50: 51: 52: 53: 54:
55: protected $_methods = array(
56: 0x0 => 'None',
57: 0x1 => 'Shrunk',
58: 0x2 => 'Super Fast',
59: 0x3 => 'Fast',
60: 0x4 => 'Normal',
61: 0x5 => 'Maximum',
62: 0x6 => 'Imploded',
63: 0x8 => 'Deflated'
64: );
65:
66: 67: 68: 69: 70:
71: protected $_ctrldir;
72:
73: 74: 75: 76: 77:
78: protected $_tmp;
79:
80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96:
97: public function compress($data, $params = array())
98: {
99: if (!Horde_Util::extensionExists('zlib')) {
100: throw new Horde_Compress_Exception(Horde_Compress_Translation::t("This server can't compress zip files."));
101: }
102:
103: $this->_ctrldir = array();
104: $this->_tmp = fopen('php://temp', 'r+');
105:
106: reset($data);
107: while (list(, $val) = each($data)) {
108: $this->_addToZipFile($val);
109: }
110:
111: 112:
113: $dir = implode('', $this->_ctrldir);
114:
115: fseek($this->_tmp, 0, SEEK_END);
116: $offset = ftell($this->_tmp);
117:
118: fwrite($this->_tmp,
119: $dir . self::CTRL_DIR_END .
120:
121: pack('v', count($this->_ctrldir)) .
122:
123: pack('v', count($this->_ctrldir)) .
124:
125: pack('V', strlen($dir)) .
126:
127: pack('V', $offset) .
128:
129: "\x00\x00"
130: );
131:
132: rewind($this->_tmp);
133:
134: if (empty($params['stream'])) {
135: $out = stream_get_contents($this->_tmp);
136: fclose($this->_tmp);
137: return $out;
138: }
139:
140: return $this->_tmp;
141: }
142:
143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170:
171: public function decompress($data, array $params = array())
172: {
173: if (isset($params['action'])) {
174: switch ($params['action']) {
175: case self::ZIP_LIST:
176: return $this->_getZipInfo($data);
177:
178: case self::ZIP_DATA:
179: return $this->_getZipData($data, $params['info'], $params['key']);
180: }
181: }
182: }
183:
184: 185: 186: 187: 188: 189: 190: 191: 192:
193: protected function _getZipInfo($data)
194: {
195: $entries = array();
196:
197:
198: $fhStart = strpos($data, self::CTRL_DIR_HEADER);
199:
200: do {
201: if (strlen($data) < $fhStart + 31) {
202: throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid ZIP data"));
203: }
204: $info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength', substr($data, $fhStart + 10, 20));
205:
206: if (!isset($this->_methods[$info['Method']])) {
207: throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid ZIP data"));
208: }
209:
210: $name = substr($data, $fhStart + 46, $info['Length']);
211:
212: $entries[$name] = array(
213: 'attr' => null,
214: 'crc' => sprintf("%08s", dechex($info['CRC32'])),
215: 'csize' => $info['Compressed'],
216: 'date' => null,
217: '_dataStart' => null,
218: 'name' => $name,
219: 'method' => $this->_methods[$info['Method']],
220: '_method' => $info['Method'],
221: 'size' => $info['Uncompressed'],
222: 'type' => null
223: );
224:
225: $entries[$name]['date'] =
226: mktime((($info['Time'] >> 11) & 0x1f),
227: (($info['Time'] >> 5) & 0x3f),
228: (($info['Time'] << 1) & 0x3e),
229: (($info['Time'] >> 21) & 0x07),
230: (($info['Time'] >> 16) & 0x1f),
231: ((($info['Time'] >> 25) & 0x7f) + 1980));
232:
233: if (strlen($data) < $fhStart + 43) {
234: throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid ZIP data"));
235: }
236: $info = unpack('vInternal/VExternal', substr($data, $fhStart + 36, 6));
237:
238: $entries[$name]['type'] = ($info['Internal'] & 0x01) ? 'text' : 'binary';
239: $entries[$name]['attr'] =
240: (($info['External'] & 0x10) ? 'D' : '-') .
241: (($info['External'] & 0x20) ? 'A' : '-') .
242: (($info['External'] & 0x03) ? 'S' : '-') .
243: (($info['External'] & 0x02) ? 'H' : '-') .
244: (($info['External'] & 0x01) ? 'R' : '-');
245: } while (($fhStart = strpos($data, self::CTRL_DIR_HEADER, $fhStart + 46)) !== false);
246:
247:
248: $fhStart = strpos($data, self::FILE_HEADER);
249:
250: $data_len = strlen($data);
251:
252: do {
253: if ($data_len < $fhStart + 34) {
254: throw new Horde_Compress_Exception(Horde_Compress_Translation::t("Invalid ZIP data"));
255: }
256: $info = unpack('vMethod/VTime/VCRC32/VCompressed/VUncompressed/vLength/vExtraLength', substr($data, $fhStart + 8, 25));
257: $name = substr($data, $fhStart + 30, $info['Length']);
258: if (isset($entries[$name])) {
259: $entries[$name]['_dataStart'] = $fhStart + 30 + $info['Length'] + $info['ExtraLength'];
260: }
261: } while ($data_len > $fhStart + 30 + $info['Length'] &&
262: ($fhStart = strpos($data, self::FILE_HEADER, $fhStart + 30 + $info['Length'])) !== false);
263:
264: return array_values($entries);
265: }
266:
267: 268: 269: 270: 271: 272: 273: 274: 275:
276: protected function _getZipData($data, $info, $key)
277: {
278: if (($info[$key]['_method'] == 0x8) &&
279: Horde_Util::extensionExists('zlib')) {
280: 281:
282: return @gzinflate(substr($data, $info[$key]['_dataStart'], $info[$key]['csize']));
283: } elseif ($info[$key]['_method'] == 0x0) {
284:
285: return substr($data, $info[$key]['_dataStart'], $info[$key]['csize']);
286: }
287:
288: return '';
289: }
290:
291: 292: 293: 294: 295: 296: 297:
298: public function checkZipData($data)
299: {
300: return (strpos($data, self::FILE_HEADER) !== false);
301: }
302:
303: 304: 305: 306: 307: 308: 309: 310: 311:
312: protected function _unix2DOSTime($unixtime = null)
313: {
314: $timearray = (is_null($unixtime)) ? getdate() : getdate($unixtime);
315:
316: if ($timearray['year'] < 1980) {
317: $timearray['year'] = 1980;
318: $timearray['mon'] = 1;
319: $timearray['mday'] = 1;
320: $timearray['hours'] = 0;
321: $timearray['minutes'] = 0;
322: $timearray['seconds'] = 0;
323: }
324:
325: return (($timearray['year'] - 1980) << 25) |
326: ($timearray['mon'] << 21) |
327: ($timearray['mday'] << 16) |
328: ($timearray['hours'] << 11) |
329: ($timearray['minutes'] << 5) |
330: ($timearray['seconds'] >> 1);
331: }
332:
333: 334: 335: 336: 337:
338: protected function _addToZipFile($file)
339: {
340: $name = str_replace('\\', '/', $file['name']);
341:
342:
343: $ftime = (isset($file['time'])) ? $file['time'] : null;
344:
345:
346: $dtime = dechex($this->_unix2DosTime($ftime));
347: $hexdtime = chr(hexdec($dtime[6] . $dtime[7])) .
348: chr(hexdec($dtime[4] . $dtime[5])) .
349: chr(hexdec($dtime[2] . $dtime[3])) .
350: chr(hexdec($dtime[0] . $dtime[1]));
351:
352:
353: if (is_resource($file['data'])) {
354: $zdata = fopen('php://temp', 'r+');
355:
356: $params = new stdClass;
357: stream_filter_register('horde_compress_filter_crc32', 'Horde_Stream_Filter_Crc32');
358: $filter = stream_filter_prepend($file['data'], 'horde_compress_filter_crc32', STREAM_FILTER_READ, $params);
359: $filter2 = stream_filter_append($zdata, 'zlib.deflate', STREAM_FILTER_WRITE);
360:
361: rewind($file['data']);
362: stream_copy_to_stream($file['data'], $zdata);
363:
364: $crc = $params->crc32;
365: $unc_len = ftell($file['data']);
366:
367: stream_filter_remove($filter2);
368: stream_filter_remove($filter);
369:
370: fseek($zdata, 0, SEEK_END);
371: $c_len = ftell($zdata);
372: } else {
373: $unc_len = strlen($file['data']);
374: $crc = crc32($file['data']);
375: $zdata = gzdeflate($file['data']);
376: $c_len = strlen($zdata);
377: }
378:
379:
380: $common =
381: "\x14\x00" .
382: "\x00\x00" .
383: "\x08\x00" .
384: $hexdtime .
385: pack('V', $crc) .
386: pack('V', $c_len) .
387: pack('V', $unc_len) .
388: pack('v', strlen($name)) .
389: pack('v', 0);
390:
391:
392: fseek($this->_tmp, 0, SEEK_END);
393: $old_offset = ftell($this->_tmp);
394:
395: fwrite($this->_tmp,
396: self::FILE_HEADER .
397: $common .
398: $name
399: );
400:
401:
402: if (is_resource($zdata)) {
403: rewind($zdata);
404: stream_copy_to_stream($zdata, $this->_tmp);
405: } else {
406: fwrite($this->_tmp, $zdata);
407: }
408:
409:
410: $this->_ctrldir[] =
411: self::CTRL_DIR_HEADER .
412: "\x00\x00" .
413: $common .
414: pack('v', 0) .
415: pack('v', 0) .
416: pack('v', 0) .
417: pack('V', 32) . 418:
419: pack('V', $old_offset) . 420:
421: $name;
422: }
423:
424: }
425: