Line data Source code
1 : // Targets of the reverse proxy, and their URLs
2 : #include "proxy_exchange.hpp"
3 : #include "../../../util/ascii.hpp"
4 : #include <algorithm>
5 : #include <cctype>
6 : #include <charconv>
7 : #include <cstdint>
8 :
9 : namespace thinger::http {
10 :
11 : namespace {
12 :
13 : using proxy_detail::is_ip_address;
14 : using proxy_detail::is_request_target;
15 : namespace ascii = ::thinger::util::ascii;
16 :
17 : // Transport and default port of a URL scheme; false if it is not one of a target
18 504 : bool set_scheme(std::string_view scheme, proxy_target& target) {
19 504 : if (ascii::iequals(scheme, "http") || ascii::iequals(scheme, "ws")) {
20 498 : target.type = proxy_target::transport::tcp;
21 498 : target.port = 80;
22 6 : } else if (ascii::iequals(scheme, "https") || ascii::iequals(scheme, "wss")) {
23 3 : target.type = proxy_target::transport::tls;
24 3 : target.port = 443;
25 : } else {
26 3 : return false;
27 : }
28 501 : return true;
29 : }
30 :
31 : // A path to prepend to the forwarded ones (empty for none), without query nor fragment
32 495 : bool is_base_path(std::string_view path) {
33 495 : return path.empty() || (path.find('?') == std::string_view::npos && is_request_target(path));
34 : }
35 :
36 480 : bool is_host_name(std::string_view host) {
37 480 : return !host.empty() && std::all_of(host.begin(), host.end(), [](unsigned char c) {
38 4350 : return std::isalnum(c) || c == '.' || c == '-' || c == '_';
39 480 : });
40 : }
41 :
42 : // A port number, 1 to 65535
43 480 : bool parse_port(std::string_view text, uint16_t& port) {
44 480 : unsigned value = 0;
45 480 : auto [end, ec] = std::from_chars(text.data(), text.data() + text.size(), value);
46 480 : if (ec != std::errc{} || end != text.data() + text.size() || value == 0 || value > 65535) return false;
47 474 : port = static_cast<uint16_t>(value);
48 474 : return true;
49 : }
50 :
51 : // Host and port of an authority: "host[:port]" or "[IPv6 address][:port]"
52 492 : bool set_authority(std::string_view authority, proxy_target& target) {
53 492 : bool bracketed = authority.front() == '[';
54 492 : size_t host_end = bracketed ? authority.find(']') : authority.find(':');
55 492 : if (bracketed && host_end == std::string_view::npos) return false;
56 489 : auto host = bracketed ? authority.substr(1, host_end - 1) : authority.substr(0, host_end);
57 489 : size_t port_start = bracketed ? host_end + 1 : host_end;
58 489 : if (port_start < authority.size()) {
59 480 : if (authority[port_start] != ':' || !parse_port(authority.substr(port_start + 1), target.port)) return false;
60 : }
61 483 : target.host = std::string(host);
62 483 : return bracketed ? is_ip_address(target.host) : is_host_name(host);
63 : }
64 :
65 : }
66 :
67 507 : std::optional<proxy_target> proxy_target::from_url(std::string_view url) {
68 507 : auto scheme_end = url.find("://");
69 507 : proxy_target target;
70 507 : if (scheme_end == std::string_view::npos || !set_scheme(url.substr(0, scheme_end), target)) return std::nullopt;
71 :
72 501 : auto rest = url.substr(scheme_end + 3);
73 501 : auto path_start = rest.find('/');
74 501 : auto authority = rest.substr(0, path_start);
75 501 : auto base_path = path_start == std::string_view::npos ? std::string_view{} : rest.substr(path_start);
76 : // No user information, query nor fragment
77 993 : if (authority.empty() || authority.find_first_of("@?#") != std::string_view::npos || !is_base_path(base_path) ||
78 492 : !set_authority(authority, target)) {
79 21 : return std::nullopt;
80 : }
81 495 : while (base_path.ends_with('/')) base_path.remove_suffix(1);
82 480 : target.base_path = std::string(base_path);
83 480 : return target;
84 507 : }
85 :
86 99 : proxy_target proxy_target::unix_socket(std::string path, std::string base_path) {
87 99 : proxy_target target;
88 99 : target.type = transport::unix_socket;
89 99 : target.socket_path = std::move(path);
90 99 : target.base_path = std::move(base_path);
91 99 : return target;
92 : }
93 :
94 861 : std::string proxy_target::authority() const {
95 1113 : if (type == transport::unix_socket) return "localhost";
96 735 : auto name = host.find(':') != std::string::npos ? "[" + host + "]" : host;
97 735 : bool default_port = (type == transport::tls && port == 443) || (type == transport::tcp && port == 80);
98 1470 : return default_port ? name : name + ":" + std::to_string(port);
99 735 : }
100 :
101 : } // namespace thinger::http
|