diff options
Diffstat (limited to 'inc/3rdparty/libraries/PHPePub/Zip.php')
-rw-r--r-- | inc/3rdparty/libraries/PHPePub/Zip.php | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/inc/3rdparty/libraries/PHPePub/Zip.php b/inc/3rdparty/libraries/PHPePub/Zip.php new file mode 100644 index 00000000..01e03566 --- /dev/null +++ b/inc/3rdparty/libraries/PHPePub/Zip.php | |||
@@ -0,0 +1,818 @@ | |||
1 | <?php | ||
2 | /** | ||
3 | * Class to create and manage a Zip file. | ||
4 | * | ||
5 | * Initially inspired by CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) | ||
6 | * and | ||
7 | * http://www.pkware.com/documents/casestudies/APPNOTE.TXT Zip file specification. | ||
8 | * | ||
9 | * License: GNU LGPL, Attribution required for commercial implementations, requested for everything else. | ||
10 | * | ||
11 | * @author A. Grandt <php@grandt.com> | ||
12 | * @copyright 2009-2014 A. Grandt | ||
13 | * @license GNU LGPL 2.1 | ||
14 | * @link http://www.phpclasses.org/package/6110 | ||
15 | * @link https://github.com/Grandt/PHPZip | ||
16 | * @version 1.60 | ||
17 | */ | ||
18 | class Zip { | ||
19 | const VERSION = 1.60; | ||
20 | |||
21 | const ZIP_LOCAL_FILE_HEADER = "\x50\x4b\x03\x04"; // Local file header signature | ||
22 | const ZIP_CENTRAL_FILE_HEADER = "\x50\x4b\x01\x02"; // Central file header signature | ||
23 | const ZIP_END_OF_CENTRAL_DIRECTORY = "\x50\x4b\x05\x06\x00\x00\x00\x00"; //end of Central directory record | ||
24 | |||
25 | const EXT_FILE_ATTR_DIR = 010173200020; // Permission 755 drwxr-xr-x = (((S_IFDIR | 0755) << 16) | S_DOS_D); | ||
26 | const EXT_FILE_ATTR_FILE = 020151000040; // Permission 644 -rw-r--r-- = (((S_IFREG | 0644) << 16) | S_DOS_A); | ||
27 | |||
28 | const ATTR_VERSION_TO_EXTRACT = "\x14\x00"; // Version needed to extract | ||
29 | const ATTR_MADE_BY_VERSION = "\x1E\x03"; // Made By Version | ||
30 | |||
31 | // Unix file types | ||
32 | const S_IFIFO = 0010000; // named pipe (fifo) | ||
33 | const S_IFCHR = 0020000; // character special | ||
34 | const S_IFDIR = 0040000; // directory | ||
35 | const S_IFBLK = 0060000; // block special | ||
36 | const S_IFREG = 0100000; // regular | ||
37 | const S_IFLNK = 0120000; // symbolic link | ||
38 | const S_IFSOCK = 0140000; // socket | ||
39 | |||
40 | // setuid/setgid/sticky bits, the same as for chmod: | ||
41 | |||
42 | const S_ISUID = 0004000; // set user id on execution | ||
43 | const S_ISGID = 0002000; // set group id on execution | ||
44 | const S_ISTXT = 0001000; // sticky bit | ||
45 | |||
46 | // And of course, the other 12 bits are for the permissions, the same as for chmod: | ||
47 | // When addding these up, you can also just write the permissions as a simgle octal number | ||
48 | // ie. 0755. The leading 0 specifies octal notation. | ||
49 | const S_IRWXU = 0000700; // RWX mask for owner | ||
50 | const S_IRUSR = 0000400; // R for owner | ||
51 | const S_IWUSR = 0000200; // W for owner | ||
52 | const S_IXUSR = 0000100; // X for owner | ||
53 | const S_IRWXG = 0000070; // RWX mask for group | ||
54 | const S_IRGRP = 0000040; // R for group | ||
55 | const S_IWGRP = 0000020; // W for group | ||
56 | const S_IXGRP = 0000010; // X for group | ||
57 | const S_IRWXO = 0000007; // RWX mask for other | ||
58 | const S_IROTH = 0000004; // R for other | ||
59 | const S_IWOTH = 0000002; // W for other | ||
60 | const S_IXOTH = 0000001; // X for other | ||
61 | const S_ISVTX = 0001000; // save swapped text even after use | ||
62 | |||
63 | // Filetype, sticky and permissions are added up, and shifted 16 bits left BEFORE adding the DOS flags. | ||
64 | |||
65 | // DOS file type flags, we really only use the S_DOS_D flag. | ||
66 | |||
67 | const S_DOS_A = 0000040; // DOS flag for Archive | ||
68 | const S_DOS_D = 0000020; // DOS flag for Directory | ||
69 | const S_DOS_V = 0000010; // DOS flag for Volume | ||
70 | const S_DOS_S = 0000004; // DOS flag for System | ||
71 | const S_DOS_H = 0000002; // DOS flag for Hidden | ||
72 | const S_DOS_R = 0000001; // DOS flag for Read Only | ||
73 | |||
74 | private $zipMemoryThreshold = 1048576; // Autocreate tempfile if the zip data exceeds 1048576 bytes (1 MB) | ||
75 | |||
76 | private $zipData = NULL; | ||
77 | private $zipFile = NULL; | ||
78 | private $zipComment = NULL; | ||
79 | private $cdRec = array(); // central directory | ||
80 | private $offset = 0; | ||
81 | private $isFinalized = FALSE; | ||
82 | private $addExtraField = TRUE; | ||
83 | |||
84 | private $streamChunkSize = 65536; | ||
85 | private $streamFilePath = NULL; | ||
86 | private $streamTimestamp = NULL; | ||
87 | private $streamFileComment = NULL; | ||
88 | private $streamFile = NULL; | ||
89 | private $streamData = NULL; | ||
90 | private $streamFileLength = 0; | ||
91 | private $streamExtFileAttr = null; | ||
92 | |||
93 | /** | ||
94 | * Constructor. | ||
95 | * | ||
96 | * @param boolean $useZipFile Write temp zip data to tempFile? Default FALSE | ||
97 | */ | ||
98 | function __construct($useZipFile = FALSE) { | ||
99 | if ($useZipFile) { | ||
100 | $this->zipFile = tmpfile(); | ||
101 | } else { | ||
102 | $this->zipData = ""; | ||
103 | } | ||
104 | } | ||
105 | |||
106 | function __destruct() { | ||
107 | if (is_resource($this->zipFile)) { | ||
108 | fclose($this->zipFile); | ||
109 | } | ||
110 | $this->zipData = NULL; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Extra fields on the Zip directory records are Unix time codes needed for compatibility on the default Mac zip archive tool. | ||
115 | * These are enabled as default, as they do no harm elsewhere and only add 26 bytes per file added. | ||
116 | * | ||
117 | * @param bool $setExtraField TRUE (default) will enable adding of extra fields, anything else will disable it. | ||
118 | */ | ||
119 | function setExtraField($setExtraField = TRUE) { | ||
120 | $this->addExtraField = ($setExtraField === TRUE); | ||
121 | } | ||
122 | |||
123 | /** | ||
124 | * Set Zip archive comment. | ||
125 | * | ||
126 | * @param string $newComment New comment. NULL to clear. | ||
127 | * @return bool $success | ||
128 | */ | ||
129 | public function setComment($newComment = NULL) { | ||
130 | if ($this->isFinalized) { | ||
131 | return FALSE; | ||
132 | } | ||
133 | $this->zipComment = $newComment; | ||
134 | |||
135 | return TRUE; | ||
136 | } | ||
137 | |||
138 | /** | ||
139 | * Set zip file to write zip data to. | ||
140 | * This will cause all present and future data written to this class to be written to this file. | ||
141 | * This can be used at any time, even after the Zip Archive have been finalized. Any previous file will be closed. | ||
142 | * Warning: If the given file already exists, it will be overwritten. | ||
143 | * | ||
144 | * @param string $fileName | ||
145 | * @return bool $success | ||
146 | */ | ||
147 | public function setZipFile($fileName) { | ||
148 | if (is_file($fileName)) { | ||
149 | unlink($fileName); | ||
150 | } | ||
151 | $fd=fopen($fileName, "x+b"); | ||
152 | if (is_resource($this->zipFile)) { | ||
153 | rewind($this->zipFile); | ||
154 | while (!feof($this->zipFile)) { | ||
155 | fwrite($fd, fread($this->zipFile, $this->streamChunkSize)); | ||
156 | } | ||
157 | |||
158 | fclose($this->zipFile); | ||
159 | } else { | ||
160 | fwrite($fd, $this->zipData); | ||
161 | $this->zipData = NULL; | ||
162 | } | ||
163 | $this->zipFile = $fd; | ||
164 | |||
165 | return TRUE; | ||
166 | } | ||
167 | |||
168 | /** | ||
169 | * Add an empty directory entry to the zip archive. | ||
170 | * Basically this is only used if an empty directory is added. | ||
171 | * | ||
172 | * @param string $directoryPath Directory Path and name to be added to the archive. | ||
173 | * @param int $timestamp (Optional) Timestamp for the added directory, if omitted or set to 0, the current time will be used. | ||
174 | * @param string $fileComment (Optional) Comment to be added to the archive for this directory. To use fileComment, timestamp must be given. | ||
175 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
176 | * @return bool $success | ||
177 | */ | ||
178 | public function addDirectory($directoryPath, $timestamp = 0, $fileComment = NULL, $extFileAttr = self::EXT_FILE_ATTR_DIR) { | ||
179 | if ($this->isFinalized) { | ||
180 | return FALSE; | ||
181 | } | ||
182 | $directoryPath = str_replace("\\", "/", $directoryPath); | ||
183 | $directoryPath = rtrim($directoryPath, "/"); | ||
184 | |||
185 | if (strlen($directoryPath) > 0) { | ||
186 | $this->buildZipEntry($directoryPath.'/', $fileComment, "\x00\x00", "\x00\x00", $timestamp, "\x00\x00\x00\x00", 0, 0, $extFileAttr); | ||
187 | return TRUE; | ||
188 | } | ||
189 | return FALSE; | ||
190 | } | ||
191 | |||
192 | /** | ||
193 | * Add a file to the archive at the specified location and file name. | ||
194 | * | ||
195 | * @param string $data File data. | ||
196 | * @param string $filePath Filepath and name to be used in the archive. | ||
197 | * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. | ||
198 | * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. | ||
199 | * @param bool $compress (Optional) Compress file, if set to FALSE the file will only be stored. Default TRUE. | ||
200 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
201 | * @return bool $success | ||
202 | */ | ||
203 | public function addFile($data, $filePath, $timestamp = 0, $fileComment = NULL, $compress = TRUE, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
204 | if ($this->isFinalized) { | ||
205 | return FALSE; | ||
206 | } | ||
207 | |||
208 | if (is_resource($data) && get_resource_type($data) == "stream") { | ||
209 | $this->addLargeFile($data, $filePath, $timestamp, $fileComment, $extFileAttr); | ||
210 | return FALSE; | ||
211 | } | ||
212 | |||
213 | $gzData = ""; | ||
214 | $gzType = "\x08\x00"; // Compression type 8 = deflate | ||
215 | $gpFlags = "\x00\x00"; // General Purpose bit flags for compression type 8 it is: 0=Normal, 1=Maximum, 2=Fast, 3=super fast compression. | ||
216 | $dataLength = strlen($data); | ||
217 | $fileCRC32 = pack("V", crc32($data)); | ||
218 | |||
219 | if ($compress) { | ||
220 | $gzTmp = gzcompress($data); | ||
221 | $gzData = substr(substr($gzTmp, 0, strlen($gzTmp) - 4), 2); // gzcompress adds a 2 byte header and 4 byte CRC we can't use. | ||
222 | // The 2 byte header does contain useful data, though in this case the 2 parameters we'd be interrested in will always be 8 for compression type, and 2 for General purpose flag. | ||
223 | $gzLength = strlen($gzData); | ||
224 | } else { | ||
225 | $gzLength = $dataLength; | ||
226 | } | ||
227 | |||
228 | if ($gzLength >= $dataLength) { | ||
229 | $gzLength = $dataLength; | ||
230 | $gzData = $data; | ||
231 | $gzType = "\x00\x00"; // Compression type 0 = stored | ||
232 | $gpFlags = "\x00\x00"; // Compression type 0 = stored | ||
233 | } | ||
234 | |||
235 | if (!is_resource($this->zipFile) && ($this->offset + $gzLength) > $this->zipMemoryThreshold) { | ||
236 | $this->zipflush(); | ||
237 | } | ||
238 | |||
239 | $this->buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr); | ||
240 | |||
241 | $this->zipwrite($gzData); | ||
242 | |||
243 | return TRUE; | ||
244 | } | ||
245 | |||
246 | /** | ||
247 | * Add the content to a directory. | ||
248 | * | ||
249 | * @author Adam Schmalhofer <Adam.Schmalhofer@gmx.de> | ||
250 | * @author A. Grandt | ||
251 | * | ||
252 | * @param string $realPath Path on the file system. | ||
253 | * @param string $zipPath Filepath and name to be used in the archive. | ||
254 | * @param bool $recursive Add content recursively, default is TRUE. | ||
255 | * @param bool $followSymlinks Follow and add symbolic links, if they are accessible, default is TRUE. | ||
256 | * @param array &$addedFiles Reference to the added files, this is used to prevent duplicates, efault is an empty array. | ||
257 | * If you start the function by parsing an array, the array will be populated with the realPath | ||
258 | * and zipPath kay/value pairs added to the archive by the function. | ||
259 | * @param bool $overrideFilePermissions Force the use of the file/dir permissions set in the $extDirAttr | ||
260 | * and $extFileAttr parameters. | ||
261 | * @param int $extDirAttr Permissions for directories. | ||
262 | * @param int $extFileAttr Permissions for files. | ||
263 | */ | ||
264 | public function addDirectoryContent($realPath, $zipPath, $recursive = TRUE, $followSymlinks = TRUE, &$addedFiles = array(), | ||
265 | $overrideFilePermissions = FALSE, $extDirAttr = self::EXT_FILE_ATTR_DIR, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
266 | if (file_exists($realPath) && !isset($addedFiles[realpath($realPath)])) { | ||
267 | if (is_dir($realPath)) { | ||
268 | if ($overrideFilePermissions) { | ||
269 | $this->addDirectory($zipPath, 0, null, $extDirAttr); | ||
270 | } else { | ||
271 | $this->addDirectory($zipPath, 0, null, self::getFileExtAttr($realPath)); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | $addedFiles[realpath($realPath)] = $zipPath; | ||
276 | |||
277 | $iter = new DirectoryIterator($realPath); | ||
278 | foreach ($iter as $file) { | ||
279 | if ($file->isDot()) { | ||
280 | continue; | ||
281 | } | ||
282 | $newRealPath = $file->getPathname(); | ||
283 | $newZipPath = self::pathJoin($zipPath, $file->getFilename()); | ||
284 | |||
285 | if (file_exists($newRealPath) && ($followSymlinks === TRUE || !is_link($newRealPath))) { | ||
286 | if ($file->isFile()) { | ||
287 | $addedFiles[realpath($newRealPath)] = $newZipPath; | ||
288 | if ($overrideFilePermissions) { | ||
289 | $this->addLargeFile($newRealPath, $newZipPath, 0, null, $extFileAttr); | ||
290 | } else { | ||
291 | $this->addLargeFile($newRealPath, $newZipPath, 0, null, self::getFileExtAttr($newRealPath)); | ||
292 | } | ||
293 | } else if ($recursive === TRUE) { | ||
294 | $this->addDirectoryContent($newRealPath, $newZipPath, $recursive, $followSymlinks, $addedFiles, $overrideFilePermissions, $extDirAttr, $extFileAttr); | ||
295 | } else { | ||
296 | if ($overrideFilePermissions) { | ||
297 | $this->addDirectory($zipPath, 0, null, $extDirAttr); | ||
298 | } else { | ||
299 | $this->addDirectory($zipPath, 0, null, self::getFileExtAttr($newRealPath)); | ||
300 | } | ||
301 | } | ||
302 | } | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | /** | ||
308 | * Add a file to the archive at the specified location and file name. | ||
309 | * | ||
310 | * @param string $dataFile File name/path. | ||
311 | * @param string $filePath Filepath and name to be used in the archive. | ||
312 | * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. | ||
313 | * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. | ||
314 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
315 | * @return bool $success | ||
316 | */ | ||
317 | public function addLargeFile($dataFile, $filePath, $timestamp = 0, $fileComment = NULL, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
318 | if ($this->isFinalized) { | ||
319 | return FALSE; | ||
320 | } | ||
321 | |||
322 | if (is_string($dataFile) && is_file($dataFile)) { | ||
323 | $this->processFile($dataFile, $filePath, $timestamp, $fileComment, $extFileAttr); | ||
324 | } else if (is_resource($dataFile) && get_resource_type($dataFile) == "stream") { | ||
325 | $fh = $dataFile; | ||
326 | $this->openStream($filePath, $timestamp, $fileComment, $extFileAttr); | ||
327 | |||
328 | while (!feof($fh)) { | ||
329 | $this->addStreamData(fread($fh, $this->streamChunkSize)); | ||
330 | } | ||
331 | $this->closeStream($this->addExtraField); | ||
332 | } | ||
333 | return TRUE; | ||
334 | } | ||
335 | |||
336 | /** | ||
337 | * Create a stream to be used for large entries. | ||
338 | * | ||
339 | * @param string $filePath Filepath and name to be used in the archive. | ||
340 | * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. | ||
341 | * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. | ||
342 | * @param int $extFileAttr (Optional) The external file reference, use generateExtAttr to generate this. | ||
343 | * @return bool $success | ||
344 | */ | ||
345 | public function openStream($filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
346 | if (!function_exists('sys_get_temp_dir')) { | ||
347 | die ("ERROR: Zip " . self::VERSION . " requires PHP version 5.2.1 or above if large files are used."); | ||
348 | } | ||
349 | |||
350 | if ($this->isFinalized) { | ||
351 | return FALSE; | ||
352 | } | ||
353 | |||
354 | $this->zipflush(); | ||
355 | |||
356 | if (strlen($this->streamFilePath) > 0) { | ||
357 | $this->closeStream(); | ||
358 | } | ||
359 | |||
360 | $this->streamFile = tempnam(sys_get_temp_dir(), 'Zip'); | ||
361 | $this->streamData = fopen($this->streamFile, "wb"); | ||
362 | $this->streamFilePath = $filePath; | ||
363 | $this->streamTimestamp = $timestamp; | ||
364 | $this->streamFileComment = $fileComment; | ||
365 | $this->streamFileLength = 0; | ||
366 | $this->streamExtFileAttr = $extFileAttr; | ||
367 | |||
368 | return TRUE; | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * Add data to the open stream. | ||
373 | * | ||
374 | * @param string $data | ||
375 | * @return mixed length in bytes added or FALSE if the archive is finalized or there are no open stream. | ||
376 | */ | ||
377 | public function addStreamData($data) { | ||
378 | if ($this->isFinalized || strlen($this->streamFilePath) == 0) { | ||
379 | return FALSE; | ||
380 | } | ||
381 | |||
382 | $length = fwrite($this->streamData, $data, strlen($data)); | ||
383 | if ($length != strlen($data)) { | ||
384 | die ("<p>Length mismatch</p>\n"); | ||
385 | } | ||
386 | $this->streamFileLength += $length; | ||
387 | |||
388 | return $length; | ||
389 | } | ||
390 | |||
391 | /** | ||
392 | * Close the current stream. | ||
393 | * | ||
394 | * @return bool $success | ||
395 | */ | ||
396 | public function closeStream() { | ||
397 | if ($this->isFinalized || strlen($this->streamFilePath) == 0) { | ||
398 | return FALSE; | ||
399 | } | ||
400 | |||
401 | fflush($this->streamData); | ||
402 | fclose($this->streamData); | ||
403 | |||
404 | $this->processFile($this->streamFile, $this->streamFilePath, $this->streamTimestamp, $this->streamFileComment, $this->streamExtFileAttr); | ||
405 | |||
406 | $this->streamData = null; | ||
407 | $this->streamFilePath = null; | ||
408 | $this->streamTimestamp = null; | ||
409 | $this->streamFileComment = null; | ||
410 | $this->streamFileLength = 0; | ||
411 | $this->streamExtFileAttr = null; | ||
412 | |||
413 | // Windows is a little slow at times, so a millisecond later, we can unlink this. | ||
414 | unlink($this->streamFile); | ||
415 | |||
416 | $this->streamFile = null; | ||
417 | |||
418 | return TRUE; | ||
419 | } | ||
420 | |||
421 | private function processFile($dataFile, $filePath, $timestamp = 0, $fileComment = null, $extFileAttr = self::EXT_FILE_ATTR_FILE) { | ||
422 | if ($this->isFinalized) { | ||
423 | return FALSE; | ||
424 | } | ||
425 | |||
426 | $tempzip = tempnam(sys_get_temp_dir(), 'ZipStream'); | ||
427 | |||
428 | $zip = new ZipArchive; | ||
429 | if ($zip->open($tempzip) === TRUE) { | ||
430 | $zip->addFile($dataFile, 'file'); | ||
431 | $zip->close(); | ||
432 | } | ||
433 | |||
434 | $file_handle = fopen($tempzip, "rb"); | ||
435 | $stats = fstat($file_handle); | ||
436 | $eof = $stats['size']-72; | ||
437 | |||
438 | fseek($file_handle, 6); | ||
439 | |||
440 | $gpFlags = fread($file_handle, 2); | ||
441 | $gzType = fread($file_handle, 2); | ||
442 | fread($file_handle, 4); | ||
443 | $fileCRC32 = fread($file_handle, 4); | ||
444 | $v = unpack("Vval", fread($file_handle, 4)); | ||
445 | $gzLength = $v['val']; | ||
446 | $v = unpack("Vval", fread($file_handle, 4)); | ||
447 | $dataLength = $v['val']; | ||
448 | |||
449 | $this->buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr); | ||
450 | |||
451 | fseek($file_handle, 34); | ||
452 | $pos = 34; | ||
453 | |||
454 | while (!feof($file_handle) && $pos < $eof) { | ||
455 | $datalen = $this->streamChunkSize; | ||
456 | if ($pos + $this->streamChunkSize > $eof) { | ||
457 | $datalen = $eof-$pos; | ||
458 | } | ||
459 | $data = fread($file_handle, $datalen); | ||
460 | $pos += $datalen; | ||
461 | |||
462 | $this->zipwrite($data); | ||
463 | } | ||
464 | |||
465 | fclose($file_handle); | ||
466 | |||
467 | unlink($tempzip); | ||
468 | } | ||
469 | |||
470 | /** | ||
471 | * Close the archive. | ||
472 | * A closed archive can no longer have new files added to it. | ||
473 | * | ||
474 | * @return bool $success | ||
475 | */ | ||
476 | public function finalize() { | ||
477 | if (!$this->isFinalized) { | ||
478 | if (strlen($this->streamFilePath) > 0) { | ||
479 | $this->closeStream(); | ||
480 | } | ||
481 | $cd = implode("", $this->cdRec); | ||
482 | |||
483 | $cdRecSize = pack("v", sizeof($this->cdRec)); | ||
484 | $cdRec = $cd . self::ZIP_END_OF_CENTRAL_DIRECTORY | ||
485 | . $cdRecSize . $cdRecSize | ||
486 | . pack("VV", strlen($cd), $this->offset); | ||
487 | if (!empty($this->zipComment)) { | ||
488 | $cdRec .= pack("v", strlen($this->zipComment)) . $this->zipComment; | ||
489 | } else { | ||
490 | $cdRec .= "\x00\x00"; | ||
491 | } | ||
492 | |||
493 | $this->zipwrite($cdRec); | ||
494 | |||
495 | $this->isFinalized = TRUE; | ||
496 | $this->cdRec = NULL; | ||
497 | |||
498 | return TRUE; | ||
499 | } | ||
500 | return FALSE; | ||
501 | } | ||
502 | |||
503 | /** | ||
504 | * Get the handle ressource for the archive zip file. | ||
505 | * If the zip haven't been finalized yet, this will cause it to become finalized | ||
506 | * | ||
507 | * @return zip file handle | ||
508 | */ | ||
509 | public function getZipFile() { | ||
510 | if (!$this->isFinalized) { | ||
511 | $this->finalize(); | ||
512 | } | ||
513 | |||
514 | $this->zipflush(); | ||
515 | |||
516 | rewind($this->zipFile); | ||
517 | |||
518 | return $this->zipFile; | ||
519 | } | ||
520 | |||
521 | /** | ||
522 | * Get the zip file contents | ||
523 | * If the zip haven't been finalized yet, this will cause it to become finalized | ||
524 | * | ||
525 | * @return zip data | ||
526 | */ | ||
527 | public function getZipData() { | ||
528 | if (!$this->isFinalized) { | ||
529 | $this->finalize(); | ||
530 | } | ||
531 | if (!is_resource($this->zipFile)) { | ||
532 | return $this->zipData; | ||
533 | } else { | ||
534 | rewind($this->zipFile); | ||
535 | $filestat = fstat($this->zipFile); | ||
536 | return fread($this->zipFile, $filestat['size']); | ||
537 | } | ||
538 | } | ||
539 | |||
540 | /** | ||
541 | * Send the archive as a zip download | ||
542 | * | ||
543 | * @param String $fileName The name of the Zip archive, in ISO-8859-1 (or ASCII) encoding, ie. "archive.zip". Optional, defaults to NULL, which means that no ISO-8859-1 encoded file name will be specified. | ||
544 | * @param String $contentType Content mime type. Optional, defaults to "application/zip". | ||
545 | * @param String $utf8FileName The name of the Zip archive, in UTF-8 encoding. Optional, defaults to NULL, which means that no UTF-8 encoded file name will be specified. | ||
546 | * @param bool $inline Use Content-Disposition with "inline" instead of "attached". Optional, defaults to FALSE. | ||
547 | * @return bool $success | ||
548 | */ | ||
549 | function sendZip($fileName = null, $contentType = "application/zip", $utf8FileName = null, $inline = false) { | ||
550 | if (!$this->isFinalized) { | ||
551 | $this->finalize(); | ||
552 | } | ||
553 | |||
554 | $headerFile = null; | ||
555 | $headerLine = null; | ||
556 | if (!headers_sent($headerFile, $headerLine) or die("<p><strong>Error:</strong> Unable to send file $fileName. HTML Headers have already been sent from <strong>$headerFile</strong> in line <strong>$headerLine</strong></p>")) { | ||
557 | if ((ob_get_contents() === FALSE || ob_get_contents() == '') or die("\n<p><strong>Error:</strong> Unable to send file <strong>$fileName</strong>. Output buffer contains the following text (typically warnings or errors):<br>" . htmlentities(ob_get_contents()) . "</p>")) { | ||
558 | if (ini_get('zlib.output_compression')) { | ||
559 | ini_set('zlib.output_compression', 'Off'); | ||
560 | } | ||
561 | |||
562 | header("Pragma: public"); | ||
563 | header("Last-Modified: " . gmdate("D, d M Y H:i:s T")); | ||
564 | header("Expires: 0"); | ||
565 | header("Accept-Ranges: bytes"); | ||
566 | header("Connection: close"); | ||
567 | header("Content-Type: " . $contentType); | ||
568 | $cd = "Content-Disposition: "; | ||
569 | if ($inline) { | ||
570 | $cd .= "inline"; | ||
571 | } else{ | ||
572 | $cd .= "attached"; | ||
573 | } | ||
574 | if ($fileName) { | ||
575 | $cd .= '; filename="' . $fileName . '"'; | ||
576 | } | ||
577 | if ($utf8FileName) { | ||
578 | $cd .= "; filename*=UTF-8''" . rawurlencode($utf8FileName); | ||
579 | } | ||
580 | header($cd); | ||
581 | header("Content-Length: ". $this->getArchiveSize()); | ||
582 | |||
583 | if (!is_resource($this->zipFile)) { | ||
584 | echo $this->zipData; | ||
585 | } else { | ||
586 | rewind($this->zipFile); | ||
587 | |||
588 | while (!feof($this->zipFile)) { | ||
589 | echo fread($this->zipFile, $this->streamChunkSize); | ||
590 | } | ||
591 | } | ||
592 | } | ||
593 | return TRUE; | ||
594 | } | ||
595 | return FALSE; | ||
596 | } | ||
597 | |||
598 | /** | ||
599 | * Return the current size of the archive | ||
600 | * | ||
601 | * @return $size Size of the archive | ||
602 | */ | ||
603 | public function getArchiveSize() { | ||
604 | if (!is_resource($this->zipFile)) { | ||
605 | return strlen($this->zipData); | ||
606 | } | ||
607 | $filestat = fstat($this->zipFile); | ||
608 | |||
609 | return $filestat['size']; | ||
610 | } | ||
611 | |||
612 | /** | ||
613 | * Calculate the 2 byte dostime used in the zip entries. | ||
614 | * | ||
615 | * @param int $timestamp | ||
616 | * @return 2-byte encoded DOS Date | ||
617 | */ | ||
618 | private function getDosTime($timestamp = 0) { | ||
619 | $timestamp = (int)$timestamp; | ||
620 | $oldTZ = @date_default_timezone_get(); | ||
621 | date_default_timezone_set('UTC'); | ||
622 | $date = ($timestamp == 0 ? getdate() : getdate($timestamp)); | ||
623 | date_default_timezone_set($oldTZ); | ||
624 | if ($date["year"] >= 1980) { | ||
625 | return pack("V", (($date["mday"] + ($date["mon"] << 5) + (($date["year"]-1980) << 9)) << 16) | | ||
626 | (($date["seconds"] >> 1) + ($date["minutes"] << 5) + ($date["hours"] << 11))); | ||
627 | } | ||
628 | return "\x00\x00\x00\x00"; | ||
629 | } | ||
630 | |||
631 | /** | ||
632 | * Build the Zip file structures | ||
633 | * | ||
634 | * @param string $filePath | ||
635 | * @param string $fileComment | ||
636 | * @param string $gpFlags | ||
637 | * @param string $gzType | ||
638 | * @param int $timestamp | ||
639 | * @param string $fileCRC32 | ||
640 | * @param int $gzLength | ||
641 | * @param int $dataLength | ||
642 | * @param int $extFileAttr Use self::EXT_FILE_ATTR_FILE for files, self::EXT_FILE_ATTR_DIR for Directories. | ||
643 | */ | ||
644 | private function buildZipEntry($filePath, $fileComment, $gpFlags, $gzType, $timestamp, $fileCRC32, $gzLength, $dataLength, $extFileAttr) { | ||
645 | $filePath = str_replace("\\", "/", $filePath); | ||
646 | $fileCommentLength = (empty($fileComment) ? 0 : strlen($fileComment)); | ||
647 | $timestamp = (int)$timestamp; | ||
648 | $timestamp = ($timestamp == 0 ? time() : $timestamp); | ||
649 | |||
650 | $dosTime = $this->getDosTime($timestamp); | ||
651 | $tsPack = pack("V", $timestamp); | ||
652 | |||
653 | $ux = "\x75\x78\x0B\x00\x01\x04\xE8\x03\x00\x00\x04\x00\x00\x00\x00"; | ||
654 | |||
655 | if (!isset($gpFlags) || strlen($gpFlags) != 2) { | ||
656 | $gpFlags = "\x00\x00"; | ||
657 | } | ||
658 | |||
659 | $isFileUTF8 = mb_check_encoding($filePath, "UTF-8") && !mb_check_encoding($filePath, "ASCII"); | ||
660 | $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, "UTF-8") && !mb_check_encoding($fileComment, "ASCII"); | ||
661 | if ($isFileUTF8 || $isCommentUTF8) { | ||
662 | $flag = 0; | ||
663 | $gpFlagsV = unpack("vflags", $gpFlags); | ||
664 | if (isset($gpFlagsV['flags'])) { | ||
665 | $flag = $gpFlagsV['flags']; | ||
666 | } | ||
667 | $gpFlags = pack("v", $flag | (1 << 11)); | ||
668 | } | ||
669 | |||
670 | $header = $gpFlags . $gzType . $dosTime. $fileCRC32 | ||
671 | . pack("VVv", $gzLength, $dataLength, strlen($filePath)); // File name length | ||
672 | |||
673 | $zipEntry = self::ZIP_LOCAL_FILE_HEADER; | ||
674 | $zipEntry .= self::ATTR_VERSION_TO_EXTRACT; | ||
675 | $zipEntry .= $header; | ||
676 | $zipEntry .= pack("v", ($this->addExtraField ? 28 : 0)); // Extra field length | ||
677 | $zipEntry .= $filePath; // FileName | ||
678 | // Extra fields | ||
679 | if ($this->addExtraField) { | ||
680 | $zipEntry .= "\x55\x54\x09\x00\x03" . $tsPack . $tsPack . $ux; | ||
681 | } | ||
682 | $this->zipwrite($zipEntry); | ||
683 | |||
684 | $cdEntry = self::ZIP_CENTRAL_FILE_HEADER; | ||
685 | $cdEntry .= self::ATTR_MADE_BY_VERSION; | ||
686 | $cdEntry .= ($dataLength === 0 ? "\x0A\x00" : self::ATTR_VERSION_TO_EXTRACT); | ||
687 | $cdEntry .= $header; | ||
688 | $cdEntry .= pack("v", ($this->addExtraField ? 24 : 0)); // Extra field length | ||
689 | $cdEntry .= pack("v", $fileCommentLength); // File comment length | ||
690 | $cdEntry .= "\x00\x00"; // Disk number start | ||
691 | $cdEntry .= "\x00\x00"; // internal file attributes | ||
692 | $cdEntry .= pack("V", $extFileAttr); // External file attributes | ||
693 | $cdEntry .= pack("V", $this->offset); // Relative offset of local header | ||
694 | $cdEntry .= $filePath; // FileName | ||
695 | // Extra fields | ||
696 | if ($this->addExtraField) { | ||
697 | $cdEntry .= "\x55\x54\x05\x00\x03" . $tsPack . $ux; | ||
698 | } | ||
699 | if (!empty($fileComment)) { | ||
700 | $cdEntry .= $fileComment; // Comment | ||
701 | } | ||
702 | |||
703 | $this->cdRec[] = $cdEntry; | ||
704 | $this->offset += strlen($zipEntry) + $gzLength; | ||
705 | } | ||
706 | |||
707 | private function zipwrite($data) { | ||
708 | if (!is_resource($this->zipFile)) { | ||
709 | $this->zipData .= $data; | ||
710 | } else { | ||
711 | fwrite($this->zipFile, $data); | ||
712 | fflush($this->zipFile); | ||
713 | } | ||
714 | } | ||
715 | |||
716 | private function zipflush() { | ||
717 | if (!is_resource($this->zipFile)) { | ||
718 | $this->zipFile = tmpfile(); | ||
719 | fwrite($this->zipFile, $this->zipData); | ||
720 | $this->zipData = NULL; | ||
721 | } | ||
722 | } | ||
723 | |||
724 | /** | ||
725 | * Join $file to $dir path, and clean up any excess slashes. | ||
726 | * | ||
727 | * @param string $dir | ||
728 | * @param string $file | ||
729 | */ | ||
730 | public static function pathJoin($dir, $file) { | ||
731 | if (empty($dir) || empty($file)) { | ||
732 | return self::getRelativePath($dir . $file); | ||
733 | } | ||
734 | return self::getRelativePath($dir . '/' . $file); | ||
735 | } | ||
736 | |||
737 | /** | ||
738 | * Clean up a path, removing any unnecessary elements such as /./, // or redundant ../ segments. | ||
739 | * If the path starts with a "/", it is deemed an absolute path and any /../ in the beginning is stripped off. | ||
740 | * The returned path will not end in a "/". | ||
741 | * | ||
742 | * Sometimes, when a path is generated from multiple fragments, | ||
743 | * you can get something like "../data/html/../images/image.jpeg" | ||
744 | * This will normalize that example path to "../data/images/image.jpeg" | ||
745 | * | ||
746 | * @param string $path The path to clean up | ||
747 | * @return string the clean path | ||
748 | */ | ||
749 | public static function getRelativePath($path) { | ||
750 | $path = preg_replace("#/+\.?/+#", "/", str_replace("\\", "/", $path)); | ||
751 | $dirs = explode("/", rtrim(preg_replace('#^(?:\./)+#', '', $path), '/')); | ||
752 | |||
753 | $offset = 0; | ||
754 | $sub = 0; | ||
755 | $subOffset = 0; | ||
756 | $root = ""; | ||
757 | |||
758 | if (empty($dirs[0])) { | ||
759 | $root = "/"; | ||
760 | $dirs = array_splice($dirs, 1); | ||
761 | } else if (preg_match("#[A-Za-z]:#", $dirs[0])) { | ||
762 | $root = strtoupper($dirs[0]) . "/"; | ||
763 | $dirs = array_splice($dirs, 1); | ||
764 | } | ||
765 | |||
766 | $newDirs = array(); | ||
767 | foreach ($dirs as $dir) { | ||
768 | if ($dir !== "..") { | ||
769 | $subOffset--; | ||
770 | $newDirs[++$offset] = $dir; | ||
771 | } else { | ||
772 | $subOffset++; | ||
773 | if (--$offset < 0) { | ||
774 | $offset = 0; | ||
775 | if ($subOffset > $sub) { | ||
776 | $sub++; | ||
777 | } | ||
778 | } | ||
779 | } | ||
780 | } | ||
781 | |||
782 | if (empty($root)) { | ||
783 | $root = str_repeat("../", $sub); | ||
784 | } | ||
785 | return $root . implode("/", array_slice($newDirs, 0, $offset)); | ||
786 | } | ||
787 | |||
788 | /** | ||
789 | * Create the file permissions for a file or directory, for use in the extFileAttr parameters. | ||
790 | * | ||
791 | * @param int $owner Unix permisions for owner (octal from 00 to 07) | ||
792 | * @param int $group Unix permisions for group (octal from 00 to 07) | ||
793 | * @param int $other Unix permisions for others (octal from 00 to 07) | ||
794 | * @param bool $isFile | ||
795 | * @return EXTRERNAL_REF field. | ||
796 | */ | ||
797 | public static function generateExtAttr($owner = 07, $group = 05, $other = 05, $isFile = true) { | ||
798 | $fp = $isFile ? self::S_IFREG : self::S_IFDIR; | ||
799 | $fp |= (($owner & 07) << 6) | (($group & 07) << 3) | ($other & 07); | ||
800 | |||
801 | return ($fp << 16) | ($isFile ? self::S_DOS_A : self::S_DOS_D); | ||
802 | } | ||
803 | |||
804 | /** | ||
805 | * Get the file permissions for a file or directory, for use in the extFileAttr parameters. | ||
806 | * | ||
807 | * @param string $filename | ||
808 | * @return external ref field, or FALSE if the file is not found. | ||
809 | */ | ||
810 | public static function getFileExtAttr($filename) { | ||
811 | if (file_exists($filename)) { | ||
812 | $fp = fileperms($filename) << 16; | ||
813 | return $fp | (is_dir($filename) ? self::S_DOS_D : self::S_DOS_A); | ||
814 | } | ||
815 | return FALSE; | ||
816 | } | ||
817 | } | ||
818 | ?> | ||