blob: cb3ebc2c79d133e2c31f9b458b1d5da7f6169742 [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
Ed Tanous80e6e252024-12-11 11:28:39 -080050 auto typeCharset = +(char_("a-zA-Z0-9.+-"));
51
52 auto parameters = *(lit(';') >> typeCharset >> lit("=") >> typeCharset);
Ed Tanous463b2932024-07-16 17:02:42 -070053 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))
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 {
Ed Tanous463b2932024-07-16 17:02:42 -070058 return ContentType::NoMatch;
George Liu647b3cd2021-07-05 12:43:56 +080059 }
Ed Tanous463b2932024-07-16 17:02:42 -070060
61 for (const ContentType parsedType : ct)
62 {
63 if (parsedType == ContentType::ANY)
64 {
65 return parsedType;
66 }
Ed Tanous276ede52024-08-28 15:57:45 -070067 auto it = std::ranges::find(preferredOrder, parsedType);
68 if (it != preferredOrder.end())
Ed Tanous463b2932024-07-16 17:02:42 -070069 {
70 return *it;
71 }
72 }
73
Ed Tanous99351cd2022-08-07 16:42:51 -070074 return ContentType::NoMatch;
75}
76
Ed Tanous4a0e1a02022-09-21 15:28:04 -070077inline bool isContentTypeAllowed(std::string_view header, ContentType type,
78 bool allowWildcard)
Ed Tanous99351cd2022-08-07 16:42:51 -070079{
80 auto types = std::to_array({type});
Ed Tanous8ece0e42024-01-02 13:16:50 -080081 ContentType allowed = getPreferredContentType(header, types);
Ed Tanous4a0e1a02022-09-21 15:28:04 -070082 if (allowed == ContentType::ANY)
83 {
84 return allowWildcard;
85 }
86
87 return type == allowed;
George Liu647b3cd2021-07-05 12:43:56 +080088}
89
Ed Tanous276ede52024-08-28 15:57:45 -070090enum 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
100inline 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
Ed Tanous23a21a12020-07-25 04:45:05 +0000158} // namespace http_helpers