NtfsExtraField.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of the nelexa/zip package.
  5. * (c) Ne-Lexa <https://github.com/Ne-Lexa/php-zip>
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. namespace PhpZip\Model\Extra\Fields;
  10. use PhpZip\Exception\InvalidArgumentException;
  11. use PhpZip\Exception\ZipException;
  12. use PhpZip\Model\Extra\ZipExtraField;
  13. use PhpZip\Model\ZipEntry;
  14. /**
  15. * NTFS Extra Field.
  16. *
  17. * @see https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT .ZIP File Format Specification
  18. */
  19. final class NtfsExtraField implements ZipExtraField
  20. {
  21. /** @var int Header id */
  22. public const HEADER_ID = 0x000A;
  23. /** @var int Tag ID */
  24. public const TIME_ATTR_TAG = 0x0001;
  25. /** @var int Attribute size */
  26. public const TIME_ATTR_SIZE = 24; // 3 * 8
  27. /**
  28. * @var int A file time is a 64-bit value that represents the number of
  29. * 100-nanosecond intervals that have elapsed since 12:00
  30. * A.M. January 1, 1601 Coordinated Universal Time (UTC).
  31. * this is the offset of Windows time 0 to Unix epoch in 100-nanosecond intervals.
  32. */
  33. public const EPOCH_OFFSET = -116444736000000000;
  34. /** @var int Modify ntfs time */
  35. private int $modifyNtfsTime;
  36. /** @var int Access ntfs time */
  37. private int $accessNtfsTime;
  38. /** @var int Create ntfs time */
  39. private int $createNtfsTime;
  40. public function __construct(int $modifyNtfsTime, int $accessNtfsTime, int $createNtfsTime)
  41. {
  42. $this->modifyNtfsTime = $modifyNtfsTime;
  43. $this->accessNtfsTime = $accessNtfsTime;
  44. $this->createNtfsTime = $createNtfsTime;
  45. }
  46. /**
  47. * @return NtfsExtraField
  48. */
  49. public static function create(
  50. \DateTimeInterface $modifyDateTime,
  51. \DateTimeInterface $accessDateTime,
  52. \DateTimeInterface $createNtfsTime
  53. ): self {
  54. return new self(
  55. self::dateTimeToNtfsTime($modifyDateTime),
  56. self::dateTimeToNtfsTime($accessDateTime),
  57. self::dateTimeToNtfsTime($createNtfsTime)
  58. );
  59. }
  60. /**
  61. * Returns the Header ID (type) of this Extra Field.
  62. * The Header ID is an unsigned short integer (two bytes)
  63. * which must be constant during the life cycle of this object.
  64. */
  65. public function getHeaderId(): int
  66. {
  67. return self::HEADER_ID;
  68. }
  69. /**
  70. * Populate data from this array as if it was in local file data.
  71. *
  72. * @param string $buffer the buffer to read data from
  73. * @param ZipEntry|null $entry optional zip entry
  74. *
  75. * @throws ZipException
  76. *
  77. * @return NtfsExtraField
  78. */
  79. public static function unpackLocalFileData(string $buffer, ?ZipEntry $entry = null): self
  80. {
  81. if (\PHP_INT_SIZE === 4) {
  82. throw new ZipException('not supported for php-32bit');
  83. }
  84. $buffer = substr($buffer, 4);
  85. $modifyTime = 0;
  86. $accessTime = 0;
  87. $createTime = 0;
  88. while ($buffer || $buffer !== '') {
  89. [
  90. 'tag' => $tag,
  91. 'sizeAttr' => $sizeAttr,
  92. ] = unpack('vtag/vsizeAttr', $buffer);
  93. if ($tag === self::TIME_ATTR_TAG && $sizeAttr === self::TIME_ATTR_SIZE) {
  94. [
  95. 'modifyTime' => $modifyTime,
  96. 'accessTime' => $accessTime,
  97. 'createTime' => $createTime,
  98. ] = unpack('PmodifyTime/PaccessTime/PcreateTime', substr($buffer, 4, 24));
  99. break;
  100. }
  101. $buffer = substr($buffer, 4 + $sizeAttr);
  102. }
  103. return new self($modifyTime, $accessTime, $createTime);
  104. }
  105. /**
  106. * Populate data from this array as if it was in central directory data.
  107. *
  108. * @param string $buffer the buffer to read data from
  109. * @param ZipEntry|null $entry optional zip entry
  110. *
  111. * @throws ZipException
  112. *
  113. * @return NtfsExtraField
  114. */
  115. public static function unpackCentralDirData(string $buffer, ?ZipEntry $entry = null): self
  116. {
  117. return self::unpackLocalFileData($buffer, $entry);
  118. }
  119. /**
  120. * The actual data to put into local file data - without Header-ID
  121. * or length specifier.
  122. *
  123. * @return string the data
  124. */
  125. public function packLocalFileData(): string
  126. {
  127. return pack(
  128. 'VvvPPP',
  129. 0,
  130. self::TIME_ATTR_TAG,
  131. self::TIME_ATTR_SIZE,
  132. $this->modifyNtfsTime,
  133. $this->accessNtfsTime,
  134. $this->createNtfsTime
  135. );
  136. }
  137. public function getModifyNtfsTime(): int
  138. {
  139. return $this->modifyNtfsTime;
  140. }
  141. public function setModifyNtfsTime(int $modifyNtfsTime): void
  142. {
  143. $this->modifyNtfsTime = $modifyNtfsTime;
  144. }
  145. public function getAccessNtfsTime(): int
  146. {
  147. return $this->accessNtfsTime;
  148. }
  149. public function setAccessNtfsTime(int $accessNtfsTime): void
  150. {
  151. $this->accessNtfsTime = $accessNtfsTime;
  152. }
  153. public function getCreateNtfsTime(): int
  154. {
  155. return $this->createNtfsTime;
  156. }
  157. public function setCreateNtfsTime(int $createNtfsTime): void
  158. {
  159. $this->createNtfsTime = $createNtfsTime;
  160. }
  161. /**
  162. * The actual data to put into central directory - without Header-ID or
  163. * length specifier.
  164. *
  165. * @return string the data
  166. */
  167. public function packCentralDirData(): string
  168. {
  169. return $this->packLocalFileData();
  170. }
  171. public function getModifyDateTime(): \DateTimeInterface
  172. {
  173. return self::ntfsTimeToDateTime($this->modifyNtfsTime);
  174. }
  175. public function setModifyDateTime(\DateTimeInterface $modifyTime): void
  176. {
  177. $this->modifyNtfsTime = self::dateTimeToNtfsTime($modifyTime);
  178. }
  179. public function getAccessDateTime(): \DateTimeInterface
  180. {
  181. return self::ntfsTimeToDateTime($this->accessNtfsTime);
  182. }
  183. public function setAccessDateTime(\DateTimeInterface $accessTime): void
  184. {
  185. $this->accessNtfsTime = self::dateTimeToNtfsTime($accessTime);
  186. }
  187. public function getCreateDateTime(): \DateTimeInterface
  188. {
  189. return self::ntfsTimeToDateTime($this->createNtfsTime);
  190. }
  191. public function setCreateDateTime(\DateTimeInterface $createTime): void
  192. {
  193. $this->createNtfsTime = self::dateTimeToNtfsTime($createTime);
  194. }
  195. /**
  196. * @param float $timestamp Float timestamp
  197. */
  198. public static function timestampToNtfsTime(float $timestamp): int
  199. {
  200. return (int) (($timestamp * 10000000) - self::EPOCH_OFFSET);
  201. }
  202. public static function dateTimeToNtfsTime(\DateTimeInterface $dateTime): int
  203. {
  204. return self::timestampToNtfsTime((float) $dateTime->format('U.u'));
  205. }
  206. /**
  207. * @return float Float unix timestamp
  208. */
  209. public static function ntfsTimeToTimestamp(int $ntfsTime): float
  210. {
  211. return (float) (($ntfsTime + self::EPOCH_OFFSET) / 10000000);
  212. }
  213. public static function ntfsTimeToDateTime(int $ntfsTime): \DateTimeInterface
  214. {
  215. $timestamp = self::ntfsTimeToTimestamp($ntfsTime);
  216. $dateTime = \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6f', $timestamp));
  217. if ($dateTime === false) {
  218. throw new InvalidArgumentException('Cannot create date/time object for timestamp ' . $timestamp);
  219. }
  220. return $dateTime;
  221. }
  222. public function __toString(): string
  223. {
  224. $args = [self::HEADER_ID];
  225. $format = '0x%04x NtfsExtra:';
  226. if ($this->modifyNtfsTime !== 0) {
  227. $format .= ' Modify:[%s]';
  228. $args[] = $this->getModifyDateTime()->format(\DATE_ATOM);
  229. }
  230. if ($this->accessNtfsTime !== 0) {
  231. $format .= ' Access:[%s]';
  232. $args[] = $this->getAccessDateTime()->format(\DATE_ATOM);
  233. }
  234. if ($this->createNtfsTime !== 0) {
  235. $format .= ' Create:[%s]';
  236. $args[] = $this->getCreateDateTime()->format(\DATE_ATOM);
  237. }
  238. return vsprintf($format, $args);
  239. }
  240. }