1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
|
#include "common.hh"
#include "urlutil.hh"
#include <optional>
#include <stdint.h>
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
|