Crow  1.1
A C++ microframework for the web
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 
11 namespace 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 */
19 int 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. */
26 size_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 */
30 int 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. */
42 char * 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 
52 inline 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 
98 inline 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 
144 inline 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 
173 inline 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 
203 inline 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 
246 inline 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 
289 namespace 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
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
char * get(const std::string &name) const
Get a value from a name, used for ?name=value.
Definition: query_string.h:369
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 * 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
The main namespace of the library. In this namespace is defined the most important classes and functi...