Commit d7b8f7ee authored by Dominik Charousset's avatar Dominik Charousset

Normalize URI fields after parsing

parent b69b3f90
...@@ -10,6 +10,7 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -10,6 +10,7 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Passing a response promise to a run-delayed continuation could result in a - Passing a response promise to a run-delayed continuation could result in a
heap-use-after-free if the actor terminates before the action runs. The heap-use-after-free if the actor terminates before the action runs. The
destructor of the promise now checks for this case. destructor of the promise now checks for this case.
- Accessing URI fields now always returns the normalized string.
### Changed ### Changed
......
...@@ -101,7 +101,6 @@ caf_add_component( ...@@ -101,7 +101,6 @@ caf_add_component(
src/deserializer.cpp src/deserializer.cpp
src/detail/abstract_worker.cpp src/detail/abstract_worker.cpp
src/detail/abstract_worker_hub.cpp src/detail/abstract_worker_hub.cpp
src/detail/append_percent_encoded.cpp
src/detail/base64.cpp src/detail/base64.cpp
src/detail/behavior_impl.cpp src/detail/behavior_impl.cpp
src/detail/behavior_stack.cpp src/detail/behavior_stack.cpp
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#pragma once
#include <string>
#include "caf/detail/core_export.hpp"
#include "caf/fwd.hpp"
namespace caf::detail {
// Escapes all reserved characters according to RFC 3986 in `x` and
// adds the encoded string to `str`.
CAF_CORE_EXPORT void append_percent_encoded(std::string& str, string_view x,
bool is_path = false);
} // namespace caf::detail
...@@ -206,6 +206,12 @@ public: ...@@ -206,6 +206,12 @@ public:
/// Returns whether `parse` would produce a valid URI. /// Returns whether `parse` would produce a valid URI.
static bool can_parse(string_view str) noexcept; static bool can_parse(string_view str) noexcept;
// -- URI encoding, AKA Percent encoding -------------------------------------
static void encode(std::string& str, string_view x, bool is_path = false);
static void decode(std::string& str);
private: private:
impl_ptr impl_; impl_ptr impl_;
}; };
......
// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.
#include "caf/detail/append_percent_encoded.hpp"
#include "caf/byte.hpp"
#include "caf/config.hpp"
#include "caf/detail/append_hex.hpp"
#include "caf/string_view.hpp"
namespace caf::detail {
void append_percent_encoded(std::string& str, string_view x, bool is_path) {
for (auto ch : x)
switch (ch) {
case ':':
case '/':
if (is_path) {
str += ch;
break;
}
[[fallthrough]];
case ' ':
case '?':
case '#':
case '[':
case ']':
case '@':
case '!':
case '$':
case '&':
case '\'':
case '"':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
str += '%';
append_hex(str, ch);
break;
default:
str += ch;
}
}
} // namespace caf::detail
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#include "caf/binary_deserializer.hpp" #include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.hpp" #include "caf/binary_serializer.hpp"
#include "caf/deserializer.hpp" #include "caf/deserializer.hpp"
#include "caf/detail/append_percent_encoded.hpp" #include "caf/detail/append_hex.hpp"
#include "caf/detail/overload.hpp" #include "caf/detail/overload.hpp"
#include "caf/detail/parse.hpp" #include "caf/detail/parse.hpp"
#include "caf/detail/parser/read_uri.hpp" #include "caf/detail/parser/read_uri.hpp"
...@@ -32,27 +32,26 @@ uri::impl_type::impl_type() : rc_(1) { ...@@ -32,27 +32,26 @@ uri::impl_type::impl_type() : rc_(1) {
void uri::impl_type::assemble_str() { void uri::impl_type::assemble_str() {
str.clear(); str.clear();
using detail::append_percent_encoded; uri::encode(str, scheme);
append_percent_encoded(str, scheme);
str += ':'; str += ':';
if (authority.empty()) { if (authority.empty()) {
CAF_ASSERT(!path.empty()); CAF_ASSERT(!path.empty());
append_percent_encoded(str, path, true); uri::encode(str, path, true);
} else { } else {
str += "//"; str += "//";
str += to_string(authority); str += to_string(authority);
if (!path.empty()) { if (!path.empty()) {
str += '/'; str += '/';
append_percent_encoded(str, path, true); uri::encode(str, path, true);
} }
} }
if (!query.empty()) { if (!query.empty()) {
str += '?'; str += '?';
auto i = query.begin(); auto i = query.begin();
auto add_kvp = [&](decltype(*i) kvp) { auto add_kvp = [&](decltype(*i) kvp) {
append_percent_encoded(str, kvp.first); uri::encode(str, kvp.first);
str += '='; str += '=';
append_percent_encoded(str, kvp.second); uri::encode(str, kvp.second);
}; };
add_kvp(*i); add_kvp(*i);
for (++i; i != query.end(); ++i) { for (++i; i != query.end(); ++i) {
...@@ -62,7 +61,7 @@ void uri::impl_type::assemble_str() { ...@@ -62,7 +61,7 @@ void uri::impl_type::assemble_str() {
} }
if (!fragment.empty()) { if (!fragment.empty()) {
str += '#'; str += '#';
append_percent_encoded(str, fragment); uri::encode(str, fragment);
} }
} }
...@@ -150,6 +149,67 @@ bool uri::can_parse(string_view str) noexcept { ...@@ -150,6 +149,67 @@ bool uri::can_parse(string_view str) noexcept {
return ps.code == pec::success; return ps.code == pec::success;
} }
// -- URI encoding -----------------------------------------------------------
void uri::encode(std::string& str, string_view x, bool is_path) {
for (auto ch : x)
switch (ch) {
case ':':
case '/':
if (is_path) {
str += ch;
break;
}
[[fallthrough]];
case ' ':
case '?':
case '#':
case '[':
case ']':
case '@':
case '!':
case '$':
case '&':
case '\'':
case '"':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
str += '%';
detail::append_hex(str, ch);
break;
default:
str += ch;
}
}
void uri::decode(std::string& str) {
// Buffer for holding temporary strings and variable for parsing into.
char str_buf[2] = {' ', '\0'};
char hex_buf[5] = {'0', 'x', '0', '0', '\0'};
uint8_t val = 0;
// Any percent-encoded string must have at least 3 characters.
if (str.size() < 3)
return;
// Iterate over the string to find '%XX' entries and replace them.
for (size_t index = 0; index < str.size() - 2; ++index) {
if (str[index] == '%') {
hex_buf[2] = str[index + 1];
hex_buf[3] = str[index + 2];
if (auto err = detail::parse(string_view{hex_buf}, val); !err) {
str_buf[0] = static_cast<char>(val);
str.replace(index, 3, str_buf, 1);
} else {
str.replace(index, 3, "?", 1);
}
}
}
}
// -- related free functions --------------------------------------------------- // -- related free functions ---------------------------------------------------
std::string to_string(const uri& x) { std::string to_string(const uri& x) {
...@@ -161,7 +221,7 @@ std::string to_string(const uri& x) { ...@@ -161,7 +221,7 @@ std::string to_string(const uri& x) {
std::string to_string(const uri::authority_type& x) { std::string to_string(const uri::authority_type& x) {
std::string str; std::string str;
if (!x.userinfo.empty()) { if (!x.userinfo.empty()) {
detail::append_percent_encoded(str, x.userinfo); uri::encode(str, x.userinfo);
str += '@'; str += '@';
} }
auto f = caf::detail::make_overload( auto f = caf::detail::make_overload(
...@@ -174,9 +234,7 @@ std::string to_string(const uri::authority_type& x) { ...@@ -174,9 +234,7 @@ std::string to_string(const uri::authority_type& x) {
str += ']'; str += ']';
} }
}, },
[&](const std::string& host) { [&](const std::string& host) { uri::encode(str, host); });
detail::append_percent_encoded(str, host);
});
visit(f, x.host); visit(f, x.host);
if (x.port != 0) { if (x.port != 0) {
str += ':'; str += ':';
......
...@@ -46,16 +46,22 @@ uri_builder& uri_builder::port(uint16_t value) { ...@@ -46,16 +46,22 @@ uri_builder& uri_builder::port(uint16_t value) {
} }
uri_builder& uri_builder::path(std::string str) { uri_builder& uri_builder::path(std::string str) {
uri::decode(str);
impl_->path = std::move(str); impl_->path = std::move(str);
return *this; return *this;
} }
uri_builder& uri_builder::query(uri::query_map map) { uri_builder& uri_builder::query(uri::query_map map) {
impl_->query = std::move(map); for (auto [key, val] : map) {
uri::decode(key);
uri::decode(val);
impl_->query.emplace(std::move(key), std::move(val));
}
return *this; return *this;
} }
uri_builder& uri_builder::fragment(std::string str) { uri_builder& uri_builder::fragment(std::string str) {
uri::decode(str);
impl_->fragment = std::move(str); impl_->fragment = std::move(str);
return *this; return *this;
} }
......
...@@ -355,7 +355,7 @@ CAF_TEST(from string) { ...@@ -355,7 +355,7 @@ CAF_TEST(from string) {
ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2#42"); ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2#42");
// percent encoding // percent encoding
ROUNDTRIP("hi%20there://it%27s@me%21/file%201#%5B42%5D"); ROUNDTRIP("hi%20there://it%27s@me%21/file%201#%5B42%5D");
ROUNDTRIP("file://localhost/tmp/test/test.{%3A04d}.exr"); ROUNDTRIP("file://localhost/tmp/test/test.{:04d}.exr?q=%3A1");
} }
#undef ROUNDTRIP #undef ROUNDTRIP
......
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