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

CrowCpp / Crow / 931

28 Feb 2026 10:57AM UTC coverage: 87.355% (+0.2%) from 87.184%
931

push

gh-actions

gittiver
Bump actions/upload-artifact from 6 to 7

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

4145 of 4745 relevant lines covered (87.36%)

611.28 hits per line

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

92.46
/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)
348✔
53
{
54
    unsigned char u1, u2, unyb, lnyb;
55

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

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

64
        if ( u1 == '+' ) {  u1 = ' ';  }
1,560✔
65
        if ( u1 == '%' ) // easier/safer than scanf
1,560✔
66
        {
67
            // Check that next two chars exist and are valid hex before reading
68
            if ( CROW_QS_ISHEX(s[0]) && CROW_QS_ISHEX(s[1]) )
4✔
69
            {
70
                unyb = static_cast<unsigned char>(*s++);
×
71
                lnyb = static_cast<unsigned char>(*s++);
×
72
                u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
×
73
            }
74
            else
75
            {
76
                u1 = '\0';
4✔
77
            }
78
        }
79

80
        if ( u2 == '+' ) {  u2 = ' ';  }
1,560✔
81
        if ( u2 == '%' ) // easier/safer than scanf
1,560✔
82
        {
83
            // Check that next two chars exist and are valid hex before reading
84
            if ( CROW_QS_ISHEX(qs[0]) && CROW_QS_ISHEX(qs[1]) )
4✔
85
            {
86
                unyb = static_cast<unsigned char>(*qs++);
×
87
                lnyb = static_cast<unsigned char>(*qs++);
×
88
                u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
×
89
            }
90
            else
91
            {
92
                u2 = '\0';
4✔
93
            }
94
        }
95

96
        if ( u1 != u2 )
1,560✔
97
            return u1 - u2;
72✔
98
        if ( u1 == '\0' )
1,488✔
99
            return 0;
4✔
100
    }
101
    if ( CROW_QS_ISQSCHR(*qs) )
272✔
102
        return -1;
4✔
103
    else
104
        return 0;
268✔
105
}
106

107

108
inline size_t qs_parse(char* qs, char* qs_kv[], size_t qs_kv_size, bool parse_url = true)
476✔
109
{
110
    size_t i, j;
111
    char * substr_ptr;
112

113
    for(i=0; i<qs_kv_size; i++)  qs_kv[i] = NULL;
122,332✔
114

115
    // find the beginning of the k/v substrings or the fragment
116
    substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
476✔
117
    if (parse_url)
476✔
118
    {
119
        if (substr_ptr[0] != '\0')
476✔
120
            substr_ptr++;
60✔
121
        else
122
            return 0; // no query or fragment
416✔
123
    }
124

125
    i=0;
60✔
126
    while(i<qs_kv_size)
124✔
127
    {
128
        qs_kv[i] = substr_ptr;
124✔
129
        j = strcspn(substr_ptr, "&");
124✔
130
        if ( substr_ptr[j] == '\0' ) { i++; break;  } // x &'s -> means x iterations of this loop -> means *x+1* k/v pairs
124✔
131
        substr_ptr += j + 1;
64✔
132
        i++;
64✔
133
    }
134

135
    // we only decode the values in place, the keys could have '='s in them
136
    // which will hose our ability to distinguish keys from values later
137
    for(j=0; j<i; j++)
184✔
138
    {
139
        substr_ptr = qs_kv[j] + strcspn(qs_kv[j], "=&#");
124✔
140
        if ( substr_ptr[0] == '&' || substr_ptr[0] == '\0')  // blank value: skip decoding
124✔
141
            substr_ptr[0] = '\0';
20✔
142
        else
143
            qs_decode(++substr_ptr);
104✔
144
    }
145

146
#ifdef _qsSORTING
147
// TODO: qsort qs_kv, using qs_strncmp() for the comparison
148
#endif
149

150
    return i;
60✔
151
}
152

153

154
inline int qs_decode(char * qs)
104✔
155
{
156
    int i=0, j=0;
104✔
157

158
    while( CROW_QS_ISQSCHR(qs[j]) )
564✔
159
    {
160
        if ( qs[j] == '+' ) {  qs[i] = ' ';  }
468✔
161
        else if ( qs[j] == '%' ) // easier/safer than scanf
468✔
162
        {
163
            // Check bounds before reading: ensure j+1 and j+2 are within string
164
            if ( qs[j+1] == '\0' || qs[j+2] == '\0' ||
20✔
165
                 ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
12✔
166
            {
167
                qs[i] = '\0';
8✔
168
                return i;
8✔
169
            }
170
            qs[i] = (CROW_QS_HEX2DEC(qs[j+1]) * 16) + CROW_QS_HEX2DEC(qs[j+2]);
4✔
171
            j+=2;
4✔
172
        }
173
        else
174
        {
175
            qs[i] = qs[j];
456✔
176
        }
177
        i++;  j++;
460✔
178
    }
179
    qs[i] = '\0';
96✔
180

181
    return i;
96✔
182
}
183

184

185
inline char * qs_k2v(const char * key, char * const * qs_kv, size_t qs_kv_size, int nth = 0)
196✔
186
{
187
    size_t i;
188
    size_t key_len, skip;
189

190
    key_len = strlen(key);
196✔
191

192
#ifdef _qsSORTING
193
// TODO: binary search for key in the sorted qs_kv
194
#else  // _qsSORTING
195
    for(i=0; i<qs_kv_size; i++)
400✔
196
    {
197
        // we rely on the unambiguous '=' to find the value in our k/v pair
198
        if ( qs_strncmp(key, qs_kv[i], key_len) == 0 )
348✔
199
        {
200
            skip = strcspn(qs_kv[i], "=");
272✔
201
            if ( qs_kv[i][skip] == '=' )
272✔
202
                skip++;
252✔
203
            // return (zero-char value) ? ptr to trailing '\0' : ptr to value
204
            if(nth == 0)
272✔
205
                return qs_kv[i] + skip;
144✔
206
            else
207
                --nth;
128✔
208
        }
209
    }
210
#endif  // _qsSORTING
211

212
    return nullptr;
52✔
213
}
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)
84✔
216
{
217
    size_t i;
218
    size_t name_len, skip_to_eq, skip_to_brace_open, skip_to_brace_close;
219

220
    name_len = strlen(dict_name);
84✔
221

222
#ifdef _qsSORTING
223
// TODO: binary search for key in the sorted qs_kv
224
#else  // _qsSORTING
225
    for(i=0; i<qs_kv_size; i++)
204✔
226
    {
227
        if ( strncmp(dict_name, qs_kv[i], name_len) == 0 )
180✔
228
        {
229
            skip_to_eq = strcspn(qs_kv[i], "=");
180✔
230
            if ( qs_kv[i][skip_to_eq] == '=' )
180✔
231
                skip_to_eq++;
180✔
232
            skip_to_brace_open = strcspn(qs_kv[i], "[");
180✔
233
            if ( qs_kv[i][skip_to_brace_open] == '[' )
180✔
234
                skip_to_brace_open++;
180✔
235
            skip_to_brace_close = strcspn(qs_kv[i], "]");
180✔
236

237
            if ( skip_to_brace_open <= skip_to_brace_close &&
180✔
238
                 skip_to_brace_open > 0 &&
180✔
239
                 skip_to_brace_close > 0 &&
180✔
240
                 nth == 0 )
241
            {
242
                auto key = std::string(qs_kv[i] + skip_to_brace_open, skip_to_brace_close - skip_to_brace_open);
120✔
243
                auto value = std::string(qs_kv[i] + skip_to_eq);
60✔
244
                return std::unique_ptr<std::pair<std::string, std::string>>(new std::pair<std::string, std::string>(key, value));
60✔
245
            }
60✔
246
            else
247
            {
248
                --nth;
120✔
249
            }
250
        }
251
    }
252
#endif  // _qsSORTING
253

254
    return nullptr;
24✔
255
}
256

257

258
inline char * qs_scanvalue(const char * key, const char * qs, char * val, size_t val_len)
259
{
260
    const char * tmp= strchr(qs, '?');
261

262
    // find the beginning of the k/v substrings
263
    if ( tmp != nullptr )
264
        qs = tmp + 1;
265

266
    const size_t key_len = strlen(key);
267
    while(*qs != '#' && *qs != '\0')
268
    {
269
        if ( qs_strncmp(key, qs, key_len) == 0 )
270
            break;
271
        qs += strcspn(qs, "&");
272
        if (*qs=='&') qs++;
273
    }
274

275
    if ( qs[0] == '\0' ) return nullptr;
276

277
    qs += strcspn(qs, "=&#");
278
    if ( qs[0] == '=' )
279
    {
280
        qs++;
281
        size_t i = strcspn(qs, "&=#");
282
#ifdef _MSC_VER
283
        strncpy_s(val, val_len, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
284
#else
285
        strncpy(val, qs, (val_len - 1)<(i + 1) ? (val_len - 1) : (i + 1));
286
#endif
287
                qs_decode(val);
288
    }
289
    else
290
    {
291
        if ( val_len > 0 )
292
            val[0] = '\0';
293
    }
294

295
    return val;
296
}
297
}
298
// ----------------------------------------------------------------------------
299

300

301
namespace crow
302
{
303
    struct request;
304
    /// A class to represent any data coming after the `?` in the request URL into key-value pairs.
305
    class query_string
306
    {
307
    public:
308
        static const int MAX_KEY_VALUE_PAIRS_COUNT = 256;
309

310
        query_string() = default;
2,152✔
311

312
        query_string(const query_string& qs):
4✔
313
          url_(qs.url_)
4✔
314
        {
315
            for (auto p : qs.key_value_pairs_)
16✔
316
            {
317
                key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
12✔
318
            }
319
        }
4✔
320

321
        query_string& operator=(const query_string& qs)
36✔
322
        {
323
            url_ = qs.url_;
36✔
324
            key_value_pairs_.clear();
36✔
325
            for (auto p : qs.key_value_pairs_)
108✔
326
            {
327
                key_value_pairs_.push_back((char*)(p - qs.url_.c_str() + url_.c_str()));
72✔
328
            }
329
            return *this;
36✔
330
        }
331

332
        query_string& operator=(query_string&& qs) noexcept
1,460✔
333
        {
334
            key_value_pairs_ = std::move(qs.key_value_pairs_);
1,460✔
335
            char* old_data = (char*)qs.url_.c_str();
1,460✔
336
            url_ = std::move(qs.url_);
1,460✔
337
            for (auto& p : key_value_pairs_)
1,544✔
338
            {
339
                p += (char*)url_.c_str() - old_data;
84✔
340
            }
341
            return *this;
1,460✔
342
        }
343

344

345
        query_string(std::string params, bool url = true):
480✔
346
          url_(std::move(params))
480✔
347
        {
348
            if (url_.empty())
480✔
349
                return;
4✔
350

351
            key_value_pairs_.resize(MAX_KEY_VALUE_PAIRS_COUNT);
476✔
352
            size_t count = qs_parse(&url_[0], &key_value_pairs_[0], MAX_KEY_VALUE_PAIRS_COUNT, url);
476✔
353

354
            key_value_pairs_.resize(count);
476✔
355
            key_value_pairs_.shrink_to_fit();
476✔
356
        }
×
357

358
        void clear()
359
        {
360
            key_value_pairs_.clear();
361
            url_.clear();
362
        }
363

364
        friend std::ostream& operator<<(std::ostream& os, const query_string& qs)
4✔
365
        {
366
            os << "[ ";
4✔
367
            for (size_t i = 0; i < qs.key_value_pairs_.size(); ++i)
4✔
368
            {
369
                if (i)
×
370
                    os << ", ";
×
371
                os << qs.key_value_pairs_[i];
×
372
            }
373
            os << " ]";
4✔
374
            return os;
4✔
375
        }
376

377
        /// Get a value from a name, used for `?name=value`.
378

379
        ///
380
        /// Note: this method returns the value of the first occurrence of the key only, to return all occurrences, see \ref get_list().
381
        char* get(const std::string& name) const
92✔
382
        {
383
            char* ret = qs_k2v(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size());
92✔
384
            return ret;
92✔
385
        }
386

387
        /// Works similar to \ref get() except it removes the item from the query string.
388
        char* pop(const std::string& name)
4✔
389
        {
390
            char* ret = get(name);
4✔
391
            if (ret != nullptr)
4✔
392
            {
393
                const std::string key_name = name + '=';
4✔
394
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
8✔
395
                {
396
                    std::string str_item(key_value_pairs_[i]);
8✔
397
                    if (str_item.find(key_name)==0)
8✔
398
                    {
399
                        key_value_pairs_.erase(key_value_pairs_.begin() + i);
4✔
400
                        break;
4✔
401
                    }
402
                }
8✔
403
            }
4✔
404
            return ret;
4✔
405
        }
406

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

409
        ///
410
        /// 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`
411
        std::vector<char*> get_list(const std::string& name, bool use_brackets = true) const
36✔
412
        {
413
            std::vector<char*> ret;
36✔
414
            std::string plus = name + (use_brackets ? "[]" : "");
36✔
415
            char* element = nullptr;
36✔
416

417
            int count = 0;
36✔
418
            while (1)
419
            {
420
                element = qs_k2v(plus.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++);
104✔
421
                if (!element)
104✔
422
                    break;
36✔
423
                ret.push_back(element);
68✔
424
            }
425
            return ret;
72✔
426
        }
36✔
427

428
        /// Similar to \ref get_list() but it removes the
429
        std::vector<char*> pop_list(const std::string& name, bool use_brackets = true)
4✔
430
        {
431
            std::vector<char*> ret = get_list(name, use_brackets);
4✔
432
            const size_t name_len = name.length();
4✔
433
            if (!ret.empty())
4✔
434
            {
435
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
16✔
436
                {
437
                    std::string str_item(key_value_pairs_[i]);
12✔
438
                    if (str_item.find(name)==0) {
12✔
439
                      if (use_brackets && str_item.find("[]=",name_len)==name_len) {
12✔
440
                        key_value_pairs_.erase(key_value_pairs_.begin() + i--);
12✔
441
                      } else if (!use_brackets && str_item.find('=',name_len)==name_len ) {
×
442
                           key_value_pairs_.erase(key_value_pairs_.begin() + i--);
×
443
                       }
444
                    }
445
                }
12✔
446
            }
447
            return ret;
4✔
448
        }
×
449

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

452
        ///
453
        /// For example calling `get_dict(yourname)` on `?yourname[sub1]=42&yourname[sub2]=84` would give a map containing `{sub1 : 42, sub2 : 84}`.
454
        ///
455
        /// 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.
456
        std::unordered_map<std::string, std::string> get_dict(const std::string& name) const
24✔
457
        {
458
            std::unordered_map<std::string, std::string> ret;
24✔
459

460
            int count = 0;
24✔
461
            while (1)
462
            {
463
                if (auto element = qs_dict_name2kv(name.c_str(), key_value_pairs_.data(), key_value_pairs_.size(), count++))
84✔
464
                    ret.insert(*element);
60✔
465
                else
466
                    break;
84✔
467
            }
60✔
468
            return ret;
24✔
469
        }
×
470

471
        /// Works the same as \ref get_dict() but removes the values from the query string.
472
        std::unordered_map<std::string, std::string> pop_dict(const std::string& name)
4✔
473
        {
474
            const std::string name_value = name +'[';
4✔
475
            std::unordered_map<std::string, std::string> ret = get_dict(name);
4✔
476
            if (!ret.empty())
4✔
477
            {
478
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
16✔
479
                {
480
                    std::string str_item(key_value_pairs_[i]);
12✔
481
                    if (str_item.find(name_value)==0)
12✔
482
                    {
483
                        key_value_pairs_.erase(key_value_pairs_.begin() + i--);
12✔
484
                    }
485
                }
12✔
486
            }
487
            return ret;
8✔
488
        }
4✔
489

490
        std::vector<std::string> keys() const
20✔
491
        {
492
            std::vector<std::string> keys;
20✔
493
            keys.reserve(key_value_pairs_.size());
20✔
494

495
            for (const char* const element : key_value_pairs_)
56✔
496
            {
497
                const char* delimiter = strchr(element, '=');
36✔
498
                if (delimiter)
36✔
499
                    keys.emplace_back(element, delimiter);
32✔
500
                else
501
                    keys.emplace_back(element);
4✔
502
            }
503

504
            return keys;
20✔
505
        }
×
506

507
    private:
508
        std::string url_;
509
        std::vector<char*> key_value_pairs_;
510
    };
511

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

© 2026 Coveralls, Inc