ExtraFields.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. <?php
  2. namespace PhpZip\Extra;
  3. use PhpZip\Exception\ZipException;
  4. /**
  5. * Represents a collection of Extra Fields as they may
  6. * be present at several locations in ZIP files.
  7. *
  8. * @author Ne-Lexa alexey@nelexa.ru
  9. * @license MIT
  10. */
  11. class ExtraFields
  12. {
  13. /**
  14. * The map of Extra Fields.
  15. * Maps from Header ID to Extra Field.
  16. * Must not be null, but may be empty if no Extra Fields are used.
  17. * The map is sorted by Header IDs in ascending order.
  18. *
  19. * @var ExtraField[]
  20. */
  21. private $extra = [];
  22. /**
  23. * Returns the number of Extra Fields in this collection.
  24. *
  25. * @return int
  26. */
  27. public function size()
  28. {
  29. return sizeof($this->extra);
  30. }
  31. /**
  32. * Returns the Extra Field with the given Header ID or null
  33. * if no such Extra Field exists.
  34. *
  35. * @param int $headerId The requested Header ID.
  36. * @return ExtraField The Extra Field with the given Header ID or
  37. * if no such Extra Field exists.
  38. * @throws ZipException If headerId is out of range.
  39. */
  40. public function get($headerId)
  41. {
  42. if (0x0000 > $headerId || $headerId > 0xffff) {
  43. throw new ZipException('headerId out of range');
  44. }
  45. if (isset($this->extra[$headerId])) {
  46. return $this->extra[$headerId];
  47. }
  48. return null;
  49. }
  50. /**
  51. * Stores the given Extra Field in this collection.
  52. *
  53. * @param ExtraField $extraField The Extra Field to store in this collection.
  54. * @return ExtraField The Extra Field previously associated with the Header ID of
  55. * of the given Extra Field or null if no such Extra Field existed.
  56. * @throws ZipException If headerId is out of range.
  57. */
  58. public function add(ExtraField $extraField)
  59. {
  60. $headerId = $extraField::getHeaderId();
  61. if (0x0000 > $headerId || $headerId > 0xffff) {
  62. throw new ZipException('headerId out of range');
  63. }
  64. $this->extra[$headerId] = $extraField;
  65. return $extraField;
  66. }
  67. /**
  68. * Returns Extra Field exists
  69. *
  70. * @param int $headerId The requested Header ID.
  71. * @return bool
  72. */
  73. public function has($headerId)
  74. {
  75. return isset($this->extra[$headerId]);
  76. }
  77. /**
  78. * Removes the Extra Field with the given Header ID.
  79. *
  80. * @param int $headerId The requested Header ID.
  81. * @return ExtraField The Extra Field with the given Header ID or null
  82. * if no such Extra Field exists.
  83. * @throws ZipException If headerId is out of range or extra field not found.
  84. */
  85. public function remove($headerId)
  86. {
  87. if (0x0000 > $headerId || $headerId > 0xffff) {
  88. throw new ZipException('headerId out of range');
  89. }
  90. if (isset($this->extra[$headerId])) {
  91. $ef = $this->extra[$headerId];
  92. unset($this->extra[$headerId]);
  93. return $ef;
  94. }
  95. throw new ZipException('ExtraField not found');
  96. }
  97. /**
  98. * Returns a protective copy of the Extra Fields.
  99. * null is never returned.
  100. *
  101. * @return string
  102. * @throws ZipException If size out of range
  103. */
  104. public function getExtra()
  105. {
  106. $size = $this->getExtraLength();
  107. if (0x0000 > $size || $size > 0xffff) {
  108. throw new ZipException('size out of range');
  109. }
  110. if (0 === $size) return '';
  111. $fp = fopen('php://memory', 'r+b');
  112. $offset = 0;
  113. /**
  114. * @var ExtraField $ef
  115. */
  116. foreach ($this->extra as $ef) {
  117. fwrite($fp, pack('vv', $ef::getHeaderId(), $ef->getDataSize()));
  118. $offset += 4;
  119. fwrite($fp, $ef->writeTo($fp, $offset));
  120. $offset += $ef->getDataSize();
  121. }
  122. rewind($fp);
  123. $content = stream_get_contents($fp);
  124. fclose($fp);
  125. return $content;
  126. }
  127. /**
  128. * Returns the number of bytes required to hold the Extra Fields.
  129. *
  130. * @return int The length of the Extra Fields in bytes. May be 0.
  131. * @see #getExtra
  132. */
  133. public function getExtraLength()
  134. {
  135. if (empty($this->extra)) {
  136. return 0;
  137. }
  138. $length = 0;
  139. /**
  140. * @var ExtraField $extraField
  141. */
  142. foreach ($this->extra as $extraField) {
  143. $length += 4 + $extraField->getDataSize();
  144. }
  145. return $length;
  146. }
  147. /**
  148. * Initializes this Extra Field by deserializing a Data Block of
  149. * size bytes $size from the resource $handle at the zero based offset $off.
  150. *
  151. * @param resource $handle
  152. * @param int $off Offset
  153. * @param int $size Size
  154. * @throws ZipException If size out of range
  155. */
  156. public function readFrom($handle, $off, $size)
  157. {
  158. if (0x0000 > $size || $size > 0xffff) {
  159. throw new ZipException('size out of range');
  160. }
  161. $map = [];
  162. if (null !== $handle && 0 < $size) {
  163. $end = $off + $size;
  164. while ($off < $end) {
  165. fseek($handle, $off);
  166. $unpack = unpack('vheaderId/vdataSize', fread($handle, 4));
  167. $off += 4;
  168. $extraField = ExtraField::create($unpack['headerId']);
  169. $extraField->readFrom($handle, $off, $unpack['dataSize']);
  170. $off += $unpack['dataSize'];
  171. $map[$unpack['headerId']] = $extraField;
  172. }
  173. assert($off === $end);
  174. }
  175. $this->extra = $map;
  176. }
  177. /**
  178. * If clone extra fields.
  179. */
  180. function __clone()
  181. {
  182. foreach ($this->extra as $k => $v) {
  183. $this->extra[$k] = clone $v;
  184. }
  185. }
  186. }