1 #pragma once
2
3 #include <algorithm>
4 #include <cctype>
5 #include <iomanip>
6 #include <ostream>
7 #include <ranges>
8 #include <span>
9 #include <string>
10 #include <string_view>
11 #include <vector>
12
13 namespace http_helpers
14 {
15
16 enum class ContentType
17 {
18 NoMatch,
19 ANY, // Accepts: */*
20 CBOR,
21 HTML,
22 JSON,
23 OctetStream,
24 EventStream,
25 };
26
27 struct ContentTypePair
28 {
29 std::string_view contentTypeString;
30 ContentType contentTypeEnum;
31 };
32
33 constexpr std::array<ContentTypePair, 5> contentTypes{{
34 {"application/cbor", ContentType::CBOR},
35 {"application/json", ContentType::JSON},
36 {"application/octet-stream", ContentType::OctetStream},
37 {"text/html", ContentType::HTML},
38 {"text/event-stream", ContentType::EventStream},
39 }};
40
getPreferredContentType(std::string_view header,std::span<const ContentType> preferedOrder)41 inline ContentType getPreferredContentType(
42 std::string_view header, std::span<const ContentType> preferedOrder)
43 {
44 size_t lastIndex = 0;
45 while (lastIndex < header.size() + 1)
46 {
47 size_t index = header.find(',', lastIndex);
48 if (index == std::string_view::npos)
49 {
50 index = header.size();
51 }
52 std::string_view encoding = header.substr(lastIndex, index);
53
54 if (!header.empty())
55 {
56 header.remove_prefix(1);
57 }
58 lastIndex = index + 1;
59 // ignore any q-factor weighting (;q=)
60 std::size_t separator = encoding.find(";q=");
61
62 if (separator != std::string_view::npos)
63 {
64 encoding = encoding.substr(0, separator);
65 }
66 // If the client allows any encoding, given them the first one on the
67 // servers list
68 if (encoding == "*/*")
69 {
70 return ContentType::ANY;
71 }
72 const auto* knownContentType = std::ranges::find_if(
73 contentTypes, [encoding](const ContentTypePair& pair) {
74 return pair.contentTypeString == encoding;
75 });
76
77 if (knownContentType == contentTypes.end())
78 {
79 // not able to find content type in list
80 continue;
81 }
82
83 // Not one of the types requested
84 if (std::ranges::find(preferedOrder,
85 knownContentType->contentTypeEnum) ==
86 preferedOrder.end())
87 {
88 continue;
89 }
90 return knownContentType->contentTypeEnum;
91 }
92 return ContentType::NoMatch;
93 }
94
isContentTypeAllowed(std::string_view header,ContentType type,bool allowWildcard)95 inline bool isContentTypeAllowed(std::string_view header, ContentType type,
96 bool allowWildcard)
97 {
98 auto types = std::to_array({type});
99 ContentType allowed = getPreferredContentType(header, types);
100 if (allowed == ContentType::ANY)
101 {
102 return allowWildcard;
103 }
104
105 return type == allowed;
106 }
107
108 } // namespace http_helpers
109