Zip64ExtraField.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <?php
  2. namespace PhpZip\Model\Extra\Fields;
  3. use PhpZip\Constants\ZipConstants;
  4. use PhpZip\Exception\RuntimeException;
  5. use PhpZip\Exception\ZipException;
  6. use PhpZip\Model\Extra\ZipExtraField;
  7. use PhpZip\Model\ZipEntry;
  8. use PhpZip\Util\PackUtil;
  9. /**
  10. * ZIP64 Extra Field.
  11. *
  12. * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
  13. */
  14. class Zip64ExtraField implements ZipExtraField
  15. {
  16. /** @var int The Header ID for a ZIP64 Extended Information Extra Field. */
  17. const HEADER_ID = 0x0001;
  18. /** @var int|null */
  19. private $uncompressedSize;
  20. /** @var int|null */
  21. private $compressedSize;
  22. /** @var int|null */
  23. private $localHeaderOffset;
  24. /** @var int|null */
  25. private $diskStart;
  26. /**
  27. * Zip64ExtraField constructor.
  28. *
  29. * @param int|null $uncompressedSize
  30. * @param int|null $compressedSize
  31. * @param int|null $localHeaderOffset
  32. * @param int|null $diskStart
  33. */
  34. public function __construct(
  35. $uncompressedSize = null,
  36. $compressedSize = null,
  37. $localHeaderOffset = null,
  38. $diskStart = null
  39. ) {
  40. $this->uncompressedSize = $uncompressedSize;
  41. $this->compressedSize = $compressedSize;
  42. $this->localHeaderOffset = $localHeaderOffset;
  43. $this->diskStart = $diskStart;
  44. }
  45. /**
  46. * Returns the Header ID (type) of this Extra Field.
  47. * The Header ID is an unsigned short integer (two bytes)
  48. * which must be constant during the life cycle of this object.
  49. *
  50. * @return int
  51. */
  52. public function getHeaderId()
  53. {
  54. return self::HEADER_ID;
  55. }
  56. /**
  57. * Populate data from this array as if it was in local file data.
  58. *
  59. * @param string $buffer the buffer to read data from
  60. * @param ZipEntry|null $entry
  61. *
  62. * @throws ZipException on error
  63. *
  64. * @return Zip64ExtraField
  65. */
  66. public static function unpackLocalFileData($buffer, ZipEntry $entry = null)
  67. {
  68. $length = \strlen($buffer);
  69. if ($length === 0) {
  70. // no local file data at all, may happen if an archive
  71. // only holds a ZIP64 extended information extra field
  72. // inside the central directory but not inside the local
  73. // file header
  74. return new self();
  75. }
  76. if ($length < 16) {
  77. throw new ZipException(
  78. 'Zip64 extended information must contain both size values in the local file header.'
  79. );
  80. }
  81. $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, 0, 8));
  82. $compressedSize = PackUtil::unpackLongLE(substr($buffer, 8, 8));
  83. return new self($uncompressedSize, $compressedSize);
  84. }
  85. /**
  86. * Populate data from this array as if it was in central directory data.
  87. *
  88. * @param string $buffer the buffer to read data from
  89. * @param ZipEntry|null $entry
  90. *
  91. * @throws ZipException
  92. *
  93. * @return Zip64ExtraField
  94. */
  95. public static function unpackCentralDirData($buffer, ZipEntry $entry = null)
  96. {
  97. if ($entry === null) {
  98. throw new RuntimeException('zipEntry is null');
  99. }
  100. $length = \strlen($buffer);
  101. $remaining = $length;
  102. $uncompressedSize = null;
  103. $compressedSize = null;
  104. $localHeaderOffset = null;
  105. $diskStart = null;
  106. if ($entry->getUncompressedSize() === ZipConstants::ZIP64_MAGIC) {
  107. if ($remaining < 8) {
  108. throw new ZipException('ZIP64 extension corrupt (no uncompressed size).');
  109. }
  110. $uncompressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
  111. $remaining -= 8;
  112. }
  113. if ($entry->getCompressedSize() === ZipConstants::ZIP64_MAGIC) {
  114. if ($remaining < 8) {
  115. throw new ZipException('ZIP64 extension corrupt (no compressed size).');
  116. }
  117. $compressedSize = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
  118. $remaining -= 8;
  119. }
  120. if ($entry->getLocalHeaderOffset() === ZipConstants::ZIP64_MAGIC) {
  121. if ($remaining < 8) {
  122. throw new ZipException('ZIP64 extension corrupt (no relative local header offset).');
  123. }
  124. $localHeaderOffset = PackUtil::unpackLongLE(substr($buffer, $length - $remaining, 8));
  125. $remaining -= 8;
  126. }
  127. if ($remaining === 4) {
  128. $diskStart = unpack('V', substr($buffer, $length - $remaining, 4))[1];
  129. }
  130. return new self($uncompressedSize, $compressedSize, $localHeaderOffset, $diskStart);
  131. }
  132. /**
  133. * The actual data to put into local file data - without Header-ID
  134. * or length specifier.
  135. *
  136. * @return string the data
  137. */
  138. public function packLocalFileData()
  139. {
  140. if ($this->uncompressedSize !== null || $this->compressedSize !== null) {
  141. if ($this->uncompressedSize === null || $this->compressedSize === null) {
  142. throw new \InvalidArgumentException(
  143. 'Zip64 extended information must contain both size values in the local file header.'
  144. );
  145. }
  146. return $this->packSizes();
  147. }
  148. return '';
  149. }
  150. /**
  151. * @return string
  152. */
  153. private function packSizes()
  154. {
  155. $data = '';
  156. if ($this->uncompressedSize !== null) {
  157. $data .= PackUtil::packLongLE($this->uncompressedSize);
  158. }
  159. if ($this->compressedSize !== null) {
  160. $data .= PackUtil::packLongLE($this->compressedSize);
  161. }
  162. return $data;
  163. }
  164. /**
  165. * The actual data to put into central directory - without Header-ID or
  166. * length specifier.
  167. *
  168. * @return string the data
  169. */
  170. public function packCentralDirData()
  171. {
  172. $data = $this->packSizes();
  173. if ($this->localHeaderOffset !== null) {
  174. $data .= PackUtil::packLongLE($this->localHeaderOffset);
  175. }
  176. if ($this->diskStart !== null) {
  177. $data .= pack('V', $this->diskStart);
  178. }
  179. return $data;
  180. }
  181. /**
  182. * @return int|null
  183. */
  184. public function getUncompressedSize()
  185. {
  186. return $this->uncompressedSize;
  187. }
  188. /**
  189. * @param int|null $uncompressedSize
  190. */
  191. public function setUncompressedSize($uncompressedSize)
  192. {
  193. $this->uncompressedSize = $uncompressedSize;
  194. }
  195. /**
  196. * @return int|null
  197. */
  198. public function getCompressedSize()
  199. {
  200. return $this->compressedSize;
  201. }
  202. /**
  203. * @param int|null $compressedSize
  204. */
  205. public function setCompressedSize($compressedSize)
  206. {
  207. $this->compressedSize = $compressedSize;
  208. }
  209. /**
  210. * @return int|null
  211. */
  212. public function getLocalHeaderOffset()
  213. {
  214. return $this->localHeaderOffset;
  215. }
  216. /**
  217. * @param int|null $localHeaderOffset
  218. */
  219. public function setLocalHeaderOffset($localHeaderOffset)
  220. {
  221. $this->localHeaderOffset = $localHeaderOffset;
  222. }
  223. /**
  224. * @return int|null
  225. */
  226. public function getDiskStart()
  227. {
  228. return $this->diskStart;
  229. }
  230. /**
  231. * @param int|null $diskStart
  232. */
  233. public function setDiskStart($diskStart)
  234. {
  235. $this->diskStart = $diskStart;
  236. }
  237. /**
  238. * @return string
  239. */
  240. public function __toString()
  241. {
  242. $args = [self::HEADER_ID];
  243. $format = '0x%04x ZIP64: ';
  244. $formats = [];
  245. if ($this->uncompressedSize !== null) {
  246. $formats[] = 'SIZE=%d';
  247. $args[] = $this->uncompressedSize;
  248. }
  249. if ($this->compressedSize !== null) {
  250. $formats[] = 'COMP_SIZE=%d';
  251. $args[] = $this->compressedSize;
  252. }
  253. if ($this->localHeaderOffset !== null) {
  254. $formats[] = 'OFFSET=%d';
  255. $args[] = $this->localHeaderOffset;
  256. }
  257. if ($this->diskStart !== null) {
  258. $formats[] = 'DISK_START=%d';
  259. $args[] = $this->diskStart;
  260. }
  261. $format .= implode(' ', $formats);
  262. return vsprintf($format, $args);
  263. }
  264. }