Skip to content

Commit 16a6174

Browse files
committed
Merge bitcoin/bitcoin#29904: refactor: Use our own implementation of urlDecode
992c714 common: Don't terminate on null character in UrlDecode (Fabian Jahr) 099fa57 scripted-diff: Modernize name of urlDecode function and param (Fabian Jahr) 8f39aaa refactor: Remove hooking code for urlDecode (Fabian Jahr) 650d43e refactor: Replace libevent use in urlDecode with our own code (Fabian Jahr) 46bc6c2 test: Add unit tests for urlDecode (Fabian Jahr) Pull request description: Fixes #29654 (as a side-effect) Removing dependencies is a general goal of the project and the xz backdoor has been an additional wake up call recently. Libevent shows many of the same symptoms, few maintainers and slow releases. While libevent can not be removed completely over night we should start removing it’s usage where it's possible, ideally with the end goal to removing it completely. This is a pretty easy win in that direction. The [`evhttp_uridecode` function from libevent](https://github.com/libevent/libevent/blob/e0a4574ba2cbcdb64bb2b593e72be7f7f4010746/http.c#L3542) we were using in `urlDecode` could be easily emulated in fewer LOC. This also ports the [applicable test vectors over from libevent](https://github.com/libevent/libevent/blob/master/test/regress_http.c#L3430). ACKs for top commit: achow101: ACK 992c714 theStack: Code-review ACK 992c714 maflcko: ACK 992c714 👈 stickies-v: ACK 992c714 Tree-SHA512: 78f76ae7ab3b6710eab2aaac20f55eb0da7803e057eaa6220e865f328666a5399ef1a479702aaf630b2f974ad3aa15e2b6adac9c11bc8c3d4be21e8af1667fea
2 parents a901178 + 992c714 commit 16a6174

File tree

13 files changed

+110
-31
lines changed

13 files changed

+110
-31
lines changed

configure.ac

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1707,7 +1707,6 @@ AM_CONDITIONAL([ENABLE_QT_TESTS], [test "$BUILD_TEST_QT" = "yes"])
17071707
AM_CONDITIONAL([ENABLE_BENCH], [test "$use_bench" = "yes"])
17081708
AM_CONDITIONAL([USE_QRCODE], [test "$use_qr" = "yes"])
17091709
AM_CONDITIONAL([USE_LCOV], [test "$use_lcov" = "yes"])
1710-
AM_CONDITIONAL([USE_LIBEVENT], [test "$use_libevent" = "yes"])
17111710
AM_CONDITIONAL([HARDEN], [test "$use_hardening" = "yes"])
17121711
AM_CONDITIONAL([ENABLE_SSE42], [test "$enable_sse42" = "yes"])
17131712
AM_CONDITIONAL([ENABLE_SSE41], [test "$enable_sse41" = "yes"])

src/Makefile.am

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -679,6 +679,7 @@ libbitcoin_common_a_SOURCES = \
679679
common/run_command.cpp \
680680
common/settings.cpp \
681681
common/system.cpp \
682+
common/url.cpp \
682683
compressor.cpp \
683684
core_read.cpp \
684685
core_write.cpp \
@@ -711,11 +712,6 @@ libbitcoin_common_a_SOURCES = \
711712
script/solver.cpp \
712713
warnings.cpp \
713714
$(BITCOIN_CORE_H)
714-
715-
if USE_LIBEVENT
716-
libbitcoin_common_a_CPPFLAGS += $(EVENT_CFLAGS)
717-
libbitcoin_common_a_SOURCES += common/url.cpp
718-
endif
719715
#
720716

721717
# util #

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ BITCOIN_TESTS =\
8585
test/checkqueue_tests.cpp \
8686
test/coins_tests.cpp \
8787
test/coinstatsindex_tests.cpp \
88+
test/common_url_tests.cpp \
8889
test/compilerbug_tests.cpp \
8990
test/compress_tests.cpp \
9091
test/crypto_tests.cpp \

src/bitcoin-cli.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <clientversion.h>
1212
#include <common/args.h>
1313
#include <common/system.h>
14-
#include <common/url.h>
1514
#include <compat/compat.h>
1615
#include <compat/stdin.h>
1716
#include <policy/feerate.h>
@@ -51,7 +50,6 @@
5150
using CliClock = std::chrono::system_clock;
5251

5352
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
54-
UrlDecodeFn* const URL_DECODE = urlDecode;
5553

5654
static const char DEFAULT_RPCCONNECT[] = "127.0.0.1";
5755
static const int DEFAULT_HTTP_CLIENT_TIMEOUT=900;

src/bitcoin-wallet.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <clientversion.h>
1212
#include <common/args.h>
1313
#include <common/system.h>
14-
#include <common/url.h>
1514
#include <compat/compat.h>
1615
#include <interfaces/init.h>
1716
#include <key.h>
@@ -28,7 +27,6 @@
2827
#include <tuple>
2928

3029
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
31-
UrlDecodeFn* const URL_DECODE = nullptr;
3230

3331
static void SetupWalletToolArgs(ArgsManager& argsman)
3432
{

src/bitcoind.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#include <common/args.h>
1313
#include <common/init.h>
1414
#include <common/system.h>
15-
#include <common/url.h>
1615
#include <compat/compat.h>
1716
#include <init.h>
1817
#include <interfaces/chain.h>
@@ -35,7 +34,6 @@
3534
using node::NodeContext;
3635

3736
const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr;
38-
UrlDecodeFn* const URL_DECODE = urlDecode;
3937

4038
#if HAVE_DECL_FORK
4139

src/common/url.cpp

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,36 @@
44

55
#include <common/url.h>
66

7-
#include <event2/http.h>
8-
9-
#include <cstdlib>
7+
#include <charconv>
108
#include <string>
9+
#include <string_view>
10+
#include <system_error>
1111

12-
std::string urlDecode(const std::string &urlEncoded) {
12+
std::string UrlDecode(std::string_view url_encoded)
13+
{
1314
std::string res;
14-
if (!urlEncoded.empty()) {
15-
char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, nullptr);
16-
if (decoded) {
17-
res = std::string(decoded);
18-
free(decoded);
15+
res.reserve(url_encoded.size());
16+
17+
for (size_t i = 0; i < url_encoded.size(); ++i) {
18+
char c = url_encoded[i];
19+
// Special handling for percent which should be followed by two hex digits
20+
// representing an octet values, see RFC 3986, Section 2.1 Percent-Encoding
21+
if (c == '%' && i + 2 < url_encoded.size()) {
22+
unsigned int decoded_value{0};
23+
auto [p, ec] = std::from_chars(url_encoded.data() + i + 1, url_encoded.data() + i + 3, decoded_value, 16);
24+
25+
// Only if there is no error and the pointer is set to the end of
26+
// the string, we can be sure both characters were valid hex
27+
if (ec == std::errc{} && p == url_encoded.data() + i + 3) {
28+
res += static_cast<char>(decoded_value);
29+
// Next two characters are part of the percent encoding
30+
i += 2;
31+
continue;
32+
}
33+
// In case of invalid percent encoding, add the '%' and continue
1934
}
35+
res += c;
2036
}
37+
2138
return res;
2239
}

src/common/url.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
#define BITCOIN_COMMON_URL_H
77

88
#include <string>
9+
#include <string_view>
910

10-
using UrlDecodeFn = std::string(const std::string& url_encoded);
11-
UrlDecodeFn urlDecode;
12-
extern UrlDecodeFn* const URL_DECODE;
11+
/* Decode a URL.
12+
*
13+
* Notably this implementation does not decode a '+' to a ' '.
14+
*/
15+
std::string UrlDecode(std::string_view url_encoded);
1316

1417
#endif // BITCOIN_COMMON_URL_H

src/qt/main.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
#include <qt/bitcoin.h>
66

7-
#include <common/url.h>
87
#include <compat/compat.h>
98
#include <util/translation.h>
109

@@ -17,7 +16,6 @@
1716
extern const std::function<std::string(const char*)> G_TRANSLATION_FUN = [](const char* psz) {
1817
return QCoreApplication::translate("bitcoin-core", psz).toStdString();
1918
};
20-
UrlDecodeFn* const URL_DECODE = urlDecode;
2119

2220
const std::function<std::string()> G_TEST_GET_FULL_NAME{};
2321

src/test/common_url_tests.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) 2024-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or https://opensource.org/license/mit/.
4+
5+
#include <common/url.h>
6+
7+
#include <string>
8+
9+
#include <boost/test/unit_test.hpp>
10+
11+
BOOST_AUTO_TEST_SUITE(common_url_tests)
12+
13+
// These test vectors were ported from test/regress.c in the libevent library
14+
// which used to be a dependency of the UrlDecode function.
15+
16+
BOOST_AUTO_TEST_CASE(encode_decode_test) {
17+
BOOST_CHECK_EQUAL(UrlDecode("Hello"), "Hello");
18+
BOOST_CHECK_EQUAL(UrlDecode("99"), "99");
19+
BOOST_CHECK_EQUAL(UrlDecode(""), "");
20+
BOOST_CHECK_EQUAL(UrlDecode("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_"),
21+
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-.~_");
22+
BOOST_CHECK_EQUAL(UrlDecode("%20"), " ");
23+
BOOST_CHECK_EQUAL(UrlDecode("%FF%F0%E0"), "\xff\xf0\xe0");
24+
BOOST_CHECK_EQUAL(UrlDecode("%01%19"), "\x01\x19");
25+
BOOST_CHECK_EQUAL(UrlDecode("http%3A%2F%2Fwww.ietf.org%2Frfc%2Frfc3986.txt"),
26+
"http://www.ietf.org/rfc/rfc3986.txt");
27+
BOOST_CHECK_EQUAL(UrlDecode("1%2B2%3D3"), "1+2=3");
28+
}
29+
30+
BOOST_AUTO_TEST_CASE(decode_malformed_test) {
31+
BOOST_CHECK_EQUAL(UrlDecode("%%xhello th+ere \xff"), "%%xhello th+ere \xff");
32+
33+
BOOST_CHECK_EQUAL(UrlDecode("%"), "%");
34+
BOOST_CHECK_EQUAL(UrlDecode("%%"), "%%");
35+
BOOST_CHECK_EQUAL(UrlDecode("%%%"), "%%%");
36+
BOOST_CHECK_EQUAL(UrlDecode("%%%%"), "%%%%");
37+
38+
BOOST_CHECK_EQUAL(UrlDecode("+"), "+");
39+
BOOST_CHECK_EQUAL(UrlDecode("++"), "++");
40+
41+
BOOST_CHECK_EQUAL(UrlDecode("?"), "?");
42+
BOOST_CHECK_EQUAL(UrlDecode("??"), "??");
43+
44+
BOOST_CHECK_EQUAL(UrlDecode("%G1"), "%G1");
45+
BOOST_CHECK_EQUAL(UrlDecode("%2"), "%2");
46+
BOOST_CHECK_EQUAL(UrlDecode("%ZX"), "%ZX");
47+
48+
BOOST_CHECK_EQUAL(UrlDecode("valid%20string%G1"), "valid string%G1");
49+
BOOST_CHECK_EQUAL(UrlDecode("%20invalid%ZX"), " invalid%ZX");
50+
BOOST_CHECK_EQUAL(UrlDecode("%20%G1%ZX"), " %G1%ZX");
51+
52+
BOOST_CHECK_EQUAL(UrlDecode("%1 "), "%1 ");
53+
BOOST_CHECK_EQUAL(UrlDecode("% 9"), "% 9");
54+
BOOST_CHECK_EQUAL(UrlDecode(" %Z "), " %Z ");
55+
BOOST_CHECK_EQUAL(UrlDecode(" % X"), " % X");
56+
57+
BOOST_CHECK_EQUAL(UrlDecode("%-1"), "%-1");
58+
BOOST_CHECK_EQUAL(UrlDecode("%1-"), "%1-");
59+
}
60+
61+
BOOST_AUTO_TEST_CASE(decode_lowercase_hex_test) {
62+
BOOST_CHECK_EQUAL(UrlDecode("%f0%a0%b0"), "\xf0\xa0\xb0");
63+
}
64+
65+
BOOST_AUTO_TEST_CASE(decode_internal_nulls_test) {
66+
std::string result1{"\0\0x\0\0", 5};
67+
BOOST_CHECK_EQUAL(UrlDecode("%00%00x%00%00"), result1);
68+
std::string result2{"abc\0\0", 5};
69+
BOOST_CHECK_EQUAL(UrlDecode("abc%00%00"), result2);
70+
}
71+
72+
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)