Commit c03dd9b0 authored by Dominik Charousset's avatar Dominik Charousset

Add percent encoding to URI

parent 1cada153
...@@ -105,6 +105,10 @@ void read_uri_query(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -105,6 +105,10 @@ void read_uri_query(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
fin(); fin();
} }
#define read_next_char(next_state) \
transition(next_state, unprotected_char, str += ch) \
fsm_transition(read_uri_percent_encoded(ps, str), next_state, '%')
template <class Iterator, class Sentinel, class Consumer> template <class Iterator, class Sentinel, class Consumer>
void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
// Local variables. // Local variables.
...@@ -122,8 +126,8 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -122,8 +126,8 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
auto path_char = [](char c) { auto path_char = [](char c) {
return in_whitelist(alphanumeric_chars, c) || c == '/'; return in_whitelist(alphanumeric_chars, c) || c == '/';
}; };
auto authority_char = [](char c) { auto unprotected_char = [](char c) {
return in_whitelist(alphanumeric_chars, c) || c == '.'; return in_whitelist(alphanumeric_chars, c) || in_whitelist("-._~", c);
}; };
// Utility setters for avoiding code duplication. // Utility setters for avoiding code duplication.
auto set_path = [&] { auto set_path = [&] {
...@@ -148,13 +152,12 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -148,13 +152,12 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
epsilon(read_scheme) epsilon(read_scheme)
} }
state(read_scheme) { state(read_scheme) {
fsm_transition(read_uri_percent_encoded(ps, str), read_scheme, '%') read_next_char(read_scheme)
transition(read_scheme, alphabetic_chars, str += ch)
transition(have_scheme, ':', consumer.scheme(take_str())) transition(have_scheme, ':', consumer.scheme(take_str()))
} }
state(have_scheme) { state(have_scheme) {
transition(disambiguate_path, '/') transition(disambiguate_path, '/')
transition(read_path, alphanumeric_chars, str += ch) read_next_char(read_path)
fsm_transition(read_uri_percent_encoded(ps, str), read_path, '%') fsm_transition(read_uri_percent_encoded(ps, str), read_path, '%')
} }
// This state is terminal, because "file:/" is a valid URI. // This state is terminal, because "file:/" is a valid URI.
...@@ -163,28 +166,32 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -163,28 +166,32 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
epsilon(read_path, any_char, str += '/') epsilon(read_path, any_char, str += '/')
} }
state(start_authority) { state(start_authority) {
transition(read_authority, authority_char, str += ch) read_next_char(read_authority)
fsm_transition(read_ipv6_address(ps, ip_consumer), end_of_ipv6_host, '[') fsm_transition(read_ipv6_address(ps, ip_consumer), await_end_of_ipv6, '[')
}
state(await_end_of_ipv6) {
transition(end_of_ipv6_host, ']')
} }
state(end_of_ipv6_host) { term_state(end_of_ipv6_host) {
transition(end_of_host, ']') transition(start_port, ':')
epsilon(end_of_authority)
} }
term_state(end_of_host) { term_state(end_of_host) {
transition(start_port, ':', set_host()) transition(start_port, ':', set_host())
epsilon(end_of_authority, "/?#", set_host()) epsilon(end_of_authority, "/?#", set_host())
} }
term_state(read_authority, set_host()) { term_state(read_authority, set_host()) {
transition(read_authority, authority_char, str += ch) read_next_char(read_authority)
transition(start_host, '@', set_userinfo()) transition(start_host, '@', set_userinfo())
transition(start_port, ':', set_host()) transition(start_port, ':', set_host())
epsilon(end_of_authority, "/?#", set_host()) epsilon(end_of_authority, "/?#", set_host())
} }
state(start_host) { state(start_host) {
transition(read_host, authority_char, str += ch) read_next_char(read_host)
fsm_transition(read_ipv6_address(ps, ip_consumer), end_of_ipv6_host, '[') fsm_transition(read_ipv6_address(ps, ip_consumer), await_end_of_ipv6, '[')
} }
term_state(read_host, set_host()) { term_state(read_host, set_host()) {
transition(read_host, authority_char, str += ch) read_next_char(read_host)
transition(start_port, ':', set_host()) transition(start_port, ':', set_host())
epsilon(end_of_authority, "/?#", set_host()) epsilon(end_of_authority, "/?#", set_host())
} }
...@@ -194,7 +201,7 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -194,7 +201,7 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
term_state(read_port, consumer.port(port)) { term_state(read_port, consumer.port(port)) {
transition(read_port, decimal_chars, add_ascii<10>(port, ch), transition(read_port, decimal_chars, add_ascii<10>(port, ch),
pec::integer_overflow) pec::integer_overflow)
epsilon(end_of_authority, "/?#", set_host()) epsilon(end_of_authority, "/?#")
} }
term_state(end_of_authority) { term_state(end_of_authority) {
transition(read_path, '/') transition(read_path, '/')
...@@ -203,6 +210,7 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -203,6 +210,7 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
} }
term_state(read_path, set_path()) { term_state(read_path, set_path()) {
transition(read_path, path_char, str += ch) transition(read_path, path_char, str += ch)
fsm_transition(read_uri_percent_encoded(ps, str), read_path, '%')
transition(start_query, '?', set_path()) transition(start_query, '?', set_path())
transition(read_fragment, '#', set_path()) transition(read_fragment, '#', set_path())
} }
...@@ -214,7 +222,7 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) { ...@@ -214,7 +222,7 @@ void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
epsilon(done) epsilon(done)
} }
term_state(read_fragment, consumer.fragment(take_str())) { term_state(read_fragment, consumer.fragment(take_str())) {
transition(read_fragment, alphanumeric_chars, str += ch) read_next_char(read_fragment)
} }
term_state(done) { term_state(done) {
// nop // nop
......
...@@ -68,6 +68,10 @@ public: ...@@ -68,6 +68,10 @@ public:
/// Assembles the human-readable string representation for this URI. /// Assembles the human-readable string representation for this URI.
void assemble_str(); void assemble_str();
// Escapes all reserved characters according to RFC 3986 in `x` and
// adds the encoded string to `str`.
void add_encoded(string_view x, bool is_path = false);
// -- friend functions ------------------------------------------------------- // -- friend functions -------------------------------------------------------
friend void intrusive_ptr_add_ref(const uri_impl* p); friend void intrusive_ptr_add_ref(const uri_impl* p);
......
...@@ -53,6 +53,13 @@ public: ...@@ -53,6 +53,13 @@ public:
inline authority_type() : port(0) { inline authority_type() : port(0) {
// nop // nop
} }
/// Returns whether `host` is empty, i.e., the host is not an IP address
/// and the string is empty.
bool empty() const noexcept {
auto str = get_if<std::string>(&host);
return str != nullptr && str->empty();
}
}; };
/// Separates the query component into key-value pairs. /// Separates the query component into key-value pairs.
...@@ -110,12 +117,6 @@ private: ...@@ -110,12 +117,6 @@ private:
// -- related free functions --------------------------------------------------- // -- related free functions ---------------------------------------------------
/// @relates uri
std::string to_string(const uri::host_type& x);
/// @relates uri
std::string to_string(const uri::authority_type& x);
/// @relates uri /// @relates uri
std::string to_string(const uri& x); std::string to_string(const uri& x);
......
...@@ -74,33 +74,6 @@ int uri::compare(string_view x) const noexcept { ...@@ -74,33 +74,6 @@ int uri::compare(string_view x) const noexcept {
// -- related free functions --------------------------------------------------- // -- related free functions ---------------------------------------------------
std::string to_string(const uri::host_type& x) {
auto str = get_if<std::string>(&x);
if (str)
return *str;
auto addr = get<ip_address>(x);
if (addr.embeds_v4())
return to_string(addr.embedded_v4());
std::string result = to_string(addr);
result.insert(result.begin(), '[');
result += ']';
return result;
}
std::string to_string(const uri::authority_type& x) {
std::string result;
if (!x.userinfo.empty()) {
result += x.userinfo;
result += '@';
}
result += to_string(x.host);
if (x.port != 0) {
result += ':';
result += std::to_string(x.port);
}
return result;
}
std::string to_string(const uri& x) { std::string to_string(const uri& x) {
auto x_str = x.str(); auto x_str = x.str();
std::string result{x_str.begin(), x_str.end()}; std::string result{x_str.begin(), x_str.end()};
......
...@@ -39,40 +39,90 @@ uri_impl uri_impl::default_instance; ...@@ -39,40 +39,90 @@ uri_impl uri_impl::default_instance;
// -- modifiers ---------------------------------------------------------------- // -- modifiers ----------------------------------------------------------------
void uri_impl::assemble_str() { void uri_impl::assemble_str() {
// TODO: all substrings need percent-escaping add_encoded(scheme);
str = scheme;
str += ':'; str += ':';
auto authority_str = to_string(authority); if (authority.empty()) {
if (!authority_str.empty()) { CAF_ASSERT(!path.empty());
add_encoded(path, true);
} else {
str += "//"; str += "//";
str += authority_str; if (!authority.userinfo.empty()) {
add_encoded(authority.userinfo);
str += '@';
}
auto addr = get_if<ip_address>(&authority.host);
if (addr == nullptr) {
add_encoded(get<std::string>(authority.host));
} else {
str += '[';
str += to_string(*addr);
str += ']';
}
if (authority.port != 0) {
str += ':';
str += std::to_string(authority.port);
}
if (!path.empty()) { if (!path.empty()) {
str += '/'; addr += '/';
str += path; add_encoded(path, true);
} }
} else if (!path.empty()) {
str += path;
} }
if (!query.empty()) { if (!query.empty()) {
str += '?'; str += '?';
auto i = query.begin(); auto i = query.begin();
str += i->first; auto add_kvp = [&](decltype(*i) kvp) {
str += '='; add_encoded(kvp.first);
str += i->second; str += '=';
add_encoded(kvp.second);
};
add_kvp(*i);
++i; ++i;
for (; i != query.end(); ++i) { for (; i != query.end(); ++i) {
str += '&'; str += '&';
str += i->first; add_kvp(*i);
str += '=';
str += i->second;
} }
} }
if (!fragment.empty()) { if (!fragment.empty()) {
str += '#'; str += '#';
str += fragment; add_encoded(fragment);
} }
} }
void uri_impl::add_encoded(string_view x, bool is_path) {
for (auto ch : x)
switch (ch) {
case '/':
if (is_path) {
str += ch;
break;
}
case ' ':
case ':':
case '?':
case '#':
case '[':
case ']':
case '@':
case '!':
case '$':
case '&':
case '\'':
case '"':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
str += '%';
detail::append_hex(str, reinterpret_cast<uint8_t*>(&ch), 1);
break;
default:
str += ch;
}
}
// -- friend functions --------------------------------------------------------- // -- friend functions ---------------------------------------------------------
void intrusive_ptr_add_ref(const uri_impl* p) { void intrusive_ptr_add_ref(const uri_impl* p) {
......
...@@ -202,6 +202,7 @@ CAF_TEST(constructing) { ...@@ -202,6 +202,7 @@ CAF_TEST(constructing) {
CAF_CHECK_EQUAL(*(http << components), *(http_str << components)) CAF_CHECK_EQUAL(*(http << components), *(http_str << components))
CAF_TEST(builder construction) { CAF_TEST(builder construction) {
// all combinations of components
BUILD(file); BUILD(file);
BUILD(file << kvp); BUILD(file << kvp);
BUILD(file << frag); BUILD(file << frag);
...@@ -238,11 +239,21 @@ CAF_TEST(builder construction) { ...@@ -238,11 +239,21 @@ CAF_TEST(builder construction) {
BUILD(me << node << port80 << file << frag); BUILD(me << node << port80 << file << frag);
BUILD(me << node << port80 << file << kvp); BUILD(me << node << port80 << file << kvp);
BUILD(me << node << port80 << file << kvp << frag); BUILD(me << node << port80 << file << kvp << frag);
// percent encoding
auto escaped = uri_builder{}
.scheme("hi there")
.userinfo("it's")
.host("me/")
.path("file 1")
.fragment("[42]")
.make();
CAF_CHECK_EQUAL(escaped, "hi%20there://it%27s@me%21/file%201#%5B42%5D");
} }
#define ROUNDTRIP(str) CAF_CHECK_EQUAL(str##_u, str) #define ROUNDTRIP(str) CAF_CHECK_EQUAL(str##_u, str)
CAF_TEST(from string) { CAF_TEST(from string) {
// all combinations of components
ROUNDTRIP("http:file"); ROUNDTRIP("http:file");
ROUNDTRIP("http:file?a=1&b=2"); ROUNDTRIP("http:file?a=1&b=2");
ROUNDTRIP("http:file#42"); ROUNDTRIP("http:file#42");
...@@ -279,6 +290,7 @@ CAF_TEST(from string) { ...@@ -279,6 +290,7 @@ CAF_TEST(from string) {
ROUNDTRIP("http://me@node:80/file?a=1&b=2"); ROUNDTRIP("http://me@node:80/file?a=1&b=2");
ROUNDTRIP("http://me@node:80/file#42"); ROUNDTRIP("http://me@node:80/file#42");
ROUNDTRIP("http://me@node:80/file?a=1&b=2#42"); ROUNDTRIP("http://me@node:80/file?a=1&b=2#42");
// all combinations of with IPv6 host
ROUNDTRIP("http://[::1]"); ROUNDTRIP("http://[::1]");
ROUNDTRIP("http://[::1]?a=1&b=2"); ROUNDTRIP("http://[::1]?a=1&b=2");
ROUNDTRIP("http://[::1]#42"); ROUNDTRIP("http://[::1]#42");
...@@ -311,6 +323,8 @@ CAF_TEST(from string) { ...@@ -311,6 +323,8 @@ CAF_TEST(from string) {
ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2"); ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2");
ROUNDTRIP("http://me@[::1]:80/file#42"); ROUNDTRIP("http://me@[::1]:80/file#42");
ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2#42"); ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2#42");
// percent encoding
ROUNDTRIP("hi%20there://it%27s@me%21/file%201#%5B42%5D");
} }
CAF_TEST(empty components) { CAF_TEST(empty components) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment