]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Add util::percent_decode
authorJoel Rosdahl <joel@rosdahl.net>
Mon, 21 Jun 2021 18:38:49 +0000 (20:38 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 27 Jun 2021 07:01:19 +0000 (09:01 +0200)
src/util/string_utils.cpp
src/util/string_utils.hpp
unittest/test_util_string_utils.cpp

index da951e5998e34248a710d04900c5a6f74abc2ebe..e6ff892a4a9e8b7204cd934c14e7240dd95e428f 100644 (file)
 
 #include "string_utils.hpp"
 
+#include <FormatNonstdStringView.hpp>
+#include <fmtmacros.hpp>
+
+// System headers
+#include <cctype>
+// End of system headers
+
 namespace util {
 
+nonstd::expected<std::string, std::string>
+percent_decode(nonstd::string_view string)
+{
+  const auto from_hex = [](const char digit) {
+    return static_cast<uint8_t>(
+      std::isdigit(digit) ? digit - '0' : std::tolower(digit) - 'a' + 10);
+  };
+
+  std::string result;
+  result.reserve(string.size());
+  for (size_t i = 0; i < string.size(); ++i) {
+    if (string[i] != '%') {
+      result += string[i];
+    } else if (i + 2 >= string.size() || !std::isxdigit(string[i + 1])
+               || !std::isxdigit(string[i + 2])) {
+      return nonstd::make_unexpected(
+        FMT("invalid percent-encoded string at position {}: {}", i, string));
+    } else {
+      const char ch = static_cast<char>(from_hex(string[i + 1]) << 4
+                                        | from_hex(string[i + 2]));
+      result += ch;
+      i += 2;
+    }
+  }
+
+  return result;
+}
+
 std::pair<nonstd::string_view, nonstd::optional<nonstd::string_view>>
 split_once(const nonstd::string_view string, const char split_char)
 {
index 7b27441866386a8e5ae56b8a29178f4ad540b89e..7f23b0fe4bed6959e80569c73b28ffc1e9c957ae 100644 (file)
@@ -18,6 +18,7 @@
 
 #pragma once
 
+#include <third_party/nonstd/expected.hpp>
 #include <third_party/nonstd/optional.hpp>
 #include <third_party/nonstd/string_view.hpp>
 
 
 namespace util {
 
+// Percent-decode[1] `string`.
+//
+// [1]: https://en.wikipedia.org/wiki/Percent-encoding
+nonstd::expected<std::string, std::string>
+percent_decode(nonstd::string_view string);
+
 // Split `string` into two parts using `split_char` as the delimiter. The second
 // part will be `nullopt` if there is no `split_char` in `string.`
 std::pair<nonstd::string_view, nonstd::optional<nonstd::string_view>>
index ea74dd007fe579b320bd23d460a44bbdff21713d..5df0159b908dc84447fb579bfa6df72c68c2b0a3 100644 (file)
@@ -28,6 +28,27 @@ operator==(
   return left.first == right.first && left.second == right.second;
 }
 
+TEST_CASE("util::percent_decode")
+{
+  CHECK(util::percent_decode("") == "");
+  CHECK(util::percent_decode("a") == "a");
+  CHECK(util::percent_decode("%61") == "a");
+  CHECK(util::percent_decode("%ab") == "\xab");
+  CHECK(util::percent_decode("%aB") == "\xab");
+  CHECK(util::percent_decode("%Ab") == "\xab");
+  CHECK(util::percent_decode("%AB") == "\xab");
+  CHECK(util::percent_decode("a%25b%7cc") == "a%b|c");
+
+  CHECK(util::percent_decode("%").error()
+        == "invalid percent-encoded string at position 0: %");
+  CHECK(util::percent_decode("%6").error()
+        == "invalid percent-encoded string at position 0: %6");
+  CHECK(util::percent_decode("%%").error()
+        == "invalid percent-encoded string at position 0: %%");
+  CHECK(util::percent_decode("a%0g").error()
+        == "invalid percent-encoded string at position 1: a%0g");
+}
+
 TEST_CASE("util::split_once")
 {
   using nonstd::nullopt;