/src/libtorrent/src/gzip.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | |
3 | | Copyright (c) 2008, 2010, 2014-2019, Arvid Norberg |
4 | | Copyright (c) 2017, 2019, Alden Torres |
5 | | All rights reserved. |
6 | | |
7 | | Redistribution and use in source and binary forms, with or without |
8 | | modification, are permitted provided that the following conditions |
9 | | are met: |
10 | | |
11 | | * Redistributions of source code must retain the above copyright |
12 | | notice, this list of conditions and the following disclaimer. |
13 | | * Redistributions in binary form must reproduce the above copyright |
14 | | notice, this list of conditions and the following disclaimer in |
15 | | the documentation and/or other materials provided with the distribution. |
16 | | * Neither the name of the author nor the names of its |
17 | | contributors may be used to endorse or promote products derived |
18 | | from this software without specific prior written permission. |
19 | | |
20 | | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
21 | | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
22 | | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
23 | | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
24 | | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
25 | | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
26 | | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
27 | | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
28 | | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
29 | | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30 | | POSSIBILITY OF SUCH DAMAGE. |
31 | | |
32 | | */ |
33 | | |
34 | | #include "libtorrent/assert.hpp" |
35 | | #include "libtorrent/puff.hpp" |
36 | | #include "libtorrent/gzip.hpp" |
37 | | |
38 | | #include <string> |
39 | | |
40 | | namespace { |
41 | | |
42 | | enum |
43 | | { |
44 | | FTEXT = 0x01, |
45 | | FHCRC = 0x02, |
46 | | FEXTRA = 0x04, |
47 | | FNAME = 0x08, |
48 | | FCOMMENT = 0x10, |
49 | | FRESERVED = 0xe0, |
50 | | |
51 | | GZIP_MAGIC0 = 0x1f, |
52 | | GZIP_MAGIC1 = 0x8b |
53 | | }; |
54 | | |
55 | | } |
56 | | |
57 | | namespace libtorrent { |
58 | | |
59 | | struct gzip_error_category final : boost::system::error_category |
60 | | { |
61 | | const char* name() const BOOST_SYSTEM_NOEXCEPT override; |
62 | | std::string message(int ev) const override; |
63 | | boost::system::error_condition default_error_condition(int ev) const BOOST_SYSTEM_NOEXCEPT override |
64 | 0 | { return {ev, *this}; } |
65 | | }; |
66 | | |
67 | | const char* gzip_error_category::name() const BOOST_SYSTEM_NOEXCEPT |
68 | 0 | { |
69 | 0 | return "gzip error"; |
70 | 0 | } |
71 | | |
72 | | std::string gzip_error_category::message(int ev) const |
73 | 0 | { |
74 | 0 | static char const* msgs[] = |
75 | 0 | { |
76 | 0 | "no error", |
77 | 0 | "invalid gzip header", |
78 | 0 | "inflated data too large", |
79 | 0 | "available inflate data did not terminate", |
80 | 0 | "output space exhausted before completing inflate", |
81 | 0 | "invalid block type (type == 3)", |
82 | 0 | "stored block length did not match one's complement", |
83 | 0 | "dynamic block code description: too many length or distance codes", |
84 | 0 | "dynamic block code description: code lengths codes incomplete", |
85 | 0 | "dynamic block code description: repeat lengths with no first length", |
86 | 0 | "dynamic block code description: repeat more than specified lengths", |
87 | 0 | "dynamic block code description: invalid literal/length code lengths", |
88 | 0 | "dynamic block code description: invalid distance code lengths", |
89 | 0 | "invalid literal/length or distance code in fixed or dynamic block", |
90 | 0 | "distance is too far back in fixed or dynamic block", |
91 | 0 | "unknown gzip error", |
92 | 0 | }; |
93 | 0 | if (ev < 0 || ev >= int(sizeof(msgs)/sizeof(msgs[0]))) |
94 | 0 | return "Unknown error"; |
95 | 0 | return msgs[ev]; |
96 | 0 | } |
97 | | |
98 | | boost::system::error_category& gzip_category() |
99 | 0 | { |
100 | 0 | static gzip_error_category category; |
101 | 0 | return category; |
102 | 0 | } |
103 | | |
104 | | namespace gzip_errors |
105 | | { |
106 | | boost::system::error_code make_error_code(error_code_enum e) |
107 | 0 | { |
108 | 0 | return {e, gzip_category()}; |
109 | 0 | } |
110 | | } |
111 | | |
112 | | namespace { |
113 | | |
114 | | // returns -1 if gzip header is invalid or the header size in bytes |
115 | | int gzip_header(span<char const> const in) |
116 | 0 | { |
117 | | // The zip header cannot be shorter than 10 bytes |
118 | 0 | if (in.size() < 10) return -1; |
119 | | |
120 | 0 | span<unsigned char const> buffer( |
121 | 0 | reinterpret_cast<const unsigned char*>(in.data()), in.size()); |
122 | | |
123 | | // gzip is defined in https://tools.ietf.org/html/rfc1952 |
124 | | |
125 | | // check the magic header of gzip |
126 | 0 | if ((buffer[0] != GZIP_MAGIC0) || (buffer[1] != GZIP_MAGIC1)) return -1; |
127 | | |
128 | 0 | int const method = buffer[2]; |
129 | 0 | int const flags = buffer[3]; |
130 | | |
131 | | // check for reserved flag and make sure it's compressed with the correct method |
132 | | // we only support deflate |
133 | 0 | if (method != 8 || (flags & FRESERVED) != 0) return -1; |
134 | | |
135 | | // skip time, xflags, OS code. The first 10 bytes of the header: |
136 | | // +---+---+---+---+---+---+---+---+---+---+ |
137 | | // |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->) |
138 | | // +---+---+---+---+---+---+---+---+---+---+ |
139 | | |
140 | 0 | buffer = buffer.subspan(10); |
141 | |
|
142 | 0 | if (flags & FEXTRA) |
143 | 0 | { |
144 | 0 | if (buffer.size() < 2) return -1; |
145 | | |
146 | 0 | auto const extra_len = (buffer[1] << 8) | buffer[0]; |
147 | 0 | if (buffer.size() < extra_len + 2) return -1; |
148 | 0 | buffer = buffer.subspan(extra_len + 2); |
149 | 0 | } |
150 | | |
151 | 0 | if (flags & FNAME) |
152 | 0 | { |
153 | 0 | if (buffer.empty()) return -1; |
154 | 0 | while (buffer[0] != 0) |
155 | 0 | { |
156 | 0 | buffer = buffer.subspan(1); |
157 | 0 | if (buffer.empty()) return -1; |
158 | 0 | } |
159 | 0 | buffer = buffer.subspan(1); |
160 | 0 | } |
161 | | |
162 | 0 | if (flags & FCOMMENT) |
163 | 0 | { |
164 | 0 | if (buffer.empty()) return -1; |
165 | 0 | while (buffer[0] != 0) |
166 | 0 | { |
167 | 0 | buffer = buffer.subspan(1); |
168 | 0 | if (buffer.empty()) return -1; |
169 | 0 | } |
170 | 0 | buffer = buffer.subspan(1); |
171 | 0 | } |
172 | | |
173 | 0 | if (flags & FHCRC) |
174 | 0 | { |
175 | 0 | if (buffer.size() < 2) return -1; |
176 | 0 | buffer = buffer.subspan(2); |
177 | 0 | } |
178 | | |
179 | 0 | return static_cast<int>(in.size() - buffer.size()); |
180 | 0 | } |
181 | | } // anonymous namespace |
182 | | |
183 | | void inflate_gzip(span<char const> in |
184 | | , std::vector<char>& buffer |
185 | | , int maximum_size |
186 | | , error_code& ec) |
187 | 0 | { |
188 | 0 | ec.clear(); |
189 | 0 | TORRENT_ASSERT(maximum_size > 0); |
190 | |
|
191 | 0 | int const header_len = gzip_header(in); |
192 | 0 | if (header_len < 0) |
193 | 0 | { |
194 | 0 | ec = gzip_errors::invalid_gzip_header; |
195 | 0 | return; |
196 | 0 | } |
197 | | |
198 | | // start off with 4 kilobytes and grow |
199 | | // if needed |
200 | 0 | unsigned long destlen = 4096; |
201 | 0 | int ret = 0; |
202 | 0 | in = in.subspan(header_len); |
203 | 0 | unsigned long srclen = std::uint32_t(in.size()); |
204 | |
|
205 | 0 | do |
206 | 0 | { |
207 | 0 | try |
208 | 0 | { |
209 | 0 | buffer.resize(destlen); |
210 | 0 | } |
211 | 0 | catch (std::exception const&) |
212 | 0 | { |
213 | 0 | ec = errors::no_memory; |
214 | 0 | return; |
215 | 0 | } |
216 | | |
217 | 0 | ret = puff(reinterpret_cast<unsigned char*>(buffer.data()) |
218 | 0 | , &destlen |
219 | 0 | , reinterpret_cast<const unsigned char*>(in.data()) |
220 | 0 | , &srclen); |
221 | | |
222 | | // if the destination buffer wasn't large enough, double its |
223 | | // size and try again. Unless it's already at its max, in which |
224 | | // case we fail |
225 | 0 | if (ret == 1) // 1: output space exhausted before completing inflate |
226 | 0 | { |
227 | 0 | if (destlen == std::uint32_t(maximum_size)) |
228 | 0 | { |
229 | 0 | ec = gzip_errors::inflated_data_too_large; |
230 | 0 | return; |
231 | 0 | } |
232 | | |
233 | 0 | destlen *= 2; |
234 | 0 | if (destlen > std::uint32_t(maximum_size)) |
235 | 0 | destlen = std::uint32_t(maximum_size); |
236 | 0 | } |
237 | 0 | } while (ret == 1); |
238 | | |
239 | 0 | if (ret != 0) |
240 | 0 | { |
241 | 0 | switch (ret) |
242 | 0 | { |
243 | 0 | case 2: ec = gzip_errors::data_did_not_terminate; return; |
244 | 0 | case 1: ec = gzip_errors::space_exhausted; return; |
245 | 0 | case -1: ec = gzip_errors::invalid_block_type; return; |
246 | 0 | case -2: ec = gzip_errors::invalid_stored_block_length; return; |
247 | 0 | case -3: ec = gzip_errors::too_many_length_or_distance_codes; return; |
248 | 0 | case -4: ec = gzip_errors::code_lengths_codes_incomplete; return; |
249 | 0 | case -5: ec = gzip_errors::repeat_lengths_with_no_first_length; return; |
250 | 0 | case -6: ec = gzip_errors::repeat_more_than_specified_lengths; return; |
251 | 0 | case -7: ec = gzip_errors::invalid_literal_length_code_lengths; return; |
252 | 0 | case -8: ec = gzip_errors::invalid_distance_code_lengths; return; |
253 | 0 | case -9: ec = gzip_errors::invalid_literal_code_in_block; return; |
254 | 0 | case -10: ec = gzip_errors::distance_too_far_back_in_block; return; |
255 | 0 | default: ec = gzip_errors::unknown_gzip_error; return; |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | 0 | if (destlen > buffer.size()) |
260 | 0 | { |
261 | 0 | ec = gzip_errors::unknown_gzip_error; |
262 | 0 | return; |
263 | 0 | } |
264 | | |
265 | 0 | buffer.resize(destlen); |
266 | 0 | } |
267 | | |
268 | | } |