Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README-websocket.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ void set_address_family(int family);
void set_ipv6_v6only(bool on);
void set_socket_options(SocketOptions socket_options);
void set_interface(const std::string &intf);
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);

// SSL configuration (wss:// only, SSL builds only)
void set_ca_cert_path(const std::string &ca_cert_file_path,
Expand Down
40 changes: 32 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1474,31 +1474,55 @@ The same method is available on `httplib::ws::WebSocketClient`.

### Override the connection target for a hostname

`set_hostname_addr_map` redirects where the socket connects, without changing
the identity of the request. The hostname the client was constructed with keeps
supplying the `Host` header, the SNI, and the name that the server certificate
is verified against, so this is a connection-level override only, not a way to
talk to a different origin.
`set_direct_hostname_addr_map` redirects where the socket connects, without
changing the identity of the request. The hostname the client was constructed
with keeps supplying the `Host` header, the SNI, and the name that the server
certificate is verified against, so this is a connection-level override only,
not a way to talk to a different origin.

```cpp
httplib::Client cli("https://example.com");

// Connect to this IP address instead of resolving "example.com"
cli.set_hostname_addr_map({{"example.com", "192.168.1.10"}});
cli.set_direct_hostname_addr_map({{"example.com", "192.168.1.10"}});
```

A mapped value may be an IP literal or another hostname. An IP literal is used
as-is; anything else is resolved as a name, so a host that is only reachable
under a different name works too:

```cpp
cli.set_hostname_addr_map({{"example.com", "internal.example.lan"}});
cli.set_direct_hostname_addr_map({{"example.com", "internal.example.lan"}});
```

An empty value is ignored, leaving the original hostname as the connection
target.

The same method is available on `httplib::ws::WebSocketClient`.
The direct map applies only when the client connects to the origin itself.
Through a proxy, use `set_tunnel_hostname_addr_map`: its value becomes the
`CONNECT` target (and that request's `Host`) that the proxy is asked to connect
to (HTTPS and WebSocket). The `Host` header, the SNI and the certificate check
inside the tunnel keep the original hostname:

```cpp
httplib::Client cli("https://sni.example");

cli.set_tunnel_hostname_addr_map({{"sni.example", "server.example"}});
cli.set_proxy("proxy.example", 3128);
// CONNECT server.example:443, SNI sni.example
```

The two maps are independent; to use the same target on both paths, set both.
They correspond to curl's `--resolve` and `--connect-to`.

Neither map applies to the proxy's own address ("direct" means not through a
proxy, as in a PAC file's `DIRECT`). To connect to the proxy at a fixed
address, pass an IP literal to `set_proxy()`.

`set_hostname_addr_map` is deprecated and works the same as
`set_direct_hostname_addr_map`.

These methods are also available on `httplib::ws::WebSocketClient`.

### Automatic Path Encoding

Expand Down
2 changes: 1 addition & 1 deletion docs-src/pages/en/tour/09-whats-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ svr.listen("/tmp/httplib.sock", 80); // The port is unused, but must not be 0
// Client
httplib::Client cli("http://localhost");
cli.set_address_family(AF_UNIX);
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});
cli.set_direct_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});

auto res = cli.Get("/");
```
Expand Down
2 changes: 1 addition & 1 deletion docs-src/pages/ja/tour/09-whats-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ svr.listen("/tmp/httplib.sock", 80); // ポート番号は使われない(0
// クライアント
httplib::Client cli("http://localhost");
cli.set_address_family(AF_UNIX);
cli.set_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});
cli.set_direct_hostname_addr_map({{"localhost", "/tmp/httplib.sock"}});

auto res = cli.Get("/");
```
Expand Down
80 changes: 74 additions & 6 deletions httplib.h
Original file line number Diff line number Diff line change
Expand Up @@ -2921,7 +2921,12 @@ class ClientImpl {
size_t is_socket_open() const;
socket_t socket() const;

[[deprecated("Use set_direct_hostname_addr_map() instead.")]]
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);

void set_default_headers(Headers headers);

Expand Down Expand Up @@ -3053,6 +3058,9 @@ class ClientImpl {
// hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_;

// Hostname to CONNECT target map, used only through a proxy.
std::map<std::string, std::string> tunnel_addr_map_;

// Default headers
Headers default_headers_;

Expand Down Expand Up @@ -3342,7 +3350,12 @@ class Client {
size_t is_socket_open() const;
socket_t socket() const;

[[deprecated("Use set_direct_hostname_addr_map() instead.")]]
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);

void set_default_headers(Headers headers);

Expand Down Expand Up @@ -4738,7 +4751,12 @@ class WebSocketClient {
set_connection_timeout(const std::chrono::duration<Rep, Period> &duration);

void set_interface(const std::string &intf);
[[deprecated("Use set_direct_hostname_addr_map() instead.")]]
void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_direct_hostname_addr_map(std::map<std::string, std::string> addr_map);
void
set_tunnel_hostname_addr_map(std::map<std::string, std::string> addr_map);

void set_proxy(const std::string &host, int port);
void set_proxy_basic_auth(const std::string &username,
Expand Down Expand Up @@ -4810,6 +4828,9 @@ class WebSocketClient {
// hostname; only the connection target changes, never the identity.
std::map<std::string, std::string> addr_map_;

// Hostname to CONNECT target map, used only through a proxy.
std::map<std::string, std::string> tunnel_addr_map_;

std::string proxy_host_;
int proxy_port_ = -1;
std::string proxy_basic_auth_username_;
Expand Down Expand Up @@ -13013,6 +13034,17 @@ make_host_and_port_string_always_port(const std::string &host, int port) {
return prepare_host_string(host) + ":" + std::to_string(port);
}

// Request target of a CONNECT for `host`, with `addr_map` applied.
inline std::string
make_connect_request_target(const std::map<std::string, std::string> &addr_map,
const std::string &host, int port) {
std::string connect_host;
std::string ip;
apply_addr_map(addr_map, host, connect_host, ip);
return make_host_and_port_string_always_port(ip.empty() ? connect_host : ip,
port);
}

// Value for the Host header a client sends when the caller supplied none.
// Only the value: callers decide where in their header list it goes.
inline std::string make_default_host_header_value(const std::string &host,
Expand Down Expand Up @@ -16528,6 +16560,7 @@ inline void ClientImpl::setup_redirect_client(ClientType &client) {

// Copy network and socket settings
client.addr_map_ = addr_map_;
client.tunnel_addr_map_ = tunnel_addr_map_;
client.set_address_family(address_family_);
client.set_tcp_nodelay(tcp_nodelay_);
client.set_ipv6_v6only(ipv6_v6only_);
Expand Down Expand Up @@ -18041,9 +18074,19 @@ inline void ClientImpl::set_path_encode(bool on) { path_encode_ = on; }

inline void
ClientImpl::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
set_direct_hostname_addr_map(std::move(addr_map));
}

inline void ClientImpl::set_direct_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
addr_map_ = std::move(addr_map);
}

inline void ClientImpl::set_tunnel_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
tunnel_addr_map_ = std::move(addr_map);
}

inline void ClientImpl::set_default_headers(Headers headers) {
default_headers_ = std::move(headers);
}
Expand Down Expand Up @@ -18752,7 +18795,17 @@ inline socket_t Client::socket() const { return cli_->socket(); }

inline void
Client::set_hostname_addr_map(std::map<std::string, std::string> addr_map) {
cli_->set_hostname_addr_map(std::move(addr_map));
cli_->set_direct_hostname_addr_map(std::move(addr_map));
}

inline void Client::set_direct_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
cli_->set_direct_hostname_addr_map(std::move(addr_map));
}

inline void Client::set_tunnel_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
cli_->set_tunnel_hostname_addr_map(std::move(addr_map));
}

inline void Client::set_default_headers(Headers headers) {
Expand Down Expand Up @@ -19120,15 +19173,19 @@ inline bool SSLClient::connect_with_proxy(
std::chrono::time_point<std::chrono::steady_clock> start_time,
Response &res, bool &success, Error &error) {
success = true;
auto connect_target =
detail::make_connect_request_target(tunnel_addr_map_, host_, port_);
auto retargeted = connect_target !=
detail::make_host_and_port_string_always_port(host_, port_);
Response proxy_res;
if (!detail::process_client_socket(
socket.sock, read_timeout_sec_, read_timeout_usec_,
write_timeout_sec_, write_timeout_usec_, max_timeout_msec_,
start_time, [&](Stream &strm) {
Request req2;
req2.method = "CONNECT";
req2.path =
detail::make_host_and_port_string_always_port(host_, port_);
req2.path = connect_target;
if (retargeted) { req2.set_header("Host", connect_target); }
if (max_timeout_msec_ > 0) {
req2.start_time_ = std::chrono::steady_clock::now();
}
Expand Down Expand Up @@ -19167,8 +19224,8 @@ inline bool SSLClient::connect_with_proxy(
start_time, [&](Stream &strm) {
Request req3;
req3.method = "CONNECT";
req3.path = detail::make_host_and_port_string_always_port(
host_, port_);
req3.path = connect_target;
if (retargeted) { req3.set_header("Host", connect_target); }
req3.headers.insert(detail::make_digest_authentication_header(
req3, make_request_target(req3), auth, 1,
detail::random_string(10), proxy_digest_auth_username_,
Expand Down Expand Up @@ -23964,9 +24021,19 @@ inline void WebSocketClient::set_interface(const std::string &intf) {

inline void WebSocketClient::set_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
set_direct_hostname_addr_map(std::move(addr_map));
}

inline void WebSocketClient::set_direct_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
addr_map_ = std::move(addr_map);
}

inline void WebSocketClient::set_tunnel_hostname_addr_map(
std::map<std::string, std::string> addr_map) {
tunnel_addr_map_ = std::move(addr_map);
}

inline bool WebSocketClient::is_proxy_enabled() const {
if (proxy_host_.empty() || proxy_port_ == -1) { return false; }
if (no_proxy_entries_.empty()) { return true; }
Expand Down Expand Up @@ -24016,7 +24083,8 @@ inline bool WebSocketClient::connect_with_proxy(Response &proxy_res,
Error &error) {
Request req;
req.method = "CONNECT";
req.path = detail::make_host_and_port_string_always_port(host_, port_);
req.path =
detail::make_connect_request_target(tunnel_addr_map_, host_, port_);
req.headers.emplace("Host", req.path);
detail::add_default_user_agent_header(req);
detail::add_authorization_header(req, proxy_basic_auth_username_,
Expand Down
Loading
Loading