Coverage Report

Created: 2026-09-14 06:49

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