Crow  1.1
A C++ microframework for the web
 
Loading...
Searching...
No Matches
query_string.h
1#pragma once
2
3#include <stdio.h>
4#include <string.h>
5#include <string>
6#include <vector>
7#include <unordered_map>
8#include <iostream>
9#include <memory>
10
11namespace crow
12{
13
14// ----------------------------------------------------------------------------
15// qs_parse (modified)
16// https://github.com/bartgrantham/qs_parse
17// ----------------------------------------------------------------------------
18/* Similar to strncmp, but handles URL-encoding for either string */
19int qs_strncmp(const char* s, const char* qs, size_t n);
20
21
22/* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
23 * Also decodes the value portion of the k/v pair *in-place*. In a future
24 * enhancement it will also have a compile-time option of sorting qs_kv
25 * alphabetically by key. */
26size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url);
27
28
29/* Used by qs_parse to decode the value portion of a k/v pair */
30int qs_decode(char * qs);
31
32
33/* Looks up the value according to the key on a pre-processed query string
34 * A future enhancement will be a compile-time option to look up the key
35 * in a pre-sorted qs_kv array via a binary search. */
36//char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
37 char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth);
38
39
40/* Non-destructive lookup of value, based on key. User provides the
41 * destinaton string and length. */
42char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len);
43
44// TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
45#undef _qsSORTING
46
47// isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
48#define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0)
49#define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0)
50#define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1)
51
52inline int qs_strncmp(const char * s, const char * qs, size_t n)
53{
54 unsigned char u1, u2, unyb, lnyb;
55
56 while(n-- > 0)
57 {
58 u1 = static_cast<unsigned char>(*s++);
59 u2 = static_cast<unsigned char>(*qs++);
60
61 if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; }
62 if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; }
63
64 if ( u1 == '+' ) { u1 = ' '; }
65 if ( u1 == '%' ) // easier/safer than scanf
66 {
67 // Check that next two chars exist and are valid hex before reading
68 if ( CROW_QS_ISHEX(s[0]) && CROW_QS_ISHEX(s[1]) )
69 {
70 unyb = static_cast<unsigned char>(*s++);
71 lnyb = static_cast<unsigned char>(*s++);
72 u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
73 }
74 else
75 {
76 u1 = '\0';
77 }
78 }
79
80 if ( u2 == '+' ) { u2 = ' '; }
81 if ( u2 == '%' ) // easier/safer than scanf
82 {
83 // Check that next two chars exist and are valid hex before reading
84 if ( CROW_QS_ISHEX(qs[0]) && CROW_QS_ISHEX(qs[1]) )
85 {
86 unyb = static_cast<unsigned char>(*qs++);
87 lnyb = static_cast<unsigned char>(*qs++);
88 u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
89 }
90 else
91 {
92 u2 = '\0';
93 }
94 }
95
96 if ( u1 != u2 )
97 return u1 - u2;
98 if ( u1 == '\0' )
99 return 0;
100 }
101 if ( CROW_QS_ISQSCHR(*qs) )
102 return -1;
103 else
104 return 0;
105}
106
107
108inline size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url = true)
109{
110 size_t i, j;
111 char * substr_ptr;
112
113 for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
114
115 // find the beginning of the k/v substrings or the fragment
116 substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
117 if (parse_url)
118 {
119 if (substr_ptr[0] != '\0')
120 substr_ptr++;
121 else
122 return 0; // no query or fragment
123 }
124
125 i=0;
126 while(i<qs_kv_size)
127 {
128 qs_kv[i] = substr_ptr;
129 j = strcspn(substr_ptr, "&");
130 if ( substr_ptr[j] == '\0' ) { i++; break; } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
131 substr_ptr += j + 1;
132 i++;
133 }
134
135 // we only decode the values in place, the keys could have '='s in them
136 // which will hose our ability to distinguish keys from values later
137 for(j=0; j<i; j++)
138 {
139 substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
140 if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0') // blank value: skip decoding
141 substr_ptr[0] = '\0';
142 else
143 qs_decode(++substr_ptr);
144 }
145
146#ifdef _qsSORTING
147// TODO: qsort qs_kv, using qs_strncmp() for the comparison
148#endif
149
150 return i;
151}
152
153
154inline int qs_decode(char * qs)
155{
156 int i=0, j=0;
157
158 while( CROW_QS_ISQSCHR(qs[j]) )
159 {
160 if ( qs[j] == '+' ) { qs[i] = ' '; }
161 else if ( qs[j] == '%' ) // easier/safer than scanf
162 {
163 // Check bounds before reading: ensure j+1 and j+2 are within string
164 if ( qs[j+1] == '\0' || qs[j+2] == '\0' ||
165 ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
166 {
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 }
173 else
174 {
175 qs[i] = qs[j];
176 }
177 i++; j++;
178 }
179 qs[i] = '\0';
180
181 return i;
182}
183
184
185inline char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth = 0)
186{
187 size_t i;
188 size_t key_len, skip;
189
190 key_len = strlen(key);
191
192#ifdef _qsSORTING
193// TODO: binary search for key in the sorted qs_kv
194#else // _qsSORTING
195 for(i=0; i<qs_kv_size; i++)
196 {
197 // we rely on the unambiguous '=' to find the value in our k/v pair
198 if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
199 {
200 skip = strcspn(qs_kv[i], "=");
201 if ( qs_kv[i][skip] == '=' )
202 skip++;
203 // return (zero-char value) ? ptr to trailing '\0' : ptr to value
204 if(nth == 0)
205 return qs_kv[i] + skip;
206 else
207 --nth;
208 }
209 }
210#endif // _qsSORTING
211
212 return nullptr;
213}
214
215inline std::unique_ptr<std::pair<std::string, std::string>> qs_dict_name2kv(const char * dict_name, char * const * qs_kv, size_t qs_kv_size, int nth = 0)
216{
217 size_t i;
218 size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close;
219
220 name_len = strlen(dict_name);
221
222#ifdef _qsSORTING
223// TODO: binary search for key in the sorted qs_kv
224#else // _qsSORTING
225 for(i=0; i<qs_kv_size; i++)
226 {
227 if ( strncmp(dict_name, qs_kv[i], name_len) == 0 )
228 {
229 skip_to_eq = strcspn(qs_kv[i], "=");
230 if ( qs_kv[i][skip_to_eq] == '=' )
231 skip_to_eq++;
232 skip_to_brace_open = strcspn(qs_kv[i], "[");
233 if ( qs_kv[i][skip_to_brace_open] == '[' )
234 skip_to_brace_open++;
235 skip_to_brace_close = strcspn(qs_kv[i], "]");
236
237 // Encoded brackets: page%5Bsize%5D=3 should match page[size]=3 (#1109).
238 if ( skip_to_brace_open == strlen(qs_kv[i]) )
239 {
240 const char* open = strstr(qs_kv[i] + name_len, "%5B");
241 if (!open)
242 open = strstr(qs_kv[i] + name_len, "%5b");
243 if ( open && open == qs_kv[i] + name_len )
244 {
245 const char* close = strstr(open + 3, "%5D");
246 if (!close)
247 close = strstr(open + 3, "%5d");
248 if ( close && nth == 0 )
249 {
250 auto key = std::string(open + 3, static_cast<size_t>(close - (open + 3)));
251 auto value = std::string(qs_kv[i] + skip_to_eq);
252 return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value));
253 }
254 else if ( close )
255 {
256 --nth;
257 continue;
258 }
259 }
260 }
261
262 if ( skip_to_brace_open <= skip_to_brace_close &&
263 skip_to_brace_open > 0 &&
264 skip_to_brace_close > 0 &&
265 nth == 0 )
266 {
267 auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open);
268 auto value = std::string(qs_kv[i] + skip_to_eq);
269 return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value));
270 }
271 else
272 {
273 --nth;
274 }
275 }
276 }
277#endif // _qsSORTING
278
279 return nullptr;
280}
281
282
283inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
284{
285 const char * tmp= strchr(qs, '?');
286
287 // find the beginning of the k/v substrings
288 if ( tmp != nullptr )
289 qs = tmp + 1;
290
291 const size_t key_len = strlen(key);
292 while(*qs != '#' && *qs != '\0')
293 {
294 if ( qs_strncmp(key, qs, key_len) == 0 )
295 break;
296 qs += strcspn(qs, "&");
297 if (*qs=='&') qs++;
298 }
299
300 if ( qs[0] == '\0' ) return nullptr;
301
302 qs += strcspn(qs, "=&#");
303 if ( qs[0] == '=' )
304 {
305 qs++;
306 size_t i = strcspn(qs, "&=#");
307#ifdef _MSC_VER
308 strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
309#else
310 strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
311#endif
312 qs_decode(val);
313 }
314 else
315 {
316 if ( val_len > 0 )
317 val[0] = '\0';
318 }
319
320 return val;
321}
322}
323// ----------------------------------------------------------------------------
324
325
326namespace crow
327{
328 struct request;
329 /// A class to represent any data coming after the `?` in the request URL into key-value pairs.
331 {
332 public:
333 static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
334
335 query_string() = default;
336
337 query_string(const query_string& qs):
338 url_(qs.url_)
339 {
340 for (auto p : qs.key_value_pairs_)
341 {
342 key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
343 }
344 }
345
346 query_string& operator=(const query_string& qs)
347 {
348 url_ = qs.url_;
349 key_value_pairs_.clear();
350 for (auto p : qs.key_value_pairs_)
351 {
352 key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
353 }
354 return *this;
355 }
356
357 query_string& operator=(query_string&& qs) noexcept
358 {
359 key_value_pairs_ = std::move(qs.key_value_pairs_);
360 char* old_data = (char*)qs.url_.c_str();
361 url_ = std::move(qs.url_);
362 for (auto& p : key_value_pairs_)
363 {
364 p += (char*)url_.c_str() - old_data;
365 }
366 return *this;
367 }
368
369
370 query_string(std::string params, bool url = true):
371 url_(std::move(params))
372 {
373 if (url_.empty())
374 return;
375
376 key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
377 size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url);
378
379 key_value_pairs_.resize(count);
380 key_value_pairs_.shrink_to_fit();
381 }
382
383 void clear()
384 {
385 key_value_pairs_.clear();
386 url_.clear();
387 }
388
389 friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
390 {
391 os << "[ ";
392 for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i)
393 {
394 if (i)
395 os << ", ";
396 os << qs.key_value_pairs_[i];
397 }
398 os << " ]";
399 return os;
400 }
401
402 /// Get a value from a name, used for `?name=value`.
403
404 ///
405 /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list().
406 char* get(const std::string& name) const
407 {
408 char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
409 return ret;
410 }
411
412 /// Works similar to \ref get() except it removes the item from the query string.
413 char* pop(const std::string& name)
414 {
415 char* ret = get(name);
416 if (ret != nullptr)
417 {
418 const std::string key_name = name + '=';
419 for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
420 {
421 std::string str_item(key_value_pairs_[i]);
422 if (str_item.find(key_name)==0)
423 {
424 key_value_pairs_.erase(key_value_pairs_.begin() + i);
425 break;
426 }
427 }
428 }
429 return ret;
430 }
431
432 /// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list.
433
434 ///
435 /// Note: Square brackets in the above example are controlled by `use_brackets` boolean (true by default). If set to false, the example becomes `?name=value1,name=value2...name=valuen`
436 std::vector<char*> get_list(const std::string& name, bool use_brackets = true) const
437 {
438 std::vector<char*> ret;
439 std::string plus = name + (use_brackets ? "[]" : "");
440 char* element = nullptr;
441
442 int count = 0;
443 while (1)
444 {
445 element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
446 if (!element)
447 break;
448 ret.push_back(element);
449 }
450 return ret;
451 }
452
453 /// Similar to \ref get_list() but it removes the
454 std::vector<char*> pop_list(const std::string& name, bool use_brackets = true)
455 {
456 std::vector<char*> ret = get_list(name, use_brackets);
457 const size_t name_len = name.length();
458 if (!ret.empty())
459 {
460 for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
461 {
462 std::string str_item(key_value_pairs_[i]);
463 if (str_item.find(name)==0) {
464 if (use_brackets && str_item.find("[]=",name_len)==name_len) {
465 key_value_pairs_.erase(key_value_pairs_.begin() + i--);
466 } else if (!use_brackets && str_item.find('=',name_len)==name_len ) {
467 key_value_pairs_.erase(key_value_pairs_.begin() + i--);
468 }
469 }
470 }
471 }
472 return ret;
473 }
474
475 /// Works similar to \ref get_list() except the brackets are mandatory must not be empty.
476
477 ///
478 /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
479 ///
480 /// if your query string has both empty brackets and ones with a key inside, use pop_list() to get all the values without a key before running this method.
481 std::unordered_map<std::string, std::string> get_dict(const std::string& name) const
482 {
483 std::unordered_map<std::string, std::string> ret;
484
485 int count = 0;
486 while (1)
487 {
488 if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
489 ret.insert(*element);
490 else
491 break;
492 }
493 return ret;
494 }
495
496 /// Works the same as \ref get_dict() but removes the values from the query string.
497 std::unordered_map<std::string, std::string> pop_dict(const std::string& name)
498 {
499 const std::string name_value = name +'[';
500 std::unordered_map<std::string, std::string> ret = get_dict(name);
501 if (!ret.empty())
502 {
503 for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
504 {
505 std::string str_item(key_value_pairs_[i]);
506 if (str_item.find(name_value)==0)
507 {
508 key_value_pairs_.erase(key_value_pairs_.begin() + i--);
509 }
510 }
511 }
512 return ret;
513 }
514
515 std::vector<std::string> keys() const
516 {
517 std::vector<std::string> keys;
518 keys.reserve(key_value_pairs_.size());
519
520 for (const char* const element : key_value_pairs_)
521 {
522 const char* delimiter = strchr(element, '=');
523 if (delimiter)
524 keys.emplace_back(element, delimiter);
525 else
526 keys.emplace_back(element);
527 }
528
529 return keys;
530 }
531
532 private:
533 std::string url_;
534 std::vector<char*> key_value_pairs_;
535 };
536
537} // namespace crow
A class to represent any data coming after the ? in the request URL into key-value pairs.
Definition query_string.h:331
std::unordered_map< std::string, std::string > pop_dict(const std::string &name)
Works the same as get_dict() but removes the values from the query string.
Definition query_string.h:497
char * pop(const std::string &name)
Works similar to get() except it removes the item from the query string.
Definition query_string.h:413
std::unordered_map< std::string, std::string > get_dict(const std::string &name) const
Works similar to get_list() except the brackets are mandatory must not be empty.
Definition query_string.h:481
std::vector< char * > get_list(const std::string &name, bool use_brackets=true) const
Returns a list of values, passed as ?name[]=value1&name[]=value2&...name[]=valuen with n being the si...
Definition query_string.h:436
std::vector< char * > pop_list(const std::string &name, bool use_brackets=true)
Similar to get_list() but it removes the.
Definition query_string.h:454
char * get(const std::string &name) const
Get a value from a name, used for ?name=value.
Definition query_string.h:406
The main namespace of the library. In this namespace is defined the most important classes and functi...