Line data Source code
1 : #ifndef THINGER_HTTP_CLIENT_PROXY_HPP
2 : #define THINGER_HTTP_CLIENT_PROXY_HPP
3 :
4 : #include <boost/asio/ip/address.hpp>
5 : #include <cstdint>
6 : #include <functional>
7 : #include <memory>
8 : #include <optional>
9 : #include <string>
10 : #include <string_view>
11 : #include <vector>
12 :
13 : namespace thinger::http {
14 :
15 : /**
16 : * Outbound proxy settings of a client or of a request (see http_client_base::proxy()).
17 : *
18 : * Plain http:// requests are sent to the proxy with an absolute-form target (RFC 9112,
19 : * section 3.2.2); https://, ws:// and wss:// ones go through a tunnel opened with CONNECT
20 : * (RFC 9110, section 9.3.6), with TLS end to end to the origin. Hosts in `no_proxy`, and
21 : * localhost, loopback addresses and Unix sockets always, are reached directly.
22 : */
23 : struct proxy_config {
24 : /// Proxy for http:// and ws:// requests, and for https:// and wss:// ones:
25 : /// "http://[user:password@]host[:port]" (the scheme is optional, the port 80 by default,
26 : /// user and password percent-encoded). Empty to connect directly. Only HTTP proxies are
27 : /// supported: an https:// (TLS to the proxy) or socks URL fails the requests using it.
28 : std::string http;
29 : std::string https;
30 :
31 : /// Hosts reached directly, comma separated (case-insensitive, spaces trimmed):
32 : /// - "example.com" or ".example.com": that domain and its subdomains ("*.example.com" too);
33 : /// - an IP address ("10.1.2.3", "::1" or "[::1]") or a CIDR range ("10.0.0.0/8");
34 : /// - any of them with a port ("example.com:8080", "[::1]:8080"): only for that port;
35 : /// - "*": every host.
36 : std::string no_proxy;
37 :
38 : /// Read an environment variable: its value, or nothing if it is not set
39 : using env_reader = std::function<std::optional<std::string>(const char* name)>;
40 :
41 : /**
42 : * Settings from the environment, as curl reads them (the first variable set and not
43 : * empty wins):
44 : * - http: http_proxy (lowercase only: HTTP_PROXY is ignored, as in a CGI program it is
45 : * the Proxy header of the request it serves: "httpoxy", CVE-2016-5385);
46 : * - https: https_proxy, HTTPS_PROXY;
47 : * - both, when not set above: all_proxy, ALL_PROXY;
48 : * - no_proxy, NO_PROXY.
49 : */
50 : static proxy_config from_environment();
51 : static proxy_config from_environment(const env_reader& getenv);
52 : };
53 :
54 : /// An HTTP proxy, from its URL (see proxy_config)
55 : struct proxy_endpoint {
56 : std::string host; // lowercase, an IPv6 address without brackets
57 : std::string port;
58 : std::string authorization; // Proxy-Authorization value ("Basic ..."), empty without credentials
59 :
60 : /// The proxy of a URL; nothing (and why in `error`) if the URL is invalid or not of an
61 : /// HTTP proxy
62 : static std::optional<proxy_endpoint> parse(std::string_view url, std::string* error = nullptr);
63 :
64 : /// "http://host:port", without the credentials (for logs and proxy_for())
65 : std::string url() const;
66 :
67 : /// Identifies the proxy and its credentials (connections through it are only reused by
68 : /// requests with the same one)
69 : std::string key() const;
70 : };
71 :
72 : /// Hosts reached without a proxy, from a NO_PROXY list (see proxy_config::no_proxy)
73 : class no_proxy_list {
74 : public:
75 3 : no_proxy_list() = default;
76 : explicit no_proxy_list(std::string_view list);
77 :
78 : /// Whether a request to host:port goes directly
79 : bool matches(std::string_view host, std::string_view port) const;
80 :
81 6 : bool empty() const { return !all_ && entries_.empty(); }
82 :
83 : private:
84 : struct entry {
85 : std::string domain; // a host name, without leading dot
86 : std::optional<boost::asio::ip::address> ip; // an address or the start of a range
87 : unsigned prefix = 0; // range bits (ip only)
88 : bool range = false;
89 : std::string port; // empty for any port
90 : };
91 : // An entry of the list (lowercase); nothing if it cannot be parsed
92 : static std::optional<entry> parse_entry(std::string_view token);
93 :
94 : bool all_ = false;
95 : std::vector<entry> entries_;
96 : };
97 :
98 : /// Whether a host is never proxied: localhost (and *.localhost) or a loopback address
99 : bool is_loopback_host(std::string_view host);
100 :
101 : /// Proxy settings parsed once, to choose the proxy of each request
102 : class proxy_resolver {
103 : public:
104 : /// What to do with a request
105 : struct choice {
106 : std::shared_ptr<const proxy_endpoint> proxy; // null to connect directly
107 : std::string error; // the configured proxy cannot be used
108 : };
109 :
110 3 : proxy_resolver() = default;
111 : explicit proxy_resolver(const proxy_config& config);
112 :
113 : /// The proxy of a request to host:port, over TLS (https, wss) or not
114 : choice select(bool secure, std::string_view host, std::string_view port) const;
115 :
116 : private:
117 : struct proxy {
118 : std::shared_ptr<const proxy_endpoint> endpoint;
119 : std::string error;
120 : bool configured = false;
121 : };
122 :
123 : static proxy parse(const std::string& url);
124 :
125 : proxy http_;
126 : proxy https_;
127 : no_proxy_list no_proxy_;
128 : };
129 :
130 : }
131 :
132 : #endif
|