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( 44 std::string_view header, std::span<const ContentType> preferedOrder) 45 { 46 size_t lastIndex = 0; 47 while (lastIndex < header.size() + 1) 48 { 49 size_t index = header.find(',', lastIndex); 50 if (index == std::string_view::npos) 51 { 52 index = header.size(); 53 } 54 std::string_view encoding = header.substr(lastIndex, index); 55 56 if (!header.empty()) 57 { 58 header.remove_prefix(1); 59 } 60 lastIndex = index + 1; 61 // ignore any q-factor weighting (;q=) 62 std::size_t separator = encoding.find(";q="); 63 64 if (separator != std::string_view::npos) 65 { 66 encoding = encoding.substr(0, separator); 67 } 68 // If the client allows any encoding, given them the first one on the 69 // servers list 70 if (encoding == "*/*") 71 { 72 return ContentType::ANY; 73 } 74 const auto* knownContentType = std::ranges::find_if( 75 contentTypes, [encoding](const ContentTypePair& pair) { 76 return pair.contentTypeString == encoding; 77 }); 78 79 if (knownContentType == contentTypes.end()) 80 { 81 // not able to find content type in list 82 continue; 83 } 84 85 // Not one of the types requested 86 if (std::ranges::find(preferedOrder, 87 knownContentType->contentTypeEnum) == 88 preferedOrder.end()) 89 { 90 continue; 91 } 92 return knownContentType->contentTypeEnum; 93 } 94 return ContentType::NoMatch; 95 } 96 97 inline bool isContentTypeAllowed(std::string_view header, ContentType type, 98 bool allowWildcard) 99 { 100 auto types = std::to_array({type}); 101 ContentType allowed = getPreferredContentType(header, types); 102 if (allowed == ContentType::ANY) 103 { 104 return allowWildcard; 105 } 106 107 return type == allowed; 108 } 109 110 } // namespace http_helpers 111