Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion doc/modules/ROOT/examples/unit/snippets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1782,6 +1782,40 @@ encoding()
}
}

void
decoding_helpers()
{
{
// tag::snippet_decoding_helpers_1[]
boost::core::string_view encoded = "name%3Dboost+url";
encoding_opts opt;
opt.space_as_plus = true;

auto const needed = decoded_size(encoded).value();
std::string buffer;
buffer.resize(needed);
auto const written = decode(&buffer[0], buffer.size(), encoded, opt).value();
buffer.resize(written);

assert(buffer == "name=boost url");
// end::snippet_decoding_helpers_1[]
}

{
// tag::snippet_decoding_helpers_2[]
encoding_opts opt;
opt.space_as_plus = true;

auto plain = decode(boost::core::string_view("city%3DSan+Jose"), opt).value();
assert(plain == "city=San Jose");

std::string scratch = "prefix:";
decode(boost::core::string_view("value%2F42"), {}, string_token::append_to(scratch)).value();
assert(scratch == "prefix:value/42");
// end::snippet_decoding_helpers_2[]
}
}

void
readme_snippets()
{
Expand Down Expand Up @@ -1845,6 +1879,7 @@ class snippets_test
normalizing();
decode_with_token();
encoding();
decoding_helpers();
ignore_unused(&readme_snippets);

BOOST_TEST_PASS();
Expand All @@ -1854,4 +1889,4 @@ class snippets_test
TEST_SUITE(snippets_test, "boost.url.snippets");

} // urls
} // boost
} // boost
4 changes: 4 additions & 0 deletions doc/modules/ROOT/pages/reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ cpp:boost::urls::encode[encode]

cpp:boost::urls::encoded_size[encoded_size]

cpp:boost::urls::decode[decode]

cpp:boost::urls::decoded_size[decoded_size]

cpp:boost::urls::make_pct_string_view[make_pct_string_view]

**Types**
Expand Down
21 changes: 21 additions & 0 deletions doc/modules/ROOT/pages/urls/percent-encoding.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ include::example$unit/snippets.cpp[tag=snippet_encoding_13,indent=0]
The member function
cpp:pct_string_view::decode[]
can be used to decode the data into a buffer.

Like the free-function
cpp:encode[], decoding options and the string token can be customized.

Expand All @@ -154,6 +155,26 @@ cpp:encode[], decoding options and the string token can be customized.
include::example$unit/snippets.cpp[tag=snippet_encoding_14,indent=0]
----

When you need to decode an ad-hoc string, the cpp:decode[] free functions mirror the
cpp:encode[] API so you do not need to construct a cpp:pct_string_view[] or a
cpp:decode_view[]. The cpp:decoded_size[] function reports the exact number of bytes required to store the decoded text,
and cpp:decode[] writes into caller-provided buffers.

// snippet_decoding_helpers_1
[source,cpp]
----
include::example$unit/snippets.cpp[tag=snippet_decoding_helpers_1,indent=0]
----

All overloads take a `core::string_view` parameter and return a
`system::result`, so the functions always validate the input and surface any
percent-encoding errors without throwing.

Just like their encoding counterparts, the string-token overloads let you
reuse storage or append to an existing container without intermediate allocations.

// snippet_decoding_helpers_2
[source,cpp]
----
include::example$unit/snippets.cpp[tag=snippet_decoding_helpers_2,indent=0]
----
1 change: 1 addition & 0 deletions include/boost/url.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <boost/url/grammar.hpp>

#include <boost/url/authority_view.hpp>
#include <boost/url/decode.hpp>
#include <boost/url/decode_view.hpp>
#include <boost/url/encode.hpp>
#include <boost/url/encoding_opts.hpp>
Expand Down
158 changes: 158 additions & 0 deletions include/boost/url/decode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
//
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//

#ifndef BOOST_URL_DECODE_HPP
#define BOOST_URL_DECODE_HPP

#include <boost/url/detail/config.hpp>
#include <boost/url/error_types.hpp>
#include <boost/url/encoding_opts.hpp>
#include <boost/url/grammar/string_token.hpp>
#include <boost/core/detail/string_view.hpp>

namespace boost {
namespace urls {

/** Return the buffer size needed for percent-decoding

This function returns the exact number of bytes needed
to store the decoded form of the specified string using
the given options. The string is validated before the
size is computed; malformed escapes cause the returned
result to contain an error instead.

@par Example
@code
auto n = decoded_size( "My%20Stuff" );
assert( n && *n == 8 );
@endcode

@par Exception Safety
Throws nothing. Validation errors are reported in the
returned result.

@return A result containing the decoded size, excluding
any null terminator.

@param s The string to measure.

@par Specification
@li <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-2.1"
>2.1. Percent-Encoding (rfc3986)</a>

@see
@ref decode,
@ref encoding_opts,
@ref make_pct_string_view.
*/
system::result<std::size_t>
decoded_size(core::string_view s) noexcept;

/** Apply percent-decoding to an arbitrary string

This function percent-decodes the specified string into
the destination buffer provided by the caller. The input
is validated first; malformed escapes cause the returned
result to hold an error instead of a size. If the buffer
is too small, the output is truncated and the number of
bytes actually written is returned.

@par Example
@code
char buf[100];
auto n = decode( buf, sizeof(buf), "Program%20Files" );
assert( n && *n == 13 );
@endcode

@par Exception Safety
Throws nothing. Validation errors are reported in the
returned result.

@return The number of characters written to the
destination buffer, or an error.

@param dest The destination buffer to write to.

@param size The number of writable characters pointed
to by `dest`. If this is less than the decoded size, the
result is truncated.

@param s The string to decode.

@param opt The decoding options. If omitted, the
default options are used.

@par Specification
@li <a href="https://datatracker.ietf.org/dodc/html/rfc3986#section-2.1"
>2.1. Percent-Encoding (rfc3986)</a>

@see
@ref decoded_size,
@ref encoding_opts,
@ref make_pct_string_view.
*/
system::result<std::size_t>
decode(
char* dest,
std::size_t size,
core::string_view s,
encoding_opts opt = {}) noexcept;

//------------------------------------------------

/** Return a percent-decoded string

This function percent-decodes the specified string and
returns the result using any @ref string_token. The
string is validated before decoding; malformed escapes
cause the returned result to hold an error.

@par Example
@code
auto plain = decode( "My%20Stuff" );
assert( plain && *plain == "My Stuff" );
@endcode

@par Exception Safety
Calls to allocate may throw. Validation errors are
reported in the returned result.

@return A result containing the decoded string in the
format described by the passed string token.

@param s The string to decode.

@param opt The decoding options. If omitted, the
default options are used.

@param token A string token.

@par Specification
@li <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-2.1"
>2.1. Percent-Encoding (rfc3986)</a>

@see
@ref decode,
@ref decoded_size,
@ref encoding_opts,
@ref string_token::return_string.
*/
template<BOOST_URL_STRTOK_TPARAM>
system::result<typename StringToken::result_type>
decode(
core::string_view s,
encoding_opts opt = {},
StringToken&& token = {}) noexcept;

} // urls
} // boost

#include <boost/url/impl/decode.hpp>

#endif
10 changes: 9 additions & 1 deletion src/detail/decode.hpp → include/boost/url/detail/decode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,32 @@
#ifndef BOOST_URL_DETAIL_DECODE_HPP
#define BOOST_URL_DETAIL_DECODE_HPP

#include "boost/url/encoding_opts.hpp"
#include <boost/url/encoding_opts.hpp>
#include <boost/core/detail/string_view.hpp>
#include <cstdlib>

namespace boost {
namespace urls {
namespace detail {

// Reads two hex digits without checking bounds or validity; invalid input or
// missing digits produces garbage and may touch bytes past the buffer.
BOOST_URL_DECL
char
decode_one(
char const* it) noexcept;

// Counts decoded bytes assuming the caller already validated escapes; a stray
// '%' still makes it skip three characters, so the reported size can be too
// small and lead to overflow when decoding.
BOOST_URL_DECL
std::size_t
decode_bytes_unsafe(
core::string_view s) noexcept;

// Writes decoded bytes trusting the buffer is large enough and escapes are
// complete; a short buffer stops decoding early, and a malformed escape zeros
// the remaining space before returning.
BOOST_URL_DECL
std::size_t
decode_unsafe(
Expand Down
83 changes: 83 additions & 0 deletions include/boost/url/impl/decode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
//
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//

#ifndef BOOST_URL_IMPL_DECODE_HPP
#define BOOST_URL_IMPL_DECODE_HPP

#include <boost/assert.hpp>
#include <boost/url/detail/decode.hpp>
#include <boost/url/detail/string_view.hpp>
#include <boost/url/pct_string_view.hpp>
#include <utility>

namespace boost {
namespace urls {

inline
system::result<std::size_t>
decoded_size(core::string_view s) noexcept
{
auto const rv = make_pct_string_view(s);
if(! rv)
return rv.error();
return rv->decoded_size();
}

inline
system::result<std::size_t>
decode(
char* dest,
std::size_t size,
core::string_view s,
encoding_opts opt) noexcept
{
auto const rv = make_pct_string_view(s);
if(! rv)
return rv.error();
return detail::decode_unsafe(
dest,
dest + size,
detail::to_sv(rv.value()),
opt);
}

template<
BOOST_URL_CONSTRAINT(string_token::StringToken) StringToken>
system::result<typename StringToken::result_type>
decode(
core::string_view s,
encoding_opts opt,
StringToken&& token) noexcept
{
static_assert(
string_token::is_token<
StringToken>::value,
"Type requirements not met");

auto const rv = make_pct_string_view(s);
if(! rv)
return rv.error();

auto const n = rv->decoded_size();
auto p = token.prepare(n);
// Some tokens might hand back a null/invalid buffer for n == 0, so skip the
// decode call entirely in that case to avoid touching unspecified memory.
if(n > 0)
detail::decode_unsafe(
p,
p + n,
detail::to_sv(rv.value()),
opt);
return token.result();
}

} // urls
} // boost

#endif
Loading
Loading