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).
- 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
destructor of the promise now checks for this case.
- Accessing URI fields now always returns the normalized string.
### Changed
......
......@@ -101,7 +101,6 @@ caf_add_component(
src/deserializer.cpp
src/detail/abstract_worker.cpp
src/detail/abstract_worker_hub.cpp
src/detail/append_percent_encoded.cpp
src/detail/base64.cpp
src/detail/behavior_impl.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:
/// Returns whether `parse` would produce a valid URI.
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:
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 @@
#include "caf/binary_deserializer.hpp"
#include "caf/binary_serializer.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/parse.hpp"
#include "caf/detail/parser/read_uri.hpp"
......@@ -32,27 +32,26 @@ uri::impl_type::impl_type() : rc_(1) {
void uri::impl_type::assemble_str() {
str.clear();
using detail::append_percent_encoded;
append_percent_encoded(str, scheme);
uri::encode(str, scheme);
str += ':';
if (authority.empty()) {
CAF_ASSERT(!path.empty());
append_percent_encoded(str, path, true);
uri::encode(str, path, true);
} else {
str += "//";
str += to_string(authority);
if (!path.empty()) {
str += '/';
append_percent_encoded(str, path, true);
uri::encode(str, path, true);
}
}
if (!query.empty()) {
str += '?';
auto i = query.begin();
auto add_kvp = [&](decltype(*i) kvp) {
append_percent_encoded(str, kvp.first);
uri::encode(str, kvp.first);
str += '=';
append_percent_encoded(str, kvp.second);
uri::encode(str, kvp.second);
};
add_kvp(*i);
for (++i; i != query.end(); ++i) {
......@@ -62,7 +61,7 @@ void uri::impl_type::assemble_str() {
}
if (!fragment.empty()) {
str += '#';
append_percent_encoded(str, fragment);
uri::encode(str, fragment);
}
}
......@@ -150,6 +149,67 @@ bool uri::can_parse(string_view str) noexcept {
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 ---------------------------------------------------
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 str;
if (!x.userinfo.empty()) {
detail::append_percent_encoded(str, x.userinfo);
uri::encode(str, x.userinfo);
str += '@';
}
auto f = caf::detail::make_overload(
......@@ -174,9 +234,7 @@ std::string to_string(const uri::authority_type& x) {
str += ']';
}
},
[&](const std::string& host) {
detail::append_percent_encoded(str, host);
});
[&](const std::string& host) { uri::encode(str, host); });
visit(f, x.host);
if (x.port != 0) {
str += ':';
......
......@@ -46,16 +46,22 @@ uri_builder& uri_builder::port(uint16_t value) {
}
uri_builder& uri_builder::path(std::string str) {
uri::decode(str);
impl_->path = std::move(str);
return *this;
}
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;
}
uri_builder& uri_builder::fragment(std::string str) {
uri::decode(str);
impl_->fragment = std::move(str);
return *this;
}
......
......@@ -355,7 +355,7 @@ CAF_TEST(from string) {
ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2#42");
// percent encoding
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
......
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