Coverage Report

Created: 2026-03-10 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/bmcweb/include/http_utility.hpp
Line
Count
Source
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
3
#pragma once
4
5
#include <boost/spirit/home/x3/char/char.hpp>
6
#include <boost/spirit/home/x3/char/char_class.hpp>
7
#include <boost/spirit/home/x3/core/parse.hpp>
8
#include <boost/spirit/home/x3/directive/no_case.hpp>
9
#include <boost/spirit/home/x3/directive/omit.hpp>
10
#include <boost/spirit/home/x3/numeric/uint.hpp>
11
#include <boost/spirit/home/x3/operator/alternative.hpp>
12
#include <boost/spirit/home/x3/operator/kleene.hpp>
13
#include <boost/spirit/home/x3/operator/optional.hpp>
14
#include <boost/spirit/home/x3/operator/plus.hpp>
15
#include <boost/spirit/home/x3/operator/sequence.hpp>
16
#include <boost/spirit/home/x3/string/literal_string.hpp>
17
#include <boost/spirit/home/x3/string/symbols.hpp>
18
19
#include <algorithm>
20
#include <array>
21
#include <ranges>
22
#include <span>
23
#include <string_view>
24
#include <vector>
25
26
namespace http_helpers
27
{
28
29
enum class ContentType
30
{
31
    NoMatch,
32
    ANY, // Accepts: */*
33
    CBOR,
34
    HTML,
35
    JSON,
36
    OctetStream,
37
    EventStream,
38
};
39
40
inline ContentType getContentType(std::string_view contentTypeHeader)
41
5.38k
{
42
5.38k
    using boost::spirit::x3::char_;
43
5.38k
    using boost::spirit::x3::lit;
44
5.38k
    using boost::spirit::x3::no_case;
45
5.38k
    using boost::spirit::x3::omit;
46
5.38k
    using boost::spirit::x3::parse;
47
5.38k
    using boost::spirit::x3::space;
48
5.38k
    using boost::spirit::x3::symbols;
49
50
5.38k
    const symbols<ContentType> knownMimeType{
51
5.38k
        {"application/cbor", ContentType::CBOR},
52
5.38k
        {"application/json", ContentType::JSON},
53
5.38k
        {"application/octet-stream", ContentType::OctetStream},
54
5.38k
        {"text/event-stream", ContentType::EventStream},
55
5.38k
        {"text/html", ContentType::HTML}};
56
57
5.38k
    ContentType ct = ContentType::NoMatch;
58
59
5.38k
    auto typeCharset = +(char_("a-zA-Z0-9.+-"));
60
61
5.38k
    auto parameters =
62
5.38k
        *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
63
5.38k
    auto parser = no_case[knownMimeType] >> omit[parameters];
64
5.38k
    std::string_view::iterator begin = contentTypeHeader.begin();
65
5.38k
    if (!parse(begin, contentTypeHeader.end(), parser, ct))
66
5.31k
    {
67
5.31k
        return ContentType::NoMatch;
68
5.31k
    }
69
73
    if (begin != contentTypeHeader.end())
70
60
    {
71
60
        return ContentType::NoMatch;
72
60
    }
73
74
13
    return ct;
75
73
}
76
77
inline ContentType getPreferredContentType(
78
    std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
79
2.40k
{
80
2.40k
    using boost::spirit::x3::char_;
81
2.40k
    using boost::spirit::x3::lit;
82
2.40k
    using boost::spirit::x3::no_case;
83
2.40k
    using boost::spirit::x3::omit;
84
2.40k
    using boost::spirit::x3::parse;
85
2.40k
    using boost::spirit::x3::space;
86
2.40k
    using boost::spirit::x3::symbols;
87
88
2.40k
    const symbols<ContentType> knownMimeType{
89
2.40k
        {"application/cbor", ContentType::CBOR},
90
2.40k
        {"application/json", ContentType::JSON},
91
2.40k
        {"application/octet-stream", ContentType::OctetStream},
92
2.40k
        {"text/html", ContentType::HTML},
93
2.40k
        {"text/event-stream", ContentType::EventStream},
94
2.40k
        {"*/*", ContentType::ANY}};
95
96
2.40k
    std::vector<ContentType> ct;
97
98
2.40k
    auto typeCharset = +(char_("a-zA-Z0-9.+-"));
99
100
2.40k
    auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
101
2.40k
    auto mimeType = no_case[knownMimeType] |
102
2.40k
                    omit[+typeCharset >> lit('/') >> +typeCharset];
103
2.40k
    auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
104
2.40k
    if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
105
1.75k
    {
106
1.75k
        return ContentType::NoMatch;
107
1.75k
    }
108
109
651
    for (const ContentType parsedType : ct)
110
8.22k
    {
111
8.22k
        if (parsedType == ContentType::ANY)
112
306
        {
113
306
            return parsedType;
114
306
        }
115
7.92k
        auto it = std::ranges::find(preferredOrder, parsedType);
116
7.92k
        if (it != preferredOrder.end())
117
0
        {
118
0
            return *it;
119
0
        }
120
7.92k
    }
121
122
345
    return ContentType::NoMatch;
123
651
}
124
125
inline bool isContentTypeAllowed(std::string_view header, ContentType type,
126
                                 bool allowWildcard)
127
1.60k
{
128
1.60k
    auto types = std::to_array({type});
129
1.60k
    ContentType allowed = getPreferredContentType(header, types);
130
1.60k
    if (allowed == ContentType::ANY)
131
204
    {
132
204
        return allowWildcard;
133
204
    }
134
135
1.40k
    return type == allowed;
136
1.60k
}
137
138
enum class Encoding
139
{
140
    ParseError,
141
    NoMatch,
142
    UnencodedBytes,
143
    GZIP,
144
    ZSTD,
145
    ANY, // represents *. Never returned.  Only used for string matching
146
};
147
148
inline Encoding getPreferredEncoding(
149
    std::string_view acceptEncoding,
150
    const std::span<const Encoding> availableEncodings)
151
802
{
152
802
    if (acceptEncoding.empty())
153
0
    {
154
0
        return Encoding::UnencodedBytes;
155
0
    }
156
157
802
    using boost::spirit::x3::char_;
158
802
    using boost::spirit::x3::lit;
159
802
    using boost::spirit::x3::omit;
160
802
    using boost::spirit::x3::parse;
161
802
    using boost::spirit::x3::space;
162
802
    using boost::spirit::x3::symbols;
163
802
    using boost::spirit::x3::uint_;
164
165
802
    const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
166
802
                                                {"zstd", Encoding::ZSTD},
167
802
                                                {"*", Encoding::ANY}};
168
169
802
    std::vector<Encoding> ct;
170
171
802
    auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
172
802
    auto typeCharset = char_("a-zA-Z.+-");
173
802
    auto encodeType = knownAcceptEncoding | omit[+typeCharset];
174
802
    auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
175
802
    if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
176
23
    {
177
23
        return Encoding::ParseError;
178
23
    }
179
180
779
    for (const Encoding parsedType : ct)
181
2.40k
    {
182
2.40k
        if (parsedType == Encoding::ANY)
183
271
        {
184
271
            if (!availableEncodings.empty())
185
271
            {
186
271
                return *availableEncodings.begin();
187
271
            }
188
271
        }
189
2.13k
        auto it = std::ranges::find(availableEncodings, parsedType);
190
2.13k
        if (it != availableEncodings.end())
191
5
        {
192
5
            return *it;
193
5
        }
194
2.13k
    }
195
196
    // Fall back to raw bytes if it was allowed
197
503
    auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
198
503
    if (it != availableEncodings.end())
199
503
    {
200
503
        return *it;
201
503
    }
202
203
0
    return Encoding::NoMatch;
204
503
}
205
206
} // namespace http_helpers