// Copyright 2020 Daniel Parker // Distributed under the Boost license, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // See https://github.com/danielaparker/jsoncons for latest version #ifndef JSONCONS_URI_HPP #define JSONCONS_URI_HPP #include // std::string #include #include #include #include namespace jsoncons { class uri { using part_type = std::pair; std::string uri_; part_type scheme_; part_type userinfo_; part_type host_; part_type port_; part_type path_; part_type query_; part_type fragment_; public: uri() = default; uri(const std::string& uri) { *this = parse(uri); } uri(jsoncons::string_view scheme, jsoncons::string_view userinfo, jsoncons::string_view host, jsoncons::string_view port, jsoncons::string_view path, jsoncons::string_view query, jsoncons::string_view fragment) { if (!scheme.empty()) { uri_.append(std::string(scheme)); scheme_.second = uri_.length(); } if (!userinfo.empty() || !host.empty() || !port.empty()) { if (!scheme.empty()) { uri_.append("://"); } if (!userinfo.empty()) { userinfo_.first = uri_.length(); uri_.append(std::string(userinfo)); userinfo_.second = uri_.length(); uri_.append("@"); } else { userinfo_.first = userinfo_.second = uri_.length(); } if (!host.empty()) { host_.first = uri_.length(); uri_.append(std::string(host)); host_.second = uri_.length(); } else { JSONCONS_THROW(json_runtime_error("uri error.")); } if (!port.empty()) { uri_.append(":"); port_.first = uri_.length(); uri_.append(std::string(port)); port_.second = uri_.length(); } else { port_.first = port_.second = uri_.length(); } } else { userinfo_.first = userinfo_.second = uri_.length(); host_.first = host_.second = uri_.length(); port_.first = port_.second = uri_.length(); if (!scheme.empty()) { if (!path.empty() || !query.empty() || !fragment.empty()) { uri_.append(":"); } else { JSONCONS_THROW(json_runtime_error("uri error.")); } } } if (!path.empty()) { // if the URI is not opaque and the path is not already prefixed // with a '/', add one. path_.first = uri_.length(); if (!host.empty() && (path.front() != '/')) { uri_.push_back('/'); } uri_.append(std::string(path)); path_.second = uri_.length(); } else { path_.first = path_.second = uri_.length(); } if (!query.empty()) { uri_.append("?"); query_.first = uri_.length(); uri_.append(std::string(query)); query_.second = uri_.length(); } else { query_.first = query_.second = uri_.length(); } if (!fragment.empty()) { uri_.append("#"); fragment_.first = uri_.length(); uri_.append(std::string(fragment)); fragment_.second = uri_.length(); } else { fragment_.first = fragment_.second = uri_.length(); } } const std::string& string() const { return uri_; } bool is_absolute() const noexcept { return scheme_.first != scheme_.second; } bool is_opaque() const noexcept { return is_absolute() && !authority().empty(); } string_view base() const noexcept { return string_view(uri_.data()+scheme_.first,(path_.second-scheme_.first)); } string_view scheme() const noexcept { return string_view(uri_.data()+scheme_.first,(scheme_.second-scheme_.first)); } string_view userinfo() const noexcept { return string_view(uri_.data()+userinfo_.first,(userinfo_.second-userinfo_.first)); } string_view host() const noexcept { return string_view(uri_.data()+host_.first,(host_.second-host_.first)); } string_view port() const noexcept { return string_view(uri_.data()+port_.first,(port_.second-port_.first)); } string_view path() const noexcept { return string_view(uri_.data()+path_.first,(path_.second-path_.first)); } string_view query() const noexcept { return string_view(uri_.data()+query_.first,(query_.second-query_.first)); } string_view fragment() const noexcept { return string_view(uri_.data()+fragment_.first,(fragment_.second-fragment_.first)); } string_view authority() const noexcept { return string_view(uri_.data()+userinfo_.first,(port_.second-userinfo_.first)); } uri resolve(const uri& base) const { // This implementation uses the psuedo-code given in // http://tools.ietf.org/html/rfc3986#section-5.2.2 if (is_absolute() && !is_opaque()) { return *this; } if (is_opaque()) { return *this; } std::string userinfo, host, port, path, query, fragment; if (!authority().empty()) { // g -> http://g if (!this->userinfo().empty()) { userinfo = std::string(this->userinfo()); } if (!this->host().empty()) { host = std::string(this->host()); } if (!this->port().empty()) { port = std::string(this->port()); } if (!this->path().empty()) { path = remove_dot_segments(this->path()); } if (!this->query().empty()) { query = std::string(this->query()); } } else { if (this->path().empty()) { if (!base.path().empty()) { path = std::string(base.path()); } if (!this->query().empty()) { query = std::string(this->query()); } else if (!base.query().empty()) { query = std::string(base.query()); } } else { if (this->path().front() == '/') { path = remove_dot_segments(this->path()); } else { path = merge_paths(base, *this); } if (!this->query().empty()) { query = std::string(this->query()); } } if (!base.userinfo().empty()) { userinfo = std::string(base.userinfo()); } if (!base.host().empty()) { host = std::string(base.host()); } if (!base.port().empty()) { port = std::string(base.port()); } } if (!this->fragment().empty()) { fragment = std::string(this->fragment()); } return uri(std::string(base.scheme()), userinfo, host, port, path, query, fragment); } int compare(const uri& other) const { int result = scheme().compare(other.scheme()); if (result != 0) return result; result = userinfo().compare(other.userinfo()); if (result != 0) return result; result = host().compare(other.host()); if (result != 0) return result; result = port().compare(other.port()); if (result != 0) return result; result = path().compare(other.path()); if (result != 0) return result; result = query().compare(other.query()); if (result != 0) return result; result = fragment().compare(other.fragment()); return result; } friend bool operator==(const uri& lhs, const uri& rhs) { return lhs.compare(rhs) == 0; } friend bool operator!=(const uri& lhs, const uri& rhs) { return lhs.compare(rhs) != 0; } friend bool operator<(const uri& lhs, const uri& rhs) { return lhs.compare(rhs) < 0; } friend bool operator<=(const uri& lhs, const uri& rhs) { return lhs.compare(rhs) <= 0; } friend bool operator>(const uri& lhs, const uri& rhs) { return lhs.compare(rhs) > 0; } friend bool operator>=(const uri& lhs, const uri& rhs) { return lhs.compare(rhs) >= 0; } private: enum class parse_state {expect_scheme, expect_first_slash, expect_second_slash, expect_authority, expect_host_ipv6, expect_userinfo, expect_host, expect_port, expect_path, expect_query, expect_fragment}; uri(const std::string& uri, part_type scheme, part_type userinfo, part_type host, part_type port, part_type path, part_type query, part_type fragment) : uri_(uri), scheme_(scheme), userinfo_(userinfo), host_(host), port_(port), path_(path), query_(query), fragment_(fragment) { } static uri parse(const std::string& s) { part_type scheme; part_type userinfo; part_type host; part_type port; part_type path; part_type query; part_type fragment; std::size_t start = 0; parse_state state = parse_state::expect_scheme; for (std::size_t i = 0; i < s.size(); ++i) { char c = s[i]; switch (state) { case parse_state::expect_scheme: switch (c) { case ':': scheme = std::make_pair(start,i); state = parse_state::expect_first_slash; start = i; break; case '#': userinfo = std::make_pair(start,start); host = std::make_pair(start,start); port = std::make_pair(start,start); path = std::make_pair(start,i); query = std::make_pair(i,i); state = parse_state::expect_fragment; start = i+1; break; default: break; } break; case parse_state::expect_first_slash: switch (c) { case '/': state = parse_state::expect_second_slash; break; default: start = i; state = parse_state::expect_path; break; } break; case parse_state::expect_second_slash: switch (c) { case '/': state = parse_state::expect_authority; start = i+1; break; default: break; } break; case parse_state::expect_authority: switch (c) { case '[': state = parse_state::expect_host_ipv6; start = i+1; break; default: state = parse_state::expect_userinfo; start = i; --i; break; } break; case parse_state::expect_host_ipv6: switch (c) { case ']': userinfo = std::make_pair(start,start); host = std::make_pair(start,i); port = std::make_pair(i,i); state = parse_state::expect_path; start = i+1; break; default: break; } break; case parse_state::expect_userinfo: switch (c) { case '@': userinfo = std::make_pair(start,i); state = parse_state::expect_host; start = i+1; break; case ':': userinfo = std::make_pair(start,start); host = std::make_pair(start,i); state = parse_state::expect_port; start = i+1; break; case '/': userinfo = std::make_pair(start,start); host = std::make_pair(start,i); port = std::make_pair(i,i); state = parse_state::expect_path; start = i; break; default: break; } break; case parse_state::expect_host: switch (c) { case ':': host = std::make_pair(start,i); state = parse_state::expect_port; start = i+1; break; default: break; } break; case parse_state::expect_port: switch (c) { case '/': port = std::make_pair(start,i); state = parse_state::expect_path; start = i; break; default: break; } break; case parse_state::expect_path: switch (c) { case '?': path = std::make_pair(start,i); state = parse_state::expect_query; start = i+1; break; case '#': path = std::make_pair(start,i); query = std::make_pair(start,start); state = parse_state::expect_fragment; start = i+1; break; default: break; } break; case parse_state::expect_query: switch (c) { case '#': query = std::make_pair(start,i); state = parse_state::expect_fragment; start = i+1; break; default: break; } break; case parse_state::expect_fragment: break; } } switch (state) { case parse_state::expect_scheme: userinfo = std::make_pair(start,start); host = std::make_pair(start,start); port = std::make_pair(start,start); path = std::make_pair(start,s.size()); break; case parse_state::expect_userinfo: userinfo = std::make_pair(start,start); host = std::make_pair(start,start); port = std::make_pair(start,start); path = std::make_pair(start,s.size()); break; case parse_state::expect_path: path = std::make_pair(start,s.size()); break; case parse_state::expect_query: query = std::make_pair(start,s.size()); break; case parse_state::expect_fragment: fragment = std::make_pair(start,s.size()); break; default: JSONCONS_THROW(std::invalid_argument("Invalid uri")); break; } return uri(s, scheme, userinfo, host, port, path, query, fragment); } static std::string remove_dot_segments(const jsoncons::string_view& input) { std::string result = std::string(input); /* std::size_t pos = 0; while (pos < input.size()) { if (input.compare(0, 3, "../")) { network_boost::erase_head(input, 3); } else if (network_boost::starts_with(input, "./")) { network_boost::erase_head(input, 2); } else if (network_boost::starts_with(input, "/./")) { network_boost::replace_head(input, 3, "/"); } else if (input == "/.") { network_boost::replace_head(input, 2, "/"); } else if (network_boost::starts_with(input, "/../")) { network_boost::erase_head(input, 3); remove_last_segment(result); } else if (network_boost::starts_with(input, "/..")) { network_boost::replace_head(input, 3, "/"); remove_last_segment(result); } else if (network_boost::algorithm::all(input, [](char ch) { return ch == '.'; })) { input.clear(); } else { int n = (input.front() == '/')? 1 : 0; auto slash = network_boost::find_nth(input, "/", n); result.append(std::begin(input), std::begin(slash)); input.erase(std::begin(input), std::begin(slash)); } } */ return result; } static std::string merge_paths(const uri& base, const uri& relative) { std::string result; if (base.path().empty()) { result = "/"; } else { const auto& base_path = base.path(); auto last_slash = base_path.rfind('/'); result.append(std::string(base_path.substr(0,last_slash+1))); } if (!relative.path().empty()) { result.append(relative.path().begin(), relative.path().end()); } return remove_dot_segments(jsoncons::string_view(result)); } static void remove_last_segment(std::string& path) { auto last_slash = path.rfind('/'); if (last_slash != std::string::npos) { path.erase(last_slash); } } }; } // namespace jsoncons #endif