blob: 158228f970c024aa53f2a97e0695a44d48b4a945 [file] [log] [blame]
#pragma once
#include <boost/spirit/home/x3.hpp>
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <ostream>
#include <ranges>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace http_helpers
{
enum class ContentType
{
NoMatch,
ANY, // Accepts: */*
CBOR,
HTML,
JSON,
OctetStream,
EventStream,
};
inline ContentType getPreferredContentType(
std::string_view header, std::span<const ContentType> preferredOrder)
{
using boost::spirit::x3::char_;
using boost::spirit::x3::lit;
using boost::spirit::x3::omit;
using boost::spirit::x3::parse;
using boost::spirit::x3::space;
using boost::spirit::x3::symbols;
using boost::spirit::x3::uint_;
const symbols<ContentType> knownMimeType{
{"application/cbor", ContentType::CBOR},
{"application/json", ContentType::JSON},
{"application/octet-stream", ContentType::OctetStream},
{"text/html", ContentType::HTML},
{"text/event-stream", ContentType::EventStream},
{"*/*", ContentType::ANY}};
std::vector<ContentType> ct;
auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
auto typeCharset = char_("a-zA-Z.+-");
auto mimeType = knownMimeType |
omit[+typeCharset >> lit('/') >> +typeCharset];
auto parser = +(mimeType >> omit[parameters >> -char_(',') >> *space]);
if (!parse(header.begin(), header.end(), parser, ct))
{
return ContentType::NoMatch;
}
for (const ContentType parsedType : ct)
{
if (parsedType == ContentType::ANY)
{
return parsedType;
}
auto it = std::ranges::find(preferredOrder, parsedType);
if (it != preferredOrder.end())
{
return *it;
}
}
return ContentType::NoMatch;
}
inline bool isContentTypeAllowed(std::string_view header, ContentType type,
bool allowWildcard)
{
auto types = std::to_array({type});
ContentType allowed = getPreferredContentType(header, types);
if (allowed == ContentType::ANY)
{
return allowWildcard;
}
return type == allowed;
}
enum class Encoding
{
ParseError,
NoMatch,
UnencodedBytes,
GZIP,
ZSTD,
ANY, // represents *. Never returned. Only used for string matching
};
inline Encoding
getPreferredEncoding(std::string_view acceptEncoding,
const std::span<const Encoding> availableEncodings)
{
if (acceptEncoding.empty())
{
return Encoding::UnencodedBytes;
}
using boost::spirit::x3::char_;
using boost::spirit::x3::lit;
using boost::spirit::x3::omit;
using boost::spirit::x3::parse;
using boost::spirit::x3::space;
using boost::spirit::x3::symbols;
using boost::spirit::x3::uint_;
const symbols<Encoding> knownAcceptEncoding{{"gzip", Encoding::GZIP},
{"zstd", Encoding::ZSTD},
{"*", Encoding::ANY}};
std::vector<Encoding> ct;
auto parameters = *(lit(';') >> lit("q=") >> uint_ >> -(lit('.') >> uint_));
auto typeCharset = char_("a-zA-Z.+-");
auto encodeType = knownAcceptEncoding | omit[+typeCharset];
auto parser = +(encodeType >> omit[parameters >> -char_(',') >> *space]);
if (!parse(acceptEncoding.begin(), acceptEncoding.end(), parser, ct))
{
return Encoding::ParseError;
}
for (const Encoding parsedType : ct)
{
if (parsedType == Encoding::ANY)
{
if (!availableEncodings.empty())
{
return *availableEncodings.begin();
}
}
auto it = std::ranges::find(availableEncodings, parsedType);
if (it != availableEncodings.end())
{
return *it;
}
}
// Fall back to raw bytes if it was allowed
auto it = std::ranges::find(availableEncodings, Encoding::UnencodedBytes);
if (it != availableEncodings.end())
{
return *it;
}
return Encoding::NoMatch;
}
} // namespace http_helpers