Skip to content

Commit 1cee7b7

Browse files
Toilettraumaclaude
andcommitted
feat(util): add url_decode (RFC 3986 percent-decode)
The inverse of url_encode, which the header was missing — a parser reading a percent-encoded slug out of a URL needs to decode it back to the raw token. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent a0c5eff commit 1cee7b7

1 file changed

Lines changed: 33 additions & 0 deletions

File tree

include/aniparse/utility/UrlEncode.hpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,37 @@ inline std::string url_encode(std::string_view value) {
4545
return out;
4646
}
4747

48+
/**
49+
* @brief Percent-decode a string per RFC 3986 — the inverse of @ref url_encode.
50+
* Each "%XX" (two hex digits) becomes the byte it names; a malformed or truncated
51+
* escape is left verbatim. '+' is passed through unchanged: it is a literal here,
52+
* NOT a space — this decodes a path or opaque segment, not an
53+
* application/x-www-form-urlencoded body (a form decoder maps '+'→' ' first).
54+
* @param value Percent-encoded value
55+
* @return Decoded value
56+
*/
57+
inline std::string url_decode(std::string_view value) {
58+
auto hex_digit = [](unsigned char c) -> int {
59+
if (c >= '0' && c <= '9') return c - '0';
60+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
61+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
62+
return -1;
63+
};
64+
std::string out;
65+
out.reserve(value.size());
66+
for (size_t i = 0; i < value.size(); ++i) {
67+
if (value[i] == '%' && i + 2 < value.size()) {
68+
int hi = hex_digit(static_cast<unsigned char>(value[i + 1]));
69+
int lo = hex_digit(static_cast<unsigned char>(value[i + 2]));
70+
if (hi >= 0 && lo >= 0) {
71+
out.push_back(static_cast<char>(hi * 16 + lo));
72+
i += 2;
73+
continue;
74+
}
75+
}
76+
out.push_back(value[i]);
77+
}
78+
return out;
79+
}
80+
4881
} // namespace aniparse

0 commit comments

Comments
 (0)