blob: 158228f970c024aa53f2a97e0695a44d48b4a945 [file] [log] [blame]
Ed Tanous9bd21fc2018-04-26 16:08:56 -07001#pragma once
Tanousf00032d2018-11-05 01:18:10 -03002
Ed Tanous463b2932024-07-16 17:02:42 -07003#include <boost/spirit/home/x3.hpp>
4
Ed Tanous18f8f602023-07-18 10:07:23 -07005#include <algorithm>
Nan Zhoud5c80ad2022-07-11 01:16:31 +00006#include <cctype>
7#include <iomanip>
8#include <ostream>
Ed Tanous3544d2a2023-08-06 18:12:20 -07009#include <ranges>
Ed Tanous99351cd2022-08-07 16:42:51 -070010#include <span>
Nan Zhoud5c80ad2022-07-11 01:16:31 +000011#include <string>
12#include <string_view>
13#include <vector>
14
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace http_helpers
16{
George Liu647b3cd2021-07-05 12:43:56 +080017
Ed Tanous99351cd2022-08-07 16:42:51 -070018enum class ContentType
George Liu647b3cd2021-07-05 12:43:56 +080019{
Ed Tanous99351cd2022-08-07 16:42:51 -070020 NoMatch,
Ed Tanous4a0e1a02022-09-21 15:28:04 -070021 ANY, // Accepts: */*
Ed Tanous99351cd2022-08-07 16:42:51 -070022 CBOR,
23 HTML,
24 JSON,
25 OctetStream,
Ed Tanous6fde95f2023-06-01 07:33:34 -070026 EventStream,
Ed Tanous99351cd2022-08-07 16:42:51 -070027};
28
Patrick Williamsbd79bce2024-08-16 15:22:20 -040029inline ContentType getPreferredContentType(
Ed Tanous276ede52024-08-28 15:57:45 -070030 std::string_view header, std::span<const ContentType> preferredOrder)
Ed Tanous99351cd2022-08-07 16:42:51 -070031{
Ed Tanous463b2932024-07-16 17:02:42 -070032 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))
Ed Tanous1abe55e2018-09-05 08:30:59 -070056 {
Ed Tanous463b2932024-07-16 17:02:42 -070057 return ContentType::NoMatch;
George Liu647b3cd2021-07-05 12:43:56 +080058 }
Ed Tanous463b2932024-07-16 17:02:42 -070059
60 for (const ContentType parsedType : ct)
61 {
62 if (parsedType == ContentType::ANY)
63 {
64 return parsedType;
65 }
Ed Tanous276ede52024-08-28 15:57:45 -070066 auto it = std::ranges::find(preferredOrder, parsedType);
67 if (it != preferredOrder.end())
Ed Tanous463b2932024-07-16 17:02:42 -070068 {
69 return *it;
70 }
71 }
72
Ed Tanous99351cd2022-08-07 16:42:51 -070073 return ContentType::NoMatch;
74}
75
Ed Tanous4a0e1a02022-09-21 15:28:04 -070076inline bool isContentTypeAllowed(std::string_view header, ContentType type,
77 bool allowWildcard)
Ed Tanous99351cd2022-08-07 16:42:51 -070078{
79 auto types = std::to_array({type});
Ed Tanous8ece0e42024-01-02 13:16:50 -080080 ContentType allowed = getPreferredContentType(header, types);
Ed Tanous4a0e1a02022-09-21 15:28:04 -070081 if (allowed == ContentType::ANY)
82 {
83 return allowWildcard;
84 }
85
86 return type == allowed;
George Liu647b3cd2021-07-05 12:43:56 +080087}
88
Ed Tanous276ede52024-08-28 15:57:45 -070089enum 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
99inline 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
Ed Tanous23a21a12020-07-25 04:45:05 +0000157} // namespace http_helpers