Coverage Report

Created: 2025-03-04 07:22

/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
}