Coverage Report

Created: 2025-11-24 06:34

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtorrent/src/http_parser.cpp
Line
Count
Source
1
/*
2
3
Copyright (c) 2008-2019, 2021, Arvid Norberg
4
Copyright (c) 2016-2018, Alden Torres
5
Copyright (c) 2017, Pavel Pimenov
6
All rights reserved.
7
8
Redistribution and use in source and binary forms, with or without
9
modification, are permitted provided that the following conditions
10
are met:
11
12
    * Redistributions of source code must retain the above copyright
13
      notice, this list of conditions and the following disclaimer.
14
    * Redistributions in binary form must reproduce the above copyright
15
      notice, this list of conditions and the following disclaimer in
16
      the documentation and/or other materials provided with the distribution.
17
    * Neither the name of the author nor the names of its
18
      contributors may be used to endorse or promote products derived
19
      from this software without specific prior written permission.
20
21
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
25
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
POSSIBILITY OF SUCH DAMAGE.
32
33
*/
34
35
#include <cctype>
36
#include <cstring>
37
#include <algorithm>
38
#include <cstdlib>
39
#include <cinttypes>
40
41
#include "libtorrent/config.hpp"
42
#include "libtorrent/http_parser.hpp"
43
#include "libtorrent/hex.hpp" // for hex_to_int
44
#include "libtorrent/assert.hpp"
45
#include "libtorrent/parse_url.hpp" // for parse_url_components
46
#include "libtorrent/string_util.hpp" // for ensure_trailing_slash, to_lower
47
#include "libtorrent/aux_/escape_string.hpp" // for read_until
48
#include "libtorrent/time.hpp" // for seconds32
49
#include "libtorrent/aux_/numeric_cast.hpp"
50
51
namespace libtorrent {
52
53
  bool is_ok_status(int http_status)
54
0
  {
55
0
    return http_status == 206 // partial content
56
0
      || http_status == 200 // OK
57
0
      || (http_status >= 300 // redirect
58
0
        && http_status < 400);
59
0
  }
60
61
  bool is_redirect(int http_status)
62
0
  {
63
0
    return http_status >= 300
64
0
      && http_status < 400;
65
0
  }
66
67
  std::string resolve_redirect_location(std::string referrer
68
    , std::string location)
69
0
  {
70
0
    if (location.empty()) return referrer;
71
72
0
    error_code ec;
73
0
    using std::ignore;
74
0
    std::tie(ignore, ignore, ignore, ignore, ignore)
75
0
      = parse_url_components(location, ec);
76
77
    // if location is a full URL, just return it
78
0
    if (!ec) return location;
79
80
    // otherwise it's likely to be just the path, or a relative path
81
0
    std::string url = referrer;
82
83
0
    if (location[0] == '/')
84
0
    {
85
      // it's an absolute path. replace the path component of
86
      // referrer with location.
87
88
      // first skip the url scheme of the referer
89
0
      std::size_t i = url.find("://");
90
91
      // if the referrer doesn't appear to have a proper URL scheme
92
      // just return the location verbatim (and probably fail)
93
0
      if (i == std::string::npos)
94
0
        return location;
95
96
      // then skip the hostname and port, it's fine for this to fail, in
97
      // case the referrer doesn't have a path component, it's just the
98
      // url-scheme and hostname, in which case we just append the location
99
0
      i = url.find_first_of('/', i + 3);
100
0
      if (i != std::string::npos)
101
0
        url.resize(i);
102
103
0
      url += location;
104
0
    }
105
0
    else
106
0
    {
107
      // some web servers send out relative paths
108
      // in the location header.
109
110
      // remove the leaf filename
111
      // first skip the url scheme of the referer
112
0
      std::size_t start = url.find("://");
113
114
      // the referrer is not a valid full URL
115
0
      if (start == std::string::npos)
116
0
        return location;
117
118
0
      std::size_t end = url.find_last_of('/');
119
      // if the / we find is part of the scheme, there is no / in the path
120
      // component or hostname.
121
0
      if (end <= start + 2) end = std::string::npos;
122
123
      // if this fails, the referrer is just url-scheme and hostname. We can
124
      // just append the location to it.
125
0
      if (end != std::string::npos)
126
0
        url.resize(end);
127
128
      // however, we may still need to insert a '/' in case neither side
129
      // has one. We know the location doesn't start with a / already.
130
      // so, if the referrer doesn't end with one, add it.
131
0
      ensure_trailing_slash(url);
132
0
      url += location;
133
0
    }
134
0
    return url;
135
0
  }
136
137
  std::string const& http_parser::header(string_view const key) const
138
0
  {
139
0
    static std::string const empty;
140
    // at least GCC-5.4 for ARM (on travis) has a libstdc++ whose debug map$
141
    // doesn't seem to support transparent comparators$
142
#if ! defined _GLIBCXX_DEBUG
143
    auto const i = m_header.find(key);
144
#else
145
0
    auto const i = m_header.find(std::string(key));
146
0
#endif
147
0
    if (i == m_header.end()) return empty;
148
0
    return i->second;
149
0
  }
150
151
  boost::optional<seconds32> http_parser::header_duration(string_view const key) const
152
0
  {
153
    // at least GCC-5.4 for ARM (on travis) has a libstdc++ whose debug map$
154
    // doesn't seem to support transparent comparators$
155
#if ! defined _GLIBCXX_DEBUG
156
    auto const i = m_header.find(key);
157
#else
158
0
    auto const i = m_header.find(std::string(key));
159
0
#endif
160
0
    if (i == m_header.end()) return boost::none;
161
0
    auto const val = std::atol(i->second.c_str());
162
0
    if (val <= 0) return boost::none;
163
0
    return seconds32(val);
164
0
  }
165
166
3.40k
  http_parser::~http_parser() = default;
167
168
3.40k
  http_parser::http_parser(int const flags) : m_flags(flags) {}
169
170
  std::tuple<int, int> http_parser::incoming(
171
    span<char const> recv_buffer, bool& error)
172
177M
  {
173
177M
    TORRENT_ASSERT(recv_buffer.size() >= m_recv_buffer.size());
174
177M
    std::tuple<int, int> ret(0, 0);
175
177M
    std::ptrdiff_t start_pos = m_recv_buffer.size();
176
177
    // early exit if there's nothing new in the receive buffer
178
177M
    if (start_pos == recv_buffer.size()) return ret;
179
177M
    m_recv_buffer = recv_buffer;
180
181
177M
    if (m_state == error_state)
182
0
    {
183
0
      error = true;
184
0
      return ret;
185
0
    }
186
187
177M
    char const* pos = recv_buffer.data() + m_recv_pos;
188
189
178M
restart_response:
190
191
178M
    if (m_state == read_status)
192
6.83M
    {
193
6.83M
      TORRENT_ASSERT(!m_finished);
194
6.83M
      TORRENT_ASSERT(pos <= recv_buffer.end());
195
6.83M
      char const* newline = std::find(pos, recv_buffer.end(), '\n');
196
      // if we don't have a full line yet, wait.
197
6.83M
      if (newline == recv_buffer.end())
198
6.27M
      {
199
6.27M
        std::get<1>(ret) += int(m_recv_buffer.size() - start_pos);
200
6.27M
        return ret;
201
6.27M
      }
202
203
554k
      if (newline == pos)
204
1.38k
      {
205
1.38k
        m_state = error_state;
206
1.38k
        error = true;
207
1.38k
        return ret;
208
1.38k
      }
209
210
553k
      char const* line_end = newline;
211
553k
      if (pos != line_end && *(line_end - 1) == '\r') --line_end;
212
213
553k
      char const* line = pos;
214
553k
      ++newline;
215
553k
      TORRENT_ASSERT(newline >= pos);
216
553k
      int incoming = int(newline - pos);
217
553k
      m_recv_pos += incoming;
218
553k
      std::get<1>(ret) += int(newline - (m_recv_buffer.data() + start_pos));
219
553k
      pos = newline;
220
221
553k
      m_protocol = read_until(line, ' ', line_end);
222
553k
      if (m_protocol.substr(0, 5) == "HTTP/")
223
333k
      {
224
333k
        m_status_code = atoi(read_until(line, ' ', line_end).c_str());
225
333k
        m_server_message = read_until(line, '\r', line_end);
226
227
        // HTTP 1.0 always closes the connection after
228
        // each request
229
333k
        if (m_protocol == "HTTP/1.0") m_connection_close = true;
230
333k
      }
231
220k
      else
232
220k
      {
233
220k
        m_method = m_protocol;
234
220k
        std::transform(m_method.begin(), m_method.end(), m_method.begin(), &to_lower);
235
        // the content length is assumed to be 0 for requests
236
220k
        m_content_length = 0;
237
220k
        m_protocol.clear();
238
220k
        m_path = read_until(line, ' ', line_end);
239
220k
        m_protocol = read_until(line, ' ', line_end);
240
220k
        m_status_code = 0;
241
220k
      }
242
553k
      m_state = read_header;
243
553k
      start_pos = pos - recv_buffer.data();
244
553k
    }
245
246
171M
    if (m_state == read_header)
247
82.1M
    {
248
82.1M
      TORRENT_ASSERT(!m_finished);
249
82.1M
      TORRENT_ASSERT(pos <= recv_buffer.end());
250
82.1M
      char const* newline = std::find(pos, recv_buffer.end(), '\n');
251
82.1M
      std::string line;
252
253
84.2M
      while (newline != recv_buffer.end() && m_state == read_header)
254
2.53M
      {
255
        // if the LF character is preceded by a CR
256
        // character, don't copy it into the line string.
257
2.53M
        char const* line_end = newline;
258
2.53M
        if (pos != line_end && *(line_end - 1) == '\r') --line_end;
259
2.53M
        line.assign(pos, line_end);
260
2.53M
        ++newline;
261
2.53M
        m_recv_pos += newline - pos;
262
2.53M
        pos = newline;
263
264
2.53M
        std::string::size_type separator = line.find(':');
265
2.53M
        if (separator == std::string::npos)
266
438k
        {
267
438k
          if (m_status_code == 100)
268
320k
          {
269
            // for 100 Continue, we need to read another response header
270
            // before reading the body
271
320k
            m_state = read_status;
272
320k
            goto restart_response;
273
320k
          }
274
          // this means we got a blank line,
275
          // the header is finished and the body
276
          // starts.
277
117k
          m_state = read_body;
278
          // if this is a request (not a response)
279
          // we're done once we reach the end of the headers
280
//          if (!m_method.empty()) m_finished = true;
281
          // the HTTP header should always be < 2 GB
282
117k
          TORRENT_ASSERT(m_recv_pos < std::numeric_limits<int>::max());
283
117k
          m_body_start_pos = int(m_recv_pos);
284
117k
          break;
285
438k
        }
286
287
2.09M
        std::string name = line.substr(0, separator);
288
2.09M
        std::transform(name.begin(), name.end(), name.begin(), &to_lower);
289
2.09M
        ++separator;
290
        // skip whitespace
291
2.15M
        while (separator < line.size()
292
1.09M
          && (line[separator] == ' ' || line[separator] == '\t'))
293
55.9k
          ++separator;
294
2.09M
        std::string value = line.substr(separator, std::string::npos);
295
2.09M
        m_header.insert(std::make_pair(name, value));
296
297
2.09M
        if (name == "content-length")
298
75.5k
        {
299
75.5k
          m_content_length = std::strtoll(value.c_str(), nullptr, 10);
300
75.5k
          if (m_content_length < 0
301
70.8k
            || m_content_length == std::numeric_limits<std::int64_t>::max())
302
4.76k
          {
303
4.76k
            m_state = error_state;
304
4.76k
            error = true;
305
4.76k
            return ret;
306
4.76k
          }
307
75.5k
        }
308
2.02M
        else if (name == "connection")
309
105k
        {
310
105k
          m_connection_close = string_begins_no_case("close", value.c_str());
311
105k
        }
312
1.91M
        else if (name == "content-range")
313
144k
        {
314
144k
          bool success = true;
315
144k
          char const* ptr = value.c_str();
316
317
          // apparently some web servers do not send the "bytes"
318
          // in their content-range. Don't treat it as an error
319
          // if we can't find it, just assume the byte counters
320
          // start immediately
321
144k
          if (string_begins_no_case("bytes ", ptr)) ptr += 6;
322
144k
          char* end;
323
144k
          m_range_start = std::strtoll(ptr, &end, 10);
324
144k
          if (m_range_start < 0
325
139k
            || m_range_start == std::numeric_limits<std::int64_t>::max())
326
5.10k
          {
327
5.10k
            m_state = error_state;
328
5.10k
            error = true;
329
5.10k
            return ret;
330
5.10k
          }
331
139k
          if (end == ptr) success = false;
332
138k
          else if (*end != '-') success = false;
333
129k
          else
334
129k
          {
335
129k
            ptr = end + 1;
336
129k
            m_range_end = std::strtoll(ptr, &end, 10);
337
129k
            if (m_range_end < 0
338
124k
              || m_range_end == std::numeric_limits<std::int64_t>::max())
339
5.17k
            {
340
5.17k
              m_state = error_state;
341
5.17k
              error = true;
342
5.17k
              return ret;
343
5.17k
            }
344
124k
            if (end == ptr) success = false;
345
124k
          }
346
347
133k
          if (!success || m_range_end < m_range_start)
348
10.1k
          {
349
10.1k
            m_state = error_state;
350
10.1k
            error = true;
351
10.1k
            return ret;
352
10.1k
          }
353
          // the http range is inclusive
354
123k
          m_content_length = m_range_end - m_range_start + 1;
355
123k
        }
356
1.77M
        else if (name == "transfer-encoding")
357
636k
        {
358
636k
          m_chunked_encoding = string_begins_no_case("chunked", value.c_str());
359
636k
        }
360
361
2.07M
        TORRENT_ASSERT(m_recv_pos <= int(recv_buffer.size()));
362
2.07M
        TORRENT_ASSERT(pos <= recv_buffer.end());
363
2.07M
        newline = std::find(pos, recv_buffer.end(), '\n');
364
2.07M
      }
365
81.8M
      std::get<1>(ret) += int(newline - (m_recv_buffer.data() + start_pos));
366
81.8M
    }
367
368
171M
    if (m_state == read_body)
369
89.7M
    {
370
89.7M
      int incoming = int(recv_buffer.end() - pos);
371
372
89.7M
      if (m_chunked_encoding && (m_flags & dont_parse_chunks) == 0)
373
46.0M
      {
374
46.0M
        if (m_cur_chunk_end == -1)
375
93.9k
          m_cur_chunk_end = m_body_start_pos;
376
377
97.7M
        while (m_cur_chunk_end <= m_recv_pos + incoming && !m_finished && incoming > 0)
378
51.7M
        {
379
51.7M
          std::int64_t payload = m_cur_chunk_end - m_recv_pos;
380
51.7M
          if (payload > 0)
381
6.29M
          {
382
6.29M
            TORRENT_ASSERT(payload < std::numeric_limits<int>::max());
383
6.29M
            m_recv_pos += payload;
384
6.29M
            std::get<0>(ret) += int(payload);
385
6.29M
            incoming -= int(payload);
386
6.29M
          }
387
51.7M
          auto const buf = span<char const>(recv_buffer)
388
51.7M
            .subspan(aux::numeric_cast<std::ptrdiff_t>(m_cur_chunk_end));
389
51.7M
          std::int64_t chunk_size;
390
51.7M
          int header_size;
391
51.7M
          if (parse_chunk_header(buf, &chunk_size, &header_size))
392
6.35M
          {
393
6.35M
            if (chunk_size < 0
394
6.35M
              || chunk_size > std::numeric_limits<std::int64_t>::max() - m_cur_chunk_end - header_size)
395
5.52k
            {
396
5.52k
              m_state = error_state;
397
5.52k
              error = true;
398
5.52k
              return ret;
399
5.52k
            }
400
6.34M
            if (chunk_size > 0)
401
6.32M
            {
402
6.32M
              std::pair<std::int64_t, std::int64_t> chunk_range(m_cur_chunk_end + header_size
403
6.32M
                , m_cur_chunk_end + header_size + chunk_size);
404
6.32M
              m_chunked_ranges.push_back(chunk_range);
405
6.32M
            }
406
6.34M
            m_cur_chunk_end += header_size + chunk_size;
407
6.34M
            if (chunk_size == 0)
408
29.6k
            {
409
29.6k
              m_finished = true;
410
29.6k
            }
411
6.34M
            header_size -= m_partial_chunk_header;
412
6.34M
            m_partial_chunk_header = 0;
413
//            std::fprintf(stderr, "parse_chunk_header(%d, -> %" PRId64 ", -> %d) -> %d\n"
414
//              "  incoming = %d\n  m_recv_pos = %d\n  m_cur_chunk_end = %" PRId64 "\n"
415
//              "  content-length = %d\n"
416
//              , int(buf.size()), chunk_size, header_size, 1, incoming, int(m_recv_pos)
417
//              , m_cur_chunk_end, int(m_content_length));
418
6.34M
          }
419
45.3M
          else
420
45.3M
          {
421
45.3M
            m_partial_chunk_header += incoming;
422
45.3M
            header_size = incoming;
423
424
//            std::fprintf(stderr, "parse_chunk_header(%d, -> %" PRId64 ", -> %d) -> %d\n"
425
//              "  incoming = %d\n  m_recv_pos = %d\n  m_cur_chunk_end = %" PRId64 "\n"
426
//              "  content-length = %d\n"
427
//              , int(buf.size()), chunk_size, header_size, 0, incoming, int(m_recv_pos)
428
//              , m_cur_chunk_end, int(m_content_length));
429
45.3M
          }
430
51.7M
          m_chunk_header_size += header_size;
431
51.7M
          m_recv_pos += header_size;
432
51.7M
          std::get<1>(ret) += header_size;
433
51.7M
          incoming -= header_size;
434
51.7M
        }
435
46.0M
        if (incoming > 0)
436
224k
        {
437
224k
          m_recv_pos += incoming;
438
224k
          std::get<0>(ret) += incoming;
439
//          incoming = 0;
440
224k
        }
441
46.0M
      }
442
43.6M
      else
443
43.6M
      {
444
43.6M
        std::int64_t payload_received = m_recv_pos - m_body_start_pos + incoming;
445
43.6M
        if (payload_received > m_content_length
446
37.9M
          && m_content_length >= 0)
447
37.8M
        {
448
37.8M
          TORRENT_ASSERT(m_content_length - m_recv_pos + m_body_start_pos
449
37.8M
            < std::numeric_limits<int>::max());
450
37.8M
          incoming = int(m_content_length - m_recv_pos + m_body_start_pos);
451
37.8M
        }
452
453
43.6M
        TORRENT_ASSERT(incoming >= 0);
454
43.6M
        m_recv_pos += incoming;
455
43.6M
        std::get<0>(ret) += incoming;
456
43.6M
      }
457
458
89.7M
      if (m_content_length >= 0
459
89.6M
        && !m_chunked_encoding
460
43.6M
        && m_recv_pos - m_body_start_pos >= m_content_length)
461
37.9M
      {
462
37.9M
        m_finished = true;
463
37.9M
      }
464
89.7M
    }
465
171M
    return ret;
466
171M
  }
467
468
  // this function signals error by assigning a negative value to "chunk_size"
469
  // the return value indicates whether enough data is available in "buf" to
470
  // completely parse the chunk header. Returning false means we need more data
471
  bool http_parser::parse_chunk_header(span<char const> buf
472
    , std::int64_t* chunk_size, int* header_size)
473
51.7M
  {
474
51.7M
    char const* pos = buf.data();
475
476
    // ignore one optional new-line. This is since each chunk
477
    // is terminated by a newline. we're likely to see one
478
    // before the actual header.
479
480
51.7M
    if (pos < buf.end() && pos[0] == '\r') ++pos;
481
51.7M
    if (pos < buf.end() && pos[0] == '\n') ++pos;
482
51.7M
    if (pos == buf.end()) return false;
483
484
51.2M
    TORRENT_ASSERT(pos <= buf.end());
485
51.2M
    char const* newline = std::find(pos, buf.end(), '\n');
486
51.2M
    if (newline == buf.end()) return false;
487
50.1M
    ++newline;
488
489
    // the chunk header is a single line, a hex length of the
490
    // chunk followed by an optional semi-colon with a comment
491
    // in case the length is 0, the stream is terminated and
492
    // there are extra tail headers, which is terminated by an
493
    // empty line
494
495
50.1M
    *header_size = int(newline - buf.data());
496
497
    // first, read the chunk length
498
50.1M
    std::int64_t size = 0;
499
87.9M
    for (char const* i = pos; i != newline; ++i)
500
68.0M
    {
501
68.0M
      if (*i == '\r') continue;
502
68.0M
      if (*i == '\n') continue;
503
48.1M
      if (*i == ';') break;
504
17.9M
      int const digit = aux::hex_to_int(*i);
505
17.9M
      if (digit < 0)
506
3.38k
      {
507
3.38k
        *chunk_size = -1;
508
3.38k
        return true;
509
3.38k
      }
510
17.9M
      if (size >= std::numeric_limits<std::int64_t>::max() / 16)
511
759
      {
512
759
        *chunk_size = -1;
513
759
        return true;
514
759
      }
515
17.9M
      size *= 16;
516
17.9M
      size += digit;
517
17.9M
    }
518
50.1M
    *chunk_size = size;
519
520
50.1M
    if (*chunk_size != 0)
521
6.32M
    {
522
      // the newline is at least 1 byte, and the length-prefix is at least 1
523
      // byte
524
6.32M
      TORRENT_ASSERT(newline - buf.data() >= 2);
525
6.32M
      return true;
526
6.32M
    }
527
528
    // this is the terminator of the stream. Also read headers
529
43.7M
    std::map<std::string, std::string> tail_headers;
530
43.7M
    pos = newline;
531
43.7M
    newline = std::find(pos, buf.end(), '\n');
532
533
43.7M
    std::string line;
534
62.2M
    while (newline != buf.end())
535
18.4M
    {
536
      // if the LF character is preceded by a CR
537
      // character, don't copy it into the line string.
538
18.4M
      char const* line_end = newline;
539
18.4M
      if (pos != line_end && *(line_end - 1) == '\r') --line_end;
540
18.4M
      line.assign(pos, line_end);
541
18.4M
      ++newline;
542
18.4M
      pos = newline;
543
544
18.4M
      std::string::size_type separator = line.find(':');
545
18.4M
      if (separator == std::string::npos)
546
29.6k
      {
547
        // this means we got a blank line,
548
        // the header is finished and the body
549
        // starts.
550
29.6k
        *header_size = int(newline - buf.data());
551
552
        // the newline alone is two bytes
553
29.6k
        TORRENT_ASSERT(newline - buf.data() > 2);
554
555
        // we were successful in parsing the headers.
556
        // add them to the headers in the parser
557
29.6k
        for (auto const& p : tail_headers)
558
411k
          m_header.insert(p);
559
560
29.6k
        return true;
561
29.6k
      }
562
563
18.4M
      std::string name = line.substr(0, separator);
564
18.4M
      std::transform(name.begin(), name.end(), name.begin(), &to_lower);
565
18.4M
      ++separator;
566
      // skip whitespace
567
21.9M
      while (separator < line.size()
568
7.17M
        && (line[separator] == ' ' || line[separator] == '\t'))
569
3.49M
        ++separator;
570
18.4M
      std::string value = line.substr(separator, std::string::npos);
571
18.4M
      tail_headers.insert(std::make_pair(name, value));
572
//      std::fprintf(stderr, "tail_header: %s: %s\n", name.c_str(), value.c_str());
573
574
18.4M
      newline = std::find(pos, buf.end(), '\n');
575
18.4M
    }
576
43.7M
    return false;
577
43.7M
  }
578
579
  span<char const> http_parser::get_body() const
580
0
  {
581
0
    if (m_state != read_body) return {};
582
0
    std::int64_t const received = m_recv_pos - m_body_start_pos;
583
584
0
    std::int64_t const body_length = m_chunked_encoding && !m_chunked_ranges.empty()
585
0
      ? std::min(m_chunked_ranges.back().second - m_body_start_pos, received)
586
0
      : m_content_length < 0 ? received : std::min(m_content_length, received);
587
588
0
    return m_recv_buffer.subspan(m_body_start_pos, aux::numeric_cast<std::ptrdiff_t>(body_length));
589
0
  }
590
591
  void http_parser::reset()
592
234k
  {
593
234k
    m_method.clear();
594
234k
    m_recv_pos = 0;
595
234k
    m_body_start_pos = 0;
596
234k
    m_status_code = -1;
597
234k
    m_content_length = -1;
598
234k
    m_range_start = -1;
599
234k
    m_range_end = -1;
600
234k
    m_finished = false;
601
234k
    m_state = read_status;
602
234k
    m_recv_buffer = span<char const>();
603
234k
    m_header.clear();
604
234k
    m_chunked_encoding = false;
605
234k
    m_chunked_ranges.clear();
606
234k
    m_cur_chunk_end = -1;
607
234k
    m_chunk_header_size = 0;
608
234k
    m_partial_chunk_header = 0;
609
234k
  }
610
611
  span<char> http_parser::collapse_chunk_headers(span<char> buffer) const
612
0
  {
613
0
    if (!chunked_encoding()) return buffer;
614
615
    // go through all chunks and compact them
616
    // since we're bottled, and the buffer is our after all
617
    // it's OK to mutate it
618
0
    char* write_ptr = buffer.data();
619
    // the offsets in the array are from the start of the
620
    // buffer, not start of the body, so subtract the size
621
    // of the HTTP header from them
622
0
    int const offset = body_start();
623
0
    for (auto const& i : chunks())
624
0
    {
625
0
      auto const chunk_start = i.first;
626
0
      auto const chunk_end = i.second;
627
0
      if (chunk_end - offset > buffer.size()
628
0
        || (i.second - i.first) >= std::numeric_limits<int>::max())
629
0
      {
630
        // invalid chunk header. Return the body we've parsed out so far
631
0
        return buffer.first(write_ptr - buffer.data());
632
0
      }
633
0
      span<char> chunk = buffer.subspan(
634
0
        aux::numeric_cast<std::ptrdiff_t>(chunk_start - offset)
635
0
        , aux::numeric_cast<std::ptrdiff_t>(chunk_end - chunk_start));
636
#if defined __GNUC__ && __GNUC__ >= 7
637
#pragma GCC diagnostic push
638
#pragma GCC diagnostic ignored "-Wstringop-overflow"
639
#endif
640
0
      std::memmove(write_ptr, chunk.data(), std::size_t(chunk.size()));
641
#if defined __GNUC__ && __GNUC__ >= 7
642
#pragma GCC diagnostic pop
643
#endif
644
0
      write_ptr += chunk.size();
645
0
    }
646
0
    return buffer.first(write_ptr - buffer.data());
647
0
  }
648
}