PageRenderTime 59ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/epub/PHPePub/Zip.php

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