Haio
0.2.X
Homebrew Asset IO/CDN
Toggle main menu visibility
Loading...
Searching...
No Matches
reader.cpp
Go to the documentation of this file.
1
#include <
bucket/bucket.hpp
>
2
#include <
haio_util.hpp
>
3
4
#include <zlib.h>
5
6
#include <cstring>
7
#include <string>
8
9
namespace
{
10
11
using
Haio::Bytes
;
12
13
constexpr
uint32_t endOfDirectory = 0x06054b50;
14
constexpr
uint32_t directoryEntry = 0x02014b50;
15
constexpr
uint32_t localHeader = 0x04034b50;
16
17
constexpr
uint16_t methodStored = 0;
18
constexpr
uint16_t methodDeflate = 8;
19
20
/**
21
* a zip whose entries expand far more than this is not a picture archive, it is an
22
* attempt to spend the server's memory. real image data barely compresses at all, so
23
* even a generous ratio is orders of magnitude away from anything legitimate.
24
*/
25
constexpr
uint64_t maxExpansionRatio = 200;
26
27
uint16_t readU16LE(Bytes data,
size_t
off) {
28
return
static_cast<
uint16_t
>
(data[off] | (data[off + 1] << 8));
29
}
30
31
/** the record sits at the end, behind a comment of up to 64k that has to be walked back over */
32
std::optional<size_t> findEndOfDirectory(Bytes data) {
33
if
(data.size() < 22)
return
std::nullopt;
34
35
const
size_t
furthest = std::min<size_t>(data.size(), 22 + 0xFFFF);
36
for
(
size_t
back = 22; back <= furthest; back++) {
37
const
auto
at = data.size() - back;
38
if
(
Haio::Util::readU32LE
(data, at) == endOfDirectory)
return
at;
39
}
40
return
std::nullopt;
41
}
42
43
}
44
45
namespace
Haio::Cdn::Bucket
{
46
47
/**
48
* enough of the zip format to read one file out of an archive already in memory.
49
*
50
* the central directory is the index the format itself keeps, so nothing has to be
51
* guessed by scanning for local headers; a file that disagrees with its own directory
52
* is refused rather than read around.
53
*
54
* @todo zip64 is not handled, so an archive over four gigabytes, or with more than
55
* 65535 entries, is refused rather than misread.
56
*/
57
Result<ZipIndex>
readZipIndex
(
Bytes
data) {
58
const
auto
end = findEndOfDirectory(data);
59
if
(!end)
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"not a zip, or its directory is missing"
});
60
61
const
auto
count = readU16LE(data, *end + 10);
62
const
auto
directorySize =
Util::readU32LE
(data, *end + 12);
63
const
auto
directoryAt =
Util::readU32LE
(data, *end + 16);
64
65
if
(directoryAt == 0xFFFFFFFF || count == 0xFFFF) {
66
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::UnsupportedFormat
,
"zip64 archives are not supported"
});
67
}
68
if
(
static_cast<
uint64_t
>
(directoryAt) + directorySize > data.size()) {
69
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"the zip directory points outside the file"
});
70
}
71
72
ZipIndex
index;
73
size_t
at = directoryAt;
74
75
for
(uint16_t entry = 0; entry < count; entry++) {
76
if
(at + 46 > data.size() ||
Util::readU32LE
(data, at) != directoryEntry) {
77
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"the zip directory is truncated"
});
78
}
79
80
ZipEntry
found;
81
found.
method
= readU16LE(data, at + 10);
82
found.
compressed
=
Util::readU32LE
(data, at + 20);
83
found.
uncompressed
=
Util::readU32LE
(data, at + 24);
84
found.
at
=
Util::readU32LE
(data, at + 42);
85
86
const
auto
nameSize = readU16LE(data, at + 28);
87
const
auto
extraSize = readU16LE(data, at + 30);
88
const
auto
commentSize = readU16LE(data, at + 32);
89
if
(at + 46 + nameSize > data.size()) {
90
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"a zip entry name runs past the end"
});
91
}
92
93
std::string name(
reinterpret_cast<
const
char
*
>
(data.data()) + at + 46, nameSize);
94
at += 46 + nameSize + extraSize + commentSize;
95
96
// a directory entry has nothing to read, and a name that climbs is not read at all
97
if
(name.empty() || name.back() ==
'/'
)
continue
;
98
if
(name.find(
".."
) != std::string::npos)
continue
;
99
100
index.
entries
.emplace(std::move(name), found);
101
}
102
return
index;
103
}
104
105
/** inflates one entry, refusing anything that expands further than it should */
106
Result<std::vector<uint8_t>
>
readZipEntry
(
Bytes
data,
const
ZipEntry
& entry,
size_t
maxSize) {
107
if
(entry.
uncompressed
> maxSize) {
108
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
109
"the file inside the zip is larger than max_unzip allows"
});
110
}
111
if
(entry.
compressed
> 0 && entry.
uncompressed
/ entry.
compressed
> maxExpansionRatio) {
112
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"the file inside the zip expands too far"
});
113
}
114
115
if
(entry.
at
+ 30 > data.size() ||
Util::readU32LE
(data, entry.
at
) != localHeader) {
116
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"a zip entry has no header where the directory says"
});
117
}
118
119
// the local header repeats the name and may carry different extra data, so its
120
// own lengths are the ones that count rather than the directory's
121
const
auto
nameSize = readU16LE(data, entry.
at
+ 26);
122
const
auto
extraSize = readU16LE(data, entry.
at
+ 28);
123
const
auto
from =
static_cast<
size_t
>
(entry.
at
) + 30 + nameSize + extraSize;
124
if
(from + entry.
compressed
> data.size()) {
125
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"a zip entry runs past the end of the file"
});
126
}
127
128
if
(entry.
method
== methodStored) {
129
if
(entry.
compressed
!= entry.
uncompressed
) {
130
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"a stored zip entry disagrees with its own size"
});
131
}
132
return
std::vector<uint8_t>(data.begin() + from, data.begin() + from + entry.
compressed
);
133
}
134
if
(entry.
method
!= methodDeflate) {
135
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::UnsupportedFormat
,
"the zip entry uses a compression haio cannot read"
});
136
}
137
138
std::vector<uint8_t> out(entry.
uncompressed
);
139
140
z_stream stream{};
141
// a negative window means raw deflate, which is what a zip stores: no zlib header
142
if
(inflateInit2(&stream, -MAX_WBITS) != Z_OK) {
143
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::Internal
,
"cannot start inflate"
});
144
}
145
146
stream.next_in =
const_cast<
Bytef*
>
(data.data() + from);
147
stream.avail_in =
static_cast<
uInt
>
(entry.
compressed
);
148
stream.next_out = out.data();
149
stream.avail_out =
static_cast<
uInt
>
(out.size());
150
151
const
auto
status = inflate(&stream, Z_FINISH);
152
const
auto
written = stream.total_out;
153
inflateEnd(&stream);
154
155
if
(status != Z_STREAM_END || written != entry.
uncompressed
) {
156
return
std::unexpected(
Haio::Error
{
Haio::ErrorCode::InvalidInput
,
"the file inside the zip could not be inflated"
});
157
}
158
return
out;
159
}
160
161
}
bucket.hpp
haio_util.hpp
Haio::Cdn::Bucket
Definition
haio_cdn.hpp:281
Haio::Cdn::Bucket::readZipEntry
Result< std::vector< uint8_t > > readZipEntry(Bytes data, const ZipEntry &entry, size_t maxSize)
Definition
reader.cpp:106
Haio::Cdn::Bucket::ZipEntry::method
uint16_t method
Definition
bucket.hpp:67
Haio::Cdn::Bucket::ZipEntry::uncompressed
uint32_t uncompressed
Definition
bucket.hpp:66
Haio::Cdn::Bucket::ZipEntry::at
uint32_t at
Definition
bucket.hpp:64
Haio::Cdn::Bucket::ZipEntry::compressed
uint32_t compressed
Definition
bucket.hpp:65
Haio::Cdn::Bucket::readZipIndex
Result< ZipIndex > readZipIndex(Bytes data)
Definition
reader.cpp:57
Haio::Cdn::Bucket::ZipIndex::entries
std::map< std::string, ZipEntry > entries
Definition
bucket.hpp:71
Haio::Cdn::Bucket::ZipEntry
Definition
bucket.hpp:63
Haio::Cdn::Bucket::ZipIndex
Definition
bucket.hpp:70
Haio::Util::readU32LE
uint32_t readU32LE(Bytes data, size_t off)
Definition
bytes.cpp:21
Haio::Result
std::expected< T, Error > Result
Definition
haio_codec.hpp:32
Haio::ErrorCode::Internal
@ Internal
Definition
haio_codec.hpp:17
Haio::ErrorCode::UnsupportedFormat
@ UnsupportedFormat
Definition
haio_codec.hpp:19
Haio::ErrorCode::InvalidInput
@ InvalidInput
Definition
haio_codec.hpp:18
Haio::Bytes
std::span< const uint8_t > Bytes
Definition
haio_codec.hpp:14
Haio::Error
Definition
haio_codec.hpp:26
library
frontend
cdn
bucket
zip
reader.cpp
Generated by
1.18.0