Coverage Report

Created: 2024-10-29 06:44

/src/pistache/src/common/mime.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
/* mime.cc
8
   Mathieu Stefani, 29 August 2015
9
10
   Implementation of MIME Type parsing
11
*/
12
13
#include <cstring>
14
15
#include <pistache/http.h>
16
#include <pistache/mime.h>
17
18
namespace Pistache::Http::Mime
19
{
20
21
    std::string Q::toString() const
22
0
    {
23
0
        if (val_ == 0)
24
0
            return "q=0";
25
0
        else if (val_ == 100)
26
0
            return "q=1";
27
28
0
        char buff[sizeof("q=0.99")] = {};
29
0
        const char* const format    = val_ % 10 == 0 ? "q=%.1f" : "q=%.2f";
30
31
0
        const int len = snprintf(buff, sizeof(buff), format, val_ / 100.0);
32
33
0
        return std::string(buff, len);
34
0
    }
35
36
    MediaType MediaType::fromString(const std::string& str)
37
0
    {
38
0
        return fromRaw(str.c_str(), str.size());
39
0
    }
40
41
    MediaType MediaType::fromString(std::string&& str)
42
0
    {
43
0
        return fromRaw(str.c_str(), str.size());
44
0
    }
45
46
    MediaType MediaType::fromRaw(const char* str, size_t len)
47
303k
    {
48
303k
        MediaType res;
49
50
303k
        res.parseRaw(str, len);
51
303k
        return res;
52
303k
    }
53
54
    MediaType MediaType::fromFile(const char* fileName)
55
0
    {
56
0
        const char* extensionOffset = nullptr;
57
0
        const char* p               = fileName;
58
0
        while (*p)
59
0
        {
60
0
            if (*p == '.')
61
0
                extensionOffset = p;
62
0
            ++p;
63
0
        }
64
65
0
        if (!extensionOffset)
66
0
            return MediaType();
67
68
0
        ++extensionOffset;
69
70
0
        struct Extension
71
0
        {
72
0
            const char* const raw;
73
0
            Mime::Type top;
74
0
            Mime::Subtype sub;
75
0
        };
76
77
        // @Data: maybe one day try to export
78
        // http://www.iana.org/assignments/media-types/media-types.xhtml as an
79
        // item-list
80
81
0
        static constexpr Extension KnownExtensions[] = {
82
0
            { "jpg", Type::Image, Subtype::Jpeg },
83
0
            { "jpeg", Type::Image, Subtype::Jpeg },
84
0
            { "png", Type::Image, Subtype::Png },
85
0
            { "bmp", Type::Image, Subtype::Bmp },
86
87
0
            { "txt", Type::Text, Subtype::Plain },
88
0
            { "md", Type::Text, Subtype::Plain },
89
90
0
            { "bin", Type::Application, Subtype::OctetStream },
91
0
        };
92
93
0
        for (const auto& ext : KnownExtensions)
94
0
        {
95
0
            if (!strcmp(extensionOffset, ext.raw))
96
0
            {
97
0
                return MediaType(ext.top, ext.sub);
98
0
            }
99
0
        }
100
101
0
        return MediaType();
102
0
    }
103
104
    void MediaType::parseRaw(const char* str, size_t len)
105
309k
    {
106
309k
        auto raise = [](const char* str) {
107
            // TODO: eventually, we should throw a more generic exception
108
            // that could then be catched in lower stack frames to rethrow
109
            // an HttpError
110
7.95k
            throw HttpError(Http::Code::Unsupported_Media_Type, str);
111
7.95k
        };
112
113
309k
        RawStreamBuf<char> buf(const_cast<char*>(str), len);
114
309k
        StreamCursor cursor(&buf);
115
116
309k
        raw_ = std::string(str, len);
117
118
309k
        Mime::Type top = Type::None;
119
120
        // The reason we are using a do { } while (0); syntax construct here is to
121
        // emulate if / else-if. Since we are using items-list macros to compare the
122
        // strings, we want to avoid evaluating all the branches when one of them
123
        // evaluates to true.
124
        //
125
        // Instead, we break the loop when a branch evaluates to true so that we do
126
        // not evaluate all the subsequent ones.
127
        //
128
        // Watch out, this pattern is repeated throughout the function
129
309k
        do
130
309k
        {
131
309k
#define TYPE(val, s)                                                         \
132
363k
    if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) \
133
363k
    {                                                                        \
134
302k
        top = Type::val;                                                     \
135
302k
        break;                                                               \
136
302k
    }
137
309k
            MIME_TYPES
138
6.76k
#undef TYPE
139
6.76k
            raise("Unknown Media Type");
140
6.76k
        } while (false);
141
142
0
        top_ = top;
143
144
309k
        if (!match_literal('/', cursor))
145
212
            raise("Malformed Media Type, expected a '/' after the top type");
146
147
309k
        if (cursor.eof())
148
34
            raise("Malformed Media type, missing subtype");
149
150
        // Parse subtype
151
309k
        Mime::Subtype sub;
152
153
309k
        StreamCursor::Token subToken(cursor);
154
155
309k
        if (match_raw("vnd.", 4, cursor))
156
234
        {
157
234
            sub = Subtype::Vendor;
158
234
        }
159
309k
        else
160
309k
        {
161
309k
            do
162
309k
            {
163
309k
#define SUB_TYPE(val, s)                                                     \
164
4.74M
    if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) \
165
4.74M
    {                                                                        \
166
38.5k
        sub = Subtype::val;                                                  \
167
38.5k
        break;                                                               \
168
38.5k
    }
169
309k
                MIME_SUBTYPES
170
270k
#undef SUB_TYPE
171
270k
                sub = Subtype::Ext;
172
270k
            } while (false);
173
309k
        }
174
175
309k
        if (sub == Subtype::Ext || sub == Subtype::Vendor)
176
263k
        {
177
263k
            (void)match_until({ ';', '+' }, cursor);
178
263k
            rawSubIndex.beg = subToken.start();
179
263k
            rawSubIndex.end = subToken.end() - 1;
180
263k
        }
181
182
309k
        sub_ = sub;
183
184
309k
        if (cursor.eof())
185
276k
            return;
186
187
        // Parse suffix
188
33.2k
        Mime::Suffix suffix = Suffix::None;
189
33.2k
        if (match_literal('+', cursor))
190
1.88k
        {
191
192
1.88k
            if (cursor.eof())
193
7
                raise("Malformed Media Type, expected suffix, got EOF");
194
195
1.88k
            StreamCursor::Token suffixToken(cursor);
196
197
1.88k
            do
198
1.88k
            {
199
1.88k
#define SUFFIX(val, s, _)                                                    \
200
9.54k
    if (match_string(s, sizeof s - 1, cursor, CaseSensitivity::Insensitive)) \
201
9.54k
    {                                                                        \
202
1.30k
        suffix = Suffix::val;                                                \
203
1.30k
        break;                                                               \
204
1.30k
    }
205
1.88k
                MIME_SUFFIXES
206
581
#undef SUFFIX
207
581
                suffix = Suffix::Ext;
208
581
            } while (false);
209
210
1.88k
            if (suffix == Suffix::Ext)
211
574
            {
212
574
                (void)match_until({ ';', '+' }, cursor);
213
574
                rawSuffixIndex.beg = suffixToken.start();
214
574
                rawSuffixIndex.end = suffixToken.end() - 1;
215
574
            }
216
217
1.88k
            suffix_ = suffix;
218
1.88k
        }
219
220
        // Parse parameters
221
309k
        while (!cursor.eof())
222
276k
        {
223
224
276k
            if (cursor.current() == ';' || cursor.current() == ' ')
225
132k
            {
226
132k
                int c;
227
132k
                if ((c = cursor.next()) == StreamCursor::Eof || c == 0)
228
219
                    raise("Malformed Media Type, expected parameter got EOF");
229
132k
                cursor.advance(1);
230
132k
            }
231
232
143k
            else if (match_literal('q', cursor))
233
3.26k
            {
234
235
3.26k
                if (cursor.eof())
236
21
                    raise("Invalid quality factor");
237
238
3.26k
                if (match_literal('=', cursor))
239
3.22k
                {
240
3.22k
                    float val;
241
3.22k
                    std::size_t qvalue_len;
242
243
3.22k
                    if (!Header::strToQvalue(cursor.offset(), &val, &qvalue_len))
244
87
                    {
245
87
                        raise("Invalid quality factor");
246
87
                    }
247
3.22k
                    cursor.advance(qvalue_len);
248
3.22k
                    q_ = Q::fromFloat(val);
249
3.22k
                }
250
40
                else
251
40
                {
252
40
                    raise("Missing quality factor");
253
40
                }
254
3.26k
            }
255
140k
            else
256
140k
            {
257
140k
                StreamCursor::Token keyToken(cursor);
258
140k
                (void)match_until('=', cursor);
259
260
140k
                int c;
261
140k
                if (cursor.eof() || (c = cursor.next()) == StreamCursor::Eof || c == 0)
262
585
                    raise("Unfinished Media Type parameter");
263
264
140k
                std::string key = keyToken.text();
265
140k
                cursor.advance(1);
266
267
140k
                StreamCursor::Token valueToken(cursor);
268
140k
                (void)match_until({ ' ', ';' }, cursor);
269
140k
                params.insert(std::make_pair(std::move(key), valueToken.text()));
270
140k
            }
271
276k
        }
272
33.2k
    }
273
274
0
    void MediaType::setQuality(Q quality) { q_ = quality; }
275
276
    std::optional<std::string> MediaType::getParam(const std::string& name) const
277
0
    {
278
0
        auto it = params.find(name);
279
0
        if (it == std::end(params))
280
0
        {
281
0
            return std::nullopt;
282
0
        }
283
284
0
        return std::optional<std::string>(it->second);
285
0
    }
286
287
    void MediaType::setParam(const std::string& name, std::string value)
288
0
    {
289
0
        params[name] = std::move(value);
290
0
    }
291
292
    std::string MediaType::toString() const
293
294k
    {
294
295
294k
        if (!raw_.empty())
296
294k
            return raw_;
297
298
1
        auto topString = [](Mime::Type top) -> const char* {
299
1
            switch (top)
300
1
            {
301
0
#define TYPE(val, str)    \
302
0
    case Mime::Type::val: \
303
0
        return str;
304
0
                MIME_TYPES
305
0
#undef TYPE
306
1
            default:
307
1
                return "";
308
1
            }
309
1
        };
310
311
1
        auto subString = [](Mime::Subtype sub) -> const char* {
312
1
            switch (sub)
313
1
            {
314
0
#define SUB_TYPE(val, str)   \
315
0
    case Mime::Subtype::val: \
316
0
        return str;
317
0
                MIME_SUBTYPES
318
0
#undef TYPE
319
1
            default:
320
1
                return "";
321
1
            }
322
1
        };
323
324
1
        auto suffixString = [](Mime::Suffix suffix) -> const char* {
325
0
            switch (suffix)
326
0
            {
327
0
#define SUFFIX(val, str, _) \
328
0
    case Mime::Suffix::val: \
329
0
        return "+" str;
330
0
                MIME_SUFFIXES
331
0
#undef SUFFIX
332
0
            default:
333
0
                return "";
334
0
            }
335
0
        };
336
337
1
        std::string res;
338
1
        res.reserve(128);
339
1
        res += topString(top_);
340
1
        res += "/";
341
1
        res += subString(sub_);
342
1
        if (suffix_ != Suffix::None)
343
0
        {
344
0
            res += suffixString(suffix_);
345
0
        }
346
347
1
        if (q_.has_value())
348
0
        {
349
0
            Q quality = *q_;
350
0
            res += "; ";
351
0
            res += quality.toString();
352
0
        }
353
354
1
        for (const auto& param : params)
355
0
        {
356
0
            res += "; ";
357
0
            res += param.first + "=" + param.second;
358
0
        }
359
360
1
        return res;
361
294k
    }
362
363
    bool MediaType::isValid() const
364
0
    {
365
0
        return top_ != Type::None && sub_ != Subtype::None;
366
0
    }
367
368
} // namespace Pistache::Http::Mime