• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kimci86 / bkcrack / 18017127585

25 Sep 2025 06:10PM UTC coverage: 77.156% (-0.05%) from 77.205%
18017127585

push

github

kimci86
Update -r option example in readme for consistency

1557 of 2018 relevant lines covered (77.16%)

1651578.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

63.43
/src/Zip.cpp
1
#include "Zip.hpp"
2

3
#include "Data.hpp"
4
#include "file.hpp"
5

6
#include <algorithm>
7
#include <iterator>
8
#include <map>
9
#include <numeric>
10
#include <variant>
11

12
namespace
13
{
14

15
template <typename T>
16
auto readInt(std::istream& is) -> T
634✔
17
{
18
    // We make no assumption about platform endianness.
19
    auto x = T{};
634✔
20
    for (auto index = std::size_t{}; index < sizeof(T); index++)
2,366✔
21
        x |= static_cast<T>(is.get()) << (8 * index);
1,732✔
22

23
    return x;
634✔
24
}
25

26
template <typename T>
27
void writeInt(std::ostream& os, const T& x)
148✔
28
{
29
    // We make no assumption about platform endianness.
30
    for (auto index = std::size_t{}; index < sizeof(T); index++)
548✔
31
        os.put(lsb(x >> (8 * index)));
400✔
32
}
148✔
33

34
auto readString(std::istream& is, std::size_t length) -> std::string
41✔
35
{
36
    auto string = std::string{};
41✔
37
    string.resize(length);
41✔
38
    is.read(string.data(), string.size());
41✔
39
    return string;
41✔
40
}
×
41

42
void writeString(std::ostream& os, const std::string& string)
8✔
43
{
44
    os.write(string.data(), string.size());
8✔
45
}
8✔
46

47
struct LocalFileHeader;
48
struct CentralDirectoryHeader;
49

50
struct ExtraField
51
{
52
    struct Aes
53
    {
54
        static constexpr auto headerId = std::uint16_t{0x9901};
55

56
        static constexpr auto getDataSize() -> std::uint16_t
×
57
        {
58
            return 7;
×
59
        }
60

61
        std::uint16_t version;
62
        std::uint16_t vendor;
63
        std::uint8_t  strength;
64
        std::uint16_t method;
65

66
        static auto read(std::istream& is, std::uint16_t dataSize) -> Aes
×
67
        {
68
            auto data = Aes{};
×
69
            if (dataSize == getDataSize())
×
70
            {
71
                data.version  = readInt<std::uint16_t>(is);
×
72
                data.vendor   = readInt<std::uint16_t>(is);
×
73
                data.strength = readInt<std::uint8_t>(is);
×
74
                data.method   = readInt<std::uint16_t>(is);
×
75
            }
76
            if (!is || dataSize != getDataSize())
×
77
                throw Zip::Error{"could not read AES extra field"};
×
78
            return data;
×
79
        }
80

81
        void writeData(std::ostream& os) const
×
82
        {
83
            writeInt(os, version);
×
84
            writeInt(os, vendor);
×
85
            writeInt(os, strength);
×
86
            writeInt(os, method);
×
87
        }
×
88
    };
89

90
    struct InfoZipUnicodePath
91
    {
92
        static constexpr auto headerId = std::uint16_t{0x7075};
93

94
        auto getDataSize() const -> std::uint16_t
×
95
        {
96
            return 5 + unicodeName.size();
×
97
        }
98

99
        std::uint8_t  version;
100
        std::uint32_t nameCrc32;
101
        std::string   unicodeName;
102

103
        static auto read(std::istream& is, std::uint16_t dataSize) -> InfoZipUnicodePath
×
104
        {
105
            auto data = InfoZipUnicodePath{};
×
106
            if (5 <= dataSize)
×
107
            {
108
                data.version     = readInt<std::uint8_t>(is);
×
109
                data.nameCrc32   = readInt<std::uint32_t>(is);
×
110
                data.unicodeName = readString(is, dataSize - 5);
×
111
            }
112
            if (!is || dataSize != data.getDataSize())
×
113
                throw Zip::Error{"could not read Info-Zip Unicode Path extra field"};
×
114
            return data;
×
115
        }
×
116

117
        void writeData(std::ostream& os) const
×
118
        {
119
            writeInt(os, version);
×
120
            writeInt(os, nameCrc32);
×
121
            writeString(os, unicodeName);
×
122
        }
×
123
    };
124

125
    struct Zip64
126
    {
127
        static constexpr auto headerId = std::uint16_t{0x0001};
128

129
        auto getDataSize() const -> std::uint16_t
×
130
        {
131
            return (uncompressedSize ? 8 : 0) + (compressedSize ? 8 : 0) + (headerOffset ? 8 : 0) +
×
132
                   (diskStartNumber ? 4 : 0);
×
133
        }
134

135
        std::optional<std::uint64_t> uncompressedSize;
136
        std::optional<std::uint64_t> compressedSize;
137
        std::optional<std::uint64_t> headerOffset;
138
        std::optional<std::uint32_t> diskStartNumber;
139

140
        template <typename Header, typename = std::enable_if_t<std::is_same_v<Header, LocalFileHeader> ||
141
                                                               std::is_same_v<Header, CentralDirectoryHeader>>>
142
        static auto read(std::istream& is, std::uint16_t dataSize, const Header& header) -> Zip64
×
143
        {
144
            auto data      = Zip64{};
×
145
            auto remaining = dataSize;
×
146
            if (8 <= remaining && header.uncompressedSize == mask<0, 32>)
×
147
            {
148
                data.uncompressedSize = readInt<std::uint64_t>(is);
×
149
                remaining -= 8;
×
150
            }
151
            if (8 <= remaining && header.compressedSize == mask<0, 32>)
×
152
            {
153
                data.compressedSize = readInt<std::uint64_t>(is);
×
154
                remaining -= 8;
×
155
            }
156
            if constexpr (std::is_same_v<Header, CentralDirectoryHeader>)
157
            {
158
                if (8 <= remaining && header.headerOffset == mask<0, 32>)
×
159
                {
160
                    data.headerOffset = readInt<std::uint64_t>(is);
×
161
                    remaining -= 8;
×
162
                }
163
                if (4 <= remaining && header.diskStartNumber == mask<0, 16>)
×
164
                {
165
                    data.diskStartNumber = readInt<std::uint32_t>(is);
×
166
                    remaining -= 4;
×
167
                }
168
            }
169

170
            if (!is || dataSize != data.getDataSize())
×
171
                throw Zip::Error{"could not read ZIP64 extra field"};
×
172

173
            return data;
×
174
        }
175

176
        void writeData(std::ostream& os) const
×
177
        {
178
            if (uncompressedSize)
×
179
                writeInt(os, *uncompressedSize);
×
180
            if (compressedSize)
×
181
                writeInt(os, *compressedSize);
×
182
            if (headerOffset)
×
183
                writeInt(os, *headerOffset);
×
184
            if (diskStartNumber)
×
185
                writeInt(os, *diskStartNumber);
×
186
        }
×
187
    };
188

189
    struct Other
190
    {
191
        std::uint16_t headerId;
192

193
        auto getDataSize() const -> std::uint16_t
15✔
194
        {
195
            return data.size();
15✔
196
        }
197

198
        std::string data;
199

200
        static auto read(std::istream& is, std::uint16_t headerId, std::uint16_t dataSize) -> Other
13✔
201
        {
202
            auto data = Other{headerId, readString(is, dataSize)};
13✔
203
            if (!is || dataSize != data.getDataSize())
13✔
204
                throw Zip::Error{"could not read extra field"};
×
205
            return data;
13✔
206
        }
×
207

208
        void writeData(std::ostream& os) const
2✔
209
        {
210
            writeString(os, data);
2✔
211
        }
2✔
212
    };
213

214
    template <typename Header, typename = std::enable_if_t<std::is_same_v<Header, LocalFileHeader> ||
215
                                                           std::is_same_v<Header, CentralDirectoryHeader>>>
216
    static auto read(std::istream& is, const Header& header) -> ExtraField
30✔
217
    {
218
        auto extraField = ExtraField{};
30✔
219
        auto remaining  = header.extraFieldLength;
30✔
220
        while (remaining)
56✔
221
        {
222
            if (remaining < 4)
26✔
223
                throw Zip::Error{"could not read extra field"};
×
224

225
            const auto headerId = readInt<std::uint16_t>(is);
26✔
226
            const auto dataSize = readInt<std::uint16_t>(is);
26✔
227
            remaining -= 4;
26✔
228

229
            if (!is || remaining < dataSize)
26✔
230
                throw Zip::Error{"could not read extra field"};
×
231

232
            switch (headerId)
26✔
233
            {
234
            case Aes::headerId:
×
235
                extraField.blocks.emplace_back(Aes::read(is, dataSize));
×
236
                break;
×
237
            case InfoZipUnicodePath::headerId:
×
238
                extraField.blocks.emplace_back(InfoZipUnicodePath::read(is, dataSize));
×
239
                break;
×
240
            case Zip64::headerId:
×
241
                extraField.blocks.emplace_back(Zip64::read(is, dataSize, header));
×
242
                break;
×
243
            default:
26✔
244
                extraField.blocks.emplace_back(Other::read(is, headerId, dataSize));
26✔
245
                break;
26✔
246
            }
247
            remaining -= dataSize;
26✔
248
        }
249
        return extraField;
30✔
250
    }
×
251

252
    void write(std::ostream& os) const
4✔
253
    {
254
        for (const auto& block : blocks)
6✔
255
            std::visit(
2✔
256
                [&os](const auto& block)
6✔
257
                {
258
                    writeInt(os, block.headerId);
4✔
259
                    writeInt(os, block.getDataSize());
4✔
260
                    block.writeData(os);
4✔
261
                },
4✔
262
                block);
263
    }
4✔
264

265
    template <typename T>
266
    auto find() const -> const T*
66✔
267
    {
268
        const auto it = std::find_if(blocks.begin(), blocks.end(),
66✔
269
                                     [](const auto& block) { return std::holds_alternative<T>(block); });
33✔
270
        return it != blocks.end() ? &std::get<T>(*it) : nullptr;
66✔
271
    }
272

273
    template <typename T>
274
    auto find() -> T*
4✔
275
    {
276
        const auto it = std::find_if(blocks.begin(), blocks.end(),
4✔
277
                                     [](const auto& block) { return std::holds_alternative<T>(block); });
2✔
278
        return it != blocks.end() ? &std::get<T>(*it) : nullptr;
4✔
279
    }
280

281
    std::vector<std::variant<Aes, InfoZipUnicodePath, Zip64, Other>> blocks;
282
};
283

284
struct LocalFileHeader
285
{
286
    std::uint16_t versionNeededToExtract;
287
    std::uint16_t flags;
288
    std::uint16_t method;
289
    std::uint16_t lastModTime;
290
    std::uint16_t lastModDate;
291
    std::uint32_t crc32;
292
    std::uint32_t compressedSize;
293
    std::uint32_t uncompressedSize;
294
    std::uint16_t filenameLength;
295
    std::uint16_t extraFieldLength;
296
    std::string   filename;
297
    ExtraField    extraField;
298

299
    static auto read(std::istream& is) -> LocalFileHeader
2✔
300
    {
301
        auto header                   = LocalFileHeader{};
2✔
302
        header.versionNeededToExtract = readInt<std::uint16_t>(is);
2✔
303
        header.flags                  = readInt<std::uint16_t>(is);
2✔
304
        header.method                 = readInt<std::uint16_t>(is);
2✔
305
        header.lastModTime            = readInt<std::uint16_t>(is);
2✔
306
        header.lastModDate            = readInt<std::uint16_t>(is);
2✔
307
        header.crc32                  = readInt<std::uint32_t>(is);
2✔
308
        header.compressedSize         = readInt<std::uint32_t>(is);
2✔
309
        header.uncompressedSize       = readInt<std::uint32_t>(is);
2✔
310
        header.filenameLength         = readInt<std::uint16_t>(is);
2✔
311
        header.extraFieldLength       = readInt<std::uint16_t>(is);
2✔
312
        header.filename               = readString(is, header.filenameLength);
2✔
313
        header.extraField             = ExtraField::read(is, header);
2✔
314

315
        if (!is)
2✔
316
            throw Zip::Error{"could not read local file header"};
×
317

318
        return header;
2✔
319
    }
×
320

321
    void write(std::ostream& os) const
2✔
322
    {
323
        writeInt(os, versionNeededToExtract);
2✔
324
        writeInt(os, flags);
2✔
325
        writeInt(os, method);
2✔
326
        writeInt(os, lastModTime);
2✔
327
        writeInt(os, lastModDate);
2✔
328
        writeInt(os, crc32);
2✔
329
        writeInt(os, compressedSize);
2✔
330
        writeInt(os, uncompressedSize);
2✔
331
        writeInt(os, filenameLength);
2✔
332
        writeInt(os, extraFieldLength);
2✔
333
        writeString(os, filename);
2✔
334
        extraField.write(os);
2✔
335
    }
2✔
336
};
337

338
struct CentralDirectoryHeader
339
{
340
    std::uint16_t versionMadeBy;
341
    std::uint16_t versionNeededToExtract;
342
    std::uint16_t flags;
343
    std::uint16_t method;
344
    std::uint16_t lastModTime;
345
    std::uint16_t lastModDate;
346
    std::uint32_t crc32;
347
    std::uint32_t compressedSize;
348
    std::uint32_t uncompressedSize;
349
    std::uint16_t filenameLength;
350
    std::uint16_t extraFieldLength;
351
    std::uint16_t fileCommentLength;
352
    std::uint16_t diskStartNumber;
353
    std::uint16_t internalFileAttributes;
354
    std::uint32_t externalFileAttributes;
355
    std::uint32_t headerOffset;
356
    std::string   filename;
357
    ExtraField    extraField;
358
    std::string   fileComment;
359

360
    static auto read(std::istream& is) -> CentralDirectoryHeader
13✔
361
    {
362
        auto header                   = CentralDirectoryHeader{};
13✔
363
        header.versionMadeBy          = readInt<std::uint16_t>(is);
13✔
364
        header.versionNeededToExtract = readInt<std::uint16_t>(is);
13✔
365
        header.flags                  = readInt<std::uint16_t>(is);
13✔
366
        header.method                 = readInt<std::uint16_t>(is);
13✔
367
        header.lastModTime            = readInt<std::uint16_t>(is);
13✔
368
        header.lastModDate            = readInt<std::uint16_t>(is);
13✔
369
        header.crc32                  = readInt<std::uint32_t>(is);
13✔
370
        header.compressedSize         = readInt<std::uint32_t>(is);
13✔
371
        header.uncompressedSize       = readInt<std::uint32_t>(is);
13✔
372
        header.filenameLength         = readInt<std::uint16_t>(is);
13✔
373
        header.extraFieldLength       = readInt<std::uint16_t>(is);
13✔
374
        header.fileCommentLength      = readInt<std::uint16_t>(is);
13✔
375
        header.diskStartNumber        = readInt<std::uint16_t>(is);
13✔
376
        header.internalFileAttributes = readInt<std::uint16_t>(is);
13✔
377
        header.externalFileAttributes = readInt<std::uint32_t>(is);
13✔
378
        header.headerOffset           = readInt<std::uint32_t>(is);
13✔
379
        header.filename               = readString(is, header.filenameLength);
13✔
380
        header.extraField             = ExtraField::read(is, header);
13✔
381
        header.fileComment            = readString(is, header.fileCommentLength);
13✔
382

383
        if (!is)
13✔
384
            throw Zip::Error{"could not read central directory header"};
×
385

386
        return header;
13✔
387
    }
×
388

389
    void write(std::ostream& os) const
2✔
390
    {
391
        writeInt(os, versionMadeBy);
2✔
392
        writeInt(os, versionNeededToExtract);
2✔
393
        writeInt(os, flags);
2✔
394
        writeInt(os, method);
2✔
395
        writeInt(os, lastModTime);
2✔
396
        writeInt(os, lastModDate);
2✔
397
        writeInt(os, crc32);
2✔
398
        writeInt(os, compressedSize);
2✔
399
        writeInt(os, uncompressedSize);
2✔
400
        writeInt(os, filenameLength);
2✔
401
        writeInt(os, extraFieldLength);
2✔
402
        writeInt(os, fileCommentLength);
2✔
403
        writeInt(os, diskStartNumber);
2✔
404
        writeInt(os, internalFileAttributes);
2✔
405
        writeInt(os, externalFileAttributes);
2✔
406
        writeInt(os, headerOffset);
2✔
407
        writeString(os, filename);
2✔
408
        extraField.write(os);
2✔
409
        writeString(os, fileComment);
2✔
410
    }
2✔
411

412
    auto getEncryption() const -> Zip::Encryption
13✔
413
    {
414
        return flags & 1
13✔
415
                   ? method == 99 || (flags >> 6) & 1 ? Zip::Encryption::Unsupported : Zip::Encryption::Traditional
13✔
416
                   : Zip::Encryption::None;
13✔
417
    }
418
};
419

420
enum class Signature : std::uint32_t
421
{
422
    LocalFileHeader        = 0x04034b50,
423
    DataDescriptor         = 0x08074b50,
424
    CentralDirectoryHeader = 0x02014b50,
425
    Zip64Eocd              = 0x06064b50,
426
    Zip64EocdLocator       = 0x07064b50,
427
    Eocd                   = 0x06054b50
428
};
429

430
auto checkSignature(std::istream& is, const Signature& signature) -> bool
29✔
431
{
432
    const auto sig = readInt<std::uint32_t>(is);
29✔
433
    return is && sig == static_cast<std::uint32_t>(signature);
29✔
434
}
435

436
auto findCentralDirectoryOffset(std::istream& is) -> std::uint64_t
6✔
437
{
438
    auto centralDirectoryOffset = std::uint64_t{};
6✔
439

440
    // find end of central directory signature
441
    {
442
        auto signature     = std::uint32_t{};
6✔
443
        auto commentLength = std::uint16_t{};
6✔
444
        do
445
        {
446
            is.seekg(-22 - commentLength, std::ios::end);
6✔
447
            signature = readInt<std::uint32_t>(is);
6✔
448
        } while (is && signature != static_cast<std::uint32_t>(Signature::Eocd) && commentLength++ < mask<0, 16>);
6✔
449

450
        if (!is || signature != static_cast<std::uint32_t>(Signature::Eocd))
6✔
451
            throw Zip::Error{"could not find end of central directory signature"};
×
452
    }
453

454
    // read end of central directory record
455
    {
456
        const auto disk = readInt<std::uint16_t>(is);
6✔
457
        is.seekg(10, std::ios::cur);
6✔
458
        centralDirectoryOffset = readInt<std::uint32_t>(is);
6✔
459

460
        if (!is)
6✔
461
            throw Zip::Error{"could not read end of central directory record"};
×
462
        if (disk != 0)
6✔
463
            throw Zip::Error{"split zip archives are not supported"};
×
464
    }
465

466
    // look for Zip64 end of central directory locator
467
    is.seekg(-40, std::ios::cur);
6✔
468
    if (is.fail())
6✔
469
    {
470
        // Ignore seekg error when file is too small (e.g. an archive with no entry)
471
        is.clear();
×
472
    }
473
    else if (checkSignature(is, Signature::Zip64EocdLocator))
6✔
474
    {
475
        is.seekg(4, std::ios::cur);
×
476
        const auto zip64EndOfCentralDirectoryOffset = readInt<std::uint64_t>(is);
×
477

478
        if (!is)
×
479
            throw Zip::Error{"could not read Zip64 end of central directory locator record"};
×
480

481
        // read Zip64 end of central directory record
482
        is.seekg(zip64EndOfCentralDirectoryOffset, std::ios::beg);
×
483
        if (checkSignature(is, Signature::Zip64Eocd))
×
484
        {
485
            is.seekg(10, std::ios::cur);
×
486
            const auto versionNeededToExtract = readInt<std::uint16_t>(is);
×
487
            is.seekg(32, std::ios::cur);
×
488
            centralDirectoryOffset = readInt<std::uint64_t>(is);
×
489

490
            if (!is)
×
491
                throw Zip::Error{"could not read Zip64 end of central directory record"};
×
492
            if (versionNeededToExtract >= 62) // Version 6.2 introduces central directory encryption.
×
493
                throw Zip::Error{"central directory encryption is not supported"};
×
494
        }
495
        else
496
            throw Zip::Error{"could not find Zip64 end of central directory record"};
×
497
    }
498

499
    return centralDirectoryOffset;
6✔
500
}
501

502
} // namespace
503

504
Zip::Error::Error(const std::string& description)
×
505
: BaseError{"Zip error", description}
×
506
{
507
}
×
508

509
Zip::Iterator::Iterator(const Zip& archive)
6✔
510
: m_is{&archive.m_is.seekg(archive.m_centralDirectoryOffset, std::ios::beg)}
6✔
511
, m_entry{Entry{}}
6✔
512
{
513
    ++(*this);
6✔
514
}
6✔
515

516
auto Zip::Iterator::operator++() -> Zip::Iterator&
15✔
517
{
518
    if (!checkSignature(*m_is, Signature::CentralDirectoryHeader))
15✔
519
        return *this = Iterator{};
4✔
520

521
    const auto header = CentralDirectoryHeader::read(*m_is);
11✔
522

523
    const auto* aesExtraData = header.extraField.find<ExtraField::Aes>();
11✔
524
    const auto* unicodePath  = header.extraField.find<ExtraField::InfoZipUnicodePath>();
11✔
525
    const auto* zip64        = header.extraField.find<ExtraField::Zip64>();
11✔
526

527
    m_entry->name =
11✔
528
        (unicodePath && (std::accumulate(header.filename.begin(), header.filename.end(), mask<0, 32>, Crc32Tab::crc32) ^
×
529
                         mask<0, 32>) == unicodePath->nameCrc32)
×
530
            ? unicodePath->unicodeName
531
            : header.filename;
22✔
532
    m_entry->encryption       = header.getEncryption();
11✔
533
    m_entry->compression      = static_cast<Zip::Compression>(aesExtraData ? aesExtraData->method : header.method);
11✔
534
    m_entry->crc32            = header.crc32;
11✔
535
    m_entry->offset           = zip64 && zip64->headerOffset ? *zip64->headerOffset : header.headerOffset;
11✔
536
    m_entry->packedSize       = zip64 && zip64->compressedSize ? *zip64->compressedSize : header.compressedSize;
11✔
537
    m_entry->uncompressedSize = zip64 && zip64->uncompressedSize ? *zip64->uncompressedSize : header.uncompressedSize;
11✔
538
    m_entry->checkByte        = (header.flags >> 3) & 1 ? lsb(header.lastModTime >> 8) : msb(header.crc32);
11✔
539

540
    return *this;
11✔
541
}
11✔
542

543
auto Zip::Iterator::operator++(int) -> Zip::Iterator
×
544
{
545
    auto copy = *this;
×
546
    ++(*this);
×
547
    return copy;
×
548
}
×
549

550
Zip::Zip(std::istream& stream)
6✔
551
: m_is{stream}
6✔
552
, m_centralDirectoryOffset{findCentralDirectoryOffset(m_is)}
6✔
553
{
554
}
6✔
555

556
auto Zip::operator[](const std::string& name) const -> Zip::Entry
2✔
557
{
558
    const auto it = std::find_if(begin(), end(), [&name](const Entry& entry) { return entry.name == name; });
5✔
559

560
    if (it == end())
2✔
561
        throw Error{"found no entry named \"" + name + "\""};
×
562
    else
563
        return *it;
4✔
564
}
2✔
565

566
auto Zip::operator[](std::size_t index) const -> Zip::Entry
×
567
{
568
    auto       nextIndex = std::size_t{};
×
569
    const auto it = std::find_if(begin(), end(), [&nextIndex, index](const Entry&) { return nextIndex++ == index; });
×
570

571
    if (it == end())
×
572
        throw Error{"found no entry at index " + std::to_string(index) + " (maximum index for this archive is " +
×
573
                    std::to_string(nextIndex - 1) + ")"};
×
574
    else
575
        return *it;
×
576
}
×
577

578
void Zip::checkEncryption(const Entry& entry, Encryption expected)
2✔
579
{
580
    if (entry.encryption != expected)
2✔
581
    {
582
        if (entry.encryption == Encryption::None)
×
583
            throw Error{"entry \"" + entry.name + "\" is not encrypted"};
×
584
        else if (expected == Encryption::None)
×
585
            throw Error{"entry \"" + entry.name + "\" is encrypted"};
×
586
        else
587
            throw Error{"entry \"" + entry.name + "\" is encrypted with an unsupported algorithm"};
×
588
    }
589
}
2✔
590

591
auto Zip::seek(const Entry& entry) const -> std::istream&
2✔
592
{
593
    m_is.seekg(entry.offset, std::ios::beg);
2✔
594
    if (!checkSignature(m_is, Signature::LocalFileHeader))
2✔
595
        throw Error{"could not find local file header"};
×
596

597
    // skip local file header
598
    m_is.seekg(22, std::ios::cur);
2✔
599
    const auto nameSize  = readInt<std::uint16_t>(m_is);
2✔
600
    const auto extraSize = readInt<std::uint16_t>(m_is);
2✔
601
    m_is.seekg(nameSize + extraSize, std::ios::cur);
2✔
602

603
    return m_is;
2✔
604
}
605

606
auto Zip::load(const Entry& entry, std::size_t count) const -> std::vector<std::uint8_t>
1✔
607
{
608
    return loadStream(seek(entry), std::min(entry.packedSize, static_cast<std::uint64_t>(count)));
1✔
609
}
610

611
void Zip::changeKeys(std::ostream& os, const Keys& oldKeys, const Keys& newKeys, Progress& progress) const
2✔
612
{
613
    // Store encrypted entries local file header offset and packed size.
614
    // Use std::map to sort them by local file header offset.
615
    auto packedSizeByLocalOffset = std::map<std::uint64_t, std::uint64_t>{};
2✔
616
    for (const auto& entry : *this)
6✔
617
        if (entry.encryption == Encryption::Traditional)
4✔
618
            packedSizeByLocalOffset.insert({entry.offset, entry.packedSize});
6✔
619

620
    // Rewind input stream and iterate on encrypted entries to change the keys, copy the rest.
621
    m_is.seekg(0, std::ios::beg);
2✔
622
    auto currentOffset = std::uint64_t{};
2✔
623

624
    progress.done  = 0;
2✔
625
    progress.total = packedSizeByLocalOffset.size();
2✔
626

627
    for (const auto& [localHeaderOffset, packedSize] : packedSizeByLocalOffset)
6✔
628
    {
629
        if (currentOffset < localHeaderOffset)
4✔
630
        {
631
            std::copy_n(std::istreambuf_iterator{m_is}, localHeaderOffset - currentOffset,
×
632
                        std::ostreambuf_iterator{os});
633
            m_is.get();
×
634
        }
635

636
        if (!checkSignature(m_is, Signature::LocalFileHeader))
4✔
637
            throw Error{"could not find local file header"};
×
638

639
        writeInt(os, static_cast<std::uint32_t>(Signature::LocalFileHeader));
4✔
640

641
        std::copy_n(std::istreambuf_iterator{m_is}, 22, std::ostreambuf_iterator{os});
4✔
642
        m_is.get();
4✔
643

644
        const auto filenameLength = readInt<std::uint16_t>(m_is);
4✔
645
        const auto extraSize      = readInt<std::uint16_t>(m_is);
4✔
646
        writeInt(os, filenameLength);
4✔
647
        writeInt(os, extraSize);
4✔
648

649
        if (0 < filenameLength + extraSize)
4✔
650
        {
651
            std::copy_n(std::istreambuf_iterator{m_is}, filenameLength + extraSize, std::ostreambuf_iterator{os});
4✔
652
            m_is.get();
4✔
653
        }
654

655
        auto decrypt = oldKeys;
4✔
656
        auto encrypt = newKeys;
4✔
657
        auto in      = std::istreambuf_iterator{m_is};
4✔
658
        std::generate_n(std::ostreambuf_iterator{os}, packedSize,
4✔
659
                        [&in, &decrypt, &encrypt]() -> char
111,954✔
660
                        {
661
                            const auto p = *in++ ^ decrypt.getK();
111,954✔
662
                            const auto c = p ^ encrypt.getK();
111,954✔
663
                            decrypt.update(p);
111,954✔
664
                            encrypt.update(p);
111,954✔
665
                            return c;
111,954✔
666
                        });
667

668
        currentOffset = localHeaderOffset + 30 + filenameLength + extraSize + packedSize;
4✔
669

670
        progress.done++;
4✔
671
    }
672

673
    std::copy(std::istreambuf_iterator{m_is}, {}, std::ostreambuf_iterator{os});
2✔
674
}
2✔
675

676
void Zip::decrypt(std::ostream& os, const Keys& keys, Progress& progress) const
1✔
677
{
678
    // Store encrypted entries local file header offset and packed size.
679
    // Use std::map to sort them by local file header offset.
680
    auto packedSizeByLocalOffset = std::map<std::uint64_t, std::uint64_t>{};
1✔
681
    for (const auto& entry : *this)
3✔
682
        if (entry.encryption == Encryption::Traditional)
2✔
683
            packedSizeByLocalOffset.insert({entry.offset, entry.packedSize});
3✔
684

685
    // Rewind input stream and iterate on encrypted entries to decipher data and update related metadata.
686
    m_is.seekg(0, std::ios::beg);
1✔
687
    auto currentOffset = std::uint64_t{};
1✔
688

689
    progress.done  = 0;
1✔
690
    progress.total = packedSizeByLocalOffset.size();
1✔
691

692
    for (const auto& [localHeaderOffset, packedSize] : packedSizeByLocalOffset)
3✔
693
    {
694
        if (currentOffset < localHeaderOffset)
2✔
695
        {
696
            std::copy_n(std::istreambuf_iterator{m_is}, localHeaderOffset - currentOffset,
×
697
                        std::ostreambuf_iterator{os});
698
            m_is.get();
×
699
        }
700

701
        // transform file header
702
        if (!checkSignature(m_is, Signature::LocalFileHeader))
2✔
703
            throw Error{"could not find local file header"};
×
704
        writeInt(os, static_cast<std::uint32_t>(Signature::LocalFileHeader));
2✔
705

706
        auto  fileHeader     = LocalFileHeader::read(m_is);
2✔
707
        auto* zip64ExtraData = fileHeader.extraField.find<ExtraField::Zip64>();
2✔
708
        fileHeader.flags &= ~1;
2✔
709
        if (zip64ExtraData && zip64ExtraData->compressedSize)
2✔
710
            *zip64ExtraData->compressedSize -= Data::encryptionHeaderSize;
×
711
        else if (Data::encryptionHeaderSize <= fileHeader.compressedSize)
2✔
712
            fileHeader.compressedSize -= Data::encryptionHeaderSize;
2✔
713
        fileHeader.write(os);
2✔
714

715
        // decipher file data
716
        decipher(m_is, packedSize, Data::encryptionHeaderSize, os, keys);
2✔
717

718
        // transform data descriptor
719
        auto dataDescriptorSize = 0;
2✔
720
        if ((fileHeader.flags >> 3) & 1)
2✔
721
        {
722
            // optional signature + crc-32
723
            const auto crc32OrSignature = readInt<std::uint32_t>(m_is);
×
724
            writeInt<std::uint32_t>(os, crc32OrSignature);
×
725
            dataDescriptorSize += 4;
×
726

727
            if (crc32OrSignature == static_cast<std::uint32_t>(Signature::DataDescriptor))
×
728
            {
729
                const auto actualCrc32 = readInt<std::uint32_t>(m_is);
×
730
                writeInt<std::uint32_t>(os, actualCrc32);
×
731
                dataDescriptorSize += 4;
×
732
            }
733

734
            // compressed size, uncompressed size
735
            if (zip64ExtraData)
×
736
            {
737
                writeInt<std::uint64_t>(os, readInt<std::uint64_t>(m_is) - Data::encryptionHeaderSize);
×
738
                writeInt<std::uint64_t>(os, readInt<std::uint64_t>(m_is));
×
739
                dataDescriptorSize += 8 + 8;
×
740
            }
741
            else
742
            {
743
                writeInt<std::uint32_t>(os, readInt<std::uint32_t>(m_is) - Data::encryptionHeaderSize);
×
744
                writeInt<std::uint32_t>(os, readInt<std::uint32_t>(m_is));
×
745
                dataDescriptorSize += 4 + 4;
×
746
            }
747
        }
748

749
        currentOffset = localHeaderOffset + 30 + fileHeader.filenameLength + fileHeader.extraFieldLength + packedSize +
2✔
750
                        dataDescriptorSize;
2✔
751

752
        progress.done++;
2✔
753
    }
2✔
754

755
    if (currentOffset < m_centralDirectoryOffset)
1✔
756
    {
757
        std::copy_n(std::istreambuf_iterator{m_is}, m_centralDirectoryOffset - currentOffset,
×
758
                    std::ostreambuf_iterator{os});
759
        m_is.get();
×
760
        currentOffset = m_centralDirectoryOffset;
×
761
    }
762

763
    const auto translateOffset = [&packedSizeByLocalOffset](auto offset)
6✔
764
    {
765
        return offset - std::distance(packedSizeByLocalOffset.begin(), packedSizeByLocalOffset.lower_bound(offset)) *
6✔
766
                            Data::encryptionHeaderSize;
6✔
767
    };
1✔
768

769
    // update metadata in central directory
770
    auto signature = readInt<std::uint32_t>(m_is);
1✔
771

772
    while (m_is && signature == static_cast<std::uint32_t>(Signature::CentralDirectoryHeader))
3✔
773
    {
774
        writeInt<std::uint32_t>(os, signature);
2✔
775

776
        auto  centralDirectoryHeader = CentralDirectoryHeader::read(m_is);
2✔
777
        auto* zip64ExtraData         = centralDirectoryHeader.extraField.find<ExtraField::Zip64>();
2✔
778
        if (centralDirectoryHeader.getEncryption() == Zip::Encryption::Traditional)
2✔
779
        {
780
            centralDirectoryHeader.flags &= ~1;
2✔
781
            if (zip64ExtraData && zip64ExtraData->compressedSize)
2✔
782
                *zip64ExtraData->compressedSize -= Data::encryptionHeaderSize;
×
783
            else if (Data::encryptionHeaderSize <= centralDirectoryHeader.compressedSize)
2✔
784
                centralDirectoryHeader.compressedSize -= Data::encryptionHeaderSize;
2✔
785
        }
786
        if (zip64ExtraData && zip64ExtraData->headerOffset)
2✔
787
            *zip64ExtraData->headerOffset = translateOffset(*zip64ExtraData->headerOffset);
×
788
        else
789
            centralDirectoryHeader.headerOffset = translateOffset(centralDirectoryHeader.headerOffset);
2✔
790
        centralDirectoryHeader.write(os);
2✔
791

792
        signature = readInt<std::uint32_t>(m_is);
2✔
793
    }
2✔
794

795
    auto isZip64 = false;
1✔
796
    if (m_is && signature == static_cast<std::uint32_t>(Signature::Zip64Eocd))
1✔
797
    {
798
        writeInt<std::uint32_t>(os, signature);
×
799
        isZip64 = true;
×
800

801
        const auto sizeOfZip64Eocd = readInt<std::uint64_t>(m_is);
×
802
        writeInt<std::uint64_t>(os, sizeOfZip64Eocd);
×
803

804
        std::copy_n(std::istreambuf_iterator{m_is}, 36, std::ostreambuf_iterator{os});
×
805
        m_is.get();
×
806

807
        const auto eocdStartOffset = readInt<std::uint64_t>(m_is);
×
808
        writeInt<std::uint64_t>(os, translateOffset(eocdStartOffset));
×
809

810
        if (44 < sizeOfZip64Eocd)
×
811
        {
812
            std::copy_n(std::istreambuf_iterator{m_is}, sizeOfZip64Eocd - 44, std::ostreambuf_iterator{os});
×
813
            m_is.get();
×
814
        }
815

816
        if (!checkSignature(m_is, Signature::Zip64EocdLocator))
×
817
            throw Error{"could not find Zip64 end of central directory locator"};
×
818
        writeInt<std::uint32_t>(os, static_cast<std::uint32_t>(Signature::Zip64EocdLocator));
×
819

820
        std::copy_n(std::istreambuf_iterator{m_is}, 4, std::ostreambuf_iterator{os});
×
821
        m_is.get();
×
822

823
        const auto zip64EocdStartOffset = readInt<std::uint64_t>(m_is);
×
824
        writeInt<std::uint64_t>(os, translateOffset(zip64EocdStartOffset));
×
825

826
        std::copy_n(std::istreambuf_iterator{m_is}, 4, std::ostreambuf_iterator{os});
×
827
        m_is.get();
×
828

829
        signature = readInt<std::uint32_t>(m_is);
×
830
    }
831

832
    if (!m_is || signature != static_cast<std::uint32_t>(Signature::Eocd))
1✔
833
        throw Error{"could not find end of central directory record"};
×
834
    writeInt<std::uint32_t>(os, static_cast<std::uint32_t>(Signature::Eocd));
1✔
835

836
    std::copy_n(std::istreambuf_iterator{m_is}, 12, std::ostreambuf_iterator{os});
1✔
837
    m_is.get();
1✔
838

839
    auto eocdOffset = readInt<std::uint32_t>(m_is);
1✔
840
    if (!isZip64 || eocdOffset != mask<0, 32>)
1✔
841
        eocdOffset = translateOffset(eocdOffset);
1✔
842
    writeInt<std::uint32_t>(os, eocdOffset);
1✔
843

844
    std::copy(std::istreambuf_iterator{m_is}, {}, std::ostreambuf_iterator{os});
1✔
845
}
1✔
846

847
void decipher(std::istream& is, std::size_t size, std::size_t discard, std::ostream& os, Keys keys)
3✔
848
{
849
    auto cipher = std::istreambuf_iterator{is};
3✔
850
    auto i      = std::size_t{};
3✔
851

852
    for (; i < discard && i < size && cipher != std::istreambuf_iterator<char>{}; i++, ++cipher)
75✔
853
        keys.update(*cipher ^ keys.getK());
36✔
854

855
    for (auto plain = std::ostreambuf_iterator{os}; i < size && cipher != std::istreambuf_iterator<char>{};
110,644✔
856
         i++, ++cipher, ++plain)
110,641✔
857
    {
858
        const auto p = *cipher ^ keys.getK();
110,641✔
859
        keys.update(p);
110,641✔
860
        *plain = p;
110,641✔
861
    }
862
}
3✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc