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