// endpoints is an in-out parameter
void http_tracker_connection::on_filter(http_connection& c
                                        , std::vector<tcp::endpoint>& endpoints)
{
    TORRENT_UNUSED(c);
    if (!tracker_req().filter) return;

    // remove endpoints that are filtered by the IP filter
    for (std::vector<tcp::endpoint>::iterator i = endpoints.begin();
            i != endpoints.end();)
    {
        if (tracker_req().filter->access(i->address()) == ip_filter::blocked)
            i = endpoints.erase(i);
        else
            ++i;
    }

#ifndef TORRENT_DISABLE_LOGGING
    boost::shared_ptr<request_callback> cb = requester();
    if (cb)
    {
        cb->debug_log("*** TRACKER_FILTER");
    }
#endif
    if (endpoints.empty())
        fail(error_code(errors::banned_by_ip_filter));
}
	void http_tracker_connection::on_filter(http_connection& c, std::list<tcp::endpoint>& endpoints)
	{
		if (tracker_req().apply_ip_filter == false) return;

		// remove endpoints that are filtered by the IP filter
		for (std::list<tcp::endpoint>::iterator i = endpoints.begin();
			i != endpoints.end();)
		{
			if (m_ses.m_ip_filter.access(i->address()) == ip_filter::blocked) 
				i = endpoints.erase(i);
			else
				++i;
		}

#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)
		boost::shared_ptr<request_callback> cb = requester();
		if (cb)
		{
			cb->debug_log("*** TRACKER_FILTER");
		}
#endif
		if (endpoints.empty())
			fail(error_code(errors::banned_by_ip_filter));
	}
	void http_tracker_connection::start()
	{
		// TODO: authentication
		std::string url = tracker_req().url;

		if (tracker_req().kind == tracker_request::scrape_request)
		{
			// find and replace "announce" with "scrape"
			// in request

			std::size_t pos = url.find("announce");
			if (pos == std::string::npos)
			{
				m_ios.post(boost::bind(&http_tracker_connection::fail_disp, self()
					, error_code(errors::scrape_not_available)));
				return;
			}
			url.replace(pos, 8, "scrape");
		}
		
#if TORRENT_USE_I2P
		bool i2p = is_i2p_url(url);
#else
		static const bool i2p = false;
#endif

		session_settings const& settings = m_ses.settings();

		// if request-string already contains
		// some parameters, append an ampersand instead
		// of a question mark
		size_t arguments_start = url.find('?');
		if (arguments_start != std::string::npos)
			url += "&";
		else
			url += "?";

		url += "info_hash=";
		url += escape_string((const char*)&tracker_req().info_hash[0], 20);
		
		if (tracker_req().kind == tracker_request::announce_request)
		{
			char str[1024];
			const bool stats = tracker_req().send_stats;
			snprintf(str, sizeof(str), "&peer_id=%s&port=%d&uploaded=%"PRId64
				"&downloaded=%"PRId64"&left=%"PRId64"&corrupt=%"PRId64"&redundant=%"PRId64
				"&compact=1&numwant=%d&key=%x&no_peer_id=1"
				, escape_string((const char*)&tracker_req().pid[0], 20).c_str()
				// the i2p tracker seems to verify that the port is not 0,
				// even though it ignores it otherwise
				, i2p ? 1 : tracker_req().listen_port
				, stats ? tracker_req().uploaded : 0
				, stats ? tracker_req().downloaded : 0
				, stats ? tracker_req().left : 0
				, stats ? tracker_req().corrupt : 0
				, stats ? tracker_req().redundant: 0
				, tracker_req().num_want
				, tracker_req().key);
			url += str;
#ifndef TORRENT_DISABLE_ENCRYPTION
			if (m_ses.get_pe_settings().in_enc_policy != pe_settings::disabled)
				url += "&supportcrypto=1";
#endif
			if (!tracker_req().trackerid.empty())
			{
				std::string id = tracker_req().trackerid;
				url += "&trackerid=";
				url += escape_string(id.c_str(), id.length());
			}

			if (tracker_req().event != tracker_request::none)
			{
				const char* event_string[] = {"completed", "started", "stopped", "paused"};
				url += "&event=";
				url += event_string[tracker_req().event - 1];
			}

#if TORRENT_USE_I2P
			if (i2p)
			{
				url += "&ip=";
				url += escape_string(m_i2p_conn->local_endpoint().c_str()
					, m_i2p_conn->local_endpoint().size());
				url += ".i2p";
			}
			else
#endif
			if (!m_ses.settings().anonymous_mode)
			{
				if (!settings.announce_ip.empty())
				{
					url += "&ip=" + escape_string(
						settings.announce_ip.c_str(), settings.announce_ip.size());
				}
				else if (m_ses.settings().announce_double_nat
					&& is_local(m_ses.listen_address()))
				{
					// only use the global external listen address here
					// if it turned out to be on a local network
					// since otherwise the tracker should use our
					// source IP to determine our origin
					url += "&ip=" + print_address(m_ses.listen_address());
				}
   
				if (!tracker_req().ipv6.empty() && !i2p)
				{
					url += "&ipv6=";
					url += tracker_req().ipv6;
				}
   
				if (!tracker_req().ipv4.empty() && !i2p)
				{
					url += "&ipv4=";
					url += tracker_req().ipv4;
				}
			}
		}

		m_tracker_connection.reset(new http_connection(m_ios, m_cc
			, boost::bind(&http_tracker_connection::on_response, self(), _1, _2, _3, _4)
			, true
			, boost::bind(&http_tracker_connection::on_connect, self(), _1)
			, boost::bind(&http_tracker_connection::on_filter, self(), _1, _2)
#ifdef TORRENT_USE_OPENSSL
			, tracker_req().ssl_ctx
#endif
			));

		int timeout = tracker_req().event==tracker_request::stopped
			?settings.stop_tracker_timeout
			:settings.tracker_completion_timeout;

		m_tracker_connection->get(url, seconds(timeout)
			, tracker_req().event == tracker_request::stopped ? 2 : 1
			, &m_ps, 5, settings.anonymous_mode ? "" : settings.user_agent
			, bind_interface()
#if TORRENT_USE_I2P
			, m_i2p_conn
#endif
			);

		// the url + 100 estimated header size
		sent_bytes(url.size() + 100);

#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)

		boost::shared_ptr<request_callback> cb = requester();
		if (cb)
		{
			cb->debug_log("==> TRACKER_REQUEST [ url: " + url + " ]");
		}
#endif
	}
	void http_tracker_connection::parse(int status_code, lazy_entry const& e)
	{
		boost::shared_ptr<request_callback> cb = requester();
		if (!cb) return;

		int interval = int(e.dict_find_int_value("interval", 1800));
		int min_interval = int(e.dict_find_int_value("min interval", 60));

		std::string trackerid;
		lazy_entry const* tracker_id = e.dict_find_string("tracker id");
		if (tracker_id)
			trackerid = tracker_id->string_value();
		// parse the response
		lazy_entry const* failure = e.dict_find_string("failure reason");
		if (failure)
		{
			fail(error_code(errors::tracker_failure), status_code
				, failure->string_value().c_str(), interval, min_interval);
			return;
		}

		lazy_entry const* warning = e.dict_find_string("warning message");
		if (warning)
			cb->tracker_warning(tracker_req(), warning->string_value());

		std::vector<peer_entry> peer_list;

		if (tracker_req().kind == tracker_request::scrape_request)
		{
			std::string ih = tracker_req().info_hash.to_string();

			lazy_entry const* files = e.dict_find_dict("files");
			if (files == 0)
			{
				fail(error_code(errors::invalid_files_entry), -1, ""
					, interval, min_interval);
				return;
			}

			lazy_entry const* scrape_data = files->dict_find_dict(ih.c_str());
			if (scrape_data == 0)
			{
				fail(error_code(errors::invalid_hash_entry), -1, ""
					, interval, min_interval);
				return;
			}
			int complete = int(scrape_data->dict_find_int_value("complete", -1));
			int incomplete = int(scrape_data->dict_find_int_value("incomplete", -1));
			int downloaded = int(scrape_data->dict_find_int_value("downloaded", -1));
			int downloaders = int(scrape_data->dict_find_int_value("downloaders", -1));
			cb->tracker_scrape_response(tracker_req(), complete
				, incomplete, downloaded, downloaders);
			return;
		}

		lazy_entry const* peers_ent = e.dict_find("peers");
		if (peers_ent && peers_ent->type() == lazy_entry::string_t)
		{
			char const* peers = peers_ent->string_ptr();
			int len = peers_ent->string_length();
			for (int i = 0; i < len; i += 6)
			{
				if (len - i < 6) break;

				peer_entry p;
				p.pid.clear();
				error_code ec;
				p.ip = detail::read_v4_address(peers).to_string(ec);
				p.port = detail::read_uint16(peers);
				if (ec) continue;
				peer_list.push_back(p);
			}
		}
		else if (peers_ent && peers_ent->type() == lazy_entry::list_t)
		{
			int len = peers_ent->list_size();
			for (int i = 0; i < len; ++i)
			{
				peer_entry p;
				if (!extract_peer_info(*peers_ent->list_at(i), p)) return;
				peer_list.push_back(p);
			}
		}
		else
		{
			peers_ent = 0;
		}

#if TORRENT_USE_IPV6
		lazy_entry const* ipv6_peers = e.dict_find_string("peers6");
		if (ipv6_peers)
		{
			char const* peers = ipv6_peers->string_ptr();
			int len = ipv6_peers->string_length();
			for (int i = 0; i < len; i += 18)
			{
				if (len - i < 18) break;

				peer_entry p;
				p.pid.clear();
				error_code ec;
				p.ip = detail::read_v6_address(peers).to_string(ec);
				p.port = detail::read_uint16(peers);
				if (ec) continue;
				peer_list.push_back(p);
			}
		}
		else
		{
			ipv6_peers = 0;
		}
#else
		lazy_entry const* ipv6_peers = 0;
#endif

		// if we didn't receive any peers. We don't care if we're stopping anyway
		if (peers_ent == 0 && ipv6_peers == 0
			&& tracker_req().event != tracker_request::stopped)
		{
			fail(error_code(errors::invalid_peers_entry), -1, ""
				, interval, min_interval);
			return;
		}


		// look for optional scrape info
		address external_ip;

		lazy_entry const* ip_ent = e.dict_find_string("external ip");
		if (ip_ent)
		{
			char const* p = ip_ent->string_ptr();
			if (ip_ent->string_length() == address_v4::bytes_type().size())
				external_ip = detail::read_v4_address(p);
#if TORRENT_USE_IPV6
			else if (ip_ent->string_length() == address_v6::bytes_type().size())
				external_ip = detail::read_v6_address(p);
#endif
		}
		
		int complete = int(e.dict_find_int_value("complete", -1));
		int incomplete = int(e.dict_find_int_value("incomplete", -1));

		std::list<address> ip_list;
		if (m_tracker_connection)
		{
			error_code ec;
			ip_list.push_back(m_tracker_connection->socket().remote_endpoint(ec).address());
			std::list<tcp::endpoint> const& epts = m_tracker_connection->endpoints();
			for (std::list<tcp::endpoint>::const_iterator i = epts.begin()
				, end(epts.end()); i != end; ++i)
			{
				ip_list.push_back(i->address());
			}
		}

		cb->tracker_response(tracker_req(), m_tracker_ip, ip_list, peer_list
			, interval, min_interval, complete, incomplete, external_ip, trackerid);
	}
	void http_tracker_connection::start()
	{
		// TODO: authentication
		std::string url = tracker_req().url;
		//feeqi add validation param
		std::string feeqi_auth = "";

		if (tracker_req().kind == tracker_request::scrape_request)
		{
			// find and replace "announce" with "scrape"
			// in request

			std::size_t pos = url.find("announce");
			if (pos == std::string::npos)
			{
				m_ios.post(boost::bind(&http_tracker_connection::fail_disp, self()
					, -1, "scrape is not available on url: '" + tracker_req().url +"'"));
				return;
			}
			url.replace(pos, 8, "scrape");
		}
		
		session_settings const& settings = m_ses.settings();

		// if request-string already contains
		// some parameters, append an ampersand instead
		// of a question mark
		size_t arguments_start = url.find('?');
		if (arguments_start != std::string::npos)
			url += "&";
		else
			url += "?";

		url += "info_hash=";
		
		url += escape_string((const char*)&tracker_req().info_hash[0], 20);
		
		if (tracker_req().kind == tracker_request::announce_request)
		{
			char str[1024];
			const bool stats = tracker_req().send_stats;
			snprintf(str, sizeof(str), "&peer_id=%s&port=%d&uploaded=%"PRId64
				"&downloaded=%"PRId64"&left=%"PRId64"&corrupt=%"PRId64"&redundant=%"PRId64
				"&compact=1&numwant=%d&key=%x&no_peer_id=1"
				, escape_string((const char*)&tracker_req().pid[0], 20).c_str()
				, tracker_req().listen_port
				, stats ? tracker_req().uploaded : 0
				, stats ? tracker_req().downloaded : 0
				, stats ? tracker_req().left : 0
				, stats ? tracker_req().corrupt : 0
				, stats ? tracker_req().redundant: 0
				, tracker_req().num_want
				, tracker_req().key);
			url += str;
#ifndef TORRENT_DISABLE_ENCRYPTION
			if (m_ses.get_pe_settings().in_enc_policy != pe_settings::disabled)
				url += "&supportcrypto=1";
#endif

			if (tracker_req().event != tracker_request::none)
			{
				const char* event_string[] = {"completed", "started", "stopped"};
				url += "&event=";
				url += event_string[tracker_req().event - 1];
			}

			if (settings.announce_ip != address())
			{
				error_code ec;
				std::string ip = settings.announce_ip.to_string(ec);
				if (!ec) url += "&ip=" + ip;
			}

			if (!tracker_req().ipv6.empty())
			{
				url += "&ipv6=";
				url += tracker_req().ipv6;
			}

			if (!tracker_req().ipv4.empty())
			{
				url += "&ipv4=";
				url += tracker_req().ipv4;
			}
		}
		//feeqi add validation param
		feeqi_auth += escape_string((const char*)&tracker_req().info_hash[0], 1); 
		feeqi_auth += escape_string((const char*)&tracker_req().pid[0], 1);
		
		url += "&feeqi_auth=";
		url += feeqi_auth;
		url += "%3d%3d";

		m_tracker_connection.reset(new http_connection(m_ios, m_cc
			, boost::bind(&http_tracker_connection::on_response, self(), _1, _2, _3, _4)
			, true
			, boost::bind(&http_tracker_connection::on_connect, self(), _1)
			, boost::bind(&http_tracker_connection::on_filter, self(), _1, _2)));

		int timeout = tracker_req().event==tracker_request::stopped
			?settings.stop_tracker_timeout
			:settings.tracker_completion_timeout;

		m_tracker_connection->get(url, seconds(timeout)
			, tracker_req().event == tracker_request::stopped ? 2 : 1
			, &m_ps, 5, settings.user_agent, bind_interface());

		// the url + 100 estimated header size
		sent_bytes(url.size() + 100);

#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)

		boost::shared_ptr<request_callback> cb = requester();
		if (cb)
		{
			cb->debug_log("==> TRACKER_REQUEST [ url: " + url + " ]");
		}
#endif
	}
	void http_tracker_connection::start()
	{
		// TODO: authentication
		std::string url = tracker_req().url;

		if (tracker_req().kind == tracker_request::scrape_request)
		{
			// find and replace "announce" with "scrape"
			// in request

			std::size_t pos = url.find("announce");
			if (pos == std::string::npos)
			{
				fail(-1, ("scrape is not available on url: '"
					+ tracker_req().url +"'").c_str());
				return;
			}
			url.replace(pos, 8, "scrape");
		}
		
		// if request-string already contains
		// some parameters, append an ampersand instead
		// of a question mark
		size_t arguments_start = url.find('?');
		if (arguments_start != std::string::npos)
			url += "&";
		else
			url += "?";

		url += "info_hash=";
		url += escape_string(
			reinterpret_cast<const char*>(tracker_req().info_hash.begin()), 20);
		
		if (tracker_req().kind == tracker_request::announce_request)
		{
			url += "&peer_id=";
			url += escape_string(
				reinterpret_cast<const char*>(tracker_req().pid.begin()), 20);

			url += "&port=";
			url += to_string(tracker_req().listen_port).elems;

			url += "&uploaded=";
			url += to_string(tracker_req().uploaded).elems;

			url += "&downloaded=";
			url += to_string(tracker_req().downloaded).elems;

			url += "&left=";
			url += to_string(tracker_req().left).elems;

			if (tracker_req().event != tracker_request::none)
			{
				const char* event_string[] = {"completed", "started", "stopped"};
				url += "&event=";
				url += event_string[tracker_req().event - 1];
			}

			url += "&key=";
			std::stringstream key_string;
			key_string << std::hex << tracker_req().key;
			url += key_string.str();

			url += "&compact=1";

			url += "&numwant=";
			url += to_string((std::min)(tracker_req().num_want, 999)).elems;

			if (m_settings.announce_ip != address())
			{
				error_code ec;
				std::string ip = m_settings.announce_ip.to_string(ec);
				if (!ec) url += "&ip=" + ip;
			}

#ifndef TORRENT_DISABLE_ENCRYPTION
			url += "&supportcrypto=1";
#endif
			if (!tracker_req().ipv6.empty())
			{
				url += "&ipv6=";
				url += tracker_req().ipv6;
			}

			if (!tracker_req().ipv4.empty())
			{
				url += "&ipv4=";
				url += tracker_req().ipv4;
			}

			// extension that tells the tracker that
			// we don't need any peer_id's in the response
			url += "&no_peer_id=1";
		}

		m_tracker_connection.reset(new http_connection(m_ios, m_cc
			, boost::bind(&http_tracker_connection::on_response, self(), _1, _2, _3, _4)));

		int timeout = tracker_req().event==tracker_request::stopped
			?m_settings.stop_tracker_timeout
			:m_settings.tracker_completion_timeout;

		m_tracker_connection->get(url, seconds(timeout)
			, 1, &m_ps, 5, m_settings.user_agent, m_bind_iface);

#if defined(TORRENT_VERBOSE_LOGGING) || defined(TORRENT_LOGGING)

		boost::shared_ptr<request_callback> cb = requester();
		if (cb)
		{
			cb->debug_log("==> TRACKER_REQUEST [ url: " + url + " ]");
		}
#endif
	}
	void http_tracker_connection::parse(int status_code, entry const& e)
	{
		boost::shared_ptr<request_callback> cb = requester();
		if (!cb) return;

		// parse the response
		entry const* failure = e.find_key("failure reason");
		if (failure && failure->type() == entry::string_t)
		{
			fail(status_code, failure->string().c_str());
			return;
		}

		entry const* warning = e.find_key("warning message");
		if (warning && warning->type() == entry::string_t)
		{
			cb->tracker_warning(tracker_req(), warning->string());
		}

		std::vector<peer_entry> peer_list;

		if (tracker_req().kind == tracker_request::scrape_request)
		{
			std::string ih = tracker_req().info_hash.to_string();

			entry const* files = e.find_key("files");
			if (files == 0 || files->type() != entry::dictionary_t)
			{
				fail(-1, "invalid or missing 'files' entry in scrape response");
				return;
			}

			entry const* scrape_data = files->find_key(ih);
			if (scrape_data == 0 || scrape_data->type() != entry::dictionary_t)
			{
				fail(-1, "missing or invalid info-hash entry in scrape response");
				return;
			}
			entry const* complete = scrape_data->find_key("complete");
			entry const* incomplete = scrape_data->find_key("incomplete");
			entry const* downloaded = scrape_data->find_key("downloaded");
			if (complete == 0 || incomplete == 0 || downloaded == 0
				|| complete->type() != entry::int_t
				|| incomplete->type() != entry::int_t
				|| downloaded->type() != entry::int_t)
			{
				fail(-1, "missing 'complete' or 'incomplete' entries in scrape response");
				return;
			}
			cb->tracker_scrape_response(tracker_req(), int(complete->integer())
				, int(incomplete->integer()), int(downloaded->integer()));
			return;
		}

		entry const* interval = e.find_key("interval");
		if (interval == 0 || interval->type() != entry::int_t)
		{
			fail(-1, "missing or invalid 'interval' entry in tracker response");
			return;
		}

		entry const* peers_ent = e.find_key("peers");
		if (peers_ent && peers_ent->type() == entry::string_t)
		{
			std::string const& peers = peers_ent->string();
			for (std::string::const_iterator i = peers.begin();
				i != peers.end();)
			{
				if (std::distance(i, peers.end()) < 6) break;

				peer_entry p;
				p.pid.clear();
				error_code ec;
				p.ip = detail::read_v4_address(i).to_string(ec);
				if (ec) continue;
				p.port = detail::read_uint16(i);
				peer_list.push_back(p);
			}
		}
		else if (peers_ent && peers_ent->type() == entry::list_t)
		{
			entry::list_type const& l = peers_ent->list();
			for(entry::list_type::const_iterator i = l.begin(); i != l.end(); ++i)
			{
				peer_entry p;
				if (!extract_peer_info(*i, p)) return;
				peer_list.push_back(p);
			}
		}
		else
		{
			peers_ent = 0;
		}

		entry const* ipv6_peers = e.find_key("peers6");
		if (ipv6_peers && ipv6_peers->type() == entry::string_t)
		{
			std::string const& peers = ipv6_peers->string();
			for (std::string::const_iterator i = peers.begin();
				i != peers.end();)
			{
				if (std::distance(i, peers.end()) < 18) break;

				peer_entry p;
				p.pid.clear();
				error_code ec;
				p.ip = detail::read_v6_address(i).to_string(ec);
				if (ec) continue;
				p.port = detail::read_uint16(i);
				peer_list.push_back(p);
			}
		}
		else
		{
			ipv6_peers = 0;
		}

		if (peers_ent == 0 && ipv6_peers == 0)
		{
			fail(-1, "missing 'peers' and 'peers6' entry in tracker response");
			return;
		}


		// look for optional scrape info
		int complete = -1;
		int incomplete = -1;
		address external_ip;

		entry const* ip_ent = e.find_key("external ip");
		if (ip_ent && ip_ent->type() == entry::string_t)
		{
			std::string const& ip = ip_ent->string();
			char const* p = &ip[0];
			if (ip.size() == address_v4::bytes_type::static_size)
				external_ip = detail::read_v4_address(p);
			else if (ip.size() == address_v6::bytes_type::static_size)
				external_ip = detail::read_v6_address(p);
		}
		
		entry const* complete_ent = e.find_key("complete");
		if (complete_ent && complete_ent->type() == entry::int_t)
			complete = int(complete_ent->integer());

		entry const* incomplete_ent = e.find_key("incomplete");
		if (incomplete_ent && incomplete_ent->type() == entry::int_t)
			incomplete = int(incomplete_ent->integer());

		cb->tracker_response(tracker_req(), peer_list, interval->integer(), complete
			, incomplete, external_ip);
	}
void http_tracker_connection::start()
{
    std::string url = tracker_req().url;

    if (0 != (tracker_req().kind & tracker_request::scrape_request))
    {
        // find and replace "announce" with "scrape"
        // in request

        std::size_t pos = url.find("announce");
        if (pos == std::string::npos)
        {
            tracker_connection::fail(error_code(errors::scrape_not_available));
            return;
        }
        url.replace(pos, 8, "scrape");
    }

#if TORRENT_USE_I2P
    bool i2p = is_i2p_url(url);
#else
    static const bool i2p = false;
#endif

    aux::session_settings const& settings = m_man.settings();

    // if request-string already contains
    // some parameters, append an ampersand instead
    // of a question mark
    size_t arguments_start = url.find('?');
    if (arguments_start != std::string::npos)
        url += "&";
    else
        url += "?";

    url += "info_hash=";
    url += escape_string(tracker_req().info_hash.data(), 20);

    if (0 == (tracker_req().kind & tracker_request::scrape_request))
    {
        const char* event_string[] = {"completed", "started", "stopped", "paused"};

        char str[1024];
        const bool stats = tracker_req().send_stats;
        snprintf(str, sizeof(str)
                 , "&peer_id=%s"
                 "&port=%d"
                 "&uploaded=%" PRId64
                 "&downloaded=%" PRId64
                 "&left=%" PRId64
                 "&corrupt=%" PRId64
                 "&key=%08X"
                 "%s%s" // event
                 "&numwant=%d"
                 "&compact=1"
                 "&no_peer_id=1"
                 , escape_string(tracker_req().pid.data(), 20).c_str()
                 // the i2p tracker seems to verify that the port is not 0,
                 // even though it ignores it otherwise
                 , i2p ? 1 : tracker_req().listen_port
                 , stats ? tracker_req().uploaded : 0
                 , stats ? tracker_req().downloaded : 0
                 , stats ? tracker_req().left : 0
                 , stats ? tracker_req().corrupt : 0
                 , tracker_req().key
                 , (tracker_req().event != tracker_request::none) ? "&event=" : ""
                 , (tracker_req().event != tracker_request::none) ? event_string[tracker_req().event - 1] : ""
                 , tracker_req().num_want);
        url += str;
#if !defined(TORRENT_DISABLE_ENCRYPTION) && !defined(TORRENT_DISABLE_EXTENSIONS)
        if (settings.get_int(settings_pack::in_enc_policy) != settings_pack::pe_disabled
                && settings.get_bool(settings_pack::announce_crypto_support))
            url += "&supportcrypto=1";
#endif
        if (stats && settings.get_bool(settings_pack::report_redundant_bytes))
        {
            url += "&redundant=";
            url += to_string(tracker_req().redundant).elems;
        }
        if (!tracker_req().trackerid.empty())
        {
            std::string id = tracker_req().trackerid;
            url += "&trackerid=";
            url += escape_string(id.c_str(), id.length());
        }

#if TORRENT_USE_I2P
        if (i2p && tracker_req().i2pconn)
        {
            if (tracker_req().i2pconn->local_endpoint().empty())
            {
                fail(error_code(errors::no_i2p_endpoint), -1, "Waiting for i2p acceptor from SAM bridge", 5);
                return;
            }
            else
            {
                url += "&ip=" + tracker_req ().i2pconn->local_endpoint () + ".i2p";
            }
        }
        else
#endif
            if (!settings.get_bool(settings_pack::anonymous_mode))
            {
                std::string announce_ip = settings.get_str(settings_pack::announce_ip);
                if (!announce_ip.empty())
                {
                    url += "&ip=" + escape_string(announce_ip.c_str(), announce_ip.size());
                }
// TODO: support this somehow
                /*				else if (settings.get_bool(settings_pack::announce_double_nat)
                					&& is_local(m_ses.listen_address()))
                				{
                					// only use the global external listen address here
                					// if it turned out to be on a local network
                					// since otherwise the tracker should use our
                					// source IP to determine our origin
                					url += "&ip=" + print_address(m_ses.listen_address());
                				}
                */
            }
    }

    m_tracker_connection.reset(new http_connection(get_io_service(), m_man.host_resolver()
                               , boost::bind(&http_tracker_connection::on_response, shared_from_this(), _1, _2, _3, _4)
                               , true, settings.get_int(settings_pack::max_http_recv_buffer_size)
                               , boost::bind(&http_tracker_connection::on_connect, shared_from_this(), _1)
                               , boost::bind(&http_tracker_connection::on_filter, shared_from_this(), _1, _2)
#ifdef TORRENT_USE_OPENSSL
                               , tracker_req().ssl_ctx
#endif
                                                  ));

    int timeout = tracker_req().event==tracker_request::stopped
                  ?settings.get_int(settings_pack::stop_tracker_timeout)
                  :settings.get_int(settings_pack::tracker_completion_timeout);

    // when sending stopped requests, prefer the cached DNS entry
    // to avoid being blocked for slow or failing responses. Chances
    // are that we're shutting down, and this should be a best-effort
    // attempt. It's not worth stalling shutdown.
    aux::proxy_settings ps(settings);
    m_tracker_connection->get(url, seconds(timeout)
                              , tracker_req().event == tracker_request::stopped ? 2 : 1
                              , ps.proxy_tracker_connections ? &ps : NULL
                              , 5, settings.get_bool(settings_pack::anonymous_mode)
                              ? "" : settings.get_str(settings_pack::user_agent)
                              , bind_interface()
                              , tracker_req().event == tracker_request::stopped
                              ? resolver_interface::prefer_cache
                              : resolver_interface::abort_on_shutdown
                              , tracker_req().auth
#if TORRENT_USE_I2P
                              , tracker_req().i2pconn
#endif
                             );

    // the url + 100 estimated header size
    sent_bytes(url.size() + 100);

#ifndef TORRENT_DISABLE_LOGGING

    boost::shared_ptr<request_callback> cb = requester();
    if (cb)
    {
        cb->debug_log("==> TRACKER_REQUEST [ url: %s ]", url.c_str());
    }
#endif
}
void http_tracker_connection::on_response(error_code const& ec
        , http_parser const& parser, char const* data, int size)
{
    // keep this alive
    boost::shared_ptr<http_tracker_connection> me(shared_from_this());

    if (ec && ec != boost::asio::error::eof)
    {
        fail(ec);
        return;
    }

    if (!parser.header_finished())
    {
        fail(boost::asio::error::eof);
        return;
    }

    if (parser.status_code() != 200)
    {
        fail(error_code(parser.status_code(), get_http_category())
             , parser.status_code(), parser.message().c_str());
        return;
    }

    if (ec && ec != boost::asio::error::eof)
    {
        fail(ec, parser.status_code());
        return;
    }

    received_bytes(size + parser.body_start());

    // handle tracker response
    error_code ecode;

    boost::shared_ptr<request_callback> cb = requester();
    if (!cb)
    {
        close();
        return;
    }

    tracker_response resp = parse_tracker_response(data, size, ecode
                            , tracker_req().kind, tracker_req().info_hash);

    if (!resp.warning_message.empty())
        cb->tracker_warning(tracker_req(), resp.warning_message);

    if (ecode)
    {
        fail(ecode, parser.status_code(), resp.failure_reason.c_str()
             , resp.interval, resp.min_interval);
        close();
        return;
    }

    // do slightly different things for scrape requests
    if (0 != (tracker_req().kind & tracker_request::scrape_request))
    {
        cb->tracker_scrape_response(tracker_req(), resp.complete
                                    , resp.incomplete, resp.downloaded, resp.downloaders);
    }
    else
    {
        std::list<address> ip_list;
        if (m_tracker_connection)
        {
            error_code ignore;
            std::vector<tcp::endpoint> const& epts = m_tracker_connection->endpoints();
            for (std::vector<tcp::endpoint>::const_iterator i = epts.begin()
                    , end(epts.end()); i != end; ++i)
            {
                ip_list.push_back(i->address());
            }
        }

        cb->tracker_response(tracker_req(), m_tracker_ip, ip_list, resp);
    }
    close();
}