#include "common.hh" #include "urlutil.hh" #include #include namespace url { namespace { constexpr char kHex[] = "0123456789ABCDEF"; std::optional 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& 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 expand_and_unescape_query( std::string_view query) { std::unordered_map 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