blob: 959eb68587b7d7963fd595a62d924af15b648beb [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
Ed Tanous1abe55e2018-09-05 08:30:59 -07009namespace crow
10{
Ed Tanous7045c8d2017-04-03 10:04:37 -070011// ----------------------------------------------------------------------------
12// qs_parse (modified)
13// https://github.com/bartgrantham/qs_parse
14// ----------------------------------------------------------------------------
15/* Similar to strncmp, but handles URL-encoding for either string */
Ed Tanous55c7b7a2018-05-22 15:27:24 -070016int qsStrncmp(const char* s, const char* qs, size_t n);
Ed Tanous7045c8d2017-04-03 10:04:37 -070017
18/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
19 * Also decodes the value portion of the k/v pair *in-place*. In a future
20 * enhancement it will also have a compile-time option of sorting qs_kv
21 * alphabetically by key. */
Ed Tanous271584a2019-07-09 16:24:22 -070022size_t qsParse(char* qs, char* qs_kv[], size_t qs_kv_size);
Ed Tanous7045c8d2017-04-03 10:04:37 -070023
24/* Used by qs_parse to decode the value portion of a k/v pair */
Ed Tanousb01bf292019-03-25 19:25:26 +000025int qsDecode(char* qs);
Ed Tanous7045c8d2017-04-03 10:04:37 -070026
27/* Looks up the value according to the key on a pre-processed query string
28 * A future enhancement will be a compile-time option to look up the key
29 * in a pre-sorted qs_kv array via a binary search. */
30// char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
Ed Tanousb01bf292019-03-25 19:25:26 +000031char* qsK2v(const char* key, char* const* qs_kv, int qs_kv_size, int nth);
Ed Tanous7045c8d2017-04-03 10:04:37 -070032
33/* Non-destructive lookup of value, based on key. User provides the
34 * destinaton string and length. */
Ed Tanous55c7b7a2018-05-22 15:27:24 -070035char* qsScanvalue(const char* key, const char* qs, char* val, size_t val_len);
Ed Tanous7045c8d2017-04-03 10:04:37 -070036
37// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
38#undef _qsSORTING
39
40// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
Ed Tanous1abe55e2018-09-05 08:30:59 -070041#define BMCWEB_QS_ISHEX(x) \
42 ((((x) >= '0' && (x) <= '9') || ((x) >= 'A' && (x) <= 'F') || \
43 ((x) >= 'a' && (x) <= 'f')) \
44 ? 1 \
45 : 0)
46#define BMCWEB_QS_HEX2DEC(x) \
47 (((x) >= '0' && (x) <= '9') \
48 ? (x)-48 \
49 : ((x) >= 'A' && (x) <= 'F') \
50 ? (x)-55 \
51 : ((x) >= 'a' && (x) <= 'f') ? (x)-87 : 0)
52#define BMCWEB_QS_ISQSCHR(x) \
53 ((((x) == '=') || ((x) == '#') || ((x) == '&') || ((x) == '\0')) ? 0 : 1)
Ed Tanous7045c8d2017-04-03 10:04:37 -070054
Ed Tanous1abe55e2018-09-05 08:30:59 -070055inline int qsStrncmp(const char* s, const char* qs, size_t n)
56{
57 int i = 0;
Ed Tanous271584a2019-07-09 16:24:22 -070058 char u1, u2;
59 char unyb, lnyb;
Ed Tanous7045c8d2017-04-03 10:04:37 -070060
Ed Tanous1abe55e2018-09-05 08:30:59 -070061 while (n-- > 0)
Ed Tanous7045c8d2017-04-03 10:04:37 -070062 {
Ed Tanous271584a2019-07-09 16:24:22 -070063 u1 = *s++;
64 u2 = *qs++;
Ed Tanous7045c8d2017-04-03 10:04:37 -070065
Ed Tanous1abe55e2018-09-05 08:30:59 -070066 if (!BMCWEB_QS_ISQSCHR(u1))
67 {
68 u1 = '\0';
69 }
70 if (!BMCWEB_QS_ISQSCHR(u2))
71 {
72 u2 = '\0';
73 }
74
75 if (u1 == '+')
76 {
77 u1 = ' ';
78 }
79 if (u1 == '%') // easier/safer than scanf
80 {
Ed Tanous271584a2019-07-09 16:24:22 -070081 unyb = static_cast<char>(*s++);
82 lnyb = static_cast<char>(*s++);
Ed Tanous1abe55e2018-09-05 08:30:59 -070083 if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb))
84 {
Ed Tanous271584a2019-07-09 16:24:22 -070085 u1 = static_cast<char>((BMCWEB_QS_HEX2DEC(unyb) * 16) +
86 BMCWEB_QS_HEX2DEC(lnyb));
Ed Tanous1abe55e2018-09-05 08:30:59 -070087 }
88 else
89 {
90 u1 = '\0';
91 }
92 }
93
94 if (u2 == '+')
95 {
96 u2 = ' ';
97 }
98 if (u2 == '%') // easier/safer than scanf
99 {
Ed Tanous271584a2019-07-09 16:24:22 -0700100 unyb = static_cast<char>(*qs++);
101 lnyb = static_cast<char>(*qs++);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700102 if (BMCWEB_QS_ISHEX(unyb) && BMCWEB_QS_ISHEX(lnyb))
103 {
Ed Tanous271584a2019-07-09 16:24:22 -0700104 u2 = static_cast<char>((BMCWEB_QS_HEX2DEC(unyb) * 16) +
105 BMCWEB_QS_HEX2DEC(lnyb));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700106 }
107 else
108 {
109 u2 = '\0';
110 }
111 }
112
113 if (u1 != u2)
114 {
115 return u1 - u2;
116 }
117 if (u1 == '\0')
118 {
119 return 0;
120 }
121 i++;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700122 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700123 if (BMCWEB_QS_ISQSCHR(*qs))
Ed Tanous7045c8d2017-04-03 10:04:37 -0700124 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700125 return -1;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700126 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700127 else
128 {
129 return 0;
Ed Tanous911ac312017-08-15 09:37:42 -0700130 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700131}
132
Ed Tanous271584a2019-07-09 16:24:22 -0700133inline size_t qsParse(char* qs, char* qs_kv[], size_t qs_kv_size)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700134{
Ed Tanous271584a2019-07-09 16:24:22 -0700135 size_t i;
136 size_t j;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700137 char* substrPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700138
Ed Tanous1abe55e2018-09-05 08:30:59 -0700139 for (i = 0; i < qs_kv_size; i++)
140 {
141 qs_kv[i] = NULL;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700142 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700143
Ed Tanous1abe55e2018-09-05 08:30:59 -0700144 // find the beginning of the k/v substrings or the fragment
145 substrPtr = qs + strcspn(qs, "?#");
146 if (substrPtr[0] != '\0')
147 {
148 substrPtr++;
Ed Tanous911ac312017-08-15 09:37:42 -0700149 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700150 else
151 {
152 return 0; // no query or fragment
153 }
154
155 i = 0;
156 while (i < qs_kv_size)
157 {
158 qs_kv[i] = substrPtr;
159 j = strcspn(substrPtr, "&");
160 if (substrPtr[j] == '\0')
161 {
162 break;
163 }
164 substrPtr += j + 1;
165 i++;
166 }
167 i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
168
169 // we only decode the values in place, the keys could have '='s in them
170 // which will hose our ability to distinguish keys from values later
171 for (j = 0; j < i; j++)
172 {
173 substrPtr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
174 if (substrPtr[0] == '&' || substrPtr[0] == '\0')
175 { // blank value: skip decoding
176 substrPtr[0] = '\0';
177 }
178 else
179 {
180 qsDecode(++substrPtr);
181 }
182 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700183
184#ifdef _qsSORTING
185// TODO: qsort qs_kv, using qs_strncmp() for the comparison
186#endif
187
Ed Tanous1abe55e2018-09-05 08:30:59 -0700188 return i;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700189}
190
Ed Tanousb01bf292019-03-25 19:25:26 +0000191inline int qsDecode(char* qs)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700192{
Ed Tanousb01bf292019-03-25 19:25:26 +0000193 int i = 0, j = 0;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700194
Ed Tanous1abe55e2018-09-05 08:30:59 -0700195 while (BMCWEB_QS_ISQSCHR(qs[j]))
Ed Tanous7045c8d2017-04-03 10:04:37 -0700196 {
Ed Tanous1abe55e2018-09-05 08:30:59 -0700197 if (qs[j] == '+')
198 {
199 qs[i] = ' ';
200 }
201 else if (qs[j] == '%') // easier/safer than scanf
202 {
203 if (!BMCWEB_QS_ISHEX(qs[j + 1]) || !BMCWEB_QS_ISHEX(qs[j + 2]))
204 {
205 qs[i] = '\0';
206 return i;
207 }
Ed Tanous271584a2019-07-09 16:24:22 -0700208 qs[i] = static_cast<char>(BMCWEB_QS_HEX2DEC(qs[j + 1] * 16) +
209 BMCWEB_QS_HEX2DEC(qs[j + 2]));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700210 j += 2;
211 }
212 else
213 {
214 qs[i] = qs[j];
215 }
216 i++;
217 j++;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700218 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700219 qs[i] = '\0';
Ed Tanous7045c8d2017-04-03 10:04:37 -0700220
Ed Tanous1abe55e2018-09-05 08:30:59 -0700221 return i;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700222}
223
Ed Tanousb01bf292019-03-25 19:25:26 +0000224inline char* qsK2v(const char* key, char* const* qs_kv, int qs_kv_size,
225 int nth = 0)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700226{
Ed Tanousb01bf292019-03-25 19:25:26 +0000227 int i;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700228 size_t keyLen, skip;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700229
Ed Tanous1abe55e2018-09-05 08:30:59 -0700230 keyLen = strlen(key);
Ed Tanous7045c8d2017-04-03 10:04:37 -0700231
232#ifdef _qsSORTING
233// TODO: binary search for key in the sorted qs_kv
Ed Tanous1abe55e2018-09-05 08:30:59 -0700234#else // _qsSORTING
235 for (i = 0; i < qs_kv_size; i++)
236 {
237 // we rely on the unambiguous '=' to find the value in our k/v pair
238 if (qsStrncmp(key, qs_kv[i], keyLen) == 0)
239 {
240 skip = strcspn(qs_kv[i], "=");
241 if (qs_kv[i][skip] == '=')
242 {
243 skip++;
244 }
245 // return (zero-char value) ? ptr to trailing '\0' : ptr to value
246 if (nth == 0)
247 {
248 return qs_kv[i] + skip;
249 }
250 else
251 {
252 --nth;
253 }
254 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700255 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700256#endif // _qsSORTING
Ed Tanous7045c8d2017-04-03 10:04:37 -0700257
Ed Tanous1abe55e2018-09-05 08:30:59 -0700258 return NULL;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700259}
260
Ed Tanous55c7b7a2018-05-22 15:27:24 -0700261inline char* qsScanvalue(const char* key, const char* qs, char* val,
Ed Tanous1abe55e2018-09-05 08:30:59 -0700262 size_t val_len)
263{
264 size_t i, keyLen;
265 const char* tmp;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700266
Ed Tanous1abe55e2018-09-05 08:30:59 -0700267 // find the beginning of the k/v substrings
268 if ((tmp = strchr(qs, '?')) != NULL)
269 {
270 qs = tmp + 1;
Ed Tanous911ac312017-08-15 09:37:42 -0700271 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700272
Ed Tanous1abe55e2018-09-05 08:30:59 -0700273 keyLen = strlen(key);
274 while (qs[0] != '#' && qs[0] != '\0')
275 {
276 if (qsStrncmp(key, qs, keyLen) == 0)
277 {
278 break;
279 }
280 qs += strcspn(qs, "&") + 1;
Ed Tanous911ac312017-08-15 09:37:42 -0700281 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700282
Ed Tanous1abe55e2018-09-05 08:30:59 -0700283 if (qs[0] == '\0')
284 {
285 return NULL;
286 }
287
288 qs += strcspn(qs, "=&#");
289 if (qs[0] == '=')
290 {
291 qs++;
292 i = strcspn(qs, "&=#");
293 strncpy(val, qs, (val_len - 1) < (i + 1) ? (val_len - 1) : (i + 1));
294 qsDecode(val);
295 }
296 else
297 {
298 if (val_len > 0)
299 {
300 val[0] = '\0';
301 }
302 }
303
304 return val;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700305}
Ed Tanous1abe55e2018-09-05 08:30:59 -0700306} // namespace crow
Ed Tanous7045c8d2017-04-03 10:04:37 -0700307// ----------------------------------------------------------------------------
308
Ed Tanous1abe55e2018-09-05 08:30:59 -0700309namespace crow
310{
311class QueryString
312{
313 public:
Ed Tanous271584a2019-07-09 16:24:22 -0700314 static const size_t maxKeyValuePairsCount = 256;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700315
Ed Tanous1abe55e2018-09-05 08:30:59 -0700316 QueryString() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700317
Ed Tanous1abe55e2018-09-05 08:30:59 -0700318 QueryString(const QueryString& qs) : url(qs.url)
319 {
320 for (auto p : qs.keyValuePairs)
321 {
322 keyValuePairs.push_back(
323 const_cast<char*>(p - qs.url.c_str() + url.c_str()));
324 }
Ed Tanous911ac312017-08-15 09:37:42 -0700325 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700326
Ed Tanous1abe55e2018-09-05 08:30:59 -0700327 QueryString& operator=(const QueryString& qs)
328 {
329 url = qs.url;
330 keyValuePairs.clear();
331 for (auto p : qs.keyValuePairs)
332 {
333 keyValuePairs.push_back(
334 const_cast<char*>(p - qs.url.c_str() + url.c_str()));
335 }
336 return *this;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700337 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700338
Ed Tanous1abe55e2018-09-05 08:30:59 -0700339 QueryString& operator=(QueryString&& qs)
340 {
341 keyValuePairs = std::move(qs.keyValuePairs);
342 auto* oldData = const_cast<char*>(qs.url.c_str());
343 url = std::move(qs.url);
344 for (auto& p : keyValuePairs)
345 {
346 p += const_cast<char*>(url.c_str()) - oldData;
347 }
348 return *this;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700349 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700350
Jason M. Bills43fcbe52018-10-16 15:19:20 -0700351 explicit QueryString(std::string newUrl) : url(std::move(newUrl))
Ed Tanous1abe55e2018-09-05 08:30:59 -0700352 {
353 if (url.empty())
354 {
355 return;
356 }
357
358 keyValuePairs.resize(maxKeyValuePairsCount);
359
Ed Tanous271584a2019-07-09 16:24:22 -0700360 size_t count =
361 qsParse(&url[0], &keyValuePairs[0], maxKeyValuePairsCount);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700362 keyValuePairs.resize(count);
363 }
364
365 void clear()
366 {
367 keyValuePairs.clear();
368 url.clear();
369 }
370
371 friend std::ostream& operator<<(std::ostream& os, const QueryString& qs)
372 {
373 os << "[ ";
374 for (size_t i = 0; i < qs.keyValuePairs.size(); ++i)
375 {
376 if (i != 0u)
377 {
378 os << ", ";
379 }
380 os << qs.keyValuePairs[i];
381 }
382 os << " ]";
383 return os;
384 }
385
386 char* get(const std::string& name) const
387 {
Ed Tanous271584a2019-07-09 16:24:22 -0700388 char* ret = qsK2v(name.c_str(), keyValuePairs.data(),
389 static_cast<int>(keyValuePairs.size()));
Ed Tanous1abe55e2018-09-05 08:30:59 -0700390 return ret;
391 }
392
393 std::vector<char*> getList(const std::string& name) const
394 {
395 std::vector<char*> ret;
396 std::string plus = name + "[]";
397 char* element = nullptr;
398
Ed Tanousb01bf292019-03-25 19:25:26 +0000399 int count = 0;
Ed Tanousf12a13b2019-10-24 11:29:41 -0700400 while (true)
Ed Tanous1abe55e2018-09-05 08:30:59 -0700401 {
402 element = qsK2v(plus.c_str(), keyValuePairs.data(),
Ed Tanous271584a2019-07-09 16:24:22 -0700403 static_cast<int>(keyValuePairs.size()), count++);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700404 if (element == nullptr)
405 {
406 break;
407 }
408 ret.push_back(element);
409 }
410 return ret;
411 }
412
413 private:
414 std::string url;
415 std::vector<char*> keyValuePairs;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700416};
417
Ed Tanous1abe55e2018-09-05 08:30:59 -0700418} // namespace crow