9#include <botan/assert.h>
10#include <botan/exceptn.h>
11#include <botan/internal/charset.h>
12#include <botan/internal/parsing.h>
18std::optional<uint16_t> parse_port(std::string_view s) {
20 if(
const auto port =
parse_u16(s,
true)) {
29bool is_valid_percent_escape(
char c1,
char c2) {
30 auto is_hex_digit = [](
char c) {
31 return (c >=
'0' && c <=
'9') || (c >=
'a' && c <=
'f') || (c >=
'A' && c <=
'F');
34 if(!is_hex_digit(c1) || !is_hex_digit(c2)) {
39 if(c1 ==
'0' && c2 ==
'0') {
46bool validate_path_query_fragment(std::string_view tail) {
60 enum class State : uint8_t { Path, Query, Fragment };
61 State state = State::Path;
63 for(
size_t i = 0; i < tail.size(); ++i) {
64 const char c = tail[i];
66 if(i + 2 >= tail.size() || !is_valid_percent_escape(tail[i + 1], tail[i + 2])) {
74 if(state == State::Path) {
81 if(state == State::Fragment) {
84 state = State::Fragment;
87 if(!is_pchar_or_slash(c)) {
94bool validate_userinfo(std::string_view userinfo) {
97 for(
size_t i = 0; i < userinfo.size(); ++i) {
98 const char c = userinfo[i];
100 if(i + 2 >= userinfo.size() || !is_valid_percent_escape(userinfo[i + 1], userinfo[i + 2])) {
106 if(!is_valid_userinfo_char(c)) {
117 const bool other_has_authority = other.
raw_authority().has_value();
119 return std::tie(m_scheme, has_authority, m_authority, m_path, m_query, m_fragment) <=>
121 other.m_scheme, other_has_authority, other.m_authority, other.m_path, other.m_query, other.m_fragment);
126 m_authority == other.m_authority && m_path == other.m_path && m_query == other.m_query &&
127 m_fragment == other.m_fragment;
131 const auto colon = m_raw.find(
':');
134 const size_t rest_offset = colon + 1;
135 if(m_raw.size() < rest_offset + 2 || m_raw[rest_offset] !=
'/' || m_raw[rest_offset + 1] !=
'/') {
139 const size_t authority_start = rest_offset + 2;
140 const auto authority_end = m_raw.find_first_of(
"/?#", authority_start);
141 const size_t authority_len =
142 (authority_end == std::string::npos) ? std::string::npos : authority_end - authority_start;
143 return std::string_view(m_raw).substr(authority_start, authority_len);
156 return std::tie(m_userinfo, m_host, m_port) <=> std::tie(other.m_userinfo, other.m_host, other.m_port);
160 return m_userinfo == other.m_userinfo && m_host == other.m_host && m_port == other.m_port;
174 const auto is_ascii_alpha = [](
char c) ->
bool {
return (c >=
'a' && c <=
'z') || (c >=
'A' && c <=
'Z'); };
177 if(!is_ascii_alpha(raw.front())) {
183 while(i < raw.size() && is_scheme_cont_char(raw[i])) {
187 if(i >= raw.size() || raw[i] !=
':') {
194 auto rest = raw.substr(i + 1);
196 std::optional<Authority> parsed_authority;
197 std::string_view path_query_fragment;
199 if(rest.starts_with(
"//")) {
200 rest.remove_prefix(2);
204 const auto end = rest.find_first_of(
"/?#");
205 const auto authority = (end == std::string_view::npos) ? rest : rest.substr(0, end);
206 path_query_fragment = (end == std::string_view::npos) ? std::string_view{} : rest.substr(end);
211 if(!parsed_authority.has_value()) {
216 path_query_fragment = rest;
220 if(!validate_path_query_fragment(path_query_fragment)) {
227 const auto hash = path_query_fragment.find(
'#');
228 const auto pre_fragment =
229 (hash == std::string_view::npos) ? path_query_fragment : path_query_fragment.substr(0, hash);
230 std::optional<std::string>
fragment;
231 if(hash != std::string_view::npos) {
232 fragment = std::string(path_query_fragment.substr(hash + 1));
235 const auto qmark = pre_fragment.find(
'?');
236 const auto path = (qmark == std::string_view::npos) ? pre_fragment : pre_fragment.substr(0, qmark);
237 std::optional<std::string>
query;
238 if(qmark != std::string_view::npos) {
239 query = std::string(pre_fragment.substr(qmark + 1));
244 std::string(raw),
scheme, std::move(parsed_authority), std::string(
path), std::move(
query), std::move(
fragment));
265 std::optional<std::string>
userinfo;
266 const auto first_at = raw.find(
'@');
267 if(first_at != std::string_view::npos) {
268 if(raw.find(
'@', first_at + 1) != std::string_view::npos) {
271 const auto userinfo_view = raw.substr(0, first_at);
272 if(!validate_userinfo(userinfo_view)) {
275 userinfo = std::string(userinfo_view);
276 raw.remove_prefix(first_at + 1);
279 std::string_view host_view;
280 std::string_view port_str;
281 std::optional<Host>
host;
283 if(!raw.empty() && raw.front() ==
'[') {
285 const auto close = raw.find(
']');
286 if(close == std::string_view::npos) {
289 host_view = raw.substr(1, close - 1);
290 if(host_view.empty()) {
293 const auto after = raw.substr(close + 1);
295 if(after.front() !=
':') {
298 port_str = after.substr(1);
301 if(!ipv6.has_value()) {
307 const auto colon = raw.find(
':');
308 if(colon == std::string_view::npos) {
311 host_view = raw.substr(0, colon);
312 port_str = raw.substr(colon + 1);
315 if(port_str.find(
':') != std::string::npos) {
320 if(host_view.empty()) {
325 if(host_view.ends_with(
'.')) {
332 host = std::move(*dns);
338 std::optional<uint16_t>
port;
340 if(!port_str.empty()) {
341 port = parse_port(port_str);
342 if(!
port.has_value()) {
351 return std::visit([](
const auto& h) -> std::string {
return h.to_string(); }, m_host);
355 if(std::holds_alternative<DNSName>(m_host)) {
356 return HostKind::DNS;
357 }
else if(std::holds_alternative<IPv4Address>(m_host)) {
358 return HostKind::IPv4;
359 }
else if(std::holds_alternative<IPv6Address>(m_host)) {
360 return HostKind::IPv6;
368 std::vector<URI> results;
372 for(
const auto& uri : uris) {
373 if(uri.scheme() == normalized_scheme && uri.authority().has_value()) {
374 results.push_back(uri);
#define BOTAN_ASSERT_NOMSG(expr)
#define BOTAN_ASSERT_UNREACHABLE()
static constexpr CharacterValidityTable alpha_numeric_plus(std::string_view extras)
static std::optional< DNSName > from_string(std::string_view name)
static std::optional< IPv4Address > from_string(std::string_view str)
static std::optional< IPv6Address > from_string(std::string_view str)
std::strong_ordering operator<=>(const Authority &other) const
const std::optional< std::string > & userinfo() const
static std::optional< Authority > from_string(std::string_view raw)
const Host & host() const
bool operator==(const Authority &other) const
std::optional< uint16_t > port() const
HostKind host_kind() const
std::string host_to_string() const
const std::string & original_input() const
const std::optional< std::string > & fragment() const
static std::optional< URI > from_string(std::string_view raw)
const std::string & scheme() const
static std::vector< URI > filter_scheme(std::string_view scheme, std::span< const URI > uris)
std::strong_ordering operator<=>(const URI &other) const
const std::optional< std::string > & query() const
const std::string & original_input() const
const std::optional< Authority > & authority() const
std::optional< std::string_view > raw_authority() const
bool operator==(const URI &other) const
const std::string & path() const
std::string tolower_string(std::string_view str)
std::optional< uint16_t > parse_u16(std::string_view input, bool require_canonical)