/** * @file url-inl.h * @brief Definitions for the URL */ #ifndef ADA_URL_INL_H #define ADA_URL_INL_H #include "ada/checkers.h" #include "ada/url.h" #include "ada/url_components.h" #include #include #if ADA_REGULAR_VISUAL_STUDIO #include #endif // ADA_REGULAR_VISUAL_STUDIO namespace ada { [[nodiscard]] ada_really_inline bool url::has_credentials() const noexcept { return !username.empty() || !password.empty(); } [[nodiscard]] ada_really_inline bool url::has_port() const noexcept { return port.has_value(); } [[nodiscard]] inline bool url::cannot_have_credentials_or_port() const { return !host.has_value() || host.value().empty() || type == ada::scheme::type::FILE; } [[nodiscard]] inline bool url::has_empty_hostname() const noexcept { if (!host.has_value()) { return false; } return host.value().empty(); } [[nodiscard]] inline bool url::has_hostname() const noexcept { return host.has_value(); } inline std::ostream &operator<<(std::ostream &out, const ada::url &u) { return out << u.to_string(); } [[nodiscard]] size_t url::get_pathname_length() const noexcept { return path.size(); } [[nodiscard]] ada_really_inline ada::url_components url::get_components() const noexcept { url_components out{}; // protocol ends with ':'. for example: "https:" out.protocol_end = uint32_t(get_protocol().size()); // Trailing index is always the next character of the current one. size_t running_index = out.protocol_end; if (host.has_value()) { // 2 characters for "//" and 1 character for starting index out.host_start = out.protocol_end + 2; if (has_credentials()) { out.username_end = uint32_t(out.host_start + username.size()); out.host_start += uint32_t(username.size()); if (!password.empty()) { out.host_start += uint32_t(password.size() + 1); } out.host_end = uint32_t(out.host_start + host.value().size()); } else { out.username_end = out.host_start; // Host does not start with "@" if it does not include credentials. out.host_end = uint32_t(out.host_start + host.value().size()) - 1; } running_index = out.host_end + 1; } else { // Update host start and end date to the same index, since it does not // exist. out.host_start = out.protocol_end; out.host_end = out.host_start; if (!has_opaque_path && checkers::begins_with(path, "//")) { // If url's host is null, url does not have an opaque path, url's path's // size is greater than 1, and url's path[0] is the empty string, then // append U+002F (/) followed by U+002E (.) to output. running_index = out.protocol_end + 2; } else { running_index = out.protocol_end; } } if (port.has_value()) { out.port = *port; running_index += helpers::fast_digit_count(*port) + 1; // Port omits ':' } out.pathname_start = uint32_t(running_index); running_index += path.size(); if (query.has_value()) { out.search_start = uint32_t(running_index); running_index += get_search().size(); if (get_search().empty()) { running_index++; } } if (hash.has_value()) { out.hash_start = uint32_t(running_index); } return out; } inline void url::update_base_hostname(std::string_view input) { host = input; } inline void url::update_unencoded_base_hash(std::string_view input) { // We do the percent encoding hash = unicode::percent_encode(input, ada::character_sets::FRAGMENT_PERCENT_ENCODE); } inline void url::update_base_search(std::string_view input, const uint8_t query_percent_encode_set[]) { query = ada::unicode::percent_encode(input, query_percent_encode_set); } inline void url::update_base_search(std::optional input) { query = input; } inline void url::update_base_pathname(const std::string_view input) { path = input; } inline void url::update_base_username(const std::string_view input) { username = input; } inline void url::update_base_password(const std::string_view input) { password = input; } inline void url::update_base_port(std::optional input) { port = input; } inline void url::clear_pathname() { path.clear(); } inline void url::clear_search() { query = std::nullopt; } [[nodiscard]] inline bool url::has_hash() const noexcept { return hash.has_value(); } [[nodiscard]] inline bool url::has_search() const noexcept { return query.has_value(); } inline void url::set_protocol_as_file() { type = ada::scheme::type::FILE; } inline void url::set_scheme(std::string &&new_scheme) noexcept { type = ada::scheme::get_scheme_type(new_scheme); // We only move the 'scheme' if it is non-special. if (!is_special()) { non_special_scheme = std::move(new_scheme); } } inline void url::copy_scheme(ada::url &&u) noexcept { non_special_scheme = u.non_special_scheme; type = u.type; } inline void url::copy_scheme(const ada::url &u) { non_special_scheme = u.non_special_scheme; type = u.type; } [[nodiscard]] ada_really_inline std::string url::get_href() const noexcept { std::string output = get_protocol(); if (host.has_value()) { output += "//"; if (has_credentials()) { output += username; if (!password.empty()) { output += ":" + get_password(); } output += "@"; } output += host.value(); if (port.has_value()) { output += ":" + get_port(); } } else if (!has_opaque_path && checkers::begins_with(path, "//")) { // If url's host is null, url does not have an opaque path, url's path's // size is greater than 1, and url's path[0] is the empty string, then // append U+002F (/) followed by U+002E (.) to output. output += "/."; } output += path; if (query.has_value()) { output += "?" + query.value(); } if (hash.has_value()) { output += "#" + hash.value(); } return output; } ada_really_inline size_t url::parse_port(std::string_view view, bool check_trailing_content) noexcept { ada_log("parse_port('", view, "') ", view.size()); if (!view.empty() && view[0] == '-') { ada_log("parse_port: view[0] == '0' && view.size() > 1"); is_valid = false; return 0; } uint16_t parsed_port{}; auto r = std::from_chars(view.data(), view.data() + view.size(), parsed_port); if (r.ec == std::errc::result_out_of_range) { ada_log("parse_port: r.ec == std::errc::result_out_of_range"); is_valid = false; return 0; } ada_log("parse_port: ", parsed_port); const size_t consumed = size_t(r.ptr - view.data()); ada_log("parse_port: consumed ", consumed); if (check_trailing_content) { is_valid &= (consumed == view.size() || view[consumed] == '/' || view[consumed] == '?' || (is_special() && view[consumed] == '\\')); } ada_log("parse_port: is_valid = ", is_valid); if (is_valid) { // scheme_default_port can return 0, and we should allow 0 as a base port. auto default_port = scheme_default_port(); bool is_port_valid = (default_port == 0 && parsed_port == 0) || (default_port != parsed_port); port = (r.ec == std::errc() && is_port_valid) ? std::optional(parsed_port) : std::nullopt; } return consumed; } } // namespace ada #endif // ADA_URL_H