Zip.h 9.4 KB

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