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 <span> 12 #include <string> 13 #include <string_view> 14 #include <vector> 15 16 // IWYU pragma: no_include <ctype.h> 17 18 namespace http_helpers 19 { 20 21 enum class ContentType 22 { 23 NoMatch, 24 ANY, // Accepts: */* 25 CBOR, 26 HTML, 27 JSON, 28 OctetStream, 29 }; 30 31 struct ContentTypePair 32 { 33 std::string_view contentTypeString; 34 ContentType contentTypeEnum; 35 }; 36 37 constexpr std::array<ContentTypePair, 4> contentTypes{{ 38 {"application/cbor", ContentType::CBOR}, 39 {"application/json", ContentType::JSON}, 40 {"application/octet-stream", ContentType::OctetStream}, 41 {"text/html", ContentType::HTML}, 42 }}; 43 44 inline ContentType 45 getPreferedContentType(std::string_view header, 46 std::span<const ContentType> preferedOrder) 47 { 48 size_t lastIndex = 0; 49 while (lastIndex < header.size() + 1) 50 { 51 size_t index = header.find(',', lastIndex); 52 if (index == std::string_view::npos) 53 { 54 index = header.size(); 55 } 56 std::string_view encoding = header.substr(lastIndex, index); 57 58 if (!header.empty()) 59 { 60 header.remove_prefix(1); 61 } 62 lastIndex = index + 1; 63 // ignore any q-factor weighting (;q=) 64 std::size_t separator = encoding.find(";q="); 65 66 if (separator != std::string_view::npos) 67 { 68 encoding = encoding.substr(0, separator); 69 } 70 // If the client allows any encoding, given them the first one on the 71 // servers list 72 if (encoding == "*/*") 73 { 74 return ContentType::ANY; 75 } 76 const auto* knownContentType = 77 std::find_if(contentTypes.begin(), contentTypes.end(), 78 [encoding](const ContentTypePair& pair) { 79 return pair.contentTypeString == encoding; 80 }); 81 82 if (knownContentType == contentTypes.end()) 83 { 84 // not able to find content type in list 85 continue; 86 } 87 88 // Not one of the types requested 89 if (std::find(preferedOrder.begin(), preferedOrder.end(), 90 knownContentType->contentTypeEnum) == preferedOrder.end()) 91 { 92 continue; 93 } 94 return knownContentType->contentTypeEnum; 95 } 96 return ContentType::NoMatch; 97 } 98 99 inline bool isContentTypeAllowed(std::string_view header, ContentType type, 100 bool allowWildcard) 101 { 102 auto types = std::to_array({type}); 103 ContentType allowed = getPreferedContentType(header, types); 104 if (allowed == ContentType::ANY) 105 { 106 return allowWildcard; 107 } 108 109 return type == allowed; 110 } 111 112 inline std::string urlEncode(const std::string_view value) 113 { 114 std::ostringstream escaped; 115 escaped.fill('0'); 116 escaped << std::hex; 117 118 for (const char c : value) 119 { 120 // Keep alphanumeric and other accepted characters intact 121 if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~') 122 { 123 escaped << c; 124 continue; 125 } 126 127 // Any other characters are percent-encoded 128 escaped << std::uppercase; 129 escaped << '%' << std::setw(2) 130 << static_cast<int>(static_cast<unsigned char>(c)); 131 escaped << std::nouppercase; 132 } 133 134 return escaped.str(); 135 } 136 } // namespace http_helpers 137