3 Copyright (c) 2003, Arvid Norberg
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
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"
39 #include <boost/bind.hpp>
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"
54 using boost::shared_ptr
;
55 using libtorrent::aux::session_impl
;
59 web_peer_connection::web_peer_connection(
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
)
68 , m_first_request(true)
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();
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
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";
97 boost::tie(protocol
, m_auth
, m_host
, m_port
, m_path
)
98 = parse_url_components(url
);
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();
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());
128 if (!m_parser
.header_finished())
130 ret
.bytes_downloaded
= 0;
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();
147 void web_peer_connection::on_connected()
149 boost::shared_ptr
<torrent
> t
= associated_torrent().lock();
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
158 reset_recv_buffer(t
->block_size() + 1024);
161 void web_peer_connection::write_request(peer_request
const& r
)
165 boost::shared_ptr
<torrent
> t
= associated_torrent().lock();
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();
177 request
.reserve(400);
180 const int block_size
= t
->block_size();
183 int request_size
= std::min(block_size
, size
);
184 peer_request pr
= {r
.piece
, r
.start
+ r
.length
- 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
)
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";
205 request
+= "\r\nUser-Agent: ";
206 request
+= m_ses
.settings().user_agent
;
210 request
+= "\r\nAuthorization: Basic ";
213 if (ps
.type
== proxy_settings::http_pw
)
215 request
+= "\r\nProxy-Authorization: Basic ";
216 request
+= base64encode(ps
.username
+ ":" + ps
.password
);
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
);
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);
236 std::vector
<file_slice
> files
= info
.map_block(r
.piece
, r
.start
239 for (std::vector
<file_slice
>::iterator i
= files
.begin();
240 i
!= files
.end(); ++i
)
242 file_slice
const& f
= *i
;
248 std::string path
= info
.file_at(f
.file_index
).path
.string();
249 request
+= escape_path(path
.c_str(), path
.length());
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";
262 request
+= "\r\nUser-Agent: ";
263 request
+= m_ses
.settings().user_agent
;
267 request
+= "\r\nAuthorization: Basic ";
270 if (ps
.type
== proxy_settings::http_pw
)
272 request
+= "\r\nProxy-Authorization: Basic ";
273 request
+= base64encode(ps
.username
+ ":" + ps
.password
);
277 request
+= "\r\nProxy-Connection: keep-alive";
279 request
+= "\r\nRange: bytes=";
280 request
+= boost::lexical_cast
<std::string
>(f
.offset
);
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";
296 send_buffer(request
.c_str(), request
.c_str() + request
.size());
299 // --------------------------
301 // --------------------------
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
)
320 boost::shared_ptr
<torrent
> t
= associated_torrent().lock();
323 incoming_piece_fragment();
327 buffer::const_interval recv_buffer
= receive_buffer();
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()
360 throw std::runtime_error(error_msg
);
362 if (!m_parser
.header_finished()) break;
364 m_body_start
= m_parser
.body_start();
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
);
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();
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
;
435 if (m_parser
.status_code() == 206)
437 std::stringstream
range_str(m_parser
.header
<std::string
>("content-range"));
440 range_str
>> bytes
>> range_start
>> dummy
>> range_end
;
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
453 range_end
= m_parser
.header
<size_type
>("content-length");
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);
529 recv_buffer
= receive_buffer();
530 assert(m_received_body
<= range_end
- range_start
);
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);
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);
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();
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();
606 #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES
607 p
.country
[0] = m_country
[0];
608 p
.country
[1] = m_country
[1];
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
)
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;
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
;
638 p
.downloading_piece_index
= -1;
639 p
.downloading_block_index
= -1;
640 p
.downloading_progress
= 0;
641 p
.downloading_total
= 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();
658 p
.client
= m_server_string
;
659 p
.connection_type
= peer_info::web_seed
;
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
)
676 m_statistics
.sent_bytes(0, bytes_transferred
);
681 void web_peer_connection::check_invariant() const
684 assert(m_num_pieces == std::count(