Coverage Report

Created: 2026-04-27 06:22

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.61k
{
42
5.61k
    using boost::spirit::x3::char_;
43
5.61k
    using boost::spirit::x3::lit;
44
5.61k
    using boost::spirit::x3::no_case;
45
5.61k
    using boost::spirit::x3::omit;
46
5.61k
    using boost::spirit::x3::parse;
47
5.61k
    using boost::spirit::x3::space;
48
5.61k
    using boost::spirit::x3::symbols;
49
50
5.61k
    const symbols<ContentType> knownMimeType{
51
5.61k
        {"application/cbor", ContentType::CBOR},
52
5.61k
        {"application/json", ContentType::JSON},
53
5.61k
        {"application/octet-stream", ContentType::OctetStream},
54
5.61k
        {"text/event-stream", ContentType::EventStream},
55
5.61k
        {"text/html", ContentType::HTML}};
56
57
5.61k
    ContentType ct = ContentType::NoMatch;
58
59
5.61k
    auto typeCharset = +(char_("a-zA-Z0-9.+-"));
60
61
5.61k
    auto parameters =
62
5.61k
        *(lit(';') >> *space >> typeCharset >> lit("=") >> typeCharset);
63
5.61k
    auto parser = no_case[knownMimeType] >> omit[parameters];
64
5.61k
    std::string_view::iterator begin = contentTypeHeader.begin();
65
5.61k
    if (!parse(begin, contentTypeHeader.end(), parser, ct))
66
5.52k
    {
67
5.52k
        return ContentType::NoMatch;
68
5.52k
    }
69
85
    if (begin != contentTypeHeader.end())
70
67
    {
71
67
        return ContentType::NoMatch;
72
67
    }
73
74
18
    return ct;
75
85
}
76
77
inline ContentType getPreferredContentType(
78
    std::string_view acceptsHeader, std::span<const ContentType> preferredOrder)
79
2.46k
{
80
2.46k
    using boost::spirit::x3::char_;
81
2.46k
    using boost::spirit::x3::lit;
82
2.46k
    using boost::spirit::x3::no_case;
83
2.46k
    using boost::spirit::x3::omit;
84
2.46k
    using boost::spirit::x3::parse;
85
2.46k
    using boost::spirit::x3::space;
86
2.46k
    using boost::spirit::x3::symbols;
87
88
2.46k
    const symbols<ContentType> knownMimeType{
89
2.46k
        {"application/cbor", ContentType::CBOR},
90
2.46k
        {"application/json", ContentType::JSON},
91
2.46k
        {"application/octet-stream", ContentType::OctetStream},
92
2.46k
        {"text/html", ContentType::HTML},
93
2.46k
        {"text/event-stream", ContentType::EventStream},
94
2.46k
        {"*/*", ContentType::ANY}};
95
96
2.46k
    std::vector<ContentType> ct;
97
98
2.46k
    auto typeCharset = +(char_("a-zA-Z0-9.+-"));
99
100
2.46k
    auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
101
2.46k
    auto mimeType = no_case[knownMimeType] |
102
2.46k
                    omit[+typeCharset >> lit('/') >> +typeCharset];
103
2.46k
    auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
104
2.46k
    if (!parse(acceptsHeader.begin(), acceptsHeader.end(), parser, ct))
105
1.78k
    {
106
1.78k
        return ContentType::NoMatch;
107
1.78k
    }
108
109
687
    for (const ContentType parsedType : ct)
110
8.77k
    {
111
8.77k
        if (parsedType == ContentType::ANY)
112
303
        {
113
303
            return parsedType;
114
303
        }
115
8.47k
        auto it = std::ranges::find(preferredOrder, parsedType);
116
8.47k
        if (it != preferredOrder.end())
117
0
        {
118
0
            return *it;
119
0
        }
120
8.47k
    }
121
122
384
    return ContentType::NoMatch;
123
687
}
124
125
inline bool isContentTypeAllowed(std::string_view header, ContentType type,
126
                                 bool allowWildcard)
127
1.64k
{
128
1.64k
    auto types = std::to_array({type});
129
1.64k
    ContentType allowed = getPreferredContentType(header, types);
130
1.64k
    if (allowed == ContentType::ANY)
131
202
    {
132
202
        return allowWildcard;
133
202
    }
134
135
1.44k
    return type == allowed;
136
1.64k
}
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
823
{
152
823
    if (acceptEncoding.empty())
153
0
    {
154
0
        return Encoding::UnencodedBytes;
155
0
    }
156
157
823
    using boost::spirit::x3::char_;
158
823
    using boost::spirit::x3::lit;
159
823
    using boost::spirit::x3::omit;
160
823
    using boost::spirit::x3::parse;
161
823
    using boost::spirit::x3::space;
162
823
    using boost::spirit::x3::symbols;
163
823
    using boost::spirit::x3::uint_;
164
165
823
    const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
166
823
                                                {"zstd", Encoding::ZSTD},
167
823
                                                {"*", Encoding::ANY}};
168
169
823
    std::vector<Encoding> ct;
170
171
823
    auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
172
823
    auto typeCharset = char_("a-zA-Z.+-");
173
823
    auto encodeType = knownAcceptEncoding | omit[+typeCharset];
174
823
    auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
175
823
    if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
176
27
    {
177
27
        return Encoding::ParseError;
178
27
    }
179
180
796
    for (const Encoding parsedType : ct)
181
2.32k
    {
182
2.32k
        if (parsedType == Encoding::ANY)
183
262
        {
184
262
            if (!availableEncodings.empty())
185
262
            {
186
262
                return *availableEncodings.begin();
187
262
            }
188
262
        }
189
2.06k
        auto it = std::ranges::find(availableEncodings, parsedType);
190
2.06k
        if (it != availableEncodings.end())
191
6
        {
192
6
            return *it;
193
6
        }
194
2.06k
    }
195
196
    // Fall back to raw bytes if it was allowed
197
528
    auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
198
528
    if (it != availableEncodings.end())
199
528
    {
200
528
        return *it;
201
528
    }
202
203
0
    return Encoding::NoMatch;
204
528
}
205
206
} // namespace http_helpers