Zip.h 8.9 KB

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