diff --git a/include/motis/config.h b/include/motis/config.h index 02564ff351..bcdd3d4f2b 100644 --- a/include/motis/config.h +++ b/include/motis/config.h @@ -209,6 +209,7 @@ struct config { unsigned http_timeout_{30}; unsigned cache_size_{50}; std::optional proxy_{}; + std::optional use_connect_{false}; std::optional ttl_{}; }; std::optional gbfs_{}; diff --git a/include/motis/http_req.h b/include/motis/http_req.h index b74c1af32e..a12fa47c41 100644 --- a/include/motis/http_req.h +++ b/include/motis/http_req.h @@ -5,19 +5,27 @@ #include #include "boost/asio/awaitable.hpp" +#include "boost/beast/core/flat_buffer.hpp" #include "boost/beast/http/dynamic_body.hpp" -#include "boost/beast/http/message.hpp" +#include "boost/beast/http/empty_body.hpp" +#include "boost/beast/http/read.hpp" +#include "boost/beast/http/write.hpp" #include "boost/url/url.hpp" +#include "utl/verify.h" + namespace motis { -constexpr auto const kBodySizeLimit = 512U * 1024U * 1024U; // 512 M +namespace beast = boost::beast; +namespace http = beast::http; -using http_response = - boost::beast::http::response; +using http_response = http::response; + +constexpr auto const kBodySizeLimit = 512U * 1024U * 1024U; // 512 M struct proxy { bool use_tls_; + bool use_connect_; std::string host_, port_; }; @@ -34,6 +42,32 @@ boost::asio::awaitable http_POST( std::chrono::seconds timeout, std::optional const& = std::nullopt); +template +boost::asio::awaitable http_CONNECT(Stream& stream, + boost::urls::url const& url, + std::optional const& proxy) { + if (!proxy) { + co_return; + } + auto const target = std::string(url.host()) + ":" + + (url.has_port() ? std::string(url.port()) : "443"); + + http::request req{http::verb::connect, target, 11}; + req.set(http::field::host, target); + beast::flat_buffer buf; + + co_await http::async_write(beast::get_lowest_layer(stream), req); + + http::response_parser res; + res.skip(true); + co_await http::async_read_header(beast::get_lowest_layer(stream), buf, res); + + if (res.get().result() != http::status::ok) { + throw utl::fail("CONNECT failed: target={}, status={}", target, + res.get().result_int()); + } +} + std::string get_http_body(http_response const&); } // namespace motis diff --git a/src/gbfs/update.cc b/src/gbfs/update.cc index 6073fe845c..3f65a67edf 100644 --- a/src/gbfs/update.cc +++ b/src/gbfs/update.cc @@ -165,12 +165,13 @@ struct gbfs_update { d_{d}, prev_d_{prev_d}, timeout_{c.http_timeout_}, - proxy_{c.proxy_.transform([](std::string const& u) { + proxy_{c.proxy_.transform([&](std::string const& u) { auto const url = boost::urls::url{u}; auto p = proxy{}; p.use_tls_ = url.scheme_id() == boost::urls::scheme::https; p.host_ = url.host(); + p.use_connect_ = c.use_connect_.value_or(false); p.port_ = url.has_port() ? url.port() : (p.use_tls_ ? "443" : "80"); return p; })} {} diff --git a/src/http_req.cc b/src/http_req.cc index c02f38d24e..9b52e77696 100644 --- a/src/http_req.cc +++ b/src/http_req.cc @@ -71,22 +71,27 @@ asio::awaitable req_tls( auto resolver = asio::ip::tcp::resolver{executor}; auto stream = ssl::stream{executor, ssl_ctx}; - auto const host = proxy ? proxy->host_ : url.host(); - auto const port = - proxy ? proxy->port_ : std::string{url.has_port() ? url.port() : "443"}; - - if (!SSL_set_tlsext_host_name(stream.native_handle(), - const_cast(host.c_str()))) { - throw boost::system::system_error{ - {static_cast(::ERR_get_error()), asio::error::get_ssl_category()}}; - } + auto const target_port = std::string{url.has_port() ? url.port() : "443"}; + auto const target_host = url.host(); auto const results = co_await resolver.async_resolve( - host, port, asio::cancel_after(timeout, asio::use_awaitable)); + proxy ? proxy->host_ : target_host, proxy ? proxy->port_ : target_port, + asio::cancel_after(timeout, asio::use_awaitable)); stream.next_layer().expires_after(timeout); co_await beast::get_lowest_layer(stream).async_connect(results); + + if (proxy && proxy->use_connect_) { + co_await http_CONNECT(stream, url, proxy); + } + + if (!SSL_set_tlsext_host_name(stream.native_handle(), + const_cast(target_host.c_str()))) { + throw boost::system::system_error{ + {static_cast(::ERR_get_error()), asio::error::get_ssl_category()}}; + } + co_await stream.async_handshake(ssl::stream_base::client); co_return co_await req(std::move(stream), url, headers, body); } @@ -134,9 +139,7 @@ asio::awaitable> http_GET( auto n_redirects = 0U; auto next_url = url; while (n_redirects < 3U) { - auto const use_tls = - proxy.has_value() ? proxy->use_tls_ - : next_url.scheme_id() == boost::urls::scheme::https; + auto const use_tls = next_url.scheme_id() == boost::urls::scheme::https; auto const res = co_await ( use_tls ? req_tls(next_url, headers, std::nullopt, timeout, proxy) : req_no_tls(next_url, headers, std::nullopt, timeout, proxy)); @@ -162,9 +165,7 @@ asio::awaitable> http_POST( auto n_redirects = 0U; auto next_url = url; while (n_redirects < 3U) { - auto const use_tls = - proxy.has_value() ? proxy->use_tls_ - : next_url.scheme_id() == boost::urls::scheme::https; + auto const use_tls = next_url.scheme_id() == boost::urls::scheme::https; auto const res = co_await ( use_tls ? req_tls(next_url, headers, body, timeout, proxy) : req_no_tls(next_url, headers, body, timeout, proxy));