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