Skip to content

Commit 811954a

Browse files
committed
feat: decode free functions
fix #947
1 parent bbe9700 commit 811954a

File tree

15 files changed

+547
-11
lines changed

15 files changed

+547
-11
lines changed

doc/modules/ROOT/examples/unit/snippets.cpp

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1782,6 +1782,40 @@ encoding()
17821782
}
17831783
}
17841784

1785+
void
1786+
decoding_helpers()
1787+
{
1788+
{
1789+
// tag::snippet_decoding_helpers_1[]
1790+
boost::core::string_view encoded = "name%3Dboost+url";
1791+
encoding_opts opt;
1792+
opt.space_as_plus = true;
1793+
1794+
auto const needed = decoded_size(encoded).value();
1795+
std::string buffer;
1796+
buffer.resize(needed);
1797+
auto const written = decode(&buffer[0], buffer.size(), encoded, opt).value();
1798+
buffer.resize(written);
1799+
1800+
assert(buffer == "name=boost url");
1801+
// end::snippet_decoding_helpers_1[]
1802+
}
1803+
1804+
{
1805+
// tag::snippet_decoding_helpers_2[]
1806+
encoding_opts opt;
1807+
opt.space_as_plus = true;
1808+
1809+
auto plain = decode(boost::core::string_view("city%3DSan+Jose"), opt).value();
1810+
assert(plain == "city=San Jose");
1811+
1812+
std::string scratch = "prefix:";
1813+
decode(boost::core::string_view("value%2F42"), {}, string_token::append_to(scratch)).value();
1814+
assert(scratch == "prefix:value/42");
1815+
// end::snippet_decoding_helpers_2[]
1816+
}
1817+
}
1818+
17851819
void
17861820
readme_snippets()
17871821
{
@@ -1845,6 +1879,7 @@ class snippets_test
18451879
normalizing();
18461880
decode_with_token();
18471881
encoding();
1882+
decoding_helpers();
18481883
ignore_unused(&readme_snippets);
18491884

18501885
BOOST_TEST_PASS();
@@ -1854,4 +1889,4 @@ class snippets_test
18541889
TEST_SUITE(snippets_test, "boost.url.snippets");
18551890

18561891
} // urls
1857-
} // boost
1892+
} // boost

doc/modules/ROOT/pages/reference.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ cpp:boost::urls::encode[encode]
118118

119119
cpp:boost::urls::encoded_size[encoded_size]
120120

121+
cpp:boost::urls::decode[decode]
122+
123+
cpp:boost::urls::decoded_size[decoded_size]
124+
121125
cpp:boost::urls::make_pct_string_view[make_pct_string_view]
122126

123127
**Types**

doc/modules/ROOT/pages/urls/percent-encoding.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ include::example$unit/snippets.cpp[tag=snippet_encoding_13,indent=0]
145145
The member function
146146
cpp:pct_string_view::decode[]
147147
can be used to decode the data into a buffer.
148+
148149
Like the free-function
149150
cpp:encode[], decoding options and the string token can be customized.
150151

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

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

165+
// snippet_decoding_helpers_1
166+
[source,cpp]
167+
----
168+
include::example$unit/snippets.cpp[tag=snippet_decoding_helpers_1,indent=0]
169+
----
158170

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

175+
Just like their encoding counterparts, the string-token overloads let you
176+
reuse storage or append to an existing container without intermediate allocations.
177+
178+
// snippet_decoding_helpers_2
179+
[source,cpp]
180+
----
181+
include::example$unit/snippets.cpp[tag=snippet_decoding_helpers_2,indent=0]
182+
----

include/boost/url.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <boost/url/grammar.hpp>
1414

1515
#include <boost/url/authority_view.hpp>
16+
#include <boost/url/decode.hpp>
1617
#include <boost/url/decode_view.hpp>
1718
#include <boost/url/encode.hpp>
1819
#include <boost/url/encoding_opts.hpp>

include/boost/url/decode.hpp

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
//
2+
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/boostorg/url
8+
//
9+
10+
#ifndef BOOST_URL_DECODE_HPP
11+
#define BOOST_URL_DECODE_HPP
12+
13+
#include <boost/url/detail/config.hpp>
14+
#include <boost/url/error_types.hpp>
15+
#include <boost/url/encoding_opts.hpp>
16+
#include <boost/url/grammar/string_token.hpp>
17+
#include <boost/core/detail/string_view.hpp>
18+
19+
namespace boost {
20+
namespace urls {
21+
22+
/** Return the buffer size needed for percent-decoding
23+
24+
This function returns the exact number of bytes needed
25+
to store the decoded form of the specified string using
26+
the given options. The string is validated before the
27+
size is computed; malformed escapes cause the returned
28+
result to contain an error instead.
29+
30+
@par Example
31+
@code
32+
auto n = decoded_size( "My%20Stuff" );
33+
assert( n && *n == 8 );
34+
@endcode
35+
36+
@par Exception Safety
37+
Throws nothing. Validation errors are reported in the
38+
returned result.
39+
40+
@return A result containing the decoded size, excluding
41+
any null terminator.
42+
43+
@param s The string to measure.
44+
45+
@par Specification
46+
@li <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-2.1"
47+
>2.1. Percent-Encoding (rfc3986)</a>
48+
49+
@see
50+
@ref decode,
51+
@ref encoding_opts,
52+
@ref make_pct_string_view.
53+
*/
54+
system::result<std::size_t>
55+
decoded_size(core::string_view s) noexcept;
56+
57+
/** Apply percent-decoding to an arbitrary string
58+
59+
This function percent-decodes the specified string into
60+
the destination buffer provided by the caller. The input
61+
is validated first; malformed escapes cause the returned
62+
result to hold an error instead of a size. If the buffer
63+
is too small, the output is truncated and the number of
64+
bytes actually written is returned.
65+
66+
@par Example
67+
@code
68+
char buf[100];
69+
auto n = decode( buf, sizeof(buf), "Program%20Files" );
70+
assert( n && *n == 13 );
71+
@endcode
72+
73+
@par Exception Safety
74+
Throws nothing. Validation errors are reported in the
75+
returned result.
76+
77+
@return The number of characters written to the
78+
destination buffer, or an error.
79+
80+
@param dest The destination buffer to write to.
81+
82+
@param size The number of writable characters pointed
83+
to by `dest`. If this is less than the decoded size, the
84+
result is truncated.
85+
86+
@param s The string to decode.
87+
88+
@param opt The decoding options. If omitted, the
89+
default options are used.
90+
91+
@par Specification
92+
@li <a href="https://datatracker.ietf.org/dodc/html/rfc3986#section-2.1"
93+
>2.1. Percent-Encoding (rfc3986)</a>
94+
95+
@see
96+
@ref decoded_size,
97+
@ref encoding_opts,
98+
@ref make_pct_string_view.
99+
*/
100+
system::result<std::size_t>
101+
decode(
102+
char* dest,
103+
std::size_t size,
104+
core::string_view s,
105+
encoding_opts opt = {}) noexcept;
106+
107+
//------------------------------------------------
108+
109+
/** Return a percent-decoded string
110+
111+
This function percent-decodes the specified string and
112+
returns the result using any @ref string_token. The
113+
string is validated before decoding; malformed escapes
114+
cause the returned result to hold an error.
115+
116+
@par Example
117+
@code
118+
auto plain = decode( "My%20Stuff" );
119+
assert( plain && *plain == "My Stuff" );
120+
@endcode
121+
122+
@par Exception Safety
123+
Calls to allocate may throw. Validation errors are
124+
reported in the returned result.
125+
126+
@return A result containing the decoded string in the
127+
format described by the passed string token.
128+
129+
@param s The string to decode.
130+
131+
@param opt The decoding options. If omitted, the
132+
default options are used.
133+
134+
@param token A string token.
135+
136+
@par Specification
137+
@li <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-2.1"
138+
>2.1. Percent-Encoding (rfc3986)</a>
139+
140+
@see
141+
@ref decode,
142+
@ref decoded_size,
143+
@ref encoding_opts,
144+
@ref string_token::return_string.
145+
*/
146+
template<BOOST_URL_STRTOK_TPARAM>
147+
system::result<typename StringToken::result_type>
148+
decode(
149+
core::string_view s,
150+
encoding_opts opt = {},
151+
StringToken&& token = {}) noexcept;
152+
153+
} // urls
154+
} // boost
155+
156+
#include <boost/url/impl/decode.hpp>
157+
158+
#endif
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,32 @@
1111
#ifndef BOOST_URL_DETAIL_DECODE_HPP
1212
#define BOOST_URL_DETAIL_DECODE_HPP
1313

14-
#include "boost/url/encoding_opts.hpp"
14+
#include <boost/url/encoding_opts.hpp>
1515
#include <boost/core/detail/string_view.hpp>
1616
#include <cstdlib>
1717

1818
namespace boost {
1919
namespace urls {
2020
namespace detail {
2121

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

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

37+
// Writes decoded bytes trusting the buffer is large enough and escapes are
38+
// complete; a short buffer stops decoding early, and a malformed escape zeros
39+
// the remaining space before returning.
3240
BOOST_URL_DECL
3341
std::size_t
3442
decode_unsafe(

include/boost/url/impl/decode.hpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//
2+
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
3+
//
4+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6+
//
7+
// Official repository: https://github.com/boostorg/url
8+
//
9+
10+
#ifndef BOOST_URL_IMPL_DECODE_HPP
11+
#define BOOST_URL_IMPL_DECODE_HPP
12+
13+
#include <boost/assert.hpp>
14+
#include <boost/url/detail/decode.hpp>
15+
#include <boost/url/detail/string_view.hpp>
16+
#include <boost/url/pct_string_view.hpp>
17+
#include <utility>
18+
19+
namespace boost {
20+
namespace urls {
21+
22+
inline
23+
system::result<std::size_t>
24+
decoded_size(core::string_view s) noexcept
25+
{
26+
auto const rv = make_pct_string_view(s);
27+
if(! rv)
28+
return rv.error();
29+
return rv->decoded_size();
30+
}
31+
32+
inline
33+
system::result<std::size_t>
34+
decode(
35+
char* dest,
36+
std::size_t size,
37+
core::string_view s,
38+
encoding_opts opt) noexcept
39+
{
40+
auto const rv = make_pct_string_view(s);
41+
if(! rv)
42+
return rv.error();
43+
return detail::decode_unsafe(
44+
dest,
45+
dest + size,
46+
detail::to_sv(rv.value()),
47+
opt);
48+
}
49+
50+
template<
51+
BOOST_URL_CONSTRAINT(string_token::StringToken) StringToken>
52+
system::result<typename StringToken::result_type>
53+
decode(
54+
core::string_view s,
55+
encoding_opts opt,
56+
StringToken&& token) noexcept
57+
{
58+
static_assert(
59+
string_token::is_token<
60+
StringToken>::value,
61+
"Type requirements not met");
62+
63+
auto const rv = make_pct_string_view(s);
64+
if(! rv)
65+
return rv.error();
66+
67+
auto const n = rv->decoded_size();
68+
auto p = token.prepare(n);
69+
// Some tokens might hand back a null/invalid buffer for n == 0, so skip the
70+
// decode call entirely in that case to avoid touching unspecified memory.
71+
if(n > 0)
72+
detail::decode_unsafe(
73+
p,
74+
p + n,
75+
detail::to_sv(rv.value()),
76+
opt);
77+
return token.result();
78+
}
79+
80+
} // urls
81+
} // boost
82+
83+
#endif

0 commit comments

Comments
 (0)