blob: 5e28e186d08e2552d8a1e3456b8e0a815e265b10 [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
2
Ed Tanous911ac312017-08-15 09:37:42 -07003#include <cstdio>
4#include <cstring>
Ed Tanous7045c8d2017-04-03 10:04:37 -07005#include <iostream>
6#include <string>
7#include <vector>
8
9namespace crow {
10// ----------------------------------------------------------------------------
11// qs_parse (modified)
12// https://github.com/bartgrantham/qs_parse
13// ----------------------------------------------------------------------------
14/* Similar to strncmp, but handles URL-encoding for either string */
15int qs_strncmp(const char* s, const char* qs, size_t n);
16
17/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
18 * Also decodes the value portion of the k/v pair *in-place*. In a future
19 * enhancement it will also have a compile-time option of sorting qs_kv
20 * alphabetically by key. */
21int qs_parse(char* qs, char* qs_kv[], int qs_kv_size);
22
23/* Used by qs_parse to decode the value portion of a k/v pair */
24int qs_decode(char* qs);
25
26/* Looks up the value according to the key on a pre-processed query string
27 * A future enhancement will be a compile-time option to look up the key
28 * in a pre-sorted qs_kv array via a binary search. */
29// char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
30char* qs_k2v(const char* key, char* const* qs_kv, int qs_kv_size, int nth);
31
32/* Non-destructive lookup of value, based on key. User provides the
33 * destinaton string and length. */
34char* qs_scanvalue(const char* key, const char* qs, char* val, size_t val_len);
35
36// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
37#undef _qsSORTING
38
39// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
40#define CROW_QS_ISHEX(x) \
41 ((((x) >= '0' && (x) <= '9') || ((x) >= 'A' && (x) <= 'F') || \
42 ((x) >= 'a' && (x) <= 'f')) \
43 ? 1 \
44 : 0)
45#define CROW_QS_HEX2DEC(x) \
46 (((x) >= '0' && (x) <= '9') \
47 ? (x)-48 \
48 : ((x) >= 'A' && (x) <= 'F') ? (x)-55 \
49 : ((x) >= 'a' && (x) <= 'f') ? (x)-87 : 0)
50#define CROW_QS_ISQSCHR(x) \
51 ((((x) == '=') || ((x) == '#') || ((x) == '&') || ((x) == '\0')) ? 0 : 1)
52
53inline int qs_strncmp(const char* s, const char* qs, size_t n) {
54 int i = 0;
55 unsigned char u1, u2, unyb, lnyb;
56
57 while (n-- > 0) {
Ed Tanous911ac312017-08-15 09:37:42 -070058 u1 = static_cast<unsigned char>(*s++);
59 u2 = static_cast<unsigned char>(*qs++);
Ed Tanous7045c8d2017-04-03 10:04:37 -070060
61 if (!CROW_QS_ISQSCHR(u1)) {
62 u1 = '\0';
63 }
64 if (!CROW_QS_ISQSCHR(u2)) {
65 u2 = '\0';
66 }
67
68 if (u1 == '+') {
69 u1 = ' ';
70 }
71 if (u1 == '%') // easier/safer than scanf
72 {
Ed Tanous911ac312017-08-15 09:37:42 -070073 unyb = static_cast<unsigned char>(*s++);
74 lnyb = static_cast<unsigned char>(*s++);
75 if (CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb)) {
Ed Tanous7045c8d2017-04-03 10:04:37 -070076 u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
Ed Tanous911ac312017-08-15 09:37:42 -070077 } else {
Ed Tanous7045c8d2017-04-03 10:04:37 -070078 u1 = '\0';
Ed Tanous911ac312017-08-15 09:37:42 -070079 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070080 }
81
82 if (u2 == '+') {
83 u2 = ' ';
84 }
85 if (u2 == '%') // easier/safer than scanf
86 {
Ed Tanous911ac312017-08-15 09:37:42 -070087 unyb = static_cast<unsigned char>(*qs++);
88 lnyb = static_cast<unsigned char>(*qs++);
89 if (CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb)) {
Ed Tanous7045c8d2017-04-03 10:04:37 -070090 u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
Ed Tanous911ac312017-08-15 09:37:42 -070091 } else {
Ed Tanous7045c8d2017-04-03 10:04:37 -070092 u2 = '\0';
Ed Tanous911ac312017-08-15 09:37:42 -070093 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070094 }
95
Ed Tanous911ac312017-08-15 09:37:42 -070096 if (u1 != u2) {
97 return u1 - u2;
98 }
99 if (u1 == '\0') {
100 return 0;
101 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700102 i++;
103 }
Ed Tanous911ac312017-08-15 09:37:42 -0700104 if (CROW_QS_ISQSCHR(*qs)) {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700105 return -1;
Ed Tanous911ac312017-08-15 09:37:42 -0700106 } else {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700107 return 0;
Ed Tanous911ac312017-08-15 09:37:42 -0700108 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700109}
110
111inline int qs_parse(char* qs, char* qs_kv[], int qs_kv_size) {
112 int i, j;
113 char* substr_ptr;
114
Ed Tanous911ac312017-08-15 09:37:42 -0700115 for (i = 0; i < qs_kv_size; i++) {
116 qs_kv[i] = NULL;
117 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700118
119 // find the beginning of the k/v substrings or the fragment
120 substr_ptr = qs + strcspn(qs, "?#");
Ed Tanous911ac312017-08-15 09:37:42 -0700121 if (substr_ptr[0] != '\0') {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700122 substr_ptr++;
Ed Tanous911ac312017-08-15 09:37:42 -0700123 } else {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700124 return 0; // no query or fragment
Ed Tanous911ac312017-08-15 09:37:42 -0700125 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700126
127 i = 0;
128 while (i < qs_kv_size) {
129 qs_kv[i] = substr_ptr;
130 j = strcspn(substr_ptr, "&");
131 if (substr_ptr[j] == '\0') {
132 break;
133 }
134 substr_ptr += j + 1;
135 i++;
136 }
137 i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
138
139 // we only decode the values in place, the keys could have '='s in them
140 // which will hose our ability to distinguish keys from values later
141 for (j = 0; j < i; j++) {
142 substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
143 if (substr_ptr[0] == '&' ||
Ed Tanous911ac312017-08-15 09:37:42 -0700144 substr_ptr[0] == '\0') { // blank value: skip decoding
Ed Tanous7045c8d2017-04-03 10:04:37 -0700145 substr_ptr[0] = '\0';
Ed Tanous911ac312017-08-15 09:37:42 -0700146 } else {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700147 qs_decode(++substr_ptr);
Ed Tanous911ac312017-08-15 09:37:42 -0700148 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700149 }
150
151#ifdef _qsSORTING
152// TODO: qsort qs_kv, using qs_strncmp() for the comparison
153#endif
154
155 return i;
156}
157
158inline int qs_decode(char* qs) {
159 int i = 0, j = 0;
160
161 while (CROW_QS_ISQSCHR(qs[j])) {
162 if (qs[j] == '+') {
163 qs[i] = ' ';
164 } else if (qs[j] == '%') // easier/safer than scanf
165 {
166 if (!CROW_QS_ISHEX(qs[j + 1]) || !CROW_QS_ISHEX(qs[j + 2])) {
167 qs[i] = '\0';
168 return i;
169 }
170 qs[i] = (CROW_QS_HEX2DEC(qs[j + 1]) * 16) + CROW_QS_HEX2DEC(qs[j + 2]);
171 j += 2;
172 } else {
173 qs[i] = qs[j];
174 }
175 i++;
176 j++;
177 }
178 qs[i] = '\0';
179
180 return i;
181}
182
183inline char* qs_k2v(const char* key, char* const* qs_kv, int qs_kv_size,
184 int nth = 0) {
185 int i;
186 size_t key_len, skip;
187
188 key_len = strlen(key);
189
190#ifdef _qsSORTING
191// TODO: binary search for key in the sorted qs_kv
192#else // _qsSORTING
193 for (i = 0; i < qs_kv_size; i++) {
194 // we rely on the unambiguous '=' to find the value in our k/v pair
195 if (qs_strncmp(key, qs_kv[i], key_len) == 0) {
196 skip = strcspn(qs_kv[i], "=");
Ed Tanous911ac312017-08-15 09:37:42 -0700197 if (qs_kv[i][skip] == '=') {
198 skip++;
199 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700200 // return (zero-char value) ? ptr to trailing '\0' : ptr to value
Ed Tanous911ac312017-08-15 09:37:42 -0700201 if (nth == 0) {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700202 return qs_kv[i] + skip;
Ed Tanous911ac312017-08-15 09:37:42 -0700203 } else {
Ed Tanous7045c8d2017-04-03 10:04:37 -0700204 --nth;
Ed Tanous911ac312017-08-15 09:37:42 -0700205 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700206 }
207 }
208#endif // _qsSORTING
209
210 return NULL;
211}
212
213inline char* qs_scanvalue(const char* key, const char* qs, char* val,
214 size_t val_len) {
215 size_t i, key_len;
216 const char* tmp;
217
218 // find the beginning of the k/v substrings
Ed Tanous911ac312017-08-15 09:37:42 -0700219 if ((tmp = strchr(qs, '?')) != NULL) {
220 qs = tmp + 1;
221 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700222
223 key_len = strlen(key);
224 while (qs[0] != '#' && qs[0] != '\0') {
Ed Tanous911ac312017-08-15 09:37:42 -0700225 if (qs_strncmp(key, qs, key_len) == 0) {
226 break;
227 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700228 qs += strcspn(qs, "&") + 1;
229 }
230
Ed Tanous911ac312017-08-15 09:37:42 -0700231 if (qs[0] == '\0') {
232 return NULL;
233 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700234
235 qs += strcspn(qs, "=&#");
236 if (qs[0] == '=') {
237 qs++;
238 i = strcspn(qs, "&=#");
239 strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1));
240 qs_decode(val);
241 } else {
Ed Tanous911ac312017-08-15 09:37:42 -0700242 if (val_len > 0) {
243 val[0] = '\0';
244 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700245 }
246
247 return val;
248}
Ed Tanous911ac312017-08-15 09:37:42 -0700249} // namespace crow
Ed Tanous7045c8d2017-04-03 10:04:37 -0700250// ----------------------------------------------------------------------------
251
252namespace crow {
253class query_string {
254 public:
255 static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
256
Ed Tanous911ac312017-08-15 09:37:42 -0700257 query_string() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700258
259 query_string(const query_string& qs) : url_(qs.url_) {
260 for (auto p : qs.key_value_pairs_) {
Ed Tanous911ac312017-08-15 09:37:42 -0700261 key_value_pairs_.push_back(
262 const_cast<char*>(p - qs.url_.c_str() + url_.c_str()));
Ed Tanous7045c8d2017-04-03 10:04:37 -0700263 }
264 }
265
266 query_string& operator=(const query_string& qs) {
267 url_ = qs.url_;
268 key_value_pairs_.clear();
269 for (auto p : qs.key_value_pairs_) {
Ed Tanous911ac312017-08-15 09:37:42 -0700270 key_value_pairs_.push_back(
271 const_cast<char*>(p - qs.url_.c_str() + url_.c_str()));
Ed Tanous7045c8d2017-04-03 10:04:37 -0700272 }
273 return *this;
274 }
275
276 query_string& operator=(query_string&& qs) {
277 key_value_pairs_ = std::move(qs.key_value_pairs_);
Ed Tanous911ac312017-08-15 09:37:42 -0700278 auto* old_data = const_cast<char*>(qs.url_.c_str());
Ed Tanous7045c8d2017-04-03 10:04:37 -0700279 url_ = std::move(qs.url_);
280 for (auto& p : key_value_pairs_) {
Ed Tanous911ac312017-08-15 09:37:42 -0700281 p += const_cast<char*>(url_.c_str()) - old_data;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700282 }
283 return *this;
284 }
285
Ed Tanous911ac312017-08-15 09:37:42 -0700286 explicit query_string(std::string url) : url_(std::move(url)) {
287 if (url_.empty()) {
288 return;
289 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700290
291 key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
292
293 int count =
294 qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
295 key_value_pairs_.resize(count);
296 }
297
298 void clear() {
299 key_value_pairs_.clear();
300 url_.clear();
301 }
302
303 friend std::ostream& operator<<(std::ostream& os, const query_string& qs) {
304 os << "[ ";
305 for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) {
Ed Tanous911ac312017-08-15 09:37:42 -0700306 if (i != 0u) {
307 os << ", ";
308 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700309 os << qs.key_value_pairs_[i];
310 }
311 os << " ]";
312 return os;
313 }
314
315 char* get(const std::string& name) const {
316 char* ret =
317 qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
318 return ret;
319 }
320
321 std::vector<char*> get_list(const std::string& name) const {
322 std::vector<char*> ret;
323 std::string plus = name + "[]";
324 char* element = nullptr;
325
326 int count = 0;
327 while (1) {
328 element = qs_k2v(plus.c_str(), key_value_pairs_.data(),
329 key_value_pairs_.size(), count++);
Ed Tanous911ac312017-08-15 09:37:42 -0700330 if (element == nullptr) {
331 break;
332 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700333 ret.push_back(element);
334 }
335 return ret;
336 }
337
338 private:
339 std::string url_;
340 std::vector<char*> key_value_pairs_;
341};
342
Ed Tanous911ac312017-08-15 09:37:42 -0700343} // namespace crow