formatting fixes in client test, and made the test build when resolve countries is...
[libtorrent-kjk.git] / src / web_peer_connection.cpp
blobbbe7c6e6609f446f4dc4c546c7097d2f27b97d92
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"
53 using boost::bind;
54 using boost::shared_ptr;
55 using libtorrent::aux::session_impl;
57 namespace libtorrent
59 web_peer_connection::web_peer_connection(
60 session_impl& ses
61 , boost::weak_ptr<torrent> t
62 , boost::shared_ptr<socket_type> s
63 , tcp::endpoint const& remote
64 , std::string const& url
65 , policy::peer* peerinfo)
66 : peer_connection(ses, t, s, remote, peerinfo)
67 , m_url(url)
68 , m_first_request(true)
70 INVARIANT_CHECK;
72 // we always prefer downloading entire
73 // pieces from web seeds
74 prefer_whole_pieces(true);
75 // we want large blocks as well, so
76 // we can request more bytes at once
77 request_large_blocks(true);
78 // we only want left-over bandwidth
79 set_non_prioritized(true);
80 shared_ptr<torrent> tor = t.lock();
81 assert(tor);
82 int blocks_per_piece = tor->torrent_file().piece_length() / tor->block_size();
84 // multiply with the blocks per piece since that many requests are
85 // merged into one http request
86 m_max_out_request_queue = ses.settings().urlseed_pipeline_size
87 * blocks_per_piece;
89 // since this is a web seed, change the timeout
90 // according to the settings.
91 set_timeout(ses.settings().urlseed_timeout);
92 #ifdef TORRENT_VERBOSE_LOGGING
93 (*m_logger) << "*** web_peer_connection\n";
94 #endif
96 std::string protocol;
97 boost::tie(protocol, m_auth, m_host, m_port, m_path)
98 = parse_url_components(url);
100 if (!m_auth.empty())
101 m_auth = base64encode(m_auth);
103 m_server_string = "URL seed @ ";
104 m_server_string += m_host;
107 web_peer_connection::~web_peer_connection()
110 boost::optional<piece_block_progress>
111 web_peer_connection::downloading_piece_progress() const
113 if (m_requests.empty())
114 return boost::optional<piece_block_progress>();
116 boost::shared_ptr<torrent> t = associated_torrent().lock();
117 assert(t);
119 piece_block_progress ret;
121 ret.piece_index = m_requests.front().piece;
122 if (!m_piece.empty())
124 ret.bytes_downloaded = int(m_piece.size());
126 else
128 if (!m_parser.header_finished())
130 ret.bytes_downloaded = 0;
132 else
134 int receive_buffer_size = receive_buffer().left() - m_parser.body_start();
135 ret.bytes_downloaded = receive_buffer_size % t->block_size();
138 ret.block_index = (m_requests.front().start + ret.bytes_downloaded) / t->block_size();
139 ret.full_block_bytes = t->block_size();
140 const int last_piece = t->torrent_file().num_pieces() - 1;
141 if (ret.piece_index == last_piece && ret.block_index
142 == t->torrent_file().piece_size(last_piece) / t->block_size())
143 ret.full_block_bytes = t->torrent_file().piece_size(last_piece) % t->block_size();
144 return ret;
147 void web_peer_connection::on_connected()
149 boost::shared_ptr<torrent> t = associated_torrent().lock();
150 assert(t);
152 // this is always a seed
153 incoming_bitfield(std::vector<bool>(
154 t->torrent_file().num_pieces(), true));
155 // it is always possible to request pieces
156 incoming_unchoke();
158 reset_recv_buffer(t->block_size() + 1024);
161 void web_peer_connection::write_request(peer_request const& r)
163 INVARIANT_CHECK;
165 boost::shared_ptr<torrent> t = associated_torrent().lock();
166 assert(t);
168 assert(t->valid_metadata());
170 bool single_file_request = false;
171 if (!m_path.empty() && m_path[m_path.size() - 1] != '/')
172 single_file_request = true;
174 torrent_info const& info = t->torrent_file();
176 std::string request;
177 request.reserve(400);
179 int size = r.length;
180 const int block_size = t->block_size();
181 while (size > 0)
183 int request_size = std::min(block_size, size);
184 peer_request pr = {r.piece, r.start + r.length - size
185 , request_size};
186 m_requests.push_back(pr);
187 size -= request_size;
190 proxy_settings const& ps = m_ses.web_seed_proxy();
191 bool using_proxy = ps.type == proxy_settings::http
192 || ps.type == proxy_settings::http_pw;
194 if (single_file_request)
196 request += "GET ";
197 // do not encode single file paths, they are
198 // assumed to be encoded in the torrent file
199 request += using_proxy ? m_url : m_path;
200 request += " HTTP/1.1\r\n";
201 request += "Host: ";
202 request += m_host;
203 if (m_first_request)
205 request += "\r\nUser-Agent: ";
206 request += m_ses.settings().user_agent;
208 if (!m_auth.empty())
210 request += "\r\nAuthorization: Basic ";
211 request += m_auth;
213 if (ps.type == proxy_settings::http_pw)
215 request += "\r\nProxy-Authorization: Basic ";
216 request += base64encode(ps.username + ":" + ps.password);
218 if (using_proxy)
220 request += "\r\nProxy-Connection: keep-alive";
222 request += "\r\nRange: bytes=";
223 request += boost::lexical_cast<std::string>(r.piece
224 * info.piece_length() + r.start);
225 request += "-";
226 request += boost::lexical_cast<std::string>(r.piece
227 * info.piece_length() + r.start + r.length - 1);
228 if (m_first_request || using_proxy)
229 request += "\r\nConnection: keep-alive";
230 request += "\r\n\r\n";
231 m_first_request = false;
232 m_file_requests.push_back(0);
234 else
236 std::vector<file_slice> files = info.map_block(r.piece, r.start
237 , r.length);
239 for (std::vector<file_slice>::iterator i = files.begin();
240 i != files.end(); ++i)
242 file_slice const& f = *i;
244 request += "GET ";
245 if (using_proxy)
247 request += m_url;
248 std::string path = info.file_at(f.file_index).path.string();
249 request += escape_path(path.c_str(), path.length());
251 else
253 std::string path = m_path;
254 path += info.file_at(f.file_index).path.string();
255 request += escape_path(path.c_str(), path.length());
257 request += " HTTP/1.1\r\n";
258 request += "Host: ";
259 request += m_host;
260 if (m_first_request)
262 request += "\r\nUser-Agent: ";
263 request += m_ses.settings().user_agent;
265 if (!m_auth.empty())
267 request += "\r\nAuthorization: Basic ";
268 request += m_auth;
270 if (ps.type == proxy_settings::http_pw)
272 request += "\r\nProxy-Authorization: Basic ";
273 request += base64encode(ps.username + ":" + ps.password);
275 if (using_proxy)
277 request += "\r\nProxy-Connection: keep-alive";
279 request += "\r\nRange: bytes=";
280 request += boost::lexical_cast<std::string>(f.offset);
281 request += "-";
282 request += boost::lexical_cast<std::string>(f.offset + f.size - 1);
283 if (m_first_request || using_proxy)
284 request += "\r\nConnection: keep-alive";
285 request += "\r\n\r\n";
286 m_first_request = false;
287 assert(f.file_index >= 0);
288 m_file_requests.push_back(f.file_index);
292 #ifdef TORRENT_VERBOSE_LOGGING
293 (*m_logger) << request << "\n";
294 #endif
296 send_buffer(request.c_str(), request.c_str() + request.size());
299 // --------------------------
300 // RECEIVE DATA
301 // --------------------------
303 namespace
305 bool range_contains(peer_request const& range, peer_request const& req)
307 return range.start <= req.start
308 && range.start + range.length >= req.start + req.length;
312 // throws exception when the client should be disconnected
313 void web_peer_connection::on_receive(asio::error_code const& error
314 , std::size_t bytes_transferred)
316 INVARIANT_CHECK;
318 if (error) return;
320 boost::shared_ptr<torrent> t = associated_torrent().lock();
321 assert(t);
323 incoming_piece_fragment();
325 for (;;)
327 buffer::const_interval recv_buffer = receive_buffer();
329 int payload;
330 int protocol;
331 bool header_finished = m_parser.header_finished();
332 if (!header_finished)
334 boost::tie(payload, protocol) = m_parser.incoming(recv_buffer);
335 m_statistics.received_bytes(payload, protocol);
337 assert(recv_buffer.left() == 0 || *recv_buffer.begin == 'H');
339 assert(recv_buffer.left() <= packet_size());
341 // this means the entire status line hasn't been received yet
342 if (m_parser.status_code() == -1) break;
344 // if the status code is not one of the accepted ones, abort
345 if (m_parser.status_code() != 206 // partial content
346 && m_parser.status_code() != 200 // OK
347 && !(m_parser.status_code() >= 300 // redirect
348 && m_parser.status_code() < 400))
350 // we should not try this server again.
351 t->remove_url_seed(m_url);
352 std::string error_msg = boost::lexical_cast<std::string>(m_parser.status_code())
353 + " " + m_parser.message();
354 if (m_ses.m_alerts.should_post(alert::warning))
356 session_impl::mutex_t::scoped_lock l(m_ses.m_mutex);
357 m_ses.m_alerts.post_alert(url_seed_alert(t->get_handle(), url()
358 , error_msg));
360 throw std::runtime_error(error_msg);
362 if (!m_parser.header_finished()) break;
364 m_body_start = m_parser.body_start();
365 m_received_body = 0;
367 else
369 m_statistics.received_bytes(bytes_transferred, 0);
372 // we just completed reading the header
373 if (!header_finished)
375 if (m_parser.status_code() >= 300 && m_parser.status_code() < 400)
377 // this means we got a redirection request
378 // look for the location header
379 std::string location = m_parser.header<std::string>("location");
381 if (location.empty())
383 // we should not try this server again.
384 t->remove_url_seed(m_url);
385 throw std::runtime_error("got HTTP redirection status without location header");
388 bool single_file_request = false;
389 if (!m_path.empty() && m_path[m_path.size() - 1] != '/')
390 single_file_request = true;
392 // add the redirected url and remove the current one
393 if (!single_file_request)
395 assert(!m_file_requests.empty());
396 int file_index = m_file_requests.front();
398 torrent_info const& info = t->torrent_file();
399 std::string path = info.file_at(file_index).path.string();
400 path = escape_path(path.c_str(), path.length());
401 size_t i = location.rfind(path);
402 if (i == std::string::npos)
404 t->remove_url_seed(m_url);
405 throw std::runtime_error("got invalid HTTP redirection location (\"" + location + "\") "
406 "expected it to end with: " + path);
408 location.resize(i);
410 t->add_url_seed(location);
411 t->remove_url_seed(m_url);
412 throw std::runtime_error("redirecting to " + location);
415 std::string server_version = m_parser.header<std::string>("server");
416 if (!server_version.empty())
418 m_server_string = "URL seed @ ";
419 m_server_string += m_host;
420 m_server_string += " (";
421 m_server_string += server_version;
422 m_server_string += ")";
425 m_body_start = m_parser.body_start();
426 m_received_body = 0;
429 recv_buffer.begin += m_body_start;
430 // we only received the header, no data
431 if (recv_buffer.left() == 0) break;
433 size_type range_start;
434 size_type range_end;
435 if (m_parser.status_code() == 206)
437 std::stringstream range_str(m_parser.header<std::string>("content-range"));
438 char dummy;
439 std::string bytes;
440 range_str >> bytes >> range_start >> dummy >> range_end;
441 if (!range_str)
443 // we should not try this server again.
444 t->remove_url_seed(m_url);
445 throw std::runtime_error("invalid range in HTTP response: " + range_str.str());
447 // the http range is inclusive
448 range_end++;
450 else
452 range_start = 0;
453 range_end = m_parser.header<size_type>("content-length");
454 if (range_end == -1)
456 // we should not try this server again.
457 t->remove_url_seed(m_url);
458 throw std::runtime_error("no content-length in HTTP response");
462 torrent_info const& info = t->torrent_file();
464 if (m_requests.empty() || m_file_requests.empty())
465 throw std::runtime_error("unexpected HTTP response");
467 int file_index = m_file_requests.front();
468 peer_request in_range = info.map_file(file_index, range_start
469 , range_end - range_start);
471 peer_request front_request = m_requests.front();
473 if (in_range.piece != front_request.piece
474 || in_range.start > front_request.start + int(m_piece.size()))
476 throw std::runtime_error("invalid range in HTTP response");
479 // skip the http header and the blocks we've already read. The
480 // http_body.begin is now in sync with the request at the front
481 // of the request queue
482 assert(in_range.start - int(m_piece.size()) <= front_request.start);
484 // the http response body consists of 3 parts
485 // 1. the middle of a block or the ending of a block
486 // 2. a number of whole blocks
487 // 3. the start of a block
488 // in that order, these parts are parsed.
490 bool range_overlaps_request = in_range.start + in_range.length
491 > front_request.start + int(m_piece.size());
493 // if the request is contained in the range (i.e. the entire request
494 // fits in the range) we should not start a partial piece, since we soon
495 // will receive enough to call incoming_piece() and pass the read buffer
496 // directly (in the next loop below).
497 if (range_overlaps_request && !range_contains(in_range, front_request))
499 // the start of the next block to receive is stored
500 // in m_piece. We need to append the rest of that
501 // block from the http receive buffer and then
502 // (if it completed) call incoming_piece() with
503 // m_piece as buffer.
505 int piece_size = int(m_piece.size());
506 int copy_size = std::min(std::min(front_request.length - piece_size
507 , recv_buffer.left()), int(range_end - range_start - m_received_body));
508 m_piece.resize(piece_size + copy_size);
509 assert(copy_size > 0);
510 std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size);
511 assert(int(m_piece.size()) <= front_request.length);
512 recv_buffer.begin += copy_size;
513 m_received_body += copy_size;
514 m_body_start += copy_size;
515 assert(m_received_body <= range_end - range_start);
516 assert(int(m_piece.size()) <= front_request.length);
517 if (int(m_piece.size()) == front_request.length)
519 // each call to incoming_piece() may result in us becoming
520 // a seed. If we become a seed, all seeds we're connected to
521 // will be disconnected, including this web seed. We need to
522 // check for the disconnect condition after the call.
524 m_requests.pop_front();
525 incoming_piece(front_request, &m_piece[0]);
526 if (associated_torrent().expired()) return;
527 cut_receive_buffer(m_body_start, t->block_size() + 1024);
528 m_body_start = 0;
529 recv_buffer = receive_buffer();
530 assert(m_received_body <= range_end - range_start);
531 m_piece.clear();
532 assert(m_piece.empty());
536 // report all received blocks to the bittorrent engine
537 while (!m_requests.empty()
538 && range_contains(in_range, m_requests.front())
539 && recv_buffer.left() >= m_requests.front().length)
541 peer_request r = m_requests.front();
542 m_requests.pop_front();
543 assert(recv_buffer.left() >= r.length);
545 incoming_piece(r, recv_buffer.begin);
546 if (associated_torrent().expired()) return;
547 m_received_body += r.length;
548 assert(receive_buffer().begin + m_body_start == recv_buffer.begin);
549 assert(m_received_body <= range_end - range_start);
550 cut_receive_buffer(r.length + m_body_start, t->block_size() + 1024);
551 m_body_start = 0;
552 recv_buffer = receive_buffer();
555 if (!m_requests.empty())
557 range_overlaps_request = in_range.start + in_range.length
558 > m_requests.front().start + int(m_piece.size());
560 if (in_range.start + in_range.length < m_requests.front().start + m_requests.front().length
561 && (m_received_body + recv_buffer.left() >= range_end - range_start))
563 int piece_size = int(m_piece.size());
564 int copy_size = std::min(std::min(m_requests.front().length - piece_size
565 , recv_buffer.left()), int(range_end - range_start - m_received_body));
566 assert(copy_size >= 0);
567 if (copy_size > 0)
569 m_piece.resize(piece_size + copy_size);
570 std::memcpy(&m_piece[0] + piece_size, recv_buffer.begin, copy_size);
571 recv_buffer.begin += copy_size;
572 m_received_body += copy_size;
573 m_body_start += copy_size;
575 assert(m_received_body == range_end - range_start);
579 assert(m_received_body <= range_end - range_start);
580 if (m_received_body == range_end - range_start)
582 cut_receive_buffer(recv_buffer.begin - receive_buffer().begin
583 , t->block_size() + 1024);
584 recv_buffer = receive_buffer();
585 m_file_requests.pop_front();
586 m_parser.reset();
587 m_body_start = 0;
588 m_received_body = 0;
589 continue;
591 break;
595 void web_peer_connection::get_peer_info(peer_info& p) const
597 assert(!associated_torrent().expired());
599 p.down_speed = statistics().download_rate();
600 p.up_speed = statistics().upload_rate();
601 p.payload_down_speed = statistics().download_payload_rate();
602 p.payload_up_speed = statistics().upload_payload_rate();
603 p.pid = pid();
604 p.ip = remote();
606 #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES
607 p.country[0] = m_country[0];
608 p.country[1] = m_country[1];
609 #endif
611 p.total_download = statistics().total_payload_download();
612 p.total_upload = statistics().total_payload_upload();
614 if (m_bandwidth_limit[upload_channel].throttle() == bandwidth_limit::inf)
615 p.upload_limit = -1;
616 else
617 p.upload_limit = m_bandwidth_limit[upload_channel].throttle();
619 if (m_bandwidth_limit[download_channel].throttle() == bandwidth_limit::inf)
620 p.download_limit = -1;
621 else
622 p.download_limit = m_bandwidth_limit[download_channel].throttle();
624 p.load_balancing = total_free_upload();
626 p.download_queue_length = (int)download_queue().size();
627 p.upload_queue_length = (int)upload_queue().size();
629 if (boost::optional<piece_block_progress> ret = downloading_piece_progress())
631 p.downloading_piece_index = ret->piece_index;
632 p.downloading_block_index = ret->block_index;
633 p.downloading_progress = ret->bytes_downloaded;
634 p.downloading_total = ret->full_block_bytes;
636 else
638 p.downloading_piece_index = -1;
639 p.downloading_block_index = -1;
640 p.downloading_progress = 0;
641 p.downloading_total = 0;
644 p.flags = 0;
645 if (is_interesting()) p.flags |= peer_info::interesting;
646 if (is_choked()) p.flags |= peer_info::choked;
647 if (is_peer_interested()) p.flags |= peer_info::remote_interested;
648 if (has_peer_choked()) p.flags |= peer_info::remote_choked;
649 if (is_local()) p.flags |= peer_info::local_connection;
650 if (!is_connecting() && m_server_string.empty())
651 p.flags |= peer_info::handshake;
652 if (is_connecting() && !is_queued()) p.flags |= peer_info::connecting;
653 if (is_queued()) p.flags |= peer_info::queued;
655 p.pieces = get_bitfield();
656 p.seed = is_seed();
658 p.client = m_server_string;
659 p.connection_type = peer_info::web_seed;
660 p.source = 0;
661 p.failcount = 0;
664 bool web_peer_connection::in_handshake() const
666 return m_server_string.empty();
669 // throws exception when the client should be disconnected
670 void web_peer_connection::on_sent(asio::error_code const& error
671 , std::size_t bytes_transferred)
673 INVARIANT_CHECK;
675 if (error) return;
676 m_statistics.sent_bytes(0, bytes_transferred);
680 #ifndef NDEBUG
681 void web_peer_connection::check_invariant() const
684 assert(m_num_pieces == std::count(
685 m_have_piece.begin()
686 , m_have_piece.end()
687 , true));
688 */ }
689 #endif