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"
52 #include "libtorrent/parse_url.hpp"
55 using boost::shared_ptr
;
56 using libtorrent::aux::session_impl
;
60 web_peer_connection::web_peer_connection(
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
)
69 , m_first_request(true)
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
81 shared_ptr
<torrent
> tor
= t
.lock();
83 int blocks_per_piece
= tor
->torrent_file().piece_length() / tor
->block_size();
85 // we always prefer downloading 1 MB chunks
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
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";
101 std::string protocol
;
103 boost::tie(protocol
, m_auth
, m_host
, m_port
, m_path
, error
)
104 = parse_url_components(url
);
105 TORRENT_ASSERT(error
== 0);
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();
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());
135 if (!m_parser
.header_finished())
137 ret
.bytes_downloaded
= 0;
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();
154 void web_peer_connection::on_connected()
156 boost::shared_ptr
<torrent
> t
= associated_torrent().lock();
159 // this is always a seed
162 // it is always possible to request pieces
165 reset_recv_buffer(t
->block_size() + 1024);
168 void web_peer_connection::write_request(peer_request
const& r
)
172 boost::shared_ptr
<torrent
> t
= associated_torrent().lock();
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();
184 request
.reserve(400);
187 const int block_size
= t
->block_size();
188 const int piece_size
= t
->torrent_file().piece_length();
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
);
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
)
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";
215 request
+= "\r\nUser-Agent: ";
216 request
+= m_ses
.settings().user_agent
;
220 request
+= "\r\nAuthorization: Basic ";
223 if (ps
.type
== proxy_settings::http_pw
)
225 request
+= "\r\nProxy-Authorization: Basic ";
226 request
+= base64encode(ps
.username
+ ":" + ps
.password
);
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
);
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);
246 std::vector
<file_slice
> files
= info
.files().map_block(r
.piece
, r
.start
249 for (std::vector
<file_slice
>::iterator i
= files
.begin();
250 i
!= files
.end(); ++i
)
252 file_slice
const& f
= *i
;
258 std::string path
= info
.files().at(f
.file_index
).path
.string();
259 request
+= escape_path(path
.c_str(), path
.length());
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";
272 request
+= "\r\nUser-Agent: ";
273 request
+= m_ses
.settings().user_agent
;
277 request
+= "\r\nAuthorization: Basic ";
280 if (ps
.type
== proxy_settings::http_pw
)
282 request
+= "\r\nProxy-Authorization: Basic ";
283 request
+= base64encode(ps
.username
+ ":" + ps
.password
);
287 request
+= "\r\nProxy-Connection: keep-alive";
289 request
+= "\r\nRange: bytes=";
290 request
+= boost::lexical_cast
<std::string
>(f
.offset
);
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";
306 send_buffer(request
.c_str(), request
.size(), message_type_request
);
309 // --------------------------
311 // --------------------------
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
)
331 #ifdef TORRENT_VERBOSE_LOGGING
332 (*m_logger
) << "*** web_peer_connection error: "
333 << error
.message() << "\n";
338 boost::shared_ptr
<torrent
> t
= associated_torrent().lock();
341 incoming_piece_fragment();
345 buffer::const_interval recv_buffer
= receive_buffer();
349 bool header_finished
= m_parser
.header_finished();
350 if (!header_finished
)
353 boost::tie(payload
, protocol
) = m_parser
.incoming(recv_buffer
, error
);
354 m_statistics
.received_bytes(0, protocol
);
355 bytes_transferred
-= protocol
;
359 disconnect("failed to parse HTTP response", 2);
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);
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()
395 disconnect(error_msg
.c_str(), 1);
398 if (!m_parser
.header_finished())
400 TORRENT_ASSERT(payload
== 0);
401 TORRENT_ASSERT(bytes_transferred
== 0);
405 m_body_start
= m_parser
.body_start();
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);
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);
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());
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();
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
;
481 if (m_parser
.status_code() == 206)
483 std::stringstream
range_str(m_parser
.header("content-range"));
486 range_str
>> bytes
>> range_start
>> dummy
>> range_end
;
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);
496 // the http range is inclusive
502 range_end
= atol(m_parser
.header("content-length").c_str());
505 // we should not try this server again.
506 t
->remove_url_seed(m_url
);
507 disconnect("no content-length in HTTP response", 2);
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);
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);
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);
599 recv_buffer
= receive_buffer();
600 TORRENT_ASSERT(m_received_body
<= range_end
- range_start
);
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);
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);
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();
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
)
694 m_statistics
.sent_bytes(0, bytes_transferred
);
699 void web_peer_connection::check_invariant() const
702 TORRENT_ASSERT(m_num_pieces == std::count(