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