LCOV - code coverage report
Current view: top level - http/server/reverse_proxy - proxy_target.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 58 58
Test Date: 2026-11-10 10:38:07 Functions: 100.0 % 9 9

            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
        

Generated by: LCOV version 2.0-1