| #ifndef THIRD_PARTY_GBMCWEB_INCLUDE_HTTP_UTILITY_H_ | |
| #define THIRD_PARTY_GBMCWEB_INCLUDE_HTTP_UTILITY_H_ | |
| #include <algorithm> | |
| #include <array> | |
| #include <cctype> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <iomanip> | |
| #include <ios> | |
| #include <span> // NOLINT | |
| #include <sstream> | |
| #include <string> | |
| #include <string_view> | |
| #include "boost/algorithm/string/classification.hpp" // NOLINT | |
| #include "boost/algorithm/string/constants.hpp" // NOLINT | |
| #include "boost/iterator/iterator_facade.hpp" // NOLINT | |
| #include "boost/type_index/type_index_facade.hpp" // NOLINT | |
| // IWYU pragma: no_include <ctype.h> | |
| namespace http_helpers { | |
| enum class ContentType : std::uint8_t { | |
| NoMatch, | |
| ANY, // Accepts: */* | |
| CBOR, | |
| HTML, | |
| JSON, | |
| OctetStream, | |
| }; | |
| struct ContentTypePair { | |
| std::string_view contentTypeString; // NOLINT | |
| ContentType contentTypeEnum; // NOLINT | |
| }; | |
| constexpr std::array<ContentTypePair, 4> contentTypes{{ | |
| {"application/cbor", ContentType::CBOR}, | |
| {"application/json", ContentType::JSON}, | |
| {"application/octet-stream", ContentType::OctetStream}, | |
| {"text/html", ContentType::HTML}, | |
| }}; | |
| inline ContentType getPreferedContentType( | |
| std::string_view header, std::span<const ContentType> preferedOrder) { | |
| size_t last_index = 0; | |
| while (last_index < header.size() + 1) { | |
| size_t index = header.find(',', last_index); | |
| if (index == std::string_view::npos) { | |
| index = header.size(); | |
| } | |
| std::string_view encoding = header.substr(last_index, index); | |
| if (!header.empty()) { | |
| header.remove_prefix(1); | |
| } | |
| last_index = index + 1; | |
| // ignore any q-factor weighting (;q=) | |
| std::size_t separator = encoding.find(";q="); | |
| if (separator != std::string_view::npos) { | |
| encoding = encoding.substr(0, separator); | |
| } | |
| // If the client allows any encoding, given them the first one on the | |
| // servers list | |
| if (encoding == "*/*") { | |
| return ContentType::ANY; | |
| } | |
| const auto* known_content_type = | |
| std::find_if(contentTypes.begin(), contentTypes.end(), | |
| [encoding](const ContentTypePair& pair) { | |
| return pair.contentTypeString == encoding; | |
| }); | |
| if (known_content_type == contentTypes.end()) { | |
| // not able to find content type in list | |
| continue; | |
| } | |
| // Not one of the types requested | |
| if (std::find(preferedOrder.begin(), preferedOrder.end(), | |
| known_content_type->contentTypeEnum) == preferedOrder.end()) { | |
| continue; | |
| } | |
| return known_content_type->contentTypeEnum; | |
| } | |
| return ContentType::NoMatch; | |
| } | |
| inline bool isContentTypeAllowed(std::string_view header, ContentType type, | |
| bool allowWildcard) { | |
| auto types = std::to_array({type}); | |
| ContentType allowed = getPreferedContentType(header, types); | |
| if (allowed == ContentType::ANY) { | |
| return allowWildcard; | |
| } | |
| return type == allowed; | |
| } | |
| inline std::string urlEncode(std::string_view value) { | |
| std::ostringstream escaped; | |
| escaped.fill('0'); | |
| escaped << std::hex; | |
| for (const char c : value) { | |
| // Keep alphanumeric and other accepted characters intact | |
| if ((isalnum(c) != 0) || c == '-' || c == '_' || c == '.' || c == '~') { | |
| escaped << c; | |
| continue; | |
| } | |
| // Any other characters are percent-encoded | |
| escaped << std::uppercase; | |
| escaped << '%' << std::setw(2) | |
| << static_cast<int>(static_cast<unsigned char>(c)); | |
| escaped << std::nouppercase; | |
| } | |
| return escaped.str(); | |
| } | |
| } // namespace http_helpers | |
| #endif // THIRD_PARTY_GBMCWEB_INCLUDE_HTTP_UTILITY_H_ |