Coverage Report

Created: 2025-07-11 06:39

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