Coverage Report

Created: 2025-08-25 06:29

/src/libtorrent/src/web_peer_connection.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
3
Copyright (c) 2006-2021, Arvid Norberg
4
Copyright (c) 2016-2017, 2020, Alden Torres
5
Copyright (c) 2016-2017, Andrei Kurushin
6
Copyright (c) 2016, Falcosc
7
Copyright (c) 2016, Steven Siloti
8
Copyright (c) 2017, Pavel Pimenov
9
Copyright (c) 2021, Dmtry Soloviev
10
All rights reserved.
11
12
Redistribution and use in source and binary forms, with or without
13
modification, are permitted provided that the following conditions
14
are met:
15
16
 * Redistributions of source code must retain the above copyright
17
  notice, this list of conditions and the following disclaimer.
18
 * Redistributions in binary form must reproduce the above copyright
19
  notice, this list of conditions and the following disclaimer in
20
  the documentation and/or other materials provided with the distribution.
21
 * Neither the name of the author nor the names of its
22
  contributors may be used to endorse or promote products derived
23
  from this software without specific prior written permission.
24
25
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
29
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35
POSSIBILITY OF SUCH DAMAGE.
36
37
*/
38
39
#include "libtorrent/config.hpp"
40
41
#include <functional>
42
#include <cstdlib>
43
#include <cstdio> // for snprintf
44
#include <cinttypes> // for PRId64 et.al.
45
46
#include "libtorrent/web_peer_connection.hpp"
47
#include "libtorrent/session.hpp"
48
#include "libtorrent/entry.hpp"
49
#include "libtorrent/bencode.hpp"
50
#include "libtorrent/alert_types.hpp"
51
#include "libtorrent/aux_/invariant_check.hpp"
52
#include "libtorrent/io.hpp"
53
#include "libtorrent/parse_url.hpp"
54
#include "libtorrent/peer_info.hpp"
55
#include "libtorrent/aux_/session_interface.hpp"
56
#include "libtorrent/aux_/alert_manager.hpp" // for alert_manager
57
#include "libtorrent/aux_/escape_string.hpp" // for escape_path
58
#include "libtorrent/hex.hpp" // for is_hex
59
#include "libtorrent/random.hpp"
60
#include "libtorrent/torrent.hpp"
61
#include "libtorrent/http_parser.hpp"
62
63
namespace libtorrent {
64
65
constexpr int request_size_overhead = 5000;
66
67
std::string escape_file_path(file_storage const& storage, file_index_t index);
68
69
web_peer_connection::web_peer_connection(peer_connection_args& pack
70
  , web_seed_t& web)
71
0
  : web_connection_base(pack, web)
72
0
  , m_url(web.url)
73
0
  , m_web(&web)
74
0
  , m_received_body(0)
75
0
  , m_chunk_pos(0)
76
0
  , m_partial_chunk_header(0)
77
0
  , m_num_responses(0)
78
0
{
79
0
  INVARIANT_CHECK;
80
81
0
  if (!m_settings.get_bool(settings_pack::report_web_seed_downloads))
82
0
    ignore_stats(true);
83
84
0
  std::shared_ptr<torrent> tor = pack.tor.lock();
85
0
  TORRENT_ASSERT(tor);
86
87
  // if the web server is known not to support keep-alive. request 4MiB
88
  // but we want to have at least piece size to prevent block based requests
89
0
  int const min_size = std::max((web.supports_keepalive ? 1 : 4) * 1024 * 1024,
90
0
    tor->torrent_file().piece_length());
91
92
  // we prefer downloading large chunks from web seeds,
93
  // but still want to be able to split requests
94
0
  int const preferred_size = std::max(min_size, m_settings.get_int(settings_pack::urlseed_max_request_bytes));
95
96
0
  prefer_contiguous_blocks(preferred_size / tor->block_size());
97
98
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
99
0
  bool const single_file_request = t->torrent_file().num_files() == 1;
100
101
0
  if (!single_file_request)
102
0
  {
103
    // handle incorrect .torrent files which are multi-file
104
    // but have web seeds not ending with a slash
105
0
    ensure_trailing_slash(m_path);
106
0
    ensure_trailing_slash(m_url);
107
0
  }
108
0
  else
109
0
  {
110
    // handle .torrent files that don't include the filename in the url
111
0
    if (m_path.empty()) m_path += '/';
112
0
    if (m_path[m_path.size() - 1] == '/')
113
0
    {
114
0
      m_path += escape_string(t->torrent_file().name());
115
0
    }
116
117
0
    if (!m_url.empty() && m_url[m_url.size() - 1] == '/')
118
0
    {
119
0
      m_url += escape_file_path(t->torrent_file().orig_files(), file_index_t(0));
120
0
    }
121
0
  }
122
123
  // we want large blocks as well, so
124
  // we can request more bytes at once
125
  // this setting will merge adjacent requests
126
  // into single larger ones
127
0
  request_large_blocks(true);
128
129
#ifndef TORRENT_DISABLE_LOGGING
130
  peer_log(peer_log_alert::info, "URL", "web_peer_connection %s", m_url.c_str());
131
#endif
132
0
}
133
134
std::string escape_file_path(file_storage const& storage, file_index_t index)
135
0
{
136
0
  std::string new_path { storage.file_path(index) };
137
#ifdef TORRENT_WINDOWS
138
  convert_path_to_posix(new_path);
139
#endif
140
0
  return escape_path(new_path);
141
0
}
142
143
void web_peer_connection::on_connected()
144
0
{
145
0
  peer_id pid;
146
0
  aux::random_bytes(pid);
147
0
  set_pid(pid);
148
149
0
  if (m_web->have_files.empty())
150
0
  {
151
0
    incoming_have_all();
152
0
  }
153
0
  else if (m_web->have_files.none_set())
154
0
  {
155
0
    incoming_have_none();
156
0
    m_web->interesting = false;
157
#ifndef TORRENT_DISABLE_LOGGING
158
    peer_log(peer_log_alert::info, "WEB-SEED", "have no files, not interesting. %s", m_url.c_str());
159
#endif
160
0
  }
161
0
  else
162
0
  {
163
0
    std::shared_ptr<torrent> t = associated_torrent().lock();
164
165
    // only advertise pieces that are contained within the files we have as
166
    // indicated by m_web->have_files AND padfiles!
167
    // it's important to include pieces that may overlap many files, as long
168
    // as we have all those files, so instead of starting with a clear bitfield
169
    // and setting the pieces corresponding to files we have, we do it the
170
    // other way around. Start with assuming we have all files, and clear
171
    // pieces overlapping with files we *don't* have.
172
0
    typed_bitfield<piece_index_t> have;
173
0
    file_storage const& fs = t->torrent_file().files();
174
0
    have.resize(fs.num_pieces(), true);
175
0
    for (auto const i : fs.file_range())
176
0
    {
177
      // if we have the file, no need to do anything
178
0
      if (m_web->have_files.get_bit(i) || fs.pad_file_at(i)) continue;
179
180
0
      auto const range = aux::file_piece_range_inclusive(fs, i);
181
0
      for (piece_index_t k = std::get<0>(range); k < std::get<1>(range); ++k)
182
0
        have.clear_bit(k);
183
0
    }
184
0
    t->set_seed(peer_info_struct(), false);
185
0
    if (have.none_set())
186
0
    {
187
0
      incoming_have_none();
188
0
      m_web->interesting = false;
189
#ifndef TORRENT_DISABLE_LOGGING
190
      peer_log(peer_log_alert::info, "WEB-SEED", "have no pieces, not interesting. %s", m_url.c_str());
191
#endif
192
0
    }
193
0
    else
194
0
    {
195
0
      incoming_bitfield(have);
196
0
    }
197
0
  }
198
199
  // TODO: 3 this should be an optional<piece_index_t>, piece index -1 should
200
  // not be allowed
201
0
  if (m_web->restart_request.piece != piece_index_t(-1))
202
0
  {
203
    // increase the chances of requesting the block
204
    // we have partial data for already, to finish it
205
0
    incoming_suggest(m_web->restart_request.piece);
206
0
  }
207
0
  web_connection_base::on_connected();
208
0
}
209
210
void web_peer_connection::disconnect(error_code const& ec
211
  , operation_t op, disconnect_severity_t const error)
212
0
{
213
0
  if (is_disconnecting()) return;
214
215
0
  if (op == operation_t::sock_write && ec == boost::system::errc::broken_pipe)
216
0
  {
217
#ifndef TORRENT_DISABLE_LOGGING
218
    // a write operation failed with broken-pipe. This typically happens
219
    // with HTTP 1.0 servers that close their incoming channel of the TCP
220
    // stream whenever they're done reading one full request. Instead of
221
    // us bailing out and failing the entire request just because our
222
    // write-end was closed, ignore it and keep reading until the read-end
223
    // also is closed.
224
    peer_log(peer_log_alert::info, "WRITE_DIRECTION", "CLOSED");
225
#endif
226
227
    // prevent the peer from trying to send anything more
228
0
    m_send_buffer.clear();
229
230
    // when the web server closed our write-end of the socket (i.e. its
231
    // read-end), if it's an HTTP 1.0 server. we will stop sending more
232
    // requests. We'll close the connection once we receive the last bytes,
233
    // and our read end is closed as well.
234
0
    incoming_choke();
235
0
    return;
236
0
  }
237
238
0
  if (op == operation_t::connect && m_web && !m_web->endpoints.empty())
239
0
  {
240
    // we failed to connect to this IP. remove it so that the next attempt
241
    // uses the next IP in the list.
242
0
    m_web->endpoints.erase(m_web->endpoints.begin());
243
0
  }
244
245
0
  if (ec == errors::uninteresting_upload_peer && m_web)
246
0
  {
247
    // if this is an "ephemeral" web seed, it means it was added by receiving
248
    // an HTTP redirect. If we disconnect because we're not interested in any
249
    // of its pieces, mark it as uninteresting, to avoid reconnecting to it
250
    // repeatedly
251
0
    if (m_web->ephemeral) m_web->interesting = false;
252
253
    // if the web seed is not ephemeral, but we're still not interested. That
254
    // implies that all files either have failed with 404 or with a
255
    // redirection to a different web server.
256
0
    m_web->retry = std::max(m_web->retry, aux::time_now32()
257
0
      + seconds32(m_settings.get_int(settings_pack::urlseed_wait_retry)));
258
0
    TORRENT_ASSERT(m_web->retry > aux::time_now32());
259
0
  }
260
261
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
262
263
0
  if (!m_requests.empty() && !m_file_requests.empty()
264
0
    && !m_piece.empty() && m_web)
265
0
  {
266
#ifndef TORRENT_DISABLE_LOGGING
267
    if (should_log(peer_log_alert::info))
268
    {
269
      peer_log(peer_log_alert::info, "SAVE_RESTART_DATA"
270
        , "data: %d req: %d off: %d"
271
        , int(m_piece.size()), int(m_requests.front().piece)
272
        , m_requests.front().start);
273
    }
274
#endif
275
0
    m_web->restart_request = m_requests.front();
276
0
    if (!m_web->restart_piece.empty())
277
0
    {
278
      // we're about to replace a different restart piece
279
      // buffer. So it was wasted download
280
0
      if (t) t->add_redundant_bytes(int(m_web->restart_piece.size())
281
0
        , waste_reason::piece_closing);
282
0
    }
283
0
    m_web->restart_piece = std::move(m_piece);
284
285
    // we have to do this to not count this data as redundant. The
286
    // upper layer will call downloading_piece_progress and assume
287
    // it's all wasted download. Since we're saving it here, it isn't.
288
0
    m_requests.clear();
289
0
  }
290
291
0
  if (m_web && !m_web->supports_keepalive && error == peer_connection_interface::normal)
292
0
  {
293
    // if the web server doesn't support keepalive and we were
294
    // disconnected as a graceful EOF, reconnect right away
295
0
    if (t) post(get_context()
296
0
      , std::bind(&torrent::maybe_connect_web_seeds, t));
297
0
  }
298
299
0
  if (error >= failure)
300
0
  {
301
0
    m_web->retry = std::max(m_web->retry, aux::time_now32()
302
0
      + seconds32(m_settings.get_int(settings_pack::urlseed_wait_retry)));
303
0
  }
304
305
0
  peer_connection::disconnect(ec, op, error);
306
0
  TORRENT_ASSERT(m_web->resolving == false);
307
0
  m_web->peer_info.connection = nullptr;
308
0
}
309
310
piece_block_progress web_peer_connection::downloading_piece_progress() const
311
0
{
312
0
  if (m_requests.empty()) return {};
313
314
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
315
0
  TORRENT_ASSERT(t);
316
317
0
  piece_block_progress ret;
318
319
0
  ret.piece_index = m_requests.front().piece;
320
0
  ret.bytes_downloaded = int(m_piece.size());
321
  // this is used to make sure that the block_index stays within
322
  // bounds. If the entire piece is downloaded, the block_index
323
  // would otherwise point to one past the end
324
0
  int correction = m_piece.empty() ? 0 : -1;
325
0
  ret.block_index = (m_requests.front().start + int(m_piece.size()) + correction) / t->block_size();
326
0
  TORRENT_ASSERT(ret.block_index < int(piece_block::invalid.block_index));
327
0
  TORRENT_ASSERT(ret.piece_index < piece_block::invalid.piece_index);
328
329
0
  ret.full_block_bytes = t->block_size();
330
0
  piece_index_t const last_piece = t->torrent_file().last_piece();
331
0
  if (ret.piece_index == last_piece && ret.block_index
332
0
    == t->torrent_file().piece_size(last_piece) / t->block_size())
333
0
  {
334
0
    ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size();
335
0
  }
336
0
  return ret;
337
0
}
338
339
void web_peer_connection::write_request(peer_request const& r)
340
0
{
341
0
  INVARIANT_CHECK;
342
343
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
344
0
  TORRENT_ASSERT(t);
345
346
0
  TORRENT_ASSERT(t->valid_metadata());
347
348
0
  torrent_info const& info = t->torrent_file();
349
0
  peer_request req = r;
350
351
0
  std::string request;
352
0
  request.reserve(400);
353
354
0
  int size = r.length;
355
0
  const int block_size = t->block_size();
356
0
  const int piece_size = t->torrent_file().piece_length();
357
0
  peer_request pr{};
358
359
0
  while (size > 0)
360
0
  {
361
0
    int request_offset = r.start + r.length - size;
362
0
    pr.start = request_offset % piece_size;
363
0
    pr.length = std::min(std::min(block_size, size), piece_size - pr.start);
364
0
    pr.piece = piece_index_t(static_cast<int>(r.piece) + request_offset / piece_size);
365
0
    TORRENT_ASSERT(validate_piece_request(pr));
366
0
    m_requests.push_back(pr);
367
368
0
    if (m_web->restart_request == m_requests.front())
369
0
    {
370
0
      TORRENT_ASSERT(int(m_piece.size()) == m_received_in_piece);
371
0
      TORRENT_ASSERT(m_piece.empty());
372
0
      m_piece = std::move(m_web->restart_piece);
373
0
      peer_request const& front = m_requests.front();
374
0
      TORRENT_ASSERT(front.length > int(m_piece.size()));
375
376
#ifndef TORRENT_DISABLE_LOGGING
377
      peer_log(peer_log_alert::info, "RESTART_DATA",
378
        "data: %d req: (%d, %d) size: %d"
379
          , int(m_piece.size()), static_cast<int>(front.piece), front.start
380
          , front.start + front.length - 1);
381
#else
382
0
      TORRENT_UNUSED(front);
383
0
#endif
384
385
0
      req.start += int(m_piece.size());
386
0
      req.length -= int(m_piece.size());
387
388
      // just to keep the accounting straight for the upper layer.
389
      // it doesn't know we just re-wrote the request
390
0
      incoming_piece_fragment(int(m_piece.size()));
391
0
      m_web->restart_request.piece = piece_index_t(-1);
392
393
0
      TORRENT_ASSERT(int(m_piece.size()) == m_received_in_piece);
394
0
    }
395
396
#if 0
397
      std::cerr << this << " REQ: p: " << pr.piece << " " << pr.start << std::endl;
398
#endif
399
0
    size -= pr.length;
400
0
  }
401
402
#ifndef TORRENT_DISABLE_LOGGING
403
  peer_log(peer_log_alert::outgoing_message, "REQUESTING", "(piece: %d start: %d) - (piece: %d end: %d)"
404
    , static_cast<int>(r.piece), r.start
405
    , static_cast<int>(pr.piece), pr.start + pr.length);
406
#endif
407
408
0
  bool const single_file_request = t->torrent_file().num_files() == 1;
409
0
  int const proxy_type = m_settings.get_int(settings_pack::proxy_type);
410
0
  bool const using_proxy = (proxy_type == settings_pack::http
411
0
    || proxy_type == settings_pack::http_pw) && !m_ssl;
412
413
  // the number of pad files that have been "requested". In case we _only_
414
  // request padfiles, we can't rely on handling them in the on_receive()
415
  // callback (because we won't receive anything), instead we have to post a
416
  // pretend read callback where we can deliver the zeroes for the partfile
417
0
  int num_pad_files = 0;
418
419
  // TODO: 3 do we really need a special case here? wouldn't the multi-file
420
  // case handle single file torrents correctly too?
421
0
  if (single_file_request)
422
0
  {
423
0
    file_request_t file_req;
424
0
    file_req.file_index = file_index_t(0);
425
0
    file_req.start = std::int64_t(static_cast<int>(req.piece)) * info.piece_length()
426
0
      + req.start;
427
0
    file_req.length = req.length;
428
429
0
    request += "GET ";
430
    // do not encode single file paths, they are
431
    // assumed to be encoded in the torrent file
432
0
    request += using_proxy ? m_url : m_path;
433
0
    request += " HTTP/1.1\r\n";
434
0
    add_headers(request, m_settings, using_proxy);
435
0
    request += "\r\nRange: bytes=";
436
0
    request += to_string(file_req.start).data();
437
0
    request += "-";
438
0
    request += to_string(file_req.start + file_req.length - 1).data();
439
0
    request += "\r\n\r\n";
440
0
    m_first_request = false;
441
442
0
    m_file_requests.push_back(file_req);
443
0
  }
444
0
  else
445
0
  {
446
0
    std::vector<file_slice> files = info.orig_files().map_block(req.piece, req.start
447
0
      , req.length);
448
449
0
    for (auto const &f : files)
450
0
    {
451
0
      file_request_t file_req;
452
0
      file_req.file_index = f.file_index;
453
0
      file_req.start = f.offset;
454
0
      file_req.length = int(f.size);
455
456
0
      if (info.orig_files().pad_file_at(f.file_index))
457
0
      {
458
0
        m_file_requests.push_back(file_req);
459
0
        ++num_pad_files;
460
0
        continue;
461
0
      }
462
463
0
      request += "GET ";
464
0
      if (using_proxy)
465
0
      {
466
        // m_url is already a properly escaped URL
467
        // with the correct slashes. Don't encode it again
468
0
        request += m_url;
469
0
      }
470
471
0
      auto redirection = m_web->redirects.find(f.file_index);
472
0
      if (redirection != m_web->redirects.end())
473
0
      {
474
0
        auto const& redirect = redirection->second;
475
        // in case of http proxy "request" already contains m_url with trailing slash, so let's skip dup slash
476
0
        bool const trailing_slash = using_proxy && !redirect.empty() && redirect[0] == '/';
477
0
        request.append(redirect, trailing_slash, std::string::npos);
478
0
      }
479
0
      else
480
0
      {
481
0
        if (!using_proxy)
482
0
        {
483
          // m_path is already a properly escaped URL
484
          // with the correct slashes. Don't encode it again
485
0
          request += m_path;
486
0
        }
487
488
0
        request += escape_file_path(info.orig_files(), f.file_index);
489
0
      }
490
0
      request += " HTTP/1.1\r\n";
491
0
      add_headers(request, m_settings, using_proxy);
492
0
      request += "\r\nRange: bytes=";
493
0
      request += to_string(f.offset).data();
494
0
      request += "-";
495
0
      request += to_string(f.offset + f.size - 1).data();
496
0
      request += "\r\n\r\n";
497
0
      m_first_request = false;
498
499
#if 0
500
      std::cerr << this << " SEND-REQUEST: f: " << f.file_index
501
        << " s: " << f.offset
502
        << " e: " << (f.offset + f.size - 1) << std::endl;
503
#endif
504
      // TODO: 3 file_index_t should not allow negative values
505
0
      TORRENT_ASSERT(f.file_index >= file_index_t(0));
506
507
0
      m_file_requests.push_back(file_req);
508
0
    }
509
0
  }
510
511
0
  if (num_pad_files == int(m_file_requests.size()))
512
0
  {
513
0
    post(get_context(), std::bind(
514
0
      &web_peer_connection::on_receive_padfile,
515
0
      std::static_pointer_cast<web_peer_connection>(self())));
516
0
    return;
517
0
  }
518
519
#ifndef TORRENT_DISABLE_LOGGING
520
  peer_log(peer_log_alert::outgoing_message, "REQUEST", "%s", request.c_str());
521
#endif
522
523
0
  send_buffer(request);
524
0
}
525
526
namespace {
527
528
  std::string get_peer_name(http_parser const& p, std::string const& host)
529
0
  {
530
0
    std::string const& server_version = p.header("server");
531
0
    if (!server_version.empty())
532
0
      return server_version;
533
0
    return host;
534
0
  }
535
536
  std::tuple<std::int64_t, std::int64_t> get_range(
537
    http_parser const& parser, error_code& ec)
538
0
  {
539
0
    std::int64_t range_start;
540
0
    std::int64_t range_end;
541
0
    if (parser.status_code() == 206)
542
0
    {
543
0
      std::tie(range_start, range_end) = parser.content_range();
544
0
      if (range_start < 0 || range_end < range_start)
545
0
      {
546
0
        ec = errors::invalid_range;
547
0
        range_start = 0;
548
0
        range_end = 0;
549
0
      }
550
0
      else
551
0
      {
552
        // the http range is inclusive
553
0
        range_end++;
554
0
      }
555
0
    }
556
0
    else
557
0
    {
558
0
      range_start = 0;
559
0
      range_end = parser.content_length();
560
0
      if (range_end < 0)
561
0
      {
562
0
        range_end = 0;
563
0
        ec = errors::no_content_length;
564
0
      }
565
0
    }
566
0
    return std::make_tuple(range_start, range_end);
567
0
  }
568
}
569
570
// --------------------------
571
// RECEIVE DATA
572
// --------------------------
573
574
bool web_peer_connection::received_invalid_data(piece_index_t const index, bool single_peer)
575
0
{
576
0
  if (!single_peer) return peer_connection::received_invalid_data(index, single_peer);
577
578
  // when a web seed fails a hash check, do the following:
579
  // 1. if the whole piece only overlaps a single file, mark that file as not
580
  //    have for this peer
581
  // 2. if the piece overlaps more than one file, mark the piece as not have
582
  //    for this peer
583
  // 3. if it's a single file torrent, just ban it right away
584
  // this handles the case where web seeds may have some files updated but not other
585
586
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
587
0
  file_storage const& fs = t->torrent_file().files();
588
589
  // single file torrent
590
0
  if (fs.num_files() == 1) return peer_connection::received_invalid_data(index, single_peer);
591
592
0
  std::vector<file_slice> files = fs.map_block(index, 0, fs.piece_size(index));
593
594
0
  if (files.size() == 1)
595
0
  {
596
    // assume the web seed has a different copy of this specific file
597
    // than what we expect, and pretend not to have it.
598
0
    auto const range = file_piece_range_inclusive(fs, files[0].file_index);
599
0
    for (piece_index_t i = std::get<0>(range); i != std::get<1>(range); ++i)
600
0
      incoming_dont_have(i);
601
0
  }
602
0
  else
603
0
  {
604
0
    incoming_dont_have(index);
605
0
  }
606
607
0
  peer_connection::received_invalid_data(index, single_peer);
608
609
  // if we don't think we have any of the files, allow banning the web seed
610
0
  if (num_have_pieces() == 0) return true;
611
612
  // don't disconnect, we won't request anything from this file again
613
0
  return false;
614
0
}
615
616
void web_peer_connection::on_receive_padfile()
617
0
{
618
0
  handle_padfile();
619
0
}
620
621
void web_peer_connection::handle_error(int const bytes_left)
622
0
{
623
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
624
0
  TORRENT_ASSERT(t);
625
626
  // TODO: 2 just make this peer not have the pieces
627
  // associated with the file we just requested. Only
628
  // when it doesn't have any of the file do the following
629
  // pad files will make it complicated
630
631
  // temporarily unavailable, retry later
632
0
  t->retry_web_seed(this, m_parser.header_duration("retry-after"));
633
0
  if (t->alerts().should_post<url_seed_alert>())
634
0
  {
635
0
    std::string const error_msg = to_string(m_parser.status_code()).data()
636
0
      + (" " + m_parser.message());
637
0
    t->alerts().emplace_alert<url_seed_alert>(t->get_handle(), m_url
638
0
      , error_msg);
639
0
  }
640
0
  received_bytes(0, bytes_left);
641
0
  disconnect(error_code(m_parser.status_code(), http_category()), operation_t::bittorrent, failure);
642
0
}
643
644
void web_peer_connection::disable(error_code const& ec)
645
0
{
646
  // we should not try this server again.
647
0
  m_web->disabled = true;
648
0
  disconnect(ec, operation_t::bittorrent, peer_error);
649
0
  if (m_web->ephemeral)
650
0
  {
651
0
    std::shared_ptr<torrent> t = associated_torrent().lock();
652
0
    TORRENT_ASSERT(t);
653
0
    t->remove_web_seed_conn(this);
654
0
  }
655
0
  m_web = nullptr;
656
0
  TORRENT_ASSERT(is_disconnecting());
657
0
}
658
659
void web_peer_connection::handle_redirect(int const bytes_left)
660
0
{
661
  // this means we got a redirection request
662
  // look for the location header
663
0
  std::string location = m_parser.header("location");
664
0
  received_bytes(0, bytes_left);
665
666
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
667
0
  TORRENT_ASSERT(t);
668
669
0
  if (location.empty())
670
0
  {
671
0
    disable(errors::missing_location);
672
0
    return;
673
0
  }
674
675
0
  bool const single_file_request = !m_path.empty()
676
0
    && m_path[m_path.size() - 1] != '/';
677
678
  // when SSRF mitigation is enabled, a web seed on the internet (is_global())
679
  // is not allowed to redirect to a server on the local network, so we set
680
  // the no_local_ips flag
681
0
  auto const web_seed_flags = torrent::ephemeral
682
0
    | ((m_settings.get_bool(settings_pack::ssrf_mitigation) && aux::is_global(remote().address()))
683
0
      ? torrent::no_local_ips : web_seed_flag_t{});
684
685
  // add the redirected url and remove the current one
686
0
  if (!single_file_request)
687
0
  {
688
0
    TORRENT_ASSERT(!m_file_requests.empty());
689
0
    file_index_t const file_index = m_file_requests.front().file_index;
690
691
0
    location = resolve_redirect_location(m_url, location);
692
#ifndef TORRENT_DISABLE_LOGGING
693
    peer_log(peer_log_alert::info, "LOCATION", "%s", location.c_str());
694
#endif
695
    // TODO: 3 this could be made more efficient for the case when we use an
696
    // HTTP proxy. Then we wouldn't need to add new web seeds to the torrent,
697
    // we could just make the redirect table contain full URLs.
698
0
    std::string redirect_base;
699
0
    std::string redirect_path;
700
0
    error_code ec;
701
0
    std::tie(redirect_base, redirect_path) = split_url(location, ec);
702
703
0
    if (ec)
704
0
    {
705
      // we should not try this server again.
706
0
      disconnect(errors::missing_location, operation_t::bittorrent, failure);
707
0
      return;
708
0
    }
709
710
    // add_web_seed won't add duplicates. If we have already added an entry
711
    // with this URL, we'll get back the existing entry
712
713
    // "ephemeral" flag should be set to avoid "web_seed_t" saving in resume data.
714
    // E.g. original "web_seed_t" request url points to "http://example1.com/file1" and
715
    // web server responses with redirect location "http://example2.com/subpath/file2".
716
    // "handle_redirect" process this location to create new "web_seed_t"
717
    // with base url=="http://example2.com/" and redirects[0]=="/subpath/file2").
718
    // If we try to load resume with such "web_seed_t" then "web_peer_connection" will send
719
    // request with wrong path "http://example2.com/file1" (cause "redirects" map is not serialized in resume)
720
0
    web_seed_t* web = t->add_web_seed(redirect_base, web_seed_entry::url_seed
721
0
      , m_external_auth, m_extra_headers, web_seed_flags);
722
0
    web->have_files.resize(t->torrent_file().num_files(), false);
723
724
    // the new web seed we're adding only has this file for now
725
    // we may add more files later
726
0
    web->redirects[file_index] = redirect_path;
727
0
    if (web->have_files.get_bit(file_index) == false)
728
0
    {
729
0
      web->have_files.set_bit(file_index);
730
731
0
      if (web->peer_info.connection != nullptr)
732
0
      {
733
0
        auto* pc = static_cast<peer_connection*>(web->peer_info.connection);
734
735
        // we just learned that this host has this file, and we're currently
736
        // connected to it. Make it advertise that it has this file to the
737
        // bittorrent engine
738
0
        file_storage const& fs = t->torrent_file().files();
739
0
        auto const range = aux::file_piece_range_inclusive(fs, file_index);
740
0
        for (piece_index_t i = std::get<0>(range); i < std::get<1>(range); ++i)
741
0
          pc->incoming_have(i);
742
0
      }
743
      // we just learned about another file this web server has, make sure
744
      // it's marked interesting to enable connecting to it
745
0
      web->interesting = true;
746
0
    }
747
748
    // we don't have this file on this server. Don't ask for it again
749
0
    m_web->have_files.resize(t->torrent_file().num_files(), true);
750
0
    if (m_web->have_files[file_index])
751
0
    {
752
0
      m_web->have_files.clear_bit(file_index);
753
#ifndef TORRENT_DISABLE_LOGGING
754
      peer_log(peer_log_alert::info, "MISSING_FILE", "redirection | file: %d"
755
        , static_cast<int>(file_index));
756
#endif
757
0
    }
758
0
    disconnect(errors::redirecting, operation_t::bittorrent, normal);
759
0
  }
760
0
  else
761
0
  {
762
0
    location = resolve_redirect_location(m_url, location);
763
#ifndef TORRENT_DISABLE_LOGGING
764
    peer_log(peer_log_alert::info, "LOCATION", "%s", location.c_str());
765
#endif
766
0
    t->add_web_seed(location, web_seed_entry::url_seed, m_external_auth
767
0
      , m_extra_headers, web_seed_flags);
768
769
    // this web seed doesn't have any files. Don't try to request from it
770
    // again this session
771
0
    m_web->have_files.resize(t->torrent_file().num_files(), false);
772
0
    disconnect(errors::redirecting, operation_t::bittorrent, normal);
773
0
    m_web = nullptr;
774
0
    TORRENT_ASSERT(is_disconnecting());
775
0
  }
776
0
}
777
778
void web_peer_connection::on_receive(error_code const& error
779
  , std::size_t bytes_transferred)
780
0
{
781
0
  INVARIANT_CHECK;
782
783
0
  if (error)
784
0
  {
785
0
    received_bytes(0, int(bytes_transferred));
786
#ifndef TORRENT_DISABLE_LOGGING
787
    if (should_log(peer_log_alert::info))
788
    {
789
      peer_log(peer_log_alert::info, "ERROR"
790
        , "web_peer_connection error: %s", error.message().c_str());
791
    }
792
#endif
793
0
    return;
794
0
  }
795
796
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
797
0
  TORRENT_ASSERT(t);
798
799
  // in case the first file on this series of requests is a padfile
800
  // we need to handle it right now
801
0
  span<char const> recv_buffer = m_recv_buffer.get();
802
0
  handle_padfile();
803
0
  if (associated_torrent().expired()) return;
804
805
0
  for (;;)
806
0
  {
807
0
    int payload;
808
0
    int protocol;
809
0
    bool header_finished = m_parser.header_finished();
810
0
    if (!header_finished)
811
0
    {
812
0
      bool failed = false;
813
0
      std::tie(payload, protocol) = m_parser.incoming(recv_buffer, failed);
814
0
      received_bytes(0, protocol);
815
0
      TORRENT_ASSERT(int(recv_buffer.size()) >= protocol);
816
817
0
      if (failed)
818
0
      {
819
0
        received_bytes(0, int(recv_buffer.size()));
820
#ifndef TORRENT_DISABLE_LOGGING
821
        if (should_log(peer_log_alert::info))
822
        {
823
          peer_log(peer_log_alert::info, "RECEIVE_BYTES"
824
            , "%*s", int(recv_buffer.size()), recv_buffer.data());
825
        }
826
#endif
827
0
        disconnect(errors::http_parse_error, operation_t::bittorrent, peer_error);
828
0
        return;
829
0
      }
830
831
0
      TORRENT_ASSERT(recv_buffer.empty() || recv_buffer[0] == 'H');
832
0
      TORRENT_ASSERT(int(recv_buffer.size()) <= m_recv_buffer.packet_size());
833
834
      // this means the entire status line hasn't been received yet
835
0
      if (m_parser.status_code() == -1)
836
0
      {
837
0
        TORRENT_ASSERT(payload == 0);
838
0
        break;
839
0
      }
840
841
0
      if (!m_parser.header_finished())
842
0
      {
843
0
        TORRENT_ASSERT(payload == 0);
844
0
        break;
845
0
      }
846
847
0
      m_body_start = m_parser.body_start();
848
0
      m_received_body = 0;
849
0
    }
850
851
    // we just completed reading the header
852
0
    if (!header_finished)
853
0
    {
854
0
      ++m_num_responses;
855
856
0
      if (m_parser.connection_close())
857
0
      {
858
0
        incoming_choke();
859
0
        if (m_num_responses == 1)
860
0
          m_web->supports_keepalive = false;
861
0
      }
862
863
#ifndef TORRENT_DISABLE_LOGGING
864
      if (should_log(peer_log_alert::info))
865
      {
866
        peer_log(peer_log_alert::info, "STATUS"
867
          , "%d %s", m_parser.status_code(), m_parser.message().c_str());
868
        auto const& headers = m_parser.headers();
869
        for (auto const &i : headers)
870
          peer_log(peer_log_alert::info, "STATUS", "   %s: %s", i.first.c_str(), i.second.c_str());
871
      }
872
#endif
873
874
      // if the status code is not one of the accepted ones, abort
875
0
      if (!is_ok_status(m_parser.status_code()))
876
0
      {
877
0
        if (!m_file_requests.empty())
878
0
        {
879
0
          file_request_t const& file_req = m_file_requests.front();
880
0
          m_web->have_files.resize(t->torrent_file().num_files(), true);
881
0
          m_web->have_files.clear_bit(file_req.file_index);
882
883
#ifndef TORRENT_DISABLE_LOGGING
884
          peer_log(peer_log_alert::info, "MISSING_FILE", "http-code: %d | file: %d"
885
            , m_parser.status_code(), static_cast<int>(file_req.file_index));
886
#endif
887
0
        }
888
0
        handle_error(int(recv_buffer.size()));
889
0
        return;
890
0
      }
891
892
0
      if (is_redirect(m_parser.status_code()))
893
0
      {
894
0
        handle_redirect(int(recv_buffer.size()));
895
0
        return;
896
0
      }
897
898
0
      m_server_string = get_peer_name(m_parser, m_host);
899
900
0
      recv_buffer = recv_buffer.subspan(m_body_start);
901
902
0
      m_body_start = m_parser.body_start();
903
0
      m_received_body = 0;
904
0
    }
905
906
    // we only received the header, no data
907
0
    if (recv_buffer.empty()) break;
908
909
    // ===================================
910
    // ======= RESPONSE BYTE RANGE =======
911
    // ===================================
912
913
    // despite the HTTP range being inclusive, range_start and range_end are
914
    // exclusive to fit better into C++. i.e. range_end points one byte past
915
    // the end of the payload
916
0
    std::int64_t range_start;
917
0
    std::int64_t range_end;
918
0
    error_code ec;
919
0
    std::tie(range_start, range_end) = get_range(m_parser, ec);
920
0
    if (ec)
921
0
    {
922
0
      received_bytes(0, int(recv_buffer.size()));
923
0
      disable(ec);
924
0
      return;
925
0
    }
926
927
0
    TORRENT_ASSERT(!m_file_requests.empty());
928
0
    file_request_t const& file_req = m_file_requests.front();
929
0
    if (range_start != file_req.start
930
0
      || range_end != file_req.start + file_req.length)
931
0
    {
932
      // the byte range in the http response is different what we expected
933
0
      received_bytes(0, int(recv_buffer.size()));
934
935
#ifndef TORRENT_DISABLE_LOGGING
936
      if (should_log(peer_log_alert::incoming))
937
      {
938
        peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE"
939
          , "in=(%d, %" PRId64 "-%" PRId64 ") expected=(%d, %" PRId64 "-%" PRId64 ") ]"
940
          , static_cast<int>(file_req.file_index), range_start, range_end
941
          , static_cast<int>(file_req.file_index), file_req.start, file_req.start + file_req.length - 1);
942
      }
943
#endif
944
0
      disconnect(errors::invalid_range, operation_t::bittorrent, peer_error);
945
0
      return;
946
0
    }
947
948
0
    if (m_parser.chunked_encoding())
949
0
    {
950
951
      // =========================
952
      // === CHUNKED ENCODING  ===
953
      // =========================
954
955
0
      while (m_chunk_pos >= 0 && !recv_buffer.empty())
956
0
      {
957
        // first deliver any payload we have in the buffer so far, ahead of
958
        // the next chunk header.
959
0
        if (m_chunk_pos > 0)
960
0
        {
961
0
          int const copy_size = std::min(m_chunk_pos, int(recv_buffer.size()));
962
0
          TORRENT_ASSERT(copy_size > 0);
963
964
0
          if (m_received_body + copy_size > file_req.length)
965
0
          {
966
            // the byte range in the http response is different what we expected
967
0
            received_bytes(0, int(recv_buffer.size()));
968
969
#ifndef TORRENT_DISABLE_LOGGING
970
            peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE"
971
              , "received body: %d request size: %d"
972
              , m_received_body, file_req.length);
973
#endif
974
0
            disconnect(errors::invalid_range, operation_t::bittorrent, peer_error);
975
0
            return;
976
0
          }
977
0
          incoming_payload(recv_buffer.data(), copy_size);
978
979
0
          recv_buffer = recv_buffer.subspan(copy_size);
980
0
          m_chunk_pos -= copy_size;
981
982
0
          if (recv_buffer.empty()) goto done;
983
0
        }
984
985
0
        TORRENT_ASSERT(m_chunk_pos == 0);
986
987
0
        int header_size = 0;
988
0
        std::int64_t chunk_size = 0;
989
0
        span<char const> chunk_start = recv_buffer.subspan(m_chunk_pos);
990
0
        TORRENT_ASSERT(chunk_start[0] == '\r'
991
0
          || aux::is_hex({chunk_start.data(), 1}));
992
0
        bool const ret = m_parser.parse_chunk_header(chunk_start, &chunk_size, &header_size);
993
0
        if (!ret)
994
0
        {
995
0
          received_bytes(0, int(chunk_start.size()) - m_partial_chunk_header);
996
0
          m_partial_chunk_header = int(chunk_start.size());
997
0
          goto done;
998
0
        }
999
#ifndef TORRENT_DISABLE_LOGGING
1000
        peer_log(peer_log_alert::info, "CHUNKED_ENCODING"
1001
          , "parsed chunk: %" PRId64 " header_size: %d"
1002
          , chunk_size, header_size);
1003
#endif
1004
0
        received_bytes(0, header_size - m_partial_chunk_header);
1005
0
        m_partial_chunk_header = 0;
1006
0
        TORRENT_ASSERT(chunk_size != 0
1007
0
          || int(chunk_start.size()) <= header_size || chunk_start[header_size] == 'H');
1008
0
        TORRENT_ASSERT(m_body_start + m_chunk_pos < INT_MAX);
1009
0
        m_chunk_pos += int(chunk_size);
1010
0
        recv_buffer = recv_buffer.subspan(header_size);
1011
1012
        // a chunk size of zero means the request is complete. Make sure the
1013
        // number of payload bytes we've received matches the number we
1014
        // requested. If that's not the case, we got an invalid response.
1015
0
        if (chunk_size == 0)
1016
0
        {
1017
0
          TORRENT_ASSERT_VAL(m_chunk_pos == 0, m_chunk_pos);
1018
1019
0
#if TORRENT_USE_ASSERTS
1020
0
          span<char const> chunk = recv_buffer.subspan(m_chunk_pos);
1021
0
          TORRENT_ASSERT(chunk.size() == 0 || chunk[0] == 'H');
1022
0
#endif
1023
0
          m_chunk_pos = -1;
1024
1025
0
          TORRENT_ASSERT(m_received_body <= file_req.length);
1026
0
          if (m_received_body != file_req.length)
1027
0
          {
1028
            // the byte range in the http response is different what we expected
1029
0
            received_bytes(0, int(recv_buffer.size()));
1030
1031
#ifndef TORRENT_DISABLE_LOGGING
1032
            peer_log(peer_log_alert::incoming, "INVALID HTTP RESPONSE"
1033
              , "received body: %d request size: %d"
1034
              , m_received_body, file_req.length);
1035
#endif
1036
0
            disconnect(errors::invalid_range, operation_t::bittorrent, peer_error);
1037
0
            return;
1038
0
          }
1039
          // we just completed an HTTP file request. pop it from m_file_requests
1040
0
          m_file_requests.pop_front();
1041
0
          m_parser.reset();
1042
0
          m_body_start = 0;
1043
0
          m_received_body = 0;
1044
0
          m_chunk_pos = 0;
1045
0
          m_partial_chunk_header = 0;
1046
1047
          // in between each file request, there may be an implicit
1048
          // pad-file request
1049
0
          handle_padfile();
1050
0
          break;
1051
0
        }
1052
1053
        // if all of the receive buffer was just consumed as chunk
1054
        // header, we're done
1055
0
        if (recv_buffer.empty()) goto done;
1056
0
      }
1057
0
    }
1058
0
    else
1059
0
    {
1060
      // this is the simple case, where we don't have chunked encoding
1061
0
      TORRENT_ASSERT(m_received_body <= file_req.length);
1062
0
      int const copy_size = std::min(file_req.length - m_received_body
1063
0
        , int(recv_buffer.size()));
1064
0
      incoming_payload(recv_buffer.data(), copy_size);
1065
0
      recv_buffer = recv_buffer.subspan(copy_size);
1066
1067
0
      TORRENT_ASSERT(m_received_body <= file_req.length);
1068
0
      if (m_received_body == file_req.length)
1069
0
      {
1070
        // we just completed an HTTP file request. pop it from m_file_requests
1071
0
        m_file_requests.pop_front();
1072
0
        m_parser.reset();
1073
0
        m_body_start = 0;
1074
0
        m_received_body = 0;
1075
0
        m_chunk_pos = 0;
1076
0
        m_partial_chunk_header = 0;
1077
1078
        // in between each file request, there may be an implicit
1079
        // pad-file request
1080
0
        handle_padfile();
1081
0
      }
1082
0
    }
1083
1084
0
    if (recv_buffer.empty()) break;
1085
0
  }
1086
0
done:
1087
1088
  // now, remove all the bytes we've processed from the receive buffer
1089
0
  m_recv_buffer.cut(int(recv_buffer.data() - m_recv_buffer.get().begin())
1090
0
    , t->block_size() + request_size_overhead);
1091
0
}
1092
1093
void web_peer_connection::incoming_payload(char const* buf, int len)
1094
0
{
1095
0
  received_bytes(len, 0);
1096
0
  m_received_body += len;
1097
1098
0
  if (is_disconnecting()) return;
1099
1100
#ifndef TORRENT_DISABLE_LOGGING
1101
  peer_log(peer_log_alert::incoming_message, "INCOMING_PAYLOAD", "%d bytes", len);
1102
#endif
1103
1104
  // deliver all complete bittorrent requests to the bittorrent engine
1105
0
  while (len > 0)
1106
0
  {
1107
0
    if (m_requests.empty())
1108
0
    {
1109
0
      TORRENT_ASSERT(m_piece.empty());
1110
0
      return;
1111
0
    }
1112
1113
0
    TORRENT_ASSERT(!m_requests.empty());
1114
0
    peer_request const& front_request = m_requests.front();
1115
0
    int const piece_size = int(m_piece.size());
1116
0
    int const copy_size = std::min(front_request.length - piece_size, len);
1117
1118
    // m_piece may not hold more than the response to the next BT request
1119
0
    TORRENT_ASSERT(front_request.length > piece_size);
1120
0
    TORRENT_ASSERT(int(m_piece.size()) == m_received_in_piece);
1121
1122
    // copy_size is the number of bytes we need to add to the end of m_piece
1123
    // to not exceed the size of the next bittorrent request to be delivered.
1124
    // m_piece can only hold the response for a single BT request at a time
1125
0
    m_piece.resize(piece_size + copy_size);
1126
0
    std::memcpy(m_piece.data() + piece_size, buf, aux::numeric_cast<std::size_t>(copy_size));
1127
0
    len -= copy_size;
1128
0
    buf += copy_size;
1129
1130
    // keep peer stats up-to-date
1131
0
    incoming_piece_fragment(copy_size);
1132
1133
0
    TORRENT_ASSERT(front_request.length >= piece_size);
1134
0
    maybe_harvest_piece();
1135
0
  }
1136
0
}
1137
1138
void web_peer_connection::incoming_zeroes(int len)
1139
0
{
1140
#ifndef TORRENT_DISABLE_LOGGING
1141
  peer_log(peer_log_alert::incoming_message, "INCOMING_ZEROES", "%d bytes", len);
1142
#endif
1143
1144
  // deliver all complete bittorrent requests to the bittorrent engine
1145
0
  while (len > 0)
1146
0
  {
1147
0
    TORRENT_ASSERT(!m_requests.empty());
1148
0
    peer_request const& front_request = m_requests.front();
1149
0
    int const piece_size = int(m_piece.size());
1150
0
    int const copy_size = std::min(front_request.length - piece_size, len);
1151
1152
    // m_piece may not hold more than the response to the next BT request
1153
0
    TORRENT_ASSERT(front_request.length > piece_size);
1154
1155
    // copy_size is the number of bytes we need to add to the end of m_piece
1156
    // to not exceed the size of the next bittorrent request to be delivered.
1157
    // m_piece can only hold the response for a single BT request at a time
1158
0
    m_piece.resize(piece_size + copy_size, 0);
1159
0
    len -= copy_size;
1160
1161
    // keep peer stats up-to-date
1162
0
    incoming_piece_fragment(copy_size);
1163
1164
0
    maybe_harvest_piece();
1165
0
  }
1166
0
}
1167
1168
void web_peer_connection::maybe_harvest_piece()
1169
0
{
1170
0
  TORRENT_ASSERT(!m_requests.empty());
1171
1172
0
  peer_request const& front_request = m_requests.front();
1173
0
  TORRENT_ASSERT(front_request.length >= int(m_piece.size()));
1174
0
  if (int(m_piece.size()) != front_request.length) return;
1175
1176
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
1177
0
  TORRENT_ASSERT(t);
1178
1179
#ifndef TORRENT_DISABLE_LOGGING
1180
  peer_log(peer_log_alert::incoming_message, "POP_REQUEST"
1181
    , "piece: %d start: %d len: %d"
1182
    , static_cast<int>(front_request.piece)
1183
    , front_request.start, front_request.length);
1184
#endif
1185
0
  peer_request const req = m_requests.front();
1186
0
  m_requests.pop_front();
1187
1188
0
  incoming_piece(req, m_piece.data());
1189
0
  m_piece.clear();
1190
0
}
1191
1192
void web_peer_connection::get_specific_peer_info(peer_info& p) const
1193
0
{
1194
0
  web_connection_base::get_specific_peer_info(p);
1195
0
  p.flags |= peer_info::local_connection;
1196
0
  p.connection_type = peer_info::web_seed;
1197
0
}
1198
1199
void web_peer_connection::handle_padfile()
1200
0
{
1201
0
  if (m_file_requests.empty()) return;
1202
0
  if (m_requests.empty()) return;
1203
1204
0
  std::shared_ptr<torrent> t = associated_torrent().lock();
1205
0
  TORRENT_ASSERT(t);
1206
0
  torrent_info const& info = t->torrent_file();
1207
1208
0
  while (!m_file_requests.empty()
1209
0
    && info.orig_files().pad_file_at(m_file_requests.front().file_index))
1210
0
  {
1211
    // the next file is a pad file. We didn't actually send
1212
    // a request for this since it most likely doesn't exist on
1213
    // the web server anyway. Just pretend that we received a
1214
    // bunch of zeroes here and pop it again
1215
0
    std::int64_t file_size = m_file_requests.front().length;
1216
1217
    // in theory the pad file can span multiple bocks, hence the loop
1218
0
    while (file_size > 0)
1219
0
    {
1220
0
      peer_request const front_request = m_requests.front();
1221
0
      TORRENT_ASSERT(int(m_piece.size()) < front_request.length);
1222
1223
0
      int pad_size = int(std::min(file_size
1224
0
          , front_request.length - std::int64_t(m_piece.size())));
1225
0
      TORRENT_ASSERT(pad_size > 0);
1226
0
      file_size -= pad_size;
1227
1228
0
      incoming_zeroes(pad_size);
1229
1230
#ifndef TORRENT_DISABLE_LOGGING
1231
      if (should_log(peer_log_alert::info))
1232
      {
1233
        peer_log(peer_log_alert::info, "HANDLE_PADFILE"
1234
          , "file: %d start: %" PRId64 " len: %d"
1235
          , static_cast<int>(m_file_requests.front().file_index)
1236
          , m_file_requests.front().start
1237
          , m_file_requests.front().length);
1238
      }
1239
#endif
1240
0
    }
1241
1242
0
    m_file_requests.pop_front();
1243
0
  }
1244
0
}
1245
1246
} // libtorrent namespace