percent_encoding.hpp
Go to the documentation of this file.
/*
restinio
*/
#pragma once
#include <string>
#include <restinio/impl/include_fmtlib.hpp>
#include <restinio/string_view.hpp>
#include <restinio/exception.hpp>
#include <restinio/expected.hpp>
#include <restinio/utils/utf8_checker.hpp>
namespace restinio
{
namespace utils
{
struct restinio_default_unescape_traits
{
static constexpr bool
ordinary_char( char c ) noexcept
{
return
( '0' <= c && c <= '9' ) ||
( 'a' <= c && c <= 'z' ) ||
( 'A' <= c && c <= 'Z' ) ||
'-' == c ||
'.' == c ||
'~' == c ||
'_' == c;
}
};
struct x_www_form_urlencoded_unescape_traits
{
static constexpr bool
ordinary_char( char c ) noexcept
{
return
( '0' <= c && c <= '9' ) ||
( 'a' <= c && c <= 'z' ) ||
( 'A' <= c && c <= 'Z' ) ||
'*' == c ||
'-' == c ||
'.' == c ||
'_' == c;
}
};
struct relaxed_unescape_traits
{
static bool
ordinary_char( char c ) noexcept
{
return nullptr != std::strchr(
" " // Space
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" // ALPHA
"abcdefghijklmnopqrstuvwxyz"
"0123456789" // DIGIT
"-._~" // unreserved
":/?#[]@" // gen-delims
"!$&'()*+,;=", c );
}
};
struct javascript_compatible_unescape_traits
{
static constexpr bool
ordinary_char( char c ) noexcept
{
return
( '0' <= c && c <= '9' ) ||
( 'a' <= c && c <= 'z' ) ||
( 'A' <= c && c <= 'Z' ) ||
'-' == c ||
'.' == c ||
'~' == c ||
'_' == c ||
'*' == c ||
'!' == c ||
'\'' == c ||
'(' == c ||
')' == c;
}
};
struct unescape_percent_encoding_success_t {};
class unescape_percent_encoding_failure_t
{
std::string m_description;
public:
unescape_percent_encoding_failure_t(
std::string description )
: m_description{ std::move(description) }
{}
RESTINIO_NODISCARD
const std::string &
description() const noexcept { return m_description; }
RESTINIO_NODISCARD
std::string
giveout_description() noexcept { return std::move(m_description); }
};
namespace impl
{
inline bool
is_hexdigit( char c )
{
return
( '0' <= c && c <= '9' ) ||
( 'a' <= c && c <= 'f' ) ||
( 'A' <= c && c <= 'F' );
}
inline char
extract_escaped_char( char c1, char c2 )
{
char result;
if( '0' <= c1 && c1 <= '9' )
result = c1 - '0';
else
{
c1 |= 0x20;
result = 10 + c1 - 'a';
}
result <<= 4;
if( '0' <= c2 && c2 <= '9' )
result += c2 - '0';
else
{
c2 |= 0x20;
result += 10 + c2 - 'a';
}
return result;
}
//
// do_unescape_percent_encoding
//
template<
typename Traits,
typename Chars_Collector >
RESTINIO_NODISCARD
expected_t<
unescape_percent_encoding_success_t,
unescape_percent_encoding_failure_t >
do_unescape_percent_encoding(
const string_view_t data,
Chars_Collector && collector )
{
std::size_t chars_to_handle = data.size();
const char * d = data.data();
utf8_checker_t utf8_checker;
bool expect_next_utf8_byte = false;
const auto current_pos = [&d, &data]() noexcept { return d - data.data(); };
while( 0 < chars_to_handle )
{
char c = *d;
if( expect_next_utf8_byte && '%' != c )
return make_unexpected( unescape_percent_encoding_failure_t{
fmt::format(
"next byte from UTF-8 sequence expected at {}",
current_pos() )
} );
if( '%' == c )
{
if( chars_to_handle >= 3 &&
is_hexdigit( d[ 1 ] ) &&
is_hexdigit( d[ 2 ] ) )
{
const auto ch = extract_escaped_char( d[ 1 ], d[ 2 ] );
if( !utf8_checker.process_byte( static_cast<std::uint8_t>(ch) ) )
return make_unexpected( unescape_percent_encoding_failure_t{
fmt::format( "invalid UTF-8 sequence detected at {}",
current_pos() )
} );
collector( ch );
chars_to_handle -= 3;
d += 3;
expect_next_utf8_byte = !utf8_checker.finalized();
if( !expect_next_utf8_byte )
utf8_checker.reset();
}
else
{
return make_unexpected( unescape_percent_encoding_failure_t{
fmt::format(
"invalid escape sequence at pos {}", current_pos() )
} );
}
}
else if( '+' == c )
{
collector( ' ' );
--chars_to_handle;
++d;
}
else if( Traits::ordinary_char( c ) )
{
collector( c );
--chars_to_handle;
++d;
}
else
{
return make_unexpected( unescape_percent_encoding_failure_t{
fmt::format(
"invalid non-escaped char with code {:#02X} at pos: {}",
c,
current_pos() )
} );
}
}
if( expect_next_utf8_byte )
return make_unexpected( unescape_percent_encoding_failure_t{
fmt::format( "unfinished UTF-8 sequence" )
} );
return unescape_percent_encoding_success_t{};
}
} /* namespace impl */
template< typename Traits = restinio_default_unescape_traits >
RESTINIO_NODISCARD
std::string
escape_percent_encoding( const string_view_t data )
{
std::string result;
const auto escaped_chars_count = static_cast<std::size_t>(
std::count_if(
data.begin(),
data.end(),
[]( auto c ){ return !Traits::ordinary_char(c); } ));
if( 0 == escaped_chars_count )
{
// No escaped chars.
result.assign( data.data(), data.size() );
}
else
{
// Having escaped chars.
result.reserve( data.size() + 2*escaped_chars_count );
for( auto c : data )
{
if( Traits::ordinary_char( c ) )
result += c;
else
{
result += fmt::format( "%{:02X}", c );
}
}
}
return result;
}
template< typename Traits = restinio_default_unescape_traits >
RESTINIO_NODISCARD
std::string
unescape_percent_encoding( const string_view_t data )
{
std::string result;
result.reserve( data.size() );
auto r = impl::do_unescape_percent_encoding<Traits>(
data,
[&result]( char ch ) { result += ch; } );
if( !r )
throw exception_t{ r.error().giveout_description() };
return result;
}
template< typename Traits = restinio_default_unescape_traits >
RESTINIO_NODISCARD
expected_t< std::string, unescape_percent_encoding_failure_t >
try_unescape_percent_encoding( const string_view_t data )
{
std::string result;
result.reserve( data.size() );
auto r = impl::do_unescape_percent_encoding<Traits>(
data,
[&result]( char ch ) { result += ch; } );
if( !r )
return make_unexpected( std::move(r.error()) );
return std::move(result);
}
template< typename Traits = restinio_default_unescape_traits >
RESTINIO_NODISCARD
std::size_t
inplace_unescape_percent_encoding( char * data, std::size_t size )
{
std::size_t result_size = 0u;
char * dest = data;
auto r = impl::do_unescape_percent_encoding<Traits>(
string_view_t{ data, size },
[&result_size, &dest]( char ch ) {
*dest++ = ch;
++result_size;
} );
if( !r )
throw exception_t{ r.error().giveout_description() };
return result_size;
}
template< typename Traits = restinio_default_unescape_traits >
RESTINIO_NODISCARD
expected_t< std::size_t, unescape_percent_encoding_failure_t >
try_inplace_unescape_percent_encoding( char * data, std::size_t size )
{
std::size_t result_size = 0u;
char * dest = data;
auto r = impl::do_unescape_percent_encoding<Traits>(
string_view_t{ data, size },
[&result_size, &dest]( char ch ) {
*dest++ = ch;
++result_size;
} );
if( !r )
return make_unexpected( std::move(r.error()) );
return result_size;
}
namespace uri_normalization
{
namespace unreserved_chars
{
namespace impl
{
RESTINIO_NODISCARD
constexpr inline bool
is_unreserved_char( const char ch ) noexcept
{
// In this version of RESTinio class restinio_default_unescape_traits
// already implements necessary check.
return restinio_default_unescape_traits::ordinary_char( ch );
}
template<
typename One_Byte_Handler,
typename Three_Byte_Handler >
void
run_normalization_algo(
string_view_t what,
One_Byte_Handler && one_byte_handler,
Three_Byte_Handler && three_byte_handler )
{
using namespace restinio::utils::impl;
std::size_t chars_to_handle = what.size();
const char * d = what.data();
utf8_checker_t utf8_checker;
bool expect_next_utf8_byte = false;
const auto current_pos = [&d, &what]() noexcept { return d - what.data(); };
while( 0 < chars_to_handle )
{
if( expect_next_utf8_byte && '%' != *d )
throw exception_t{
fmt::format( "next byte from UTF-8 sequence expected at {}",
current_pos() )
};
if( '%' != *d )
{
// Just one symbol to the output.
one_byte_handler( *d );
++d;
--chars_to_handle;
}
else if( chars_to_handle >= 3 &&
is_hexdigit( d[ 1 ] ) && is_hexdigit( d[ 2 ] ) )
{
const char ch = extract_escaped_char( d[ 1 ], d[ 2 ] );
if( !utf8_checker.process_byte( static_cast<std::uint8_t>(ch) ) )
throw exception_t{
fmt::format( "invalid UTF-8 sequence detected at {}",
current_pos() )
};
bool keep_three_bytes = true;
if( utf8_checker.finalized() )
{
expect_next_utf8_byte = false;
const auto symbol = utf8_checker.current_symbol();
utf8_checker.reset();
if( symbol < 0x80u )
{
const char ascii_char = static_cast<char>(symbol);
if( is_unreserved_char( ascii_char ) )
{
// percent encoded char will be replaced by one char.
one_byte_handler( ascii_char );
keep_three_bytes = false;
}
}
}
else
{
expect_next_utf8_byte = true;
}
if( keep_three_bytes )
{
// this part of multi-byte char will go to the output as is.
three_byte_handler( d[ 0 ], d[ 1 ], d[ 2 ] );
}
chars_to_handle -= 3;
d += 3u;
}
else
{
throw exception_t{
fmt::format( "invalid escape sequence at pos {}", current_pos() )
};
}
}
if( expect_next_utf8_byte )
throw exception_t{ fmt::format( "unfinished UTF-8 sequence" ) };
}
} /* namespace impl */
RESTINIO_NODISCARD
inline std::size_t
estimate_required_capacity(
string_view_t what )
{
std::size_t calculated_capacity = 0u;
impl::run_normalization_algo( what,
[&calculated_capacity]( char ) noexcept {
++calculated_capacity;
},
[&calculated_capacity]( char, char, char ) noexcept {
calculated_capacity += 3u;
} );
return calculated_capacity;
}
inline void
normalize_to(
string_view_t what,
char * dest )
{
impl::run_normalization_algo( what,
[&dest]( char ch ) noexcept {
*dest++ = ch;
},
[&dest]( char ch1, char ch2, char ch3 ) noexcept {
dest[ 0 ] = ch1;
dest[ 1 ] = ch2;
dest[ 2 ] = ch3;
dest += 3;
} );
}
} /* namespace unreserved_chars */
} /* namespace uri_normalization */
} /* namespace utils */
} /* namespace restinio */
restinio::exception_t
Exception class for all exceptions thrown by RESTinio.
Definition: exception.hpp:26
restinio::utils::unescape_percent_encoding_failure_t
Type that indicates a failure of unescaping of percent-encoded symbols.
Definition: percent_encoding.hpp:160
restinio::utils::unescape_percent_encoding_failure_t::description
RESTINIO_NODISCARD const std::string & description() const noexcept
Get a reference to the description of the failure.
Definition: percent_encoding.hpp:173
restinio::utils::unescape_percent_encoding_failure_t::m_description
std::string m_description
Description of a failure.
Definition: percent_encoding.hpp:162
restinio::utils::unescape_percent_encoding_failure_t::giveout_description
RESTINIO_NODISCARD std::string giveout_description() noexcept
Get out the value of the description of the failure.
Definition: percent_encoding.hpp:183
restinio::utils::unescape_percent_encoding_failure_t::unescape_percent_encoding_failure_t
unescape_percent_encoding_failure_t(std::string description)
Definition: percent_encoding.hpp:165
restinio::utils::utf8_checker_t
Helper class for checking UTF-8 byte sequence during parsing URI or incoming byte stream.
Definition: utf8_checker.hpp:35
restinio::utils::utf8_checker_t::finalized
RESTINIO_NODISCARD bool finalized() const noexcept
Definition: utf8_checker.hpp:120
restinio::utils::utf8_checker_t::process_byte
RESTINIO_NODISCARD bool process_byte(std::uint8_t byte) noexcept
Definition: utf8_checker.hpp:41
restinio::utils::utf8_checker_t::current_symbol
RESTINIO_NODISCARD std::uint32_t current_symbol() const noexcept
Definition: utf8_checker.hpp:134
restinio::utils::utf8_checker_t::reset
void reset() noexcept
Definition: utf8_checker.hpp:126
RESTINIO_NODISCARD
#define RESTINIO_NODISCARD
Definition: compiler_features.hpp:33
exception.hpp
expected.hpp
include_fmtlib.hpp
A special wrapper around fmtlib include files.
nonstd::optional_lite::std11::move
T & move(T &t)
Definition: optional.hpp:465
restinio::easy_parser::symbol
RESTINIO_NODISCARD auto symbol(char expected) noexcept
A factory function to create a clause that expects the speficied symbol, extracts it and then skips i...
Definition: easy_parser.hpp:4032
restinio::utils::impl
Definition: bitops.hpp:17
restinio::utils::impl::do_unescape_percent_encoding
RESTINIO_NODISCARD expected_t< unescape_percent_encoding_success_t, unescape_percent_encoding_failure_t > do_unescape_percent_encoding(const string_view_t data, Chars_Collector &&collector)
The actual implementation of unescape-percent-encoding procedure.
Definition: percent_encoding.hpp:239
restinio::utils::impl::extract_escaped_char
char extract_escaped_char(char c1, char c2)
Definition: percent_encoding.hpp:199
restinio::utils::impl::is_hexdigit
bool is_hexdigit(char c)
Definition: percent_encoding.hpp:190
restinio::utils::uri_normalization::unreserved_chars::impl::is_unreserved_char
RESTINIO_NODISCARD constexpr bool is_unreserved_char(const char ch) noexcept
Is this symbol a part of unreserved set?
Definition: percent_encoding.hpp:478
restinio::utils::uri_normalization::unreserved_chars::impl::run_normalization_algo
void run_normalization_algo(string_view_t what, One_Byte_Handler &&one_byte_handler, Three_Byte_Handler &&three_byte_handler)
Internal helper to perform the main logic of enumeration of symbols in URI.
Definition: percent_encoding.hpp:503
restinio::utils::uri_normalization::unreserved_chars::normalize_to
void normalize_to(string_view_t what, char *dest)
Perform normalization of URI value.
Definition: percent_encoding.hpp:640
restinio::utils::uri_normalization::unreserved_chars::estimate_required_capacity
RESTINIO_NODISCARD std::size_t estimate_required_capacity(string_view_t what)
Calculate the size of a buffer to hold normalized value of a URI.
Definition: percent_encoding.hpp:605
restinio::utils::try_unescape_percent_encoding
RESTINIO_NODISCARD expected_t< std::string, unescape_percent_encoding_failure_t > try_unescape_percent_encoding(const string_view_t data)
Helper function for unescaping percent-encoded string.
Definition: percent_encoding.hpp:392
restinio::utils::try_inplace_unescape_percent_encoding
RESTINIO_NODISCARD expected_t< std::size_t, unescape_percent_encoding_failure_t > try_inplace_unescape_percent_encoding(char *data, std::size_t size)
Helper function for unescaping percent-encoded string inplace.
Definition: percent_encoding.hpp:441
restinio::utils::inplace_unescape_percent_encoding
RESTINIO_NODISCARD std::size_t inplace_unescape_percent_encoding(char *data, std::size_t size)
Definition: percent_encoding.hpp:409
restinio::utils::unescape_percent_encoding
RESTINIO_NODISCARD std::string unescape_percent_encoding(const string_view_t data)
Definition: percent_encoding.hpp:363
restinio::utils::escape_percent_encoding
RESTINIO_NODISCARD std::string escape_percent_encoding(const string_view_t data)
Percent encoding.
Definition: percent_encoding.hpp:328
restinio
Definition: asio_include.hpp:21
restinio::string_view_t
nonstd::string_view string_view_t
Definition: string_view.hpp:19
restinio::expected_t
nonstd::expected< T, E > expected_t
Definition: expected.hpp:22
std
STL namespace.
string_view.hpp
restinio::utils::javascript_compatible_unescape_traits
The traits for escaping and unexcaping symbols in JavaScript-compatible mode.
Definition: percent_encoding.hpp:125
restinio::utils::javascript_compatible_unescape_traits::ordinary_char
static constexpr bool ordinary_char(char c) noexcept
Definition: percent_encoding.hpp:127
restinio::utils::relaxed_unescape_traits
Traits for escaping and unescaping symbols in a query string in very relaxed mode.
Definition: percent_encoding.hpp:97
restinio::utils::relaxed_unescape_traits::ordinary_char
static bool ordinary_char(char c) noexcept
Definition: percent_encoding.hpp:99
restinio::utils::restinio_default_unescape_traits
The default traits for escaping and unexcaping symbols in a query string.
Definition: percent_encoding.hpp:36
restinio::utils::restinio_default_unescape_traits::ordinary_char
static constexpr bool ordinary_char(char c) noexcept
Definition: percent_encoding.hpp:38
restinio::utils::unescape_percent_encoding_success_t
Type that indicates that unescaping of percent-encoded symbols completed successfully.
Definition: percent_encoding.hpp:151
restinio::utils::x_www_form_urlencoded_unescape_traits
Traits for escaping and unexcaping symbols in a query string in correspondence with application/x-www...
Definition: percent_encoding.hpp:61
restinio::utils::x_www_form_urlencoded_unescape_traits::ordinary_char
static constexpr bool ordinary_char(char c) noexcept
Definition: percent_encoding.hpp:63
utf8_checker.hpp
An implementation of checker for UTF-8 sequences.
const
#define const
Definition: zconf.h:230