diff --git a/source/mijin/net/url.hpp b/source/mijin/net/url.hpp new file mode 100644 index 0000000..37b57b8 --- /dev/null +++ b/source/mijin/net/url.hpp @@ -0,0 +1,169 @@ + + +#pragma once + +#if !defined(MIJIN_NET_URL_HPP_INCLUDED) +#define MIJIN_NET_URL_HPP_INCLUDED 1 + +#include +#include +#include "../util/string.hpp" + +namespace mijin +{ +template, typename TAllocator = std::allocator> +class URLBase +{ +public: + using string_t = std::basic_string; + using string_view_t = std::basic_string_view; +private: + string_t base_; + string_view_t scheme_; + string_view_t userinfo_; + string_view_t host_; + string_view_t path_; + string_view_t query_; + string_view_t fragment_; + string_view_t pathQueryFragment_; + std::uint16_t port_ = 0; +public: + constexpr URLBase() noexcept = default; + constexpr URLBase(const URLBase&) = default; + constexpr URLBase(URLBase&&) noexcept = default; + constexpr URLBase(string_t base) noexcept : base_(std::move(base)) { parse(); } + constexpr URLBase(string_view_t base) : URLBase(string_t(base.begin(), base.end())) {} + constexpr URLBase(const TChar* base) : URLBase(string_t(base)) {} + + constexpr URLBase& operator=(const URLBase&) = default; + constexpr URLBase& operator=(URLBase&&) noexcept = default; + + [[nodiscard]] constexpr bool isValid() const noexcept { return !base_.empty(); } + [[nodiscard]] constexpr string_view_t getScheme() const noexcept { return scheme_; } + [[nodiscard]] constexpr string_view_t getUserInfo() const noexcept { return userinfo_; } + [[nodiscard]] constexpr string_view_t getHost() const noexcept { return host_; } + [[nodiscard]] constexpr string_view_t getPath() const noexcept { return path_; } + [[nodiscard]] constexpr string_view_t getQuery() const noexcept { return query_; } + [[nodiscard]] constexpr string_view_t getFragment() const noexcept { return fragment_; } + [[nodiscard]] constexpr string_view_t getPathQueryFragment() const noexcept { return pathQueryFragment_; } + [[nodiscard]] constexpr std::uint16_t getPort() const noexcept { return port_; } + + + constexpr void clear() noexcept; +private: + constexpr void parse() noexcept; + constexpr bool parseAuthority(string_view_t authority) noexcept; +}; + +using URL = URLBase; + +template +constexpr void URLBase::clear() noexcept +{ + base_.clear(); + scheme_ = {}; + userinfo_ = {}; + host_ = {}; + path_ = {}; + query_ = {}; + fragment_ = {}; + port_ = 0; +} + +template +constexpr void URLBase::parse() noexcept +{ + if (base_.empty()) + { + return; + } + + string_view_t toParse = base_; + typename string_view_t::size_type pos = toParse.find(':'); + if (pos == string_t::npos) + { + clear(); + return; + } + scheme_ = toParse.substr(0, pos); + toParse = toParse.substr(pos + 1); + + if (!toParse.starts_with("//")) + { + userinfo_ = host_ = {}; + port_ = 0; + } + else + { + toParse = toParse.substr(2); // skip the slashes + pos = toParse.find('/'); + if (!parseAuthority(toParse.substr(0, pos))) + { + clear(); + return; + } + if (pos == string_view_t::npos) + { + path_ = query_ = fragment_ = pathQueryFragment_ = {}; + return; + } + toParse = toParse.substr(pos); + } + pathQueryFragment_ = toParse; + pos = toParse.find('#'); + if (pos == string_view_t::npos) + { + fragment_ = {}; + } + else + { + fragment_ = toParse.substr(pos + 1); + toParse = toParse.substr(0, pos); + } + pos = toParse.find('?'); + if (pos == string_view_t::npos) + { + query_ = {}; + path_ = toParse; + } + else + { + query_ = toParse.substr(pos + 1); + path_ = toParse.substr(0, pos); + } +} + +template +constexpr bool URLBase::parseAuthority(string_view_t authority) noexcept +{ + string_view_t toParse = authority; + typename string_view_t::size_type pos = toParse.find('@'); + if (pos == string_view_t::npos) + { + userinfo_ = {}; + } + else + { + userinfo_ = toParse.substr(0, pos); + toParse = toParse.substr(pos + 1); + } + pos = toParse.find(':'); // TODO: IPv6 + if (pos == string_view_t::npos) + { + port_ = 0; + host_ = toParse; + } + else + { + if (!toNumber(toParse.substr(pos + 1), port_)) + { + return false; + } + host_ = toParse.substr(0, pos); + } + return true; +} +} + + +#endif // !defined(MIJIN_NET_URL_HPP_INCLUDED)