xref: /openbmc/bmcweb/include/http_utility.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
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