Crow  0.3
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 <boost/optional.hpp>
10 
11 namespace crow
12 {
13 // ----------------------------------------------------------------------------
14 // qs_parse (modified)
15 // https://github.com/bartgrantham/qs_parse
16 // ----------------------------------------------------------------------------
17 /* Similar to strncmp, but handles URL-encoding for either string */
18 int qs_strncmp(const char * s, const char * qs, size_t n);
19 
20 
21 /* Finds the beginning of each key/value pair and stores a pointer in qs_kv.
22  * Also decodes the value portion of the k/v pair *in-place*. In a future
23  * enhancement it will also have a compile-time option of sorting qs_kv
24  * alphabetically by key. */
25 int qs_parse(char * qs, char * qs_kv[], int qs_kv_size);
26 
27 
28 /* Used by qs_parse to decode the value portion of a k/v pair */
29 int qs_decode(char * qs);
30 
31 
32 /* Looks up the value according to the key on a pre-processed query string
33  * A future enhancement will be a compile-time option to look up the key
34  * in a pre-sorted qs_kv array via a binary search. */
35 //char * qs_k2v(const char * key, char * qs_kv[], int qs_kv_size);
36  char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth);
37 
38 
39 /* Non-destructive lookup of value, based on key. User provides the
40  * destinaton string and length. */
41 char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len);
42 
43 // TODO: implement sorting of the qs_kv array; for now ensure it's not compiled
44 #undef _qsSORTING
45 
46 // isxdigit _is_ available in <ctype.h>, but let's avoid another header instead
47 #define CROW_QS_ISHEX(x) ((((x)>='0'&&(x)<='9') || ((x)>='A'&&(x)<='F') || ((x)>='a'&&(x)<='f')) ? 1 : 0)
48 #define CROW_QS_HEX2DEC(x) (((x)>='0'&&(x)<='9') ? (x)-48 : ((x)>='A'&&(x)<='F') ? (x)-55 : ((x)>='a'&&(x)<='f') ? (x)-87 : 0)
49 #define CROW_QS_ISQSCHR(x) ((((x)=='=')||((x)=='#')||((x)=='&')||((x)=='\0')) ? 0 : 1)
50 
51 inline int qs_strncmp(const char * s, const char * qs, size_t n)
52 {
53  int i=0;
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  i++;
91  }
92  if ( CROW_QS_ISQSCHR(*qs) )
93  return -1;
94  else
95  return 0;
96 }
97 
98 
99 inline int qs_parse(char * qs, char * qs_kv[], int qs_kv_size)
100 {
101  int i, j;
102  char * substr_ptr;
103 
104  for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL;
105 
106  // find the beginning of the k/v substrings or the fragment
107  substr_ptr = qs + strcspn(qs, "?#");
108  if (substr_ptr[0] != '\0')
109  substr_ptr++;
110  else
111  return 0; // no query or fragment
112 
113  i=0;
114  while(i<qs_kv_size)
115  {
116  qs_kv[i] = substr_ptr;
117  j = strcspn(substr_ptr, "&");
118  if ( substr_ptr[j] == '\0' ) { break; }
119  substr_ptr += j + 1;
120  i++;
121  }
122  i++; // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
123 
124  // we only decode the values in place, the keys could have '='s in them
125  // which will hose our ability to distinguish keys from values later
126  for(j=0; j<i; j++)
127  {
128  substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
129  if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0') // blank value: skip decoding
130  substr_ptr[0] = '\0';
131  else
132  qs_decode(++substr_ptr);
133  }
134 
135 #ifdef _qsSORTING
136 // TODO: qsort qs_kv, using qs_strncmp() for the comparison
137 #endif
138 
139  return i;
140 }
141 
142 
143 inline int qs_decode(char * qs)
144 {
145  int i=0, j=0;
146 
147  while( CROW_QS_ISQSCHR(qs[j]) )
148  {
149  if ( qs[j] == '+' ) { qs[i] = ' '; }
150  else if ( qs[j] == '%' ) // easier/safer than scanf
151  {
152  if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
153  {
154  qs[i] = '\0';
155  return i;
156  }
157  qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]);
158  j+=2;
159  }
160  else
161  {
162  qs[i] = qs[j];
163  }
164  i++; j++;
165  }
166  qs[i] = '\0';
167 
168  return i;
169 }
170 
171 
172 inline char * qs_k2v(const char * key, char * const * qs_kv, int qs_kv_size, int nth = 0)
173 {
174  int i;
175  size_t key_len, skip;
176 
177  key_len = strlen(key);
178 
179 #ifdef _qsSORTING
180 // TODO: binary search for key in the sorted qs_kv
181 #else // _qsSORTING
182  for(i=0; i<qs_kv_size; i++)
183  {
184  // we rely on the unambiguous '=' to find the value in our k/v pair
185  if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
186  {
187  skip = strcspn(qs_kv[i], "=");
188  if ( qs_kv[i][skip] == '=' )
189  skip++;
190  // return (zero-char value) ? ptr to trailing '\0' : ptr to value
191  if(nth == 0)
192  return qs_kv[i] + skip;
193  else
194  --nth;
195  }
196  }
197 #endif // _qsSORTING
198 
199  return nullptr;
200 }
201 
202 inline boost::optional<std::pair<std::string, std::string>> qs_dict_name2kv(const char * dict_name, char * const * qs_kv, int qs_kv_size, int nth = 0)
203 {
204  int i;
205  size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close;
206 
207  name_len = strlen(dict_name);
208 
209 #ifdef _qsSORTING
210 // TODO: binary search for key in the sorted qs_kv
211 #else // _qsSORTING
212  for(i=0; i<qs_kv_size; i++)
213  {
214  if ( strncmp(dict_name, qs_kv[i], name_len) == 0 )
215  {
216  skip_to_eq = strcspn(qs_kv[i], "=");
217  if ( qs_kv[i][skip_to_eq] == '=' )
218  skip_to_eq++;
219  skip_to_brace_open = strcspn(qs_kv[i], "[");
220  if ( qs_kv[i][skip_to_brace_open] == '[' )
221  skip_to_brace_open++;
222  skip_to_brace_close = strcspn(qs_kv[i], "]");
223 
224  if ( skip_to_brace_open <= skip_to_brace_close &&
225  skip_to_brace_open > 0 &&
226  skip_to_brace_close > 0 &&
227  nth == 0 )
228  {
229  auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open);
230  auto value = std::string(qs_kv[i] + skip_to_eq);
231  return boost::make_optional(std::make_pair(key, value));
232  }
233  else
234  {
235  --nth;
236  }
237  }
238  }
239 #endif // _qsSORTING
240 
241  return boost::none;
242 }
243 
244 
245 inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
246 {
247  size_t i, key_len;
248  const char * tmp;
249 
250  // find the beginning of the k/v substrings
251  if ( (tmp = strchr(qs, '?')) != NULL )
252  qs = tmp + 1;
253 
254  key_len = strlen(key);
255  while(qs[0] != '#' && qs[0] != '\0')
256  {
257  if ( qs_strncmp(key, qs, key_len) == 0 )
258  break;
259  qs += strcspn(qs, "&") + 1;
260  }
261 
262  if ( qs[0] == '\0' ) return NULL;
263 
264  qs += strcspn(qs, "=&#");
265  if ( qs[0] == '=' )
266  {
267  qs++;
268  i = strcspn(qs, "&=#");
269 #ifdef _MSC_VER
270  strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
271 #else
272  strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
273 #endif
274  qs_decode(val);
275  }
276  else
277  {
278  if ( val_len > 0 )
279  val[0] = '\0';
280  }
281 
282  return val;
283 }
284 }
285 // ----------------------------------------------------------------------------
286 
287 
288 namespace crow
289 {
290  /// A class to represent any data coming after the `?` in the request URL into key-value pairs.
292  {
293  public:
294  static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
295 
296  query_string()
297  {
298 
299  }
300 
301  query_string(const query_string& qs)
302  : url_(qs.url_)
303  {
304  for(auto p:qs.key_value_pairs_)
305  {
306  key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str()));
307  }
308  }
309 
310  query_string& operator = (const query_string& qs)
311  {
312  url_ = qs.url_;
313  key_value_pairs_.clear();
314  for(auto p:qs.key_value_pairs_)
315  {
316  key_value_pairs_.push_back((char*)(p-qs.url_.c_str()+url_.c_str()));
317  }
318  return *this;
319  }
320 
321  query_string& operator = (query_string&& qs)
322  {
323  key_value_pairs_ = std::move(qs.key_value_pairs_);
324  char* old_data = (char*)qs.url_.c_str();
325  url_ = std::move(qs.url_);
326  for(auto& p:key_value_pairs_)
327  {
328  p += (char*)url_.c_str() - old_data;
329  }
330  return *this;
331  }
332 
333 
334  query_string(std::string url)
335  : url_(std::move(url))
336  {
337  if (url_.empty())
338  return;
339 
340  key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
341 
342  int count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT);
343  key_value_pairs_.resize(count);
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  if (i)
357  os << ", ";
358  os << qs.key_value_pairs_[i];
359  }
360  os << " ]";
361  return os;
362 
363  }
364 
365  /// Get a value from a name, used for `?name=value`.
366  ///
367  /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list().
368  char* get (const std::string& name) const
369  {
370  char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
371  return ret;
372  }
373 
374  /// Works similar to \ref get() except it removes the item from the query string.
375  char* pop (const std::string& name)
376  {
377  char* ret = get(name);
378  if (ret != nullptr)
379  {
380  for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
381  {
382  std::string str_item(key_value_pairs_[i]);
383  if (str_item.substr(0, name.size()+1) == name+'=')
384  {
385  key_value_pairs_.erase(key_value_pairs_.begin()+i);
386  break;
387  }
388  }
389  }
390  return ret;
391  }
392 
393  /// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list.
394  ///
395  /// 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`
396  std::vector<char*> get_list (const std::string& name, bool use_brackets = true) const
397  {
398  std::vector<char*> ret;
399  std::string plus = name + (use_brackets ? "[]" : "");
400  char* element = nullptr;
401 
402  int count = 0;
403  while(1)
404  {
405  element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
406  if (!element)
407  break;
408  ret.push_back(element);
409  }
410  return ret;
411  }
412 
413  /// Similar to \ref get_list() but it removes the
414  std::vector<char*> pop_list (const std::string& name, bool use_brackets = true)
415  {
416  std::vector<char*> ret = get_list(name, use_brackets);
417  if (!ret.empty())
418  {
419  for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
420  {
421  std::string str_item(key_value_pairs_[i]);
422  if ((use_brackets ? (str_item.substr(0, name.size()+3) == name+"[]=") : (str_item.substr(0, name.size()+1) == name+'=')))
423  {
424  key_value_pairs_.erase(key_value_pairs_.begin()+i--);
425  }
426  }
427  }
428  return ret;
429  }
430 
431  /// Works similar to \ref get_list() except the brackets are mandatory must not be empty.
432  ///
433  /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
434  ///
435  /// 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.
436  std::unordered_map<std::string, std::string> get_dict (const std::string& name) const
437  {
438  std::unordered_map<std::string, std::string> ret;
439 
440  int count = 0;
441  while(1)
442  {
443  if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
444  ret.insert(*element);
445  else
446  break;
447  }
448  return ret;
449  }
450 
451  /// Works the same as \ref get_dict() but removes the values from the query string.
452  std::unordered_map<std::string, std::string> pop_dict (const std::string& name)
453  {
454  std::unordered_map<std::string, std::string> ret = get_dict(name);
455  if (!ret.empty())
456  {
457  for (unsigned int i = 0; i<key_value_pairs_.size(); i++)
458  {
459  std::string str_item(key_value_pairs_[i]);
460  if (str_item.substr(0, name.size()+1) == name+'[')
461  {
462  key_value_pairs_.erase(key_value_pairs_.begin()+i--);
463  }
464  }
465  }
466  return ret;
467  }
468 
469  std::vector<std::string> keys() const
470  {
471  std::vector<std::string> ret;
472  for (auto element: key_value_pairs_)
473  {
474  std::string str_element(element);
475  ret.emplace_back(str_element.substr(0, str_element.find('=')));
476  }
477  return ret;
478  }
479 
480  private:
481  std::string url_;
482  std::vector<char*> key_value_pairs_;
483  };
484 
485 } // end namespace
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:375
crow::query_string::get_list
std::vector< char * > get_list(const std::string &name, bool use_brackets=true) const
Definition: query_string.h:396
crow::query_string
A class to represent any data coming after the ? in the request URL into key-value pairs.
Definition: query_string.h:291
crow::query_string::get_dict
std::unordered_map< std::string, std::string > get_dict(const std::string &name) const
Definition: query_string.h:436
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:452
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:414
crow::query_string::get
char * get(const std::string &name) const
Definition: query_string.h:368