• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CrowCpp / Crow / 607

10 Feb 2025 01:02PM UTC coverage: 88.004%. First build
607

push

gh-actions

gittiver
Merge branch 'master' into v1.2.1

* master: (22 commits)
  a hotfix to ssl test which depends also on the length of crow version due to literal length use
  fixed header length calculation in unittest
  based on CMake option the return code for OPTION request method is switched between 200 (OK) and 204 (no content)
  added testcase for requested samples from issue
  replaced += by append and changed the order of strings to remove necessitity to substring at the the end.
  Made Origin header choosing logic more clear
  Update testing documentation to use correct function
  replaced not reliable sleep by a asio::waitable_timer (steady_timer) which is more precise and reliable.
  Writing to socket synchronously
  "Allow-Origin" behavior in OPTIONS requests fixed
  tcp endpoint created before server creation, invalid address error is logged now, server runs into timeout in that case added testcase for invalid address
  added wait timeout to server start
  Credential is not supported if Allowed Origin is '*'

65 of 69 new or added lines in 8 files covered. (94.2%)

4013 of 4560 relevant lines covered (88.0%)

128.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.87
/include/crow/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)
83✔
53
{
54
    unsigned char u1, u2, unyb, lnyb;
55

56
    while(n-- > 0)
445✔
57
    {
58
        u1 = static_cast<unsigned char>(*s++);
380✔
59
        u2 = static_cast<unsigned char>(*qs++);
380✔
60

61
        if ( ! CROW_QS_ISQSCHR(u1) ) {  u1 = '\0';  }
380✔
62
        if ( ! CROW_QS_ISQSCHR(u2) ) {  u2 = '\0';  }
380✔
63

64
        if ( u1 == '+' ) {  u1 = ' ';  }
380✔
65
        if ( u1 == '%' ) // easier/safer than scanf
380✔
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 = ' ';  }
380✔
76
        if ( u2 == '%' ) // easier/safer than scanf
380✔
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 )
380✔
87
            return u1 - u2;
18✔
88
        if ( u1 == '\0' )
362✔
89
            return 0;
×
90
    }
91
    if ( CROW_QS_ISQSCHR(*qs) )
65✔
92
        return -1;
1✔
93
    else
94
        return 0;
64✔
95
}
96

97

98
inline size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url = true)
63✔
99
{
100
    size_t i, j;
101
    char * substr_ptr;
102

103
    for(i=0; i<qs_kv_size; i++)  qs_kv[i] = NULL;
16,191✔
104

105
    // find the beginning of the k/v substrings or the fragment
106
    substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
63✔
107
    if (parse_url)
63✔
108
    {
109
        if (substr_ptr[0] != '\0')
63✔
110
            substr_ptr++;
11✔
111
        else
112
            return 0; // no query or fragment
52✔
113
    }
114

115
    i=0;
11✔
116
    while(i<qs_kv_size)
27✔
117
    {
118
        qs_kv[i] = substr_ptr;
27✔
119
        j = strcspn(substr_ptr, "&");
27✔
120
        if ( substr_ptr[j] == '\0' ) { i++; break;  } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
27✔
121
        substr_ptr += j + 1;
16✔
122
        i++;
16✔
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++)
38✔
128
    {
129
        substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
27✔
130
        if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0')  // blank value: skip decoding
27✔
131
            substr_ptr[0] = '\0';
4✔
132
        else
133
            qs_decode(++substr_ptr);
23✔
134
    }
135

136
#ifdef _qsSORTING
137
// TODO: qsort qs_kv, using qs_strncmp() for the comparison
138
#endif
139

140
    return i;
11✔
141
}
142

143

144
inline int qs_decode(char * qs)
23✔
145
{
146
    int i=0, j=0;
23✔
147

148
    while( CROW_QS_ISQSCHR(qs[j]) )
132✔
149
    {
150
        if ( qs[j] == '+' ) {  qs[i] = ' ';  }
109✔
151
        else if ( qs[j] == '%' ) // easier/safer than scanf
109✔
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];
109✔
164
        }
165
        i++;  j++;
109✔
166
    }
167
    qs[i] = '\0';
23✔
168

169
    return i;
23✔
170
}
171

172

173
inline char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth = 0)
45✔
174
{
175
    size_t i;
176
    size_t key_len, skip;
177

178
    key_len = strlen(key);
45✔
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++)
96✔
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 )
83✔
187
        {
188
            skip = strcspn(qs_kv[i], "=");
64✔
189
            if ( qs_kv[i][skip] == '=' )
64✔
190
                skip++;
60✔
191
            // return (zero-char value) ? ptr to trailing '\0' : ptr to value
192
            if(nth == 0)
64✔
193
                return qs_kv[i] + skip;
32✔
194
            else
195
                --nth;
32✔
196
        }
197
    }
198
#endif  // _qsSORTING
199

200
    return nullptr;
13✔
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)
21✔
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);
21✔
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++)
51✔
214
    {
215
        if ( strncmp(dict_name, qs_kv[i], name_len) == 0 )
45✔
216
        {
217
            skip_to_eq = strcspn(qs_kv[i], "=");
45✔
218
            if ( qs_kv[i][skip_to_eq] == '=' )
45✔
219
                skip_to_eq++;
45✔
220
            skip_to_brace_open = strcspn(qs_kv[i], "[");
45✔
221
            if ( qs_kv[i][skip_to_brace_open] == '[' )
45✔
222
                skip_to_brace_open++;
45✔
223
            skip_to_brace_close = strcspn(qs_kv[i], "]");
45✔
224

225
            if ( skip_to_brace_open <= skip_to_brace_close &&
45✔
226
                 skip_to_brace_open > 0 &&
45✔
227
                 skip_to_brace_close > 0 &&
45✔
228
                 nth == 0 )
229
            {
230
                auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open);
30✔
231
                auto value = std::string(qs_kv[i] + skip_to_eq);
15✔
232
                return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value));
15✔
233
            }
15✔
234
            else
235
            {
236
                --nth;
30✔
237
            }
238
        }
239
    }
240
#endif  // _qsSORTING
241

242
    return nullptr;
6✔
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.
293
    class query_string
294
    {
295
    public:
296
        static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
297

298
        query_string() = default;
318✔
299

300
        query_string(const query_string& qs):
1✔
301
          url_(qs.url_)
1✔
302
        {
303
            for (auto p : qs.key_value_pairs_)
4✔
304
            {
305
                key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
3✔
306
            }
307
        }
1✔
308

309
        query_string& operator=(const query_string& qs)
9✔
310
        {
311
            url_ = qs.url_;
9✔
312
            key_value_pairs_.clear();
9✔
313
            for (auto p : qs.key_value_pairs_)
27✔
314
            {
315
                key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
18✔
316
            }
317
            return *this;
9✔
318
        }
319

320
        query_string& operator=(query_string&& qs) noexcept
129✔
321
        {
322
            key_value_pairs_ = std::move(qs.key_value_pairs_);
129✔
323
            char* old_data = (char*)qs.url_.c_str();
129✔
324
            url_ = std::move(qs.url_);
129✔
325
            for (auto& p : key_value_pairs_)
150✔
326
            {
327
                p += (char*)url_.c_str() - old_data;
21✔
328
            }
329
            return *this;
129✔
330
        }
331

332

333
        query_string(std::string params, bool url = true):
64✔
334
          url_(std::move(params))
64✔
335
        {
336
            if (url_.empty())
64✔
337
                return;
1✔
338

339
            key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
63✔
340
            size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url);
63✔
341

342
            key_value_pairs_.resize(count);
63✔
343
            key_value_pairs_.shrink_to_fit();
63✔
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)
1✔
353
        {
354
            os << "[ ";
1✔
355
            for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i)
1✔
356
            {
357
                if (i)
×
358
                    os << ", ";
×
359
                os << qs.key_value_pairs_[i];
×
360
            }
361
            os << " ]";
1✔
362
            return os;
1✔
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
19✔
370
        {
371
            char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
19✔
372
            return ret;
19✔
373
        }
374

375
        /// Works similar to \ref get() except it removes the item from the query string.
376
        char* pop(const std::string& name)
1✔
377
        {
378
            char* ret = get(name);
1✔
379
            if (ret != nullptr)
1✔
380
            {
381
                const std::string key_name = name + '=';
1✔
382
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
2✔
383
                {
384
                    std::string str_item(key_value_pairs_[i]);
2✔
385
                    if (str_item.find(key_name)==0)
2✔
386
                    {
387
                        key_value_pairs_.erase(key_value_pairs_.begin() + i);
1✔
388
                        break;
1✔
389
                    }
390
                }
2✔
391
            }
1✔
392
            return ret;
1✔
393
        }
394

395
        /// Returns a list of values, passed as `?name[]=value1&name[]=value2&...name[]=valuen` with n being the size of the list.
396

397
        ///
398
        /// 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`
399
        std::vector<char*> get_list(const std::string& name, bool use_brackets = true) const
9✔
400
        {
401
            std::vector<char*> ret;
9✔
402
            std::string plus = name + (use_brackets ? "[]" : "");
9✔
403
            char* element = nullptr;
9✔
404

405
            int count = 0;
9✔
406
            while (1)
407
            {
408
                element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
26✔
409
                if (!element)
26✔
410
                    break;
9✔
411
                ret.push_back(element);
17✔
412
            }
413
            return ret;
18✔
414
        }
9✔
415

416
        /// Similar to \ref get_list() but it removes the
417
        std::vector<char*> pop_list(const std::string& name, bool use_brackets = true)
1✔
418
        {
419
            std::vector<char*> ret = get_list(name, use_brackets);
1✔
420
            const size_t name_len = name.length();
1✔
421
            if (!ret.empty())
1✔
422
            {
423
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
4✔
424
                {
425
                    std::string str_item(key_value_pairs_[i]);
3✔
426
                    if (str_item.find(name)==0) {
3✔
427
                      if (use_brackets && str_item.find("[]=",name_len)==name_len) {
3✔
428
                        key_value_pairs_.erase(key_value_pairs_.begin() + i--);
3✔
NEW
429
                      } else if (!use_brackets && str_item.find('=',name_len)==name_len ) {
×
NEW
430
                           key_value_pairs_.erase(key_value_pairs_.begin() + i--);
×
431
                       }
432
                    }
433
                }
3✔
434
            }
435
            return ret;
1✔
436
        }
×
437

438
        /// Works similar to \ref get_list() except the brackets are mandatory must not be empty.
439

440
        ///
441
        /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
442
        ///
443
        /// 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.
444
        std::unordered_map<std::string, std::string> get_dict(const std::string& name) const
6✔
445
        {
446
            std::unordered_map<std::string, std::string> ret;
6✔
447

448
            int count = 0;
6✔
449
            while (1)
450
            {
451
                if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
21✔
452
                    ret.insert(*element);
15✔
453
                else
454
                    break;
21✔
455
            }
15✔
456
            return ret;
6✔
457
        }
×
458

459
        /// Works the same as \ref get_dict() but removes the values from the query string.
460
        std::unordered_map<std::string, std::string> pop_dict(const std::string& name)
1✔
461
        {
462
            const std::string name_value = name +'[';
1✔
463
            std::unordered_map<std::string, std::string> ret = get_dict(name);
1✔
464
            if (!ret.empty())
1✔
465
            {
466
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
4✔
467
                {
468
                    std::string str_item(key_value_pairs_[i]);
3✔
469
                    if (str_item.find(name_value)==0)
3✔
470
                    {
471
                        key_value_pairs_.erase(key_value_pairs_.begin() + i--);
3✔
472
                    }
473
                }
3✔
474
            }
475
            return ret;
2✔
476
        }
1✔
477

478
        std::vector<std::string> keys() const
2✔
479
        {
480
            std::vector<std::string> keys;
2✔
481
            keys.reserve(key_value_pairs_.size());
2✔
482

483
            for (const char* const element : key_value_pairs_)
8✔
484
            {
485
                const char* delimiter = strchr(element, '=');
6✔
486
                if (delimiter)
6✔
487
                    keys.emplace_back(element, delimiter);
6✔
488
                else
489
                    keys.emplace_back(element);
×
490
            }
491

492
            return keys;
2✔
493
        }
×
494

495
    private:
496
        std::string url_;
497
        std::vector<char*> key_value_pairs_;
498
    };
499

500
} // namespace crow
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc