Line data Source code
1 : #include "url.hpp"
2 :
3 : namespace thinger::http::util::url{
4 :
5 : namespace {
6 : constexpr char hex_chars[] = "0123456789ABCDEF";
7 :
8 248 : inline int hex_digit(char c) {
9 248 : if (c >= '0' && c <= '9') return c - '0';
10 69 : if (c >= 'a' && c <= 'f') return c - 'a' + 10;
11 66 : if (c >= 'A' && c <= 'F') return c - 'A' + 10;
12 18 : return -1;
13 : }
14 :
15 1158 : inline bool is_unreserved(char c) {
16 1158 : return std::isalnum(static_cast<unsigned char>(c))
17 1158 : || c == '-' || c == '_' || c == '.' || c == '~';
18 : }
19 : }
20 :
21 : // RFC 3986 Section 2.3: unreserved characters are not percent-encoded
22 168 : std::string url_encode(const std::string &value) {
23 168 : std::string result;
24 168 : result.reserve(value.size() * 1.2);
25 :
26 1005 : for (unsigned char c : value) {
27 837 : if (is_unreserved(c)) {
28 726 : result += static_cast<char>(c);
29 : } else {
30 111 : result += '%';
31 111 : result += hex_chars[c >> 4];
32 111 : result += hex_chars[c & 0x0F];
33 : }
34 : }
35 :
36 168 : return result;
37 0 : }
38 :
39 : // RFC 3986 path encoding: also preserves '/' and '~'
40 36 : std::string uri_path_encode(const std::string &value) {
41 36 : std::string result;
42 36 : result.reserve(value.size() * 1.2);
43 :
44 357 : for (unsigned char c : value) {
45 321 : if (is_unreserved(c) || c == '/' ) {
46 312 : result += static_cast<char>(c);
47 : } else {
48 9 : result += '%';
49 9 : result += hex_chars[c >> 4];
50 9 : result += hex_chars[c & 0x0F];
51 : }
52 : }
53 :
54 36 : return result;
55 0 : }
56 :
57 3357 : bool url_decode(const std::string &in, std::string &out) {
58 3357 : out.clear();
59 3357 : out.reserve(in.size());
60 :
61 27575 : for (size_t i = 0; i < in.size(); ++i) {
62 24233 : if (in[i] == '%') {
63 130 : if (i + 2 >= in.size()) return false;
64 124 : int hi = hex_digit(in[i + 1]);
65 124 : int lo = hex_digit(in[i + 2]);
66 124 : if (hi < 0 || lo < 0) return false;
67 115 : out += static_cast<char>((hi << 4) | lo);
68 115 : i += 2;
69 24103 : } else if (in[i] == '+') {
70 8 : out += ' ';
71 : } else {
72 24095 : out += in[i];
73 : }
74 : }
75 3342 : return true;
76 : }
77 :
78 2410 : std::string url_decode(const std::string &in) {
79 2410 : std::string out;
80 2410 : if (url_decode(in, out)) {
81 2407 : return out;
82 : }
83 3 : return {};
84 2410 : }
85 :
86 30 : void parse_url_encoded_data(const std::string &data, std::multimap<std::string, std::string>& store) {
87 30 : auto start = data.cbegin();
88 30 : auto end = data.cend();
89 30 : parse_url_encoded_data(start, end, store);
90 30 : }
91 :
92 188 : void parse_url_encoded_data(std::string::const_iterator& start, std::string::const_iterator& end, std::multimap<std::string, std::string>& store) {
93 188 : if (start == end) return;
94 :
95 182 : auto it = start;
96 401 : while (it != end) {
97 : // find the end of the current key=value pair
98 219 : auto pair_end = std::find(it, end, '&');
99 :
100 : // find the '=' separator within the pair
101 219 : auto eq_pos = std::find(it, pair_end, '=');
102 :
103 219 : std::string key(it, eq_pos);
104 219 : std::string value;
105 219 : if (eq_pos != pair_end) {
106 216 : value.assign(eq_pos + 1, pair_end);
107 : }
108 :
109 219 : if (!key.empty()) {
110 219 : store.emplace(url_decode(key), url_decode(value));
111 : }
112 :
113 219 : it = (pair_end != end) ? pair_end + 1 : end;
114 219 : }
115 :
116 182 : start = it;
117 : }
118 :
119 36 : std::string get_url_encoded_data(const std::multimap<std::string, std::string>& store) {
120 36 : std::string result;
121 36 : bool first = true;
122 99 : for (const auto& [key, value] : store) {
123 63 : if (!first) result += '&';
124 63 : result += url_encode(key);
125 63 : result += '=';
126 63 : result += url_encode(value);
127 63 : first = false;
128 : }
129 36 : return result;
130 0 : }
131 :
132 : }
|