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 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 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