Coverage Report

Created: 2025-08-26 06:31

/src/pistache/src/common/http_headers.cc
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * SPDX-FileCopyrightText: 2015 Mathieu Stefani
3
 *
4
 * SPDX-License-Identifier: Apache-2.0
5
 */
6
7
/* http_headers.cc
8
   Mathieu Stefani, 19 August 2015
9
10
   Headers registry
11
*/
12
13
#include <pistache/http_headers.h>
14
15
#include <memory>
16
#include <stdexcept>
17
#include <unordered_map>
18
#include <vector>
19
20
namespace Pistache::Http::Header
21
{
22
    RegisterHeader(Accept);
23
    RegisterHeader(AccessControlAllowOrigin);
24
    RegisterHeader(AccessControlAllowHeaders);
25
    RegisterHeader(AccessControlExposeHeaders);
26
    RegisterHeader(AccessControlAllowMethods);
27
    RegisterHeader(Allow);
28
    RegisterHeader(CacheControl);
29
    RegisterHeader(Connection);
30
    RegisterHeader(AcceptEncoding);
31
    RegisterHeader(ContentEncoding);
32
    RegisterHeader(TransferEncoding);
33
    RegisterHeader(ContentLength);
34
    RegisterHeader(ContentType);
35
    RegisterHeader(Authorization);
36
    RegisterHeader(Date);
37
    RegisterHeader(Expect);
38
    RegisterHeader(Host);
39
    RegisterHeader(LastModified);
40
    RegisterHeader(Location);
41
    RegisterHeader(Server);
42
    RegisterHeader(UserAgent);
43
44
    bool strToQvalue(const char* str, float* qvalue, std::size_t* qvalueLen)
45
13.1k
    {
46
13.1k
        constexpr char offset = '0';
47
48
13.1k
        *qvalueLen = 0;
49
50
        // It is useless to read more than 6 chars, as the maximum allowed
51
        // number of digits after the dot is 3, so n.nnn is 5.
52
        // The 6th character is read to check if the user specified a qvalue
53
        // with too many digits.
54
41.0k
        for (; *qvalueLen < 6; (*qvalueLen)++)
55
40.6k
        {
56
            // the decimal dot is only allowed at index 1;
57
            // 0.15  ok
58
            // 1.10  ok
59
            // 1.0.1 no
60
            // .40   no
61
40.6k
            if (str[*qvalueLen] == '.' && *qvalueLen != 1)
62
919
            {
63
919
                return false;
64
919
            }
65
66
            // The only valid characters are digits and the decimal dot,
67
            // anything else signals the end of the string
68
39.7k
            if (str[*qvalueLen] != '.' && !std::isdigit(str[*qvalueLen]))
69
11.9k
            {
70
11.9k
                break;
71
11.9k
            }
72
39.7k
        }
73
74
        // Guards against numbers like:
75
        // empty
76
        // 1.
77
        // 0.1234
78
12.2k
        if (*qvalueLen < 1 || *qvalueLen == 2 || *qvalueLen > 5)
79
778
        {
80
778
            return false;
81
778
        }
82
83
        // The first char can only be 0 or 1
84
11.4k
        if (str[0] != '0' && str[0] != '1')
85
245
        {
86
245
            return false;
87
245
        }
88
89
11.2k
        int qint = 0;
90
91
11.2k
        switch (*qvalueLen)
92
11.2k
        {
93
584
        case 5:
94
584
            qint += (str[4] - offset);
95
584
            [[fallthrough]];
96
2.41k
        case 4:
97
2.41k
            qint += (str[3] - offset) * 10;
98
2.41k
            [[fallthrough]];
99
3.81k
        case 3:
100
3.81k
            qint += (str[2] - offset) * 100;
101
3.81k
            [[fallthrough]];
102
11.2k
        case 1:
103
11.2k
            qint += (str[0] - offset) * 1000;
104
11.2k
        }
105
106
11.2k
        *qvalue = static_cast<short>(qint) / 1000.0F;
107
108
11.2k
        if (*qvalue > 1)
109
45
        {
110
45
            return false;
111
45
        }
112
113
11.2k
        return true;
114
11.2k
    }
115
116
    std::string toLowercase(std::string str)
117
134k
    {
118
134k
        std::transform(str.begin(), str.end(), str.begin(),
119
134k
                       [](const char ch)
120
1.33M
                       {
121
1.33M
                           const unsigned char uch =
122
1.33M
                               static_cast<unsigned char>(ch);
123
1.33M
                           auto ires = ::tolower(uch);
124
1.33M
                           return(static_cast<char>(ires));
125
1.33M
                       });
126
        // Note re: the lambda function being used for std::transform
127
        // above. Previously (before 10/2024), std::transform was simply being
128
        // passed ::tolower/::toupper for the transformer function, but in fact
129
        // we need to make two changes to that: i) we need to cast the input
130
        // parm to "unsigned char" to avoid errors due to sign extension as
131
        // explained on the Linux man page
132
        // (e.g. https://www.man7.org/linux/man-pages/man3/toupper.3.html,
133
        // Notes section); and ii) since the result of the transformer function
134
        // is written into std::string, i.e. to a char, the transformer
135
        // function needs to return a char, not (as ::tolower/::toupper does)
136
        // an int, otherwise the compiler may complain about loss of integer
137
        // size in writing the int to a char (and in fact MSVC was complaining
138
        // in exactly this way).
139
134k
        return str;
140
134k
    }
141
142
    bool LowercaseEqualStatic(const std::string& dynamic,
143
                              const std::string& statik)
144
73.3k
    {
145
73.3k
        return std::equal(
146
73.3k
            dynamic.begin(), dynamic.end(), statik.begin(), statik.end(),
147
73.3k
            [](const char& a, const char& b) { return std::tolower(a) == b; });
148
73.3k
    }
149
150
    Registry& Registry::instance()
151
54.8k
    {
152
54.8k
        static Registry instance;
153
154
54.8k
        return instance;
155
54.8k
    }
156
157
2
    Registry::Registry() = default;
158
159
0
    Registry::~Registry() = default;
160
161
    void Registry::registerHeader(const std::string& name,
162
                                  Registry::RegistryFunc func)
163
42
    {
164
42
        auto it = registry.find(name);
165
42
        if (it != std::end(registry))
166
0
        {
167
0
            throw std::runtime_error("Header already registered");
168
0
        }
169
170
42
        registry.insert(std::make_pair(name, std::move(func)));
171
42
    }
172
173
    std::vector<std::string> Registry::headersList()
174
0
    {
175
0
        std::vector<std::string> names;
176
0
        names.reserve(registry.size());
177
178
0
        for (const auto& header : registry)
179
0
        {
180
0
            names.push_back(header.first);
181
0
        }
182
183
0
        return names;
184
0
    }
185
186
    std::unique_ptr<Header> Registry::makeHeader(const std::string& name)
187
20.1k
    {
188
20.1k
        auto it = registry.find(name);
189
20.1k
        if (it == std::end(registry))
190
0
        {
191
0
            throw std::runtime_error("Unknown header");
192
0
        }
193
194
20.1k
        return it->second();
195
20.1k
    }
196
197
    bool Registry::isRegistered(const std::string& name)
198
34.6k
    {
199
34.6k
        auto it = registry.find(name);
200
34.6k
        return it != std::end(registry);
201
34.6k
    }
202
203
    Collection& Collection::add(const std::shared_ptr<Header>& header)
204
19.9k
    {
205
19.9k
        headers.insert(std::make_pair(header->name(), header));
206
207
19.9k
        return *this;
208
19.9k
    }
209
210
    Collection& Collection::addRaw(const Raw& raw)
211
38.5k
    {
212
38.5k
        rawHeaders.insert(std::make_pair(raw.name(), raw));
213
38.5k
        return *this;
214
38.5k
    }
215
216
    std::shared_ptr<const Header> Collection::get(const std::string& name) const
217
0
    {
218
0
        auto header = getImpl(name);
219
0
        if (!header.first)
220
0
        {
221
0
            throw std::runtime_error("Could not find header");
222
0
        }
223
224
0
        return header.second;
225
0
    }
226
227
    std::shared_ptr<Header> Collection::get(const std::string& name)
228
0
    {
229
0
        auto header = getImpl(name);
230
0
        if (!header.first)
231
0
        {
232
0
            throw std::runtime_error("Could not find header");
233
0
        }
234
235
0
        return header.second;
236
0
    }
237
238
    Raw Collection::getRaw(const std::string& name) const
239
0
    {
240
0
        auto it = rawHeaders.find(name);
241
0
        if (it == std::end(rawHeaders))
242
0
        {
243
0
            throw std::runtime_error("Could not find header");
244
0
        }
245
246
0
        return it->second;
247
0
    }
248
249
    std::shared_ptr<const Header>
250
    Collection::tryGet(const std::string& name) const
251
0
    {
252
0
        auto header = getImpl(name);
253
0
        if (!header.first)
254
0
            return nullptr;
255
256
0
        return header.second;
257
0
    }
258
259
    std::shared_ptr<Header> Collection::tryGet(const std::string& name)
260
1.58k
    {
261
1.58k
        auto header = getImpl(name);
262
1.58k
        if (!header.first)
263
880
            return nullptr;
264
265
704
        return header.second;
266
1.58k
    }
267
268
    std::optional<Raw> Collection::tryGetRaw(const std::string& name) const
269
0
    {
270
0
        auto it = rawHeaders.find(name);
271
0
        if (it == std::end(rawHeaders))
272
0
        {
273
0
            return std::nullopt;
274
0
        }
275
276
0
        return std::optional<Raw>(it->second);
277
0
    }
278
279
    bool Collection::has(const std::string& name) const
280
0
    {
281
0
        return getImpl(name).first;
282
0
    }
283
284
    std::vector<std::shared_ptr<Header>> Collection::list() const
285
0
    {
286
0
        std::vector<std::shared_ptr<Header>> ret;
287
0
        ret.reserve(headers.size());
288
0
        for (const auto& h : headers)
289
0
        {
290
0
            ret.push_back(h.second);
291
0
        }
292
293
0
        return ret;
294
0
    }
295
296
    bool Collection::remove(const std::string& name)
297
0
    {
298
0
        auto tit = headers.find(name);
299
0
        if (tit == std::end(headers))
300
0
        {
301
0
            auto rit = rawHeaders.find(name);
302
0
            if (rit == std::end(rawHeaders))
303
0
                return false;
304
305
0
            rawHeaders.erase(rit);
306
0
            return true;
307
0
        }
308
0
        headers.erase(tit);
309
0
        return true;
310
0
    }
311
312
    void Collection::clear()
313
0
    {
314
0
        headers.clear();
315
0
        rawHeaders.clear();
316
0
    }
317
318
    std::pair<bool, std::shared_ptr<Header>>
319
    Collection::getImpl(const std::string& name) const
320
1.58k
    {
321
1.58k
        auto it = headers.find(name);
322
1.58k
        if (it == std::end(headers))
323
880
        {
324
880
            return std::make_pair(false, nullptr);
325
880
        }
326
327
704
        return std::make_pair(true, it->second);
328
1.58k
    }
329
330
} // namespace Pistache::Http::Header