xref: /openbmc/bmcweb/include/http_utility.hpp (revision 56b81992ba8a8e644f2e75251a94df4f4d0d0880)
1 #pragma once
2 
3 #include <boost/spirit/home/x3.hpp>
4 
5 #include <algorithm>
6 #include <cctype>
7 #include <iomanip>
8 #include <ostream>
9 #include <ranges>
10 #include <span>
11 #include <string>
12 #include <string_view>
13 #include <vector>
14 
15 namespace http_helpers
16 {
17 
18 enum class ContentType
19 {
20     NoMatch,
21     ANY, // Accepts: */*
22     CBOR,
23     HTML,
24     JSON,
25     OctetStream,
26     EventStream,
27 };
28 
29 inline ContentType getPreferredContentType(
30     std::string_view header, std::span<const ContentType> preferredOrder)
31 {
32     using boost::spirit::x3::char_;
33     using boost::spirit::x3::lit;
34     using boost::spirit::x3::omit;
35     using boost::spirit::x3::parse;
36     using boost::spirit::x3::space;
37     using boost::spirit::x3::symbols;
38     using boost::spirit::x3::uint_;
39 
40     const symbols<ContentType> knownMimeType{
41         {"application/cbor", ContentType::CBOR},
42         {"application/json", ContentType::JSON},
43         {"application/octet-stream", ContentType::OctetStream},
44         {"text/html", ContentType::HTML},
45         {"text/event-stream", ContentType::EventStream},
46         {"*/*", ContentType::ANY}};
47 
48     std::vector<ContentType> ct;
49 
50     auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
51     auto typeCharset = char_("a-zA-Z.+-");
52     auto mimeType = knownMimeType |
53                     omit[+typeCharset >> lit('/') >> +typeCharset];
54     auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
55     if (!parse(header.begin(), header.end(), parser, ct))
56     {
57         return ContentType::NoMatch;
58     }
59 
60     for (const ContentType parsedType : ct)
61     {
62         if (parsedType == ContentType::ANY)
63         {
64             return parsedType;
65         }
66         auto it = std::ranges::find(preferredOrder, parsedType);
67         if (it != preferredOrder.end())
68         {
69             return *it;
70         }
71     }
72 
73     return ContentType::NoMatch;
74 }
75 
76 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
77                                  bool allowWildcard)
78 {
79     auto types = std::to_array({type});
80     ContentType allowed = getPreferredContentType(header, types);
81     if (allowed == ContentType::ANY)
82     {
83         return allowWildcard;
84     }
85 
86     return type == allowed;
87 }
88 
89 enum class Encoding
90 {
91     ParseError,
92     NoMatch,
93     UnencodedBytes,
94     GZIP,
95     ZSTD,
96     ANY, // represents *. Never returned.  Only used for string matching
97 };
98 
99 inline Encoding
100     getPreferredEncoding(std::string_view acceptEncoding,
101                          const std::span<const Encoding> availableEncodings)
102 {
103     if (acceptEncoding.empty())
104     {
105         return Encoding::UnencodedBytes;
106     }
107 
108     using boost::spirit::x3::char_;
109     using boost::spirit::x3::lit;
110     using boost::spirit::x3::omit;
111     using boost::spirit::x3::parse;
112     using boost::spirit::x3::space;
113     using boost::spirit::x3::symbols;
114     using boost::spirit::x3::uint_;
115 
116     const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
117                                                 {"zstd", Encoding::ZSTD},
118                                                 {"*", Encoding::ANY}};
119 
120     std::vector<Encoding> ct;
121 
122     auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
123     auto typeCharset = char_("a-zA-Z.+-");
124     auto encodeType = knownAcceptEncoding | omit[+typeCharset];
125     auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
126     if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
127     {
128         return Encoding::ParseError;
129     }
130 
131     for (const Encoding parsedType : ct)
132     {
133         if (parsedType == Encoding::ANY)
134         {
135             if (!availableEncodings.empty())
136             {
137                 return *availableEncodings.begin();
138             }
139         }
140         auto it = std::ranges::find(availableEncodings, parsedType);
141         if (it != availableEncodings.end())
142         {
143             return *it;
144         }
145     }
146 
147     // Fall back to raw bytes if it was allowed
148     auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
149     if (it != availableEncodings.end())
150     {
151         return *it;
152     }
153 
154     return Encoding::NoMatch;
155 }
156 
157 } // namespace http_helpers
158