xref: /openbmc/bmcweb/include/http_utility.hpp (revision 80e6e25e7d721fa03fcc2b194881d8d8a64fe416)
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 typeCharset = +(char_("a-zA-Z0-9.+-"));
51 
52     auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
53     auto mimeType = knownMimeType |
54                     omit[+typeCharset >> lit('/') >> +typeCharset];
55     auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
56     if (!parse(header.begin(), header.end(), parser, ct))
57     {
58         return ContentType::NoMatch;
59     }
60 
61     for (const ContentType parsedType : ct)
62     {
63         if (parsedType == ContentType::ANY)
64         {
65             return parsedType;
66         }
67         auto it = std::ranges::find(preferredOrder, parsedType);
68         if (it != preferredOrder.end())
69         {
70             return *it;
71         }
72     }
73 
74     return ContentType::NoMatch;
75 }
76 
77 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
78                                  bool allowWildcard)
79 {
80     auto types = std::to_array({type});
81     ContentType allowed = getPreferredContentType(header, types);
82     if (allowed == ContentType::ANY)
83     {
84         return allowWildcard;
85     }
86 
87     return type == allowed;
88 }
89 
90 enum class Encoding
91 {
92     ParseError,
93     NoMatch,
94     UnencodedBytes,
95     GZIP,
96     ZSTD,
97     ANY, // represents *. Never returned.  Only used for string matching
98 };
99 
100 inline Encoding
101     getPreferredEncoding(std::string_view acceptEncoding,
102                          const std::span<const Encoding> availableEncodings)
103 {
104     if (acceptEncoding.empty())
105     {
106         return Encoding::UnencodedBytes;
107     }
108 
109     using boost::spirit::x3::char_;
110     using boost::spirit::x3::lit;
111     using boost::spirit::x3::omit;
112     using boost::spirit::x3::parse;
113     using boost::spirit::x3::space;
114     using boost::spirit::x3::symbols;
115     using boost::spirit::x3::uint_;
116 
117     const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
118                                                 {"zstd", Encoding::ZSTD},
119                                                 {"*", Encoding::ANY}};
120 
121     std::vector<Encoding> ct;
122 
123     auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
124     auto typeCharset = char_("a-zA-Z.+-");
125     auto encodeType = knownAcceptEncoding | omit[+typeCharset];
126     auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
127     if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
128     {
129         return Encoding::ParseError;
130     }
131 
132     for (const Encoding parsedType : ct)
133     {
134         if (parsedType == Encoding::ANY)
135         {
136             if (!availableEncodings.empty())
137             {
138                 return *availableEncodings.begin();
139             }
140         }
141         auto it = std::ranges::find(availableEncodings, parsedType);
142         if (it != availableEncodings.end())
143         {
144             return *it;
145         }
146     }
147 
148     // Fall back to raw bytes if it was allowed
149     auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
150     if (it != availableEncodings.end())
151     {
152         return *it;
153     }
154 
155     return Encoding::NoMatch;
156 }
157 
158 } // namespace http_helpers
159