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