summaryrefslogtreecommitdiff
path: root/src/urlutil.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@spawned.biz>2021-11-17 22:34:57 +0100
committerJoel Klinghed <the_jk@spawned.biz>2021-11-17 22:34:57 +0100
commit6232d13f5321b87ddf12a1aa36b4545da45f173d (patch)
tree23f3316470a14136debd9d02f9e920ca2b06f4cc /src/urlutil.cc
Travel3: Simple image and video display site
Reads the images and videos from filesystem and builds a site in memroy.
Diffstat (limited to 'src/urlutil.cc')
-rw-r--r--src/urlutil.cc138
1 files changed, 138 insertions, 0 deletions
diff --git a/src/urlutil.cc b/src/urlutil.cc
new file mode 100644
index 0000000..00ec713
--- /dev/null
+++ b/src/urlutil.cc
@@ -0,0 +1,138 @@
+#include "common.hh"
+
+#include "urlutil.hh"
+
+#include <optional>
+
+namespace url {
+
+namespace {
+
+constexpr char kHex[] = "0123456789ABCDEF";
+
+std::optional<uint8_t> unhex(char c) {
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return 10 + (c - 'A');
+ if (c >= 'a' && c <= 'f')
+ return 10 + (c - 'a');
+ return std::nullopt;
+}
+
+bool is_unreserved(char c) {
+ if (c >= 'A' && c <= 'Z')
+ return true;
+ if (c >= 'a' && c <= 'z')
+ return true;
+ if (c >= '0' && c <= '9')
+ return true;
+ return c == '-' || c == '_' || c == '.' || c == '~';
+}
+
+std::string query_unescape(std::string_view str) {
+ std::string ret;
+ size_t start = 0;
+ while (true) {
+ auto next = str.find('+', start);
+ if (next == std::string::npos) {
+ unescape(str.substr(start), ret);
+ break;
+ }
+ unescape(str.substr(start, next - start), ret);
+ ret.push_back(' ');
+ start = next + 1;
+ }
+ return ret;
+}
+
+} // namespace
+
+std::string escape(std::string_view str, EscapeFlags flags) {
+ std::string out;
+ escape(str, out, flags);
+ return out;
+}
+
+void escape(std::string_view str, std::string& out, EscapeFlags flags) {
+ out.reserve(out.size() + str.size());
+ bool const keep_slash =
+ (flags & EscapeFlags::KEEP_SLASH) == EscapeFlags::KEEP_SLASH;
+ for (char c : str) {
+ if (is_unreserved(c) || (c == '/' && keep_slash)) {
+ out.push_back(c);
+ } else {
+ out.push_back('%');
+ out.push_back(kHex[(c & 0xff) >> 4]);
+ out.push_back(kHex[(c & 0xff) & 0xf]);
+ }
+ }
+}
+
+std::string unescape(std::string_view str) {
+ std::string ret;
+ unescape(str, ret);
+ return ret;
+}
+
+void unescape(std::string_view str, std::string& out) {
+ out.reserve(out.size() + str.size());
+
+ size_t last = 0;
+ while (true) {
+ auto next = str.find('%', last);
+ if (next == std::string::npos || next + 3 > str.size())
+ break;
+ auto a = unhex(str[next + 1]);
+ auto b = unhex(str[next + 2]);
+ if (a && b) {
+ out.append(str, last, next - last);
+ out.push_back(a.value() << 4 | b.value());
+ } else {
+ // Keep invalid escape sequences as-is.
+ out.append(str, last, next + 3 - last);
+ }
+ last = next + 3;
+ }
+ out.append(str, last);
+}
+
+void split_and_unescape_path_and_query(
+ std::string_view url,
+ std::string& path,
+ std::unordered_map<std::string, std::string>& query) {
+ auto start = url.find('?');
+ if (start == std::string_view::npos) {
+ path = unescape(url);
+ query.clear();
+ } else {
+ path = unescape(url.substr(0, start));
+ query = expand_and_unescape_query(url.substr(start + 1));
+ }
+}
+
+std::unordered_map<std::string, std::string> expand_and_unescape_query(
+ std::string_view query) {
+ std::unordered_map<std::string, std::string> ret;
+ size_t start = 0;
+ while (true) {
+ auto next = query.find('&', start);
+ auto pair = next == std::string::npos
+ ? query.substr(start)
+ : query.substr(start, next - start);
+ auto eq = pair.find('=');
+ if (eq == std::string::npos) {
+ if (!pair.empty())
+ ret.emplace(query_unescape(pair), std::string());
+ } else {
+ ret.emplace(query_unescape(pair.substr(0, eq)),
+ query_unescape(pair.substr(eq + 1)));
+ }
+ if (next == std::string::npos)
+ break;
+ start = next + 1;
+ }
+ return ret;
+}
+
+} // namespace url