/src/crow/include/crow/query_string.h
Line | Count | Source |
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 | 0 | { |
54 | 0 | unsigned char u1, u2, unyb, lnyb; |
55 | 0 |
|
56 | 0 | while(n-- > 0) |
57 | 0 | { |
58 | 0 | u1 = static_cast<unsigned char>(*s++); |
59 | 0 | u2 = static_cast<unsigned char>(*qs++); |
60 | 0 |
|
61 | 0 | if ( ! CROW_QS_ISQSCHR(u1) ) { u1 = '\0'; } |
62 | 0 | if ( ! CROW_QS_ISQSCHR(u2) ) { u2 = '\0'; } |
63 | 0 |
|
64 | 0 | if ( u1 == '+' ) { u1 = ' '; } |
65 | 0 | if ( u1 == '%' ) // easier/safer than scanf |
66 | 0 | { |
67 | 0 | // Check that next two chars exist and are valid hex before reading |
68 | 0 | if ( CROW_QS_ISHEX(s[0]) && CROW_QS_ISHEX(s[1]) ) |
69 | 0 | { |
70 | 0 | unyb = static_cast<unsigned char>(*s++); |
71 | 0 | lnyb = static_cast<unsigned char>(*s++); |
72 | 0 | u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); |
73 | 0 | } |
74 | 0 | else |
75 | 0 | { |
76 | 0 | u1 = '\0'; |
77 | 0 | } |
78 | 0 | } |
79 | 0 |
|
80 | 0 | if ( u2 == '+' ) { u2 = ' '; } |
81 | 0 | if ( u2 == '%' ) // easier/safer than scanf |
82 | 0 | { |
83 | 0 | // Check that next two chars exist and are valid hex before reading |
84 | 0 | if ( CROW_QS_ISHEX(qs[0]) && CROW_QS_ISHEX(qs[1]) ) |
85 | 0 | { |
86 | 0 | unyb = static_cast<unsigned char>(*qs++); |
87 | 0 | lnyb = static_cast<unsigned char>(*qs++); |
88 | 0 | u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb); |
89 | 0 | } |
90 | 0 | else |
91 | 0 | { |
92 | 0 | u2 = '\0'; |
93 | 0 | } |
94 | 0 | } |
95 | 0 |
|
96 | 0 | if ( u1 != u2 ) |
97 | 0 | return u1 - u2; |
98 | 0 | if ( u1 == '\0' ) |
99 | 0 | return 0; |
100 | 0 | } |
101 | 0 | if ( CROW_QS_ISQSCHR(*qs) ) |
102 | 0 | return -1; |
103 | 0 | else |
104 | 0 | return 0; |
105 | 0 | } |
106 | | |
107 | | |
108 | | inline size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url = true) |
109 | 0 | { |
110 | 0 | size_t i, j; |
111 | 0 | char * substr_ptr; |
112 | 0 |
|
113 | 0 | for(i=0; i<qs_kv_size; i++) qs_kv[i] = NULL; |
114 | 0 |
|
115 | 0 | // find the beginning of the k/v substrings or the fragment |
116 | 0 | substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs; |
117 | 0 | if (parse_url) |
118 | 0 | { |
119 | 0 | if (substr_ptr[0] != '\0') |
120 | 0 | substr_ptr++; |
121 | 0 | else |
122 | 0 | return 0; // no query or fragment |
123 | 0 | } |
124 | 0 |
|
125 | 0 | i=0; |
126 | 0 | while(i<qs_kv_size) |
127 | 0 | { |
128 | 0 | qs_kv[i] = substr_ptr; |
129 | 0 | j = strcspn(substr_ptr, "&"); |
130 | 0 | if ( substr_ptr[j] == '\0' ) { i++; break; } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs |
131 | 0 | substr_ptr += j + 1; |
132 | 0 | i++; |
133 | 0 | } |
134 | 0 |
|
135 | 0 | // we only decode the values in place, the keys could have '='s in them |
136 | 0 | // which will hose our ability to distinguish keys from values later |
137 | 0 | for(j=0; j<i; j++) |
138 | 0 | { |
139 | 0 | substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#"); |
140 | 0 | if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0') // blank value: skip decoding |
141 | 0 | substr_ptr[0] = '\0'; |
142 | 0 | else |
143 | 0 | qs_decode(++substr_ptr); |
144 | 0 | } |
145 | 0 |
|
146 | 0 | #ifdef _qsSORTING |
147 | 0 | // TODO: qsort qs_kv, using qs_strncmp() for the comparison |
148 | 0 | #endif |
149 | 0 |
|
150 | 0 | return i; |
151 | 0 | } |
152 | | |
153 | | |
154 | | inline int qs_decode(char * qs) |
155 | 0 | { |
156 | 0 | int i=0, j=0; |
157 | 0 |
|
158 | 0 | while( CROW_QS_ISQSCHR(qs[j]) ) |
159 | 0 | { |
160 | 0 | if ( qs[j] == '+' ) { qs[i] = ' '; } |
161 | 0 | else if ( qs[j] == '%' ) // easier/safer than scanf |
162 | 0 | { |
163 | 0 | // Check bounds before reading: ensure j+1 and j+2 are within string |
164 | 0 | if ( qs[j+1] == '\0' || qs[j+2] == '\0' || |
165 | 0 | ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) ) |
166 | 0 | { |
167 | 0 | qs[i] = '\0'; |
168 | 0 | return i; |
169 | 0 | } |
170 | 0 | qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]); |
171 | 0 | j+=2; |
172 | 0 | } |
173 | 0 | else |
174 | 0 | { |
175 | 0 | qs[i] = qs[j]; |
176 | 0 | } |
177 | 0 | i++; j++; |
178 | 0 | } |
179 | 0 | qs[i] = '\0'; |
180 | 0 |
|
181 | 0 | return i; |
182 | 0 | } |
183 | | |
184 | | |
185 | | inline char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth = 0) |
186 | 0 | { |
187 | 0 | size_t i; |
188 | 0 | size_t key_len, skip; |
189 | 0 |
|
190 | 0 | key_len = strlen(key); |
191 | 0 |
|
192 | 0 | #ifdef _qsSORTING |
193 | 0 | // TODO: binary search for key in the sorted qs_kv |
194 | 0 | #else // _qsSORTING |
195 | 0 | for(i=0; i<qs_kv_size; i++) |
196 | 0 | { |
197 | 0 | // we rely on the unambiguous '=' to find the value in our k/v pair |
198 | 0 | if ( qs_strncmp(key, qs_kv[i], key_len) == 0 ) |
199 | 0 | { |
200 | 0 | skip = strcspn(qs_kv[i], "="); |
201 | 0 | if ( qs_kv[i][skip] == '=' ) |
202 | 0 | skip++; |
203 | 0 | // return (zero-char value) ? ptr to trailing '\0' : ptr to value |
204 | 0 | if(nth == 0) |
205 | 0 | return qs_kv[i] + skip; |
206 | 0 | else |
207 | 0 | --nth; |
208 | 0 | } |
209 | 0 | } |
210 | 0 | #endif // _qsSORTING |
211 | 0 |
|
212 | 0 | return nullptr; |
213 | 0 | } |
214 | | |
215 | | 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) |
216 | 0 | { |
217 | 0 | size_t i; |
218 | 0 | size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close; |
219 | 0 |
|
220 | 0 | name_len = strlen(dict_name); |
221 | 0 |
|
222 | 0 | #ifdef _qsSORTING |
223 | 0 | // TODO: binary search for key in the sorted qs_kv |
224 | 0 | #else // _qsSORTING |
225 | 0 | for(i=0; i<qs_kv_size; i++) |
226 | 0 | { |
227 | 0 | if ( strncmp(dict_name, qs_kv[i], name_len) == 0 ) |
228 | 0 | { |
229 | 0 | skip_to_eq = strcspn(qs_kv[i], "="); |
230 | 0 | if ( qs_kv[i][skip_to_eq] == '=' ) |
231 | 0 | skip_to_eq++; |
232 | 0 | skip_to_brace_open = strcspn(qs_kv[i], "["); |
233 | 0 | if ( qs_kv[i][skip_to_brace_open] == '[' ) |
234 | 0 | skip_to_brace_open++; |
235 | 0 | skip_to_brace_close = strcspn(qs_kv[i], "]"); |
236 | 0 |
|
237 | 0 | // Encoded brackets: page%5Bsize%5D=3 should match page[size]=3 (#1109). |
238 | 0 | if ( skip_to_brace_open == strlen(qs_kv[i]) ) |
239 | 0 | { |
240 | 0 | const char* open = strstr(qs_kv[i] + name_len, "%5B"); |
241 | 0 | if (!open) |
242 | 0 | open = strstr(qs_kv[i] + name_len, "%5b"); |
243 | 0 | if ( open && open == qs_kv[i] + name_len ) |
244 | 0 | { |
245 | 0 | const char* close = strstr(open + 3, "%5D"); |
246 | 0 | if (!close) |
247 | 0 | close = strstr(open + 3, "%5d"); |
248 | 0 | if ( close && nth == 0 ) |
249 | 0 | { |
250 | 0 | auto key = std::string(open + 3, static_cast<size_t>(close - (open + 3))); |
251 | 0 | auto value = std::string(qs_kv[i] + skip_to_eq); |
252 | 0 | return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value)); |
253 | 0 | } |
254 | 0 | else if ( close ) |
255 | 0 | { |
256 | 0 | --nth; |
257 | 0 | continue; |
258 | 0 | } |
259 | 0 | } |
260 | 0 | } |
261 | 0 |
|
262 | 0 | if ( skip_to_brace_open <= skip_to_brace_close && |
263 | 0 | skip_to_brace_open > 0 && |
264 | 0 | skip_to_brace_close > 0 && |
265 | 0 | nth == 0 ) |
266 | 0 | { |
267 | 0 | auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open); |
268 | 0 | auto value = std::string(qs_kv[i] + skip_to_eq); |
269 | 0 | return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value)); |
270 | 0 | } |
271 | 0 | else |
272 | 0 | { |
273 | 0 | --nth; |
274 | 0 | } |
275 | 0 | } |
276 | 0 | } |
277 | 0 | #endif // _qsSORTING |
278 | 0 |
|
279 | 0 | return nullptr; |
280 | 0 | } |
281 | | |
282 | | |
283 | | inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len) |
284 | 0 | { |
285 | 0 | const char * tmp= strchr(qs, '?'); |
286 | 0 |
|
287 | 0 | // find the beginning of the k/v substrings |
288 | 0 | if ( tmp != nullptr ) |
289 | 0 | qs = tmp + 1; |
290 | 0 |
|
291 | 0 | const size_t key_len = strlen(key); |
292 | 0 | while(*qs != '#' && *qs != '\0') |
293 | 0 | { |
294 | 0 | if ( qs_strncmp(key, qs, key_len) == 0 ) |
295 | 0 | break; |
296 | 0 | qs += strcspn(qs, "&"); |
297 | 0 | if (*qs=='&') qs++; |
298 | 0 | } |
299 | 0 |
|
300 | 0 | if ( qs[0] == '\0' ) return nullptr; |
301 | 0 |
|
302 | 0 | qs += strcspn(qs, "=&#"); |
303 | 0 | if ( qs[0] == '=' ) |
304 | 0 | { |
305 | 0 | qs++; |
306 | 0 | size_t i = strcspn(qs, "&=#"); |
307 | 0 | #ifdef _MSC_VER |
308 | 0 | strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1)); |
309 | 0 | #else |
310 | 0 | strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1)); |
311 | 0 | #endif |
312 | 0 | qs_decode(val); |
313 | 0 | } |
314 | 0 | else |
315 | 0 | { |
316 | 0 | if ( val_len > 0 ) |
317 | 0 | val[0] = '\0'; |
318 | 0 | } |
319 | 0 |
|
320 | 0 | return val; |
321 | 0 | } |
322 | | } |
323 | | // ---------------------------------------------------------------------------- |
324 | | |
325 | | |
326 | | namespace crow |
327 | | { |
328 | | struct request; |
329 | | /// A class to represent any data coming after the `?` in the request URL into key-value pairs. |
330 | | class query_string |
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 | 0 | { |
340 | 0 | for (auto p : qs.key_value_pairs_) |
341 | 0 | { |
342 | 0 | key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); |
343 | 0 | } |
344 | 0 | } |
345 | | |
346 | | query_string& operator=(const query_string& qs) |
347 | 0 | { |
348 | 0 | url_ = qs.url_; |
349 | 0 | key_value_pairs_.clear(); |
350 | 0 | for (auto p : qs.key_value_pairs_) |
351 | 0 | { |
352 | 0 | key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str())); |
353 | 0 | } |
354 | 0 | return *this; |
355 | 0 | } |
356 | | |
357 | | query_string& operator=(query_string&& qs) noexcept |
358 | 0 | { |
359 | 0 | key_value_pairs_ = std::move(qs.key_value_pairs_); |
360 | 0 | char* old_data = (char*)qs.url_.c_str(); |
361 | 0 | url_ = std::move(qs.url_); |
362 | 0 | for (auto& p : key_value_pairs_) |
363 | 0 | { |
364 | 0 | p += (char*)url_.c_str() - old_data; |
365 | 0 | } |
366 | 0 | return *this; |
367 | 0 | } |
368 | | |
369 | | |
370 | | query_string(std::string params, bool url = true): |
371 | | url_(std::move(params)) |
372 | 0 | { |
373 | 0 | if (url_.empty()) |
374 | 0 | return; |
375 | 0 |
|
376 | 0 | key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT); |
377 | 0 | size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url); |
378 | 0 |
|
379 | 0 | key_value_pairs_.resize(count); |
380 | 0 | key_value_pairs_.shrink_to_fit(); |
381 | 0 | } |
382 | | |
383 | | void clear() |
384 | 0 | { |
385 | 0 | key_value_pairs_.clear(); |
386 | 0 | url_.clear(); |
387 | 0 | } |
388 | | |
389 | | friend std::ostream& operator<<(std::ostream& os, const query_string& qs) |
390 | 0 | { |
391 | 0 | os << "[ "; |
392 | 0 | for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i) |
393 | 0 | { |
394 | 0 | if (i) |
395 | 0 | os << ", "; |
396 | 0 | os << qs.key_value_pairs_[i]; |
397 | 0 | } |
398 | 0 | os << " ]"; |
399 | 0 | return os; |
400 | 0 | } |
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 | 0 | { |
408 | 0 | char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size()); |
409 | 0 | return ret; |
410 | 0 | } |
411 | | |
412 | | /// Works similar to \ref get() except it removes the item from the query string. |
413 | | char* pop(const std::string& name) |
414 | 0 | { |
415 | 0 | char* ret = get(name); |
416 | 0 | if (ret != nullptr) |
417 | 0 | { |
418 | 0 | const std::string key_name = name + '='; |
419 | 0 | for (unsigned int i = 0; i < key_value_pairs_.size(); i++) |
420 | 0 | { |
421 | 0 | std::string str_item(key_value_pairs_[i]); |
422 | 0 | if (str_item.find(key_name)==0) |
423 | 0 | { |
424 | 0 | key_value_pairs_.erase(key_value_pairs_.begin() + i); |
425 | 0 | break; |
426 | 0 | } |
427 | 0 | } |
428 | 0 | } |
429 | 0 | return ret; |
430 | 0 | } |
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 | 0 | { |
438 | 0 | std::vector<char*> ret; |
439 | 0 | std::string plus = name + (use_brackets ? "[]" : ""); |
440 | 0 | char* element = nullptr; |
441 | 0 |
|
442 | 0 | int count = 0; |
443 | 0 | while (1) |
444 | 0 | { |
445 | 0 | element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++); |
446 | 0 | if (!element) |
447 | 0 | break; |
448 | 0 | ret.push_back(element); |
449 | 0 | } |
450 | 0 | return ret; |
451 | 0 | } |
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 | 0 | { |
456 | 0 | std::vector<char*> ret = get_list(name, use_brackets); |
457 | 0 | const size_t name_len = name.length(); |
458 | 0 | if (!ret.empty()) |
459 | 0 | { |
460 | 0 | for (unsigned int i = 0; i < key_value_pairs_.size(); i++) |
461 | 0 | { |
462 | 0 | std::string str_item(key_value_pairs_[i]); |
463 | 0 | if (str_item.find(name)==0) { |
464 | 0 | if (use_brackets && str_item.find("[]=",name_len)==name_len) { |
465 | 0 | key_value_pairs_.erase(key_value_pairs_.begin() + i--); |
466 | 0 | } else if (!use_brackets && str_item.find('=',name_len)==name_len ) { |
467 | 0 | key_value_pairs_.erase(key_value_pairs_.begin() + i--); |
468 | 0 | } |
469 | 0 | } |
470 | 0 | } |
471 | 0 | } |
472 | 0 | return ret; |
473 | 0 | } |
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 | 0 | { |
483 | 0 | std::unordered_map<std::string, std::string> ret; |
484 | 0 |
|
485 | 0 | int count = 0; |
486 | 0 | while (1) |
487 | 0 | { |
488 | 0 | if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++)) |
489 | 0 | ret.insert(*element); |
490 | 0 | else |
491 | 0 | break; |
492 | 0 | } |
493 | 0 | return ret; |
494 | 0 | } |
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 | 0 | { |
499 | 0 | const std::string name_value = name +'['; |
500 | 0 | std::unordered_map<std::string, std::string> ret = get_dict(name); |
501 | 0 | if (!ret.empty()) |
502 | 0 | { |
503 | 0 | for (unsigned int i = 0; i < key_value_pairs_.size(); i++) |
504 | 0 | { |
505 | 0 | std::string str_item(key_value_pairs_[i]); |
506 | 0 | if (str_item.find(name_value)==0) |
507 | 0 | { |
508 | 0 | key_value_pairs_.erase(key_value_pairs_.begin() + i--); |
509 | 0 | } |
510 | 0 | } |
511 | 0 | } |
512 | 0 | return ret; |
513 | 0 | } |
514 | | |
515 | | std::vector<std::string> keys() const |
516 | 0 | { |
517 | 0 | std::vector<std::string> keys; |
518 | 0 | keys.reserve(key_value_pairs_.size()); |
519 | 0 |
|
520 | 0 | for (const char* const element : key_value_pairs_) |
521 | 0 | { |
522 | 0 | const char* delimiter = strchr(element, '='); |
523 | 0 | if (delimiter) |
524 | 0 | keys.emplace_back(element, delimiter); |
525 | 0 | else |
526 | 0 | keys.emplace_back(element); |
527 | 0 | } |
528 | 0 |
|
529 | 0 | return keys; |
530 | 0 | } |
531 | | |
532 | | private: |
533 | | std::string url_; |
534 | | std::vector<char*> key_value_pairs_; |
535 | | }; |
536 | | |
537 | | } // namespace crow |