Zip.h 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. * Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #pragma once
  7. #include <AK/Function.h>
  8. #include <AK/IterationDecision.h>
  9. #include <AK/Span.h>
  10. #include <AK/Stream.h>
  11. #include <AK/String.h>
  12. #include <AK/Vector.h>
  13. #include <string.h>
  14. namespace Archive {
  15. // NOTE: Due to the format of zip files compression is streamed and decompression is random access.
  16. static constexpr u8 end_of_central_directory_signature[] = { 0x50, 0x4b, 0x05, 0x06 }; // 'PK\x05\x06'
  17. struct [[gnu::packed]] EndOfCentralDirectory {
  18. u16 disk_number;
  19. u16 central_directory_start_disk;
  20. u16 disk_records_count;
  21. u16 total_records_count;
  22. u32 central_directory_size;
  23. u32 central_directory_offset;
  24. u16 comment_length;
  25. const u8* comment;
  26. bool read(ReadonlyBytes buffer)
  27. {
  28. auto fields_size = sizeof(EndOfCentralDirectory) - sizeof(u8*);
  29. if (buffer.size() < sizeof(end_of_central_directory_signature) + fields_size)
  30. return false;
  31. if (memcmp(buffer.data(), end_of_central_directory_signature, sizeof(end_of_central_directory_signature)) != 0)
  32. return false;
  33. memcpy(reinterpret_cast<void*>(&disk_number), buffer.data() + sizeof(end_of_central_directory_signature), fields_size);
  34. if (buffer.size() < sizeof(end_of_central_directory_signature) + fields_size + comment_length)
  35. return false;
  36. comment = buffer.data() + sizeof(end_of_central_directory_signature) + fields_size;
  37. return true;
  38. }
  39. void write(OutputStream& stream) const
  40. {
  41. stream.write_or_error({ end_of_central_directory_signature, sizeof(end_of_central_directory_signature) });
  42. stream << disk_number;
  43. stream << central_directory_start_disk;
  44. stream << disk_records_count;
  45. stream << total_records_count;
  46. stream << central_directory_size;
  47. stream << central_directory_offset;
  48. stream << comment_length;
  49. if (comment_length > 0)
  50. stream.write_or_error({ comment, comment_length });
  51. }
  52. };
  53. static constexpr u8 central_directory_record_signature[] = { 0x50, 0x4b, 0x01, 0x02 }; // 'PK\x01\x02'
  54. struct [[gnu::packed]] CentralDirectoryRecord {
  55. u16 made_by_version;
  56. u16 minimum_version;
  57. u16 general_purpose_flags;
  58. u16 compression_method;
  59. u16 modification_time;
  60. u16 modification_date;
  61. u32 crc32;
  62. u32 compressed_size;
  63. u32 uncompressed_size;
  64. u16 name_length;
  65. u16 extra_data_length;
  66. u16 comment_length;
  67. u16 start_disk;
  68. u16 internal_attributes;
  69. u32 external_attributes;
  70. u32 local_file_header_offset;
  71. const u8* name;
  72. const u8* extra_data;
  73. const u8* comment;
  74. bool read(ReadonlyBytes buffer)
  75. {
  76. auto fields_size = sizeof(CentralDirectoryRecord) - (sizeof(u8*) * 3);
  77. if (buffer.size() < sizeof(central_directory_record_signature) + fields_size)
  78. return false;
  79. if (memcmp(buffer.data(), central_directory_record_signature, sizeof(central_directory_record_signature)) != 0)
  80. return false;
  81. memcpy(reinterpret_cast<void*>(&made_by_version), buffer.data() + sizeof(central_directory_record_signature), fields_size);
  82. if (buffer.size() < sizeof(end_of_central_directory_signature) + fields_size + comment_length + name_length + extra_data_length)
  83. return false;
  84. name = buffer.data() + sizeof(central_directory_record_signature) + fields_size;
  85. extra_data = name + name_length;
  86. comment = extra_data + extra_data_length;
  87. return true;
  88. }
  89. void write(OutputStream& stream) const
  90. {
  91. stream.write_or_error({ central_directory_record_signature, sizeof(central_directory_record_signature) });
  92. stream << made_by_version;
  93. stream << minimum_version;
  94. stream << general_purpose_flags;
  95. stream << compression_method;
  96. stream << modification_time;
  97. stream << modification_date;
  98. stream << crc32;
  99. stream << compressed_size;
  100. stream << uncompressed_size;
  101. stream << name_length;
  102. stream << extra_data_length;
  103. stream << comment_length;
  104. stream << start_disk;
  105. stream << internal_attributes;
  106. stream << external_attributes;
  107. stream << local_file_header_offset;
  108. if (name_length > 0)
  109. stream.write_or_error({ name, name_length });
  110. if (extra_data_length > 0)
  111. stream.write_or_error({ extra_data, extra_data_length });
  112. if (comment_length > 0)
  113. stream.write_or_error({ comment, comment_length });
  114. }
  115. [[nodiscard]] size_t size() const
  116. {
  117. return sizeof(central_directory_record_signature) + (sizeof(CentralDirectoryRecord) - (sizeof(u8*) * 3)) + name_length + extra_data_length + comment_length;
  118. }
  119. };
  120. static constexpr u32 zip_directory_external_attribute = 1 << 4;
  121. static constexpr u8 local_file_header_signature[] = { 0x50, 0x4b, 0x03, 0x04 }; // 'PK\x03\x04'
  122. struct [[gnu::packed]] LocalFileHeader {
  123. u16 minimum_version;
  124. u16 general_purpose_flags;
  125. u16 compression_method;
  126. u16 modification_time;
  127. u16 modification_date;
  128. u32 crc32;
  129. u32 compressed_size;
  130. u32 uncompressed_size;
  131. u16 name_length;
  132. u16 extra_data_length;
  133. const u8* name;
  134. const u8* extra_data;
  135. const u8* compressed_data;
  136. bool read(ReadonlyBytes buffer)
  137. {
  138. auto fields_size = sizeof(LocalFileHeader) - (sizeof(u8*) * 3);
  139. if (buffer.size() < sizeof(local_file_header_signature) + fields_size)
  140. return false;
  141. if (memcmp(buffer.data(), local_file_header_signature, sizeof(local_file_header_signature)) != 0)
  142. return false;
  143. memcpy(reinterpret_cast<void*>(&minimum_version), buffer.data() + sizeof(local_file_header_signature), fields_size);
  144. if (buffer.size() < sizeof(end_of_central_directory_signature) + fields_size + name_length + extra_data_length + compressed_size)
  145. return false;
  146. name = buffer.data() + sizeof(local_file_header_signature) + fields_size;
  147. extra_data = name + name_length;
  148. compressed_data = extra_data + extra_data_length;
  149. return true;
  150. }
  151. void write(OutputStream& stream) const
  152. {
  153. stream.write_or_error({ local_file_header_signature, sizeof(local_file_header_signature) });
  154. stream << minimum_version;
  155. stream << general_purpose_flags;
  156. stream << compression_method;
  157. stream << modification_time;
  158. stream << modification_date;
  159. stream << crc32;
  160. stream << compressed_size;
  161. stream << uncompressed_size;
  162. stream << name_length;
  163. stream << extra_data_length;
  164. if (name_length > 0)
  165. stream.write_or_error({ name, name_length });
  166. if (extra_data_length > 0)
  167. stream.write_or_error({ extra_data, extra_data_length });
  168. if (compressed_size > 0)
  169. stream.write_or_error({ compressed_data, compressed_size });
  170. }
  171. };
  172. enum ZipCompressionMethod : u16 {
  173. Store = 0,
  174. Shrink = 1,
  175. Reduce1 = 2,
  176. Reduce2 = 3,
  177. Reduce3 = 4,
  178. Reduce4 = 5,
  179. Implode = 6,
  180. Reserved = 7,
  181. Deflate = 8
  182. };
  183. struct ZipMember {
  184. String name;
  185. ReadonlyBytes compressed_data; // TODO: maybe the decompression/compression should be handled by LibArchive instead of the user?
  186. ZipCompressionMethod compression_method;
  187. u32 uncompressed_size;
  188. u32 crc32;
  189. bool is_directory;
  190. };
  191. class Zip {
  192. public:
  193. static Optional<Zip> try_create(const ReadonlyBytes& buffer);
  194. bool for_each_member(Function<IterationDecision(const ZipMember&)>);
  195. private:
  196. static bool find_end_of_central_directory_offset(const ReadonlyBytes&, size_t& offset);
  197. u16 member_count { 0 };
  198. size_t members_start_offset { 0 };
  199. ReadonlyBytes m_input_data;
  200. };
  201. class ZipOutputStream {
  202. public:
  203. ZipOutputStream(OutputStream&);
  204. void add_member(const ZipMember&);
  205. void finish();
  206. private:
  207. OutputStream& m_stream;
  208. Vector<ZipMember> m_members;
  209. bool m_finished { false };
  210. };
  211. }