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 unyb = static_cast<unsigned char>(*s++);
68 lnyb = static_cast<unsigned char>(*s++);
69 if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
70 u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
71 else
72 u1 = '\0';
73 }
74
75 if ( u2 == '+' ) { u2 = ' '; }
76 if ( u2 == '%' ) // easier/safer than scanf
77 {
78 unyb = static_cast<unsigned char>(*qs++);
79 lnyb = static_cast<unsigned char>(*qs++);
80 if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
81 u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
82 else
83 u2 = '\0';
84 }
85
86 if ( u1 != u2 )
87 return u1 - u2;
88 if ( u1 == '\0' )
89 return 0;
90 }
91 if ( CROW_QS_ISQSCHR(*qs) )
92 return -1;
93 else
94 return 0;
95}
96
97
98inline size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url = true)
99{
100 size_t i, j;
101 char * substr_ptr;
102
103 for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
104
105 // find the beginning of the k/v substrings or the fragment
106 substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
107 if (parse_url)
108 {
109 if (substr_ptr[0] != '\0')
110 substr_ptr++;
111 else
112 return 0; // no query or fragment
113 }
114
115 i=0;
116 while(i<qs_kv_size)
117 {
118 qs_kv[i] = substr_ptr;
119 j = strcspn(substr_ptr, "&");
120 if ( substr_ptr[j] == '\0' ) { i++; break; } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
121 substr_ptr += j + 1;
122 i++;
123 }
124
125 // we only decode the values in place, the keys could have '='s in them
126 // which will hose our ability to distinguish keys from values later
127 for(j=0; j<i; j++)
128 {
129 substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
130 if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0') // blank value: skip decoding
131 substr_ptr[0] = '\0';
132 else
133 qs_decode(++substr_ptr);
134 }
135
136#ifdef _qsSORTING
137// TODO: qsort qs_kv, using qs_strncmp() for the comparison
138#endif
139
140 return i;
141}
142
143
144inline int qs_decode(char * qs)
145{
146 int i=0, j=0;
147
148 while( CROW_QS_ISQSCHR(qs[j]) )
149 {
150 if ( qs[j] == '+' ) { qs[i] = ' '; }
151 else if ( qs[j] == '%' ) // easier/safer than scanf
152 {
153 if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
154 {
155 qs[i] = '\0';
156 return i;
157 }
158 qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]);
159 j+=2;
160 }
161 else
162 {
163 qs[i] = qs[j];
164 }
165 i++; j++;
166 }
167 qs[i] = '\0';
168
169 return i;
170}
171
172
173inline char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth = 0)
174{
175 size_t i;
176 size_t key_len, skip;
177
178 key_len = strlen(key);
179
180#ifdef _qsSORTING
181// TODO: binary search for key in the sorted qs_kv
182#else // _qsSORTING
183 for(i=0; i<qs_kv_size; i++)
184 {
185 // we rely on the unambiguous '=' to find the value in our k/v pair
186 if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
187 {
188 skip = strcspn(qs_kv[i], "=");
189 if ( qs_kv[i][skip] == '=' )
190 skip++;
191 // return (zero-char value) ? ptr to trailing '\0' : ptr to value
192 if(nth == 0)
193 return qs_kv[i] + skip;
194 else
195 --nth;
196 }
197 }
198#endif // _qsSORTING
199
200 return nullptr;
201}
202
203inline 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)
204{
205 size_t i;
206 size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close;
207
208 name_len = strlen(dict_name);
209
210#ifdef _qsSORTING
211// TODO: binary search for key in the sorted qs_kv
212#else // _qsSORTING
213 for(i=0; i<qs_kv_size; i++)
214 {
215 if ( strncmp(dict_name, qs_kv[i], name_len) == 0 )
216 {
217 skip_to_eq = strcspn(qs_kv[i], "=");
218 if ( qs_kv[i][skip_to_eq] == '=' )
219 skip_to_eq++;
220 skip_to_brace_open = strcspn(qs_kv[i], "[");
221 if ( qs_kv[i][skip_to_brace_open] == '[' )
222 skip_to_brace_open++;
223 skip_to_brace_close = strcspn(qs_kv[i], "]");
224
225 if ( skip_to_brace_open <= skip_to_brace_close &&
226 skip_to_brace_open > 0 &&
227 skip_to_brace_close > 0 &&
228 nth == 0 )
229 {
230 auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open);
231 auto value = std::string(qs_kv[i] + skip_to_eq);
232 return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value));
233 }
234 else
235 {
236 --nth;
237 }
238 }
239 }
240#endif // _qsSORTING
241
242 return nullptr;
243}
244
245
246inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
247{
248 size_t i, key_len;
249 const char * tmp;
250
251 // find the beginning of the k/v substrings
252 if ( (tmp = strchr(qs, '?')) != NULL )
253 qs = tmp + 1;
254
255 key_len = strlen(key);
256 while(qs[0] != '#' && qs[0] != '\0')
257 {
258 if ( qs_strncmp(key, qs, key_len) == 0 )
259 break;
260 qs += strcspn(qs, "&") + 1;
261 }
262
263 if ( qs[0] == '\0' ) return NULL;
264
265 qs += strcspn(qs, "=&#");
266 if ( qs[0] == '=' )
267 {
268 qs++;
269 i = strcspn(qs, "&=#");
270#ifdef _MSC_VER
271 strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
272#else
273 strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
274#endif
275 qs_decode(val);
276 }
277 else
278 {
279 if ( val_len > 0 )
280 val[0] = '\0';
281 }
282
283 return val;
284}
285}
286// ----------------------------------------------------------------------------
287
288
289namespace crow
290{
291 struct request;
292 /// A class to represent any data coming after the `?` in the request URL into key-value pairs.
294 {
295 public:
296 static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
297
298 query_string() = default;
299
300 query_string(const query_string& qs):
301 url_(qs.url_)
302 {
303 for (auto p : qs.key_value_pairs_)
304 {
305 key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
306 }
307 }
308
309 query_string& operator=(const query_string& qs)
310 {
311 url_ = qs.url_;
312 key_value_pairs_.clear();
313 for (auto p : qs.key_value_pairs_)
314 {
315 key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
316 }
317 return *this;
318 }
319
320 query_string& operator=(query_string&& qs) noexcept
321 {
322 key_value_pairs_ = std::move(qs.key_value_pairs_);
323 char* old_data = (char*)qs.url_.c_str();
324 url_ = std::move(qs.url_);
325 for (auto& p : key_value_pairs_)
326 {
327 p += (char*)url_.c_str() - old_data;
328 }
329 return *this;
330 }
331
332
333 query_string(std::string params, bool url = true):
334 url_(std::move(params))
335 {
336 if (url_.empty())
337 return;
338
339 key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
340 size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url);
341
342 key_value_pairs_.resize(count);
343 key_value_pairs_.shrink_to_fit();
344 }
345
346 void clear()
347 {
348 key_value_pairs_.clear();
349 url_.clear();
350 }
351
352 friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
353 {
354 os << "[ ";
355 for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i)
356 {
357 if (i)
358 os << ", ";
359 os << qs.key_value_pairs_[i];
360 }
361 os << " ]";
362 return os;
363 }
364
365 /// Get a value from a name, used for `?name=value`.
366
367 ///
368 /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list().
369 char* get(const std::string& name) const
370 {
371 char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
372 return ret;
373 }
374
375 /// Works similar to \ref get() except it removes the item from the query string.
376 char* pop(const std::string& name)
377 {
378 char* ret = get(name);
379 if (ret != nullptr)
380 {
381 for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
382 {
383 std::string str_item(key_value_pairs_[i]);
384 if (str_item.substr(0, name.size() + 1) == name + '=')
385 {
386 key_value_pairs_.erase(key_value_pairs_.begin() + i);
387 break;
388 }
389 }
390 }
391 return ret;
392 }
393
394 /// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list.
395
396 ///
397 /// 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`
398 std::vector<char*> get_list(const std::string& name, bool use_brackets = true) const
399 {
400 std::vector<char*> ret;
401 std::string plus = name + (use_brackets ? "[]" : "");
402 char* element = nullptr;
403
404 int count = 0;
405 while (1)
406 {
407 element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
408 if (!element)
409 break;
410 ret.push_back(element);
411 }
412 return ret;
413 }
414
415 /// Similar to \ref get_list() but it removes the
416 std::vector<char*> pop_list(const std::string& name, bool use_brackets = true)
417 {
418 std::vector<char*> ret = get_list(name, use_brackets);
419 if (!ret.empty())
420 {
421 for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
422 {
423 std::string str_item(key_value_pairs_[i]);
424 if ((use_brackets ? (str_item.substr(0, name.size() + 3) == name + "[]=") : (str_item.substr(0, name.size() + 1) == name + '=')))
425 {
426 key_value_pairs_.erase(key_value_pairs_.begin() + i--);
427 }
428 }
429 }
430 return ret;
431 }
432
433 /// Works similar to \ref get_list() except the brackets are mandatory must not be empty.
434
435 ///
436 /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
437 ///
438 /// 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.
439 std::unordered_map<std::string, std::string> get_dict(const std::string& name) const
440 {
441 std::unordered_map<std::string, std::string> ret;
442
443 int count = 0;
444 while (1)
445 {
446 if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
447 ret.insert(*element);
448 else
449 break;
450 }
451 return ret;
452 }
453
454 /// Works the same as \ref get_dict() but removes the values from the query string.
455 std::unordered_map<std::string, std::string> pop_dict(const std::string& name)
456 {
457 std::unordered_map<std::string, std::string> ret = get_dict(name);
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.substr(0, name.size() + 1) == name + '[')
464 {
465 key_value_pairs_.erase(key_value_pairs_.begin() + i--);
466 }
467 }
468 }
469 return ret;
470 }
471
472 std::vector<std::string> keys() const
473 {
474 std::vector<std::string> keys;
475 keys.reserve(key_value_pairs_.size());
476
477 for (const char* const element : key_value_pairs_)
478 {
479 const char* delimiter = strchr(element, '=');
480 if (delimiter)
481 keys.emplace_back(element, delimiter);
482 else
483 keys.emplace_back(element);
484 }
485
486 return keys;
487 }
488
489 private:
490 std::string url_;
491 std::vector<char*> key_value_pairs_;
492 };
493
494} // namespace crow
A class to represent any data coming after the ? in the request URL into key-value pairs.
Definition query_string.h:294
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:455
char * pop(const std::string &name)
Works similar to get() except it removes the item from the query string.
Definition query_string.h:376
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:439
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:398
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:416
char * get(const std::string &name) const
Get a value from a name, used for ?name=value.
Definition query_string.h:369
The main namespace of the library. In this namespace is defined the most important classes and functi...