AUTO_LT_SYNC
[tore.git] / libtorrent / src / web_peer_connection.cpp
blobab3b0bf64aa27b09ee32d04c18f8d8507009cfc7
1 /*
3 Copyright (c) 2003, Arvid Norberg
4 All rights reserved.
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
10 * Redistributions of source code must retain the above copyright
11 notice, this list of conditions and the following disclaimer.
12 * Redistributions in binary form must reproduce the above copyright
13 notice, this list of conditions and the following disclaimer in
14 the documentation and/or other materials provided with the distribution.
15 * Neither the name of the author nor the names of its
16 contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
33 #include "libtorrent/pch.hpp"
35 #include <vector>
36 #include <iostream>
37 #include <iomanip>
38 #include <limits>
39 #include <boost/bind.hpp>
40 #include <sstream>
42 #include "libtorrent/web_peer_connection.hpp"
43 #include "libtorrent/session.hpp"
44 #include "libtorrent/identify_client.hpp"
45 #include "libtorrent/entry.hpp"
46 #include "libtorrent/bencode.hpp"
47 #include "libtorrent/alert_types.hpp"
48 #include "libtorrent/invariant_check.hpp"
49 #include "libtorrent/io.hpp"
50 #include "libtorrent/version.hpp"
51 #include "libtorrent/aux_/session_impl.hpp"
52 #include "libtorrent/parse_url.hpp"
54 using boost::bind;
55 using boost::shared_ptr;
56 using libtorrent::aux::session_impl;
58 namespace libtorrent
60 web_peer_connection::web_peer_connection(
61 session_impl& ses
62 , boost::weak_ptr<torrent> t
63 , boost::shared_ptr<socket_type> s
64 , tcp::endpoint const& remote
65 , std::string const& url
66 , policy::peer* peerinfo)
67 : peer_connection(ses, t, s, remote, peerinfo)
68 , m_url(url)
69 , m_first_request(true)
70 , m_range_pos(0)
72 INVARIANT_CHECK;
74 // we want large blocks as well, so
75 // we can request more bytes at once
76 request_large_blocks(true);
77 set_upload_only(true);
79 // we only want left-over bandwidth
80 set_priority(0);
81 shared_ptr<torrent> tor = t.lock();
82 TORRENT_ASSERT(tor);
83 int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size();
85 // we always prefer downloading 1 MB chunks
86 // from web seeds
87 prefer_whole_pieces((1024 * 1024) / tor->torrent_file().piece_length());
89 // multiply with the blocks per piece since that many requests are
90 // merged into one http request
91 m_max_out_request_queue = ses.settings().urlseed_pipeline_size
92 * blocks_per_piece;
94 // since this is a web seed, change the timeout
95 // according to the settings.
96 set_timeout(ses.settings().urlseed_timeout);
97 #ifdef TORRENT_VERBOSE_LOGGING
98 (*m_logger) << "*** web_peer_connection\n";
99 #endif
101 std::string protocol;
102 char const* error;
103 boost::tie(protocol, m_auth, m_host, m_port, m_path, error)
104 = parse_url_components(url);
105 TORRENT_ASSERT(error == 0);
107 if (!m_auth.empty())
108 m_auth = base64encode(m_auth);
110 m_server_string = "URL seed @ ";
111 m_server_string += m_host;
114 web_peer_connection::~web_peer_connection()
117 boost::optional<piece_block_progress>
118 web_peer_connection::downloading_piece_progress() const
120 if (m_requests.empty())
121 return boost::optional<piece_block_progress>();
123 boost::shared_ptr<torrent> t = associated_torrent().lock();
124 TORRENT_ASSERT(t);
126 piece_block_progress ret;
128 ret.piece_index = m_requests.front().piece;
129 if (!m_piece.empty())
131 ret.bytes_downloaded = int(m_piece.size());
133 else
135 if (!m_parser.header_finished())
137 ret.bytes_downloaded = 0;
139 else
141 int receive_buffer_size = receive_buffer().left() - m_parser.body_start();
142 ret.bytes_downloaded = receive_buffer_size % t->block_size();
145 ret.block_index = (m_requests.front().start + ret.bytes_downloaded) / t->block_size();
146 ret.full_block_bytes = t->block_size();
147 const int last_piece = t->torrent_file().num_pieces() - 1;
148 if (ret.piece_index == last_piece && ret.block_index
149 == t->torrent_file().piece_size(last_piece) / t->block_size())
150 ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size();
151 return ret;
154 void web_peer_connection::on_connected()
156 boost::shared_ptr<torrent> t = associated_torrent().lock();
157 TORRENT_ASSERT(t);
159 // this is always a seed
160 incoming_have_all();
162 // it is always possible to request pieces
163 incoming_unchoke();
165 reset_recv_buffer(t->block_size() + 1024);
168 void web_peer_connection::write_request(peer_request const& r)
170 INVARIANT_CHECK;
172 boost::shared_ptr<torrent> t = associated_torrent().lock();
173 TORRENT_ASSERT(t);
175 TORRENT_ASSERT(t->valid_metadata());
177 bool single_file_request = false;
178 if (!m_path.empty() && m_path[m_path.size() - 1] != '/')
179 single_file_request = true;
181 torrent_info const& info = t->torrent_file();
183 std::string request;
184 request.reserve(400);
186 int size = r.length;
187 const int block_size = t->block_size();
188 const int piece_size = t->torrent_file().piece_length();
189 peer_request pr;
190 while (size > 0)
192 int request_offset = r.start + r.length - size;
193 pr.start = request_offset % piece_size;
194 pr.length = (std::min)(block_size, size);
195 pr.piece = r.piece + request_offset / piece_size;
196 m_requests.push_back(pr);
197 size -= pr.length;
200 proxy_settings const& ps = m_ses.web_seed_proxy();
201 bool using_proxy = ps.type == proxy_settings::http
202 || ps.type == proxy_settings::http_pw;
204 if (single_file_request)
206 request += "GET ";
207 // do not encode single file paths, they are
208 // assumed to be encoded in the torrent file
209 request += using_proxy ? m_url : m_path;
210 request += " HTTP/1.1\r\n";
211 request += "Host: ";
212 request += m_host;
213 if (m_first_request)
215 request += "\r\nUser-Agent: ";
216 request += m_ses.settings().user_agent;
218 if (!m_auth.empty())
220 request += "\r\nAuthorization: Basic ";
221 request += m_auth;
223 if (ps.type == proxy_settings::http_pw)
225 request += "\r\nProxy-Authorization: Basic ";
226 request += base64encode(ps.username + ":" + ps.password);
228 if (using_proxy)
230 request += "\r\nProxy-Connection: keep-alive";
232 request += "\r\nRange: bytes=";
233 request += boost::lexical_cast<std::string>(size_type(r.piece)
234 * info.piece_length() + r.start);
235 request += "-";
236 request += boost::lexical_cast<std::string>(r.piece
237 * info.piece_length() + r.start + r.length - 1);
238 if (m_first_request || using_proxy)
239 request += "\r\nConnection: keep-alive";
240 request += "\r\n\r\n";
241 m_first_request = false;
242 m_file_requests.push_back(0);
244 else
246 std::vector<file_slice> files = info.files().map_block(r.piece, r.start
247 , r.length);
249 for (std::vector<file_slice>::iterator i = files.begin();
250 i != files.end(); ++i)
252 file_slice const& f = *i;
254 request += "GET ";
255 if (using_proxy)
257 request += m_url;
258 std::string path = info.files().at(f.file_index).path.string();
259 request += escape_path(path.c_str(), path.length());
261 else
263 std::string path = m_path;
264 path += info.files().at(f.file_index).path.string();
265 request += escape_path(path.c_str(), path.length());
267 request += " HTTP/1.1\r\n";
268 request += "Host: ";
269 request += m_host;
270 if (m_first_request)
272 request += "\r\nUser-Agent: ";
273 request += m_ses.settings().user_agent;
275 if (!m_auth.empty())
277 request += "\r\nAuthorization: Basic ";
278 request += m_auth;
280 if (ps.type == proxy_settings::http_pw)
282 request += "\r\nProxy-Authorization: Basic ";
283 request += base64encode(ps.username + ":" + ps.password);
285 if (using_proxy)
287 request += "\r\nProxy-Connection: keep-alive";
289 request += "\r\nRange: bytes=";
290 request += boost::lexical_cast<std::string>(f.offset);
291 request += "-";
292 request += boost::lexical_cast<std::string>(f.offset + f.size - 1);
293 if (m_first_request || using_proxy)
294 request += "\r\nConnection: keep-alive";
295 request += "\r\n\r\n";
296 m_first_request = false;
297 TORRENT_ASSERT(f.file_index >= 0);
298 m_file_requests.push_back(f.file_index);
302 #ifdef TORRENT_VERBOSE_LOGGING
303 (*m_logger) << request << "\n";
304 #endif
306 send_buffer(request.c_str(), request.size(), message_type_request);
309 // --------------------------
310 // RECEIVE DATA
311 // --------------------------
313 namespace
315 bool range_contains(peer_request const& range, peer_request const& req, int piece_size)
317 size_type range_start = size_type(range.piece) * piece_size + range.start;
318 size_type req_start = size_type(req.piece) * piece_size + req.start;
319 return range_start <= req_start
320 && range_start + range.length >= req_start + req.length;
324 void web_peer_connection::on_receive(error_code const& error
325 , std::size_t bytes_transferred)
327 INVARIANT_CHECK;
329 if (error)
331 #ifdef TORRENT_VERBOSE_LOGGING
332 (*m_logger) << "*** web_peer_connection error: "
333 << error.message() << "\n";
334 #endif
335 return;
338 boost::shared_ptr<torrent> t = associated_torrent().lock();
339 TORRENT_ASSERT(t);
341 incoming_piece_fragment();
343 for (;;)
345 buffer::const_interval recv_buffer = receive_buffer();
347 int payload;
348 int protocol;
349 bool header_finished = m_parser.header_finished();
350 if (!header_finished)
352 bool error = false;
353 boost::tie(payload, protocol) = m_parser.incoming(recv_buffer, error);
354 m_statistics.received_bytes(0, protocol);
355 bytes_transferred -= protocol;
357 if (error)
359 disconnect("failed to parse HTTP response", 2);
360 return;
363 TORRENT_ASSERT(recv_buffer.left() == 0 || *recv_buffer.begin == 'H');
365 TORRENT_ASSERT(recv_buffer.left() <= packet_size());
367 // this means the entire status line hasn't been received yet
368 if (m_parser.status_code() == -1)
370 TORRENT_ASSERT(payload == 0);
371 TORRENT_ASSERT(bytes_transferred == 0);
372 break;
375 // if the status code is not one of the accepted ones, abort
376 if (m_parser.status_code() != 206 // partial content
377 && m_parser.status_code() != 200 // OK
378 && !(m_parser.status_code() >= 300 // redirect
379 && m_parser.status_code() < 400))
381 if (m_parser.status_code() == 503)
383 // temporarily unavailable, retry later
384 t->retry_url_seed(m_url);
386 t->remove_url_seed(m_url);
387 std::string error_msg = boost::lexical_cast<std::string>(m_parser.status_code())
388 + " " + m_parser.message();
389 if (m_ses.m_alerts.should_post<url_seed_alert>())
391 session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
392 m_ses.m_alerts.post_alert(url_seed_alert(t->get_handle(), url()
393 , error_msg));
395 disconnect(error_msg.c_str(), 1);
396 return;
398 if (!m_parser.header_finished())
400 TORRENT_ASSERT(payload == 0);
401 TORRENT_ASSERT(bytes_transferred == 0);
402 break;
405 m_body_start = m_parser.body_start();
406 m_received_body = 0;
409 // we just completed reading the header
410 if (!header_finished)
412 if (m_parser.status_code() >= 300 && m_parser.status_code() < 400)
414 // this means we got a redirection request
415 // look for the location header
416 std::string location = m_parser.header("location");
418 if (location.empty())
420 // we should not try this server again.
421 t->remove_url_seed(m_url);
422 disconnect("got HTTP redirection status without location header", 2);
423 return;
426 bool single_file_request = false;
427 if (!m_path.empty() && m_path[m_path.size() - 1] != '/')
428 single_file_request = true;
430 // add the redirected url and remove the current one
431 if (!single_file_request)
433 TORRENT_ASSERT(!m_file_requests.empty());
434 int file_index = m_file_requests.front();
436 torrent_info const& info = t->torrent_file();
437 std::string path = info.files().at(file_index).path.string();
438 path = escape_path(path.c_str(), path.length());
439 size_t i = location.rfind(path);
440 if (i == std::string::npos)
442 t->remove_url_seed(m_url);
443 std::stringstream msg;
444 msg << "got invalid HTTP redirection location (\"" << location << "\") "
445 "expected it to end with: " << path;
446 disconnect(msg.str().c_str(), 2);
447 return;
449 location.resize(i);
451 t->add_url_seed(location);
452 t->remove_url_seed(m_url);
453 std::stringstream msg;
454 msg << "redirecting to \"" << location << "\"";
455 disconnect(msg.str().c_str());
456 return;
459 std::string const& server_version = m_parser.header("server");
460 if (!server_version.empty())
462 m_server_string = "URL seed @ ";
463 m_server_string += m_host;
464 m_server_string += " (";
465 m_server_string += server_version;
466 m_server_string += ")";
469 m_body_start = m_parser.body_start();
470 m_received_body = 0;
471 m_range_pos = 0;
474 recv_buffer.begin += m_body_start;
476 // we only received the header, no data
477 if (recv_buffer.left() == 0) break;
479 size_type range_start;
480 size_type range_end;
481 if (m_parser.status_code() == 206)
483 std::stringstream range_str(m_parser.header("content-range"));
484 char dummy;
485 std::string bytes;
486 range_str >> bytes >> range_start >> dummy >> range_end;
487 if (!range_str)
489 // we should not try this server again.
490 t->remove_url_seed(m_url);
491 std::stringstream msg;
492 msg << "invalid range in HTTP response: " << range_str.str();
493 disconnect(msg.str().c_str(), 2);
494 return;
496 // the http range is inclusive
497 range_end++;
499 else
501 range_start = 0;
502 range_end = atol(m_parser.header("content-length").c_str());
503 if (range_end == -1)
505 // we should not try this server again.
506 t->remove_url_seed(m_url);
507 disconnect("no content-length in HTTP response", 2);
508 return;
512 int left_in_response = range_end - range_start - m_range_pos;
513 int payload_transferred = (std::min)(left_in_response, int(bytes_transferred));
514 m_statistics.received_bytes(payload_transferred, 0);
515 bytes_transferred -= payload_transferred;
516 m_range_pos += payload_transferred;;
517 if (m_range_pos > range_end - range_start) m_range_pos = range_end - range_start;
519 // std::cerr << "REQUESTS: m_requests: " << m_requests.size()
520 // << " file_requests: " << m_file_requests.size() << std::endl;
522 torrent_info const& info = t->torrent_file();
524 if (m_requests.empty() || m_file_requests.empty())
526 disconnect("unexpected HTTP response", 2);
527 return;
530 int file_index = m_file_requests.front();
531 peer_request in_range = info.files().map_file(file_index, range_start
532 , int(range_end - range_start));
534 peer_request front_request = m_requests.front();
536 size_type rs = size_type(in_range.piece) * info.piece_length() + in_range.start;
537 size_type re = rs + in_range.length;
538 size_type fs = size_type(front_request.piece) * info.piece_length() + front_request.start;
540 size_type fe = fs + front_request.length;
542 std::cerr << "RANGE: r = (" << rs << ", " << re << " ) "
543 "f = (" << fs << ", " << fe << ") "
544 "file_index = " << file_index << " received_body = " << m_received_body << std::endl;
547 // the http response body consists of 3 parts
548 // 1. the middle of a block or the ending of a block
549 // 2. a number of whole blocks
550 // 3. the start of a block
551 // in that order, these parts are parsed.
553 bool range_overlaps_request = re > fs + int(m_piece.size());
555 if (!range_overlaps_request)
557 // this means the end of the incoming request ends _before_ the
558 // first expected byte (fs + m_piece.size())
559 disconnect("invalid range in HTTP response", 2);
560 return;
563 // if the request is contained in the range (i.e. the entire request
564 // fits in the range) we should not start a partial piece, since we soon
565 // will receive enough to call incoming_piece() and pass the read buffer
566 // directly (in the next loop below).
567 if (range_overlaps_request && !range_contains(in_range, front_request, info.piece_length()))
569 // the start of the next block to receive is stored
570 // in m_piece. We need to append the rest of that
571 // block from the http receive buffer and then
572 // (if it completed) call incoming_piece() with
573 // m_piece as buffer.
575 int piece_size = int(m_piece.size());
576 int copy_size = (std::min)((std::min)(front_request.length - piece_size
577 , recv_buffer.left()), int(range_end - range_start - m_received_body));
578 m_piece.resize(piece_size + copy_size);
579 TORRENT_ASSERT(copy_size > 0);
580 std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size);
581 TORRENT_ASSERT(int(m_piece.size()) <= front_request.length);
582 recv_buffer.begin += copy_size;
583 m_received_body += copy_size;
584 m_body_start += copy_size;
585 TORRENT_ASSERT(m_received_body <= range_end - range_start);
586 TORRENT_ASSERT(int(m_piece.size()) <= front_request.length);
587 if (int(m_piece.size()) == front_request.length)
589 // each call to incoming_piece() may result in us becoming
590 // a seed. If we become a seed, all seeds we're connected to
591 // will be disconnected, including this web seed. We need to
592 // check for the disconnect condition after the call.
594 m_requests.pop_front();
595 incoming_piece(front_request, &m_piece[0]);
596 if (associated_torrent().expired()) return;
597 cut_receive_buffer(m_body_start, t->block_size() + 1024);
598 m_body_start = 0;
599 recv_buffer = receive_buffer();
600 TORRENT_ASSERT(m_received_body <= range_end - range_start);
601 m_piece.clear();
602 TORRENT_ASSERT(m_piece.empty());
606 // report all received blocks to the bittorrent engine
607 while (!m_requests.empty()
608 && range_contains(in_range, m_requests.front(), info.piece_length())
609 && recv_buffer.left() >= m_requests.front().length)
611 peer_request r = m_requests.front();
612 m_requests.pop_front();
613 TORRENT_ASSERT(recv_buffer.left() >= r.length);
615 incoming_piece(r, recv_buffer.begin);
616 if (associated_torrent().expired()) return;
617 m_received_body += r.length;
618 TORRENT_ASSERT(receive_buffer().begin + m_body_start == recv_buffer.begin);
619 TORRENT_ASSERT(m_received_body <= range_end - range_start);
620 cut_receive_buffer(r.length + m_body_start, t->block_size() + 1024);
621 m_body_start = 0;
622 recv_buffer = receive_buffer();
625 if (!m_requests.empty())
627 range_overlaps_request = in_range.start + in_range.length
628 > m_requests.front().start + int(m_piece.size());
630 if (in_range.start + in_range.length < m_requests.front().start + m_requests.front().length
631 && (m_received_body + recv_buffer.left() >= range_end - range_start))
633 int piece_size = int(m_piece.size());
634 int copy_size = (std::min)((std::min)(m_requests.front().length - piece_size
635 , recv_buffer.left()), int(range_end - range_start - m_received_body));
636 TORRENT_ASSERT(copy_size >= 0);
637 if (copy_size > 0)
639 m_piece.resize(piece_size + copy_size);
640 std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size);
641 recv_buffer.begin += copy_size;
642 m_received_body += copy_size;
643 m_body_start += copy_size;
645 TORRENT_ASSERT(m_received_body == range_end - range_start);
649 TORRENT_ASSERT(m_received_body <= range_end - range_start);
650 if (m_received_body == range_end - range_start)
652 cut_receive_buffer(recv_buffer.begin - receive_buffer().begin
653 , t->block_size() + 1024);
654 recv_buffer = receive_buffer();
655 m_file_requests.pop_front();
656 m_parser.reset();
657 m_body_start = 0;
658 m_received_body = 0;
659 continue;
661 if (bytes_transferred == 0) break;
663 TORRENT_ASSERT(bytes_transferred == 0);
666 void web_peer_connection::get_specific_peer_info(peer_info& p) const
668 if (is_interesting()) p.flags |= peer_info::interesting;
669 if (is_choked()) p.flags |= peer_info::choked;
670 if (is_peer_interested()) p.flags |= peer_info::remote_interested;
671 if (has_peer_choked()) p.flags |= peer_info::remote_choked;
672 if (is_local()) p.flags |= peer_info::local_connection;
673 if (!is_connecting() && m_server_string.empty())
674 p.flags |= peer_info::handshake;
675 if (is_connecting() && !is_queued()) p.flags |= peer_info::connecting;
676 if (is_queued()) p.flags |= peer_info::queued;
678 p.client = m_server_string;
679 p.connection_type = peer_info::web_seed;
682 bool web_peer_connection::in_handshake() const
684 return m_server_string.empty();
687 // throws exception when the client should be disconnected
688 void web_peer_connection::on_sent(error_code const& error
689 , std::size_t bytes_transferred)
691 INVARIANT_CHECK;
693 if (error) return;
694 m_statistics.sent_bytes(0, bytes_transferred);
698 #ifndef NDEBUG
699 void web_peer_connection::check_invariant() const
702 TORRENT_ASSERT(m_num_pieces == std::count(
703 m_have_piece.begin()
704 , m_have_piece.end()
705 , true));
706 */ }
707 #endif