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

CrowCpp / Crow / 250

06 May 2024 12:20PM UTC coverage: 87.853% (+0.07%) from 87.782%
250

push

gh-actions

gittiver
Get rid of unnecessary string copies in the the keys() method

10 of 11 new or added lines in 1 file covered. (90.91%)

2 existing lines in 1 file now uncovered.

3826 of 4355 relevant lines covered (87.85%)

109.32 hits per line

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

86.91
/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)
72✔
99
{
100
    size_t i, j;
101
    char * substr_ptr;
102

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

105
    // find the beginning of the k/v substrings or the fragment
106
    substr_ptr = parse_url ? qs + strcspn(qs, "?#") : qs;
72✔
107
    if (parse_url)
72✔
108
    {
109
        if (substr_ptr[0] != '\0')
72✔
110
            substr_ptr++;
11✔
111
        else
112
            return 0; // no query or fragment
61✔
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);
15✔
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;
244✔
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
187✔
321
        {
322
            key_value_pairs_ = std::move(qs.key_value_pairs_);
187✔
323
            char* old_data = (char*)qs.url_.c_str();
187✔
324
            url_ = std::move(qs.url_);
187✔
325
            for (auto& p : key_value_pairs_)
208✔
326
            {
327
                p += (char*)url_.c_str() - old_data;
21✔
328
            }
329
            return *this;
187✔
330
        }
331

332

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

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

342
            key_value_pairs_.resize(count);
72✔
343
            key_value_pairs_.shrink_to_fit();
72✔
UNCOV
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
                for (unsigned int i = 0; i < key_value_pairs_.size(); i++)
2✔
382
                {
383
                    std::string str_item(key_value_pairs_[i]);
2✔
384
                    if (str_item.substr(0, name.size() + 1) == name + '=')
2✔
385
                    {
386
                        key_value_pairs_.erase(key_value_pairs_.begin() + i);
1✔
387
                        break;
1✔
388
                    }
389
                }
2✔
390
            }
391
            return ret;
1✔
392
        }
393

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

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

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

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

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

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

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

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

472
        std::vector<std::string> keys() const
2✔
473
        {
474
            std::vector<std::string> keys;
2✔
475
            keys.reserve(key_value_pairs_.size());
2✔
476

477
            for (const char* const element : key_value_pairs_)
8✔
478
            {
479
                const char* delimiter = strchr(element, '=');
6✔
480
                if (delimiter)
6✔
481
                    keys.emplace_back(element, delimiter);
6✔
482
                else
NEW
483
                    keys.emplace_back(element);
×
484
            }
485

486
            return keys;
2✔
UNCOV
487
        }
×
488

489
    private:
490
        std::string url_;
491
        std::vector<char*> key_value_pairs_;
492
    };
493

494
} // 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