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