aboutsummaryrefslogtreecommitdiff
path: root/include/jsoncons/uri.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/jsoncons/uri.hpp')
-rw-r--r--include/jsoncons/uri.hpp635
1 files changed, 635 insertions, 0 deletions
diff --git a/include/jsoncons/uri.hpp b/include/jsoncons/uri.hpp
new file mode 100644
index 0000000..51249ef
--- /dev/null
+++ b/include/jsoncons/uri.hpp
@@ -0,0 +1,635 @@
+// 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 <string> // std::string
+#include <algorithm>
+#include <sstream>
+#include <jsoncons/config/jsoncons_config.hpp>
+#include <jsoncons/json_exception.hpp>
+
+namespace jsoncons {
+
+ class uri
+ {
+ using part_type = std::pair<std::size_t,std::size_t>;
+
+ 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<std::invalid_argument>("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<std::invalid_argument>("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