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 getPreferedContentType(std::string_view header, 45 std::span<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 = 76 std::find_if(contentTypes.begin(), contentTypes.end(), 77 [encoding](const ContentTypePair& pair) { 78 return pair.contentTypeString == encoding; 79 }); 80 81 if (knownContentType == contentTypes.end()) 82 { 83 // not able to find content type in list 84 continue; 85 } 86 87 // Not one of the types requested 88 if (std::find(preferedOrder.begin(), preferedOrder.end(), 89 knownContentType->contentTypeEnum) == 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 = getPreferedContentType(header, types); 103 if (allowed == ContentType::ANY) 104 { 105 return allowWildcard; 106 } 107 108 return type == allowed; 109 } 110 111 inline std::string urlEncode(const std::string_view value) 112 { 113 std::ostringstream escaped; 114 escaped.fill('0'); 115 escaped << std::hex; 116 117 for (const char c : value) 118 { 119 // Keep alphanumeric and other accepted characters intact 120 if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~') 121 { 122 escaped << c; 123 continue; 124 } 125 126 // Any other characters are percent-encoded 127 escaped << std::uppercase; 128 escaped << '%' << std::setw(2) 129 << static_cast<int>(static_cast<unsigned char>(c)); 130 escaped << std::nouppercase; 131 } 132 133 return escaped.str(); 134 } 135 } // namespace http_helpers 136