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