blob: d72c1f157779fa66f2132a857cde7adb03b7eca2 [file]
/** @file
A brief file description
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include "tscore/ink_platform.h"
#include "tscore/Diags.h"
#include "tscore/ink_memory.h"
#include "proxy/hdrs/HdrToken.h"
#include "proxy/hdrs/MIME.h"
#include <array>
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <string_view>
namespace
{
DbgCtl dbg_ctl_hdr_token{"hdr_token"};
/*
Indexes into this array are stored inside cached objects, but they are not a format commitment:
every reader rebuilds them from the header strings the object also stores, in
HTTPHdrImpl::recompute_wks_indices(). Strings may therefore be added, removed, reordered or
edited here without invalidating anyone's cache.
What that does require is that no ATS predating the rebuild ever read an object written against a
different table, since it would resolve the stored indexes against its own. Cache version 24.3 is
where the rebuild landed, and an older ATS rejects anything newer than its own version, so the
bump to 24.3 settles that for good. See CACHE_DB_MINOR_VERSION in iocore/cache/CacheDefs.h.
*/
constexpr std::string_view _hdrtoken_strs[] = {
// MIME Field names
"Accept-Charset", "Accept-Encoding", "Accept-Language", "Accept-Ranges", "Accept", "Age", "Allow",
"Approved", // NNTP
"Authorization",
"Bytes", // NNTP
"Cache-Control", "Client-ip", "Connection", "Content-Base", "Content-Encoding", "Content-Language", "Content-Length",
"Content-Location", "Content-MD5", "Content-Range", "Content-Type",
"Control", // NNTP
"Cookie", "Date",
"Distribution", // NNTP
"Etag", "Expect", "Expires",
"Followup-To", // NNTP
"From", "Host", "If-Match", "If-Modified-Since", "If-None-Match", "If-Range", "If-Unmodified-Since", "Keep-Alive",
"Keywords", // NNTP
"Last-Modified",
"Lines", // NNTP
"Location", "Max-Forwards",
"Message-ID", // NNTP
"MIME-Version",
"Newsgroups", // NNTP
"Organization", // NNTP
"Path", // NNTP
"Pragma", "Proxy-Authenticate", "Proxy-Authorization", "Proxy-Connection", "Public", "Range",
"References", // NNTP
"Referer",
"Reply-To", // NNTP
"Retry-After",
"Sender", // NNTP
"Server", "Set-Cookie",
"Subject", // NNTP
"Summary", // NNTP
"Transfer-Encoding", "Upgrade", "User-Agent", "Vary", "Via", "Warning", "Www-Authenticate",
"Xref", // NNTP
"@Ats-Internal", // Internal Hack
// Accept-Encoding
"compress", "deflate", "gzip", "identity",
// Cache-Control flags
"max-age", "max-stale", "min-fresh", "must-revalidate", "no-cache", "no-store", "no-transform", "only-if-cached", "private",
"proxy-revalidate", "s-maxage", "need-revalidate-once",
// HTTP miscellaneous
"none", "chunked", "close",
// WS
"websocket", "Sec-WebSocket-Key", "Sec-WebSocket-Version",
// HTTP/2 cleartext
MIME_UPGRADE_H2C_TOKEN, "HTTP2-Settings",
// URL schemes
"file", "ftp", "gopher", "https", "http", "mailto", "news", "nntp", "prospero", "telnet", "tunnel", "wais", "pnm", "rtspu",
"rtsp", "mmsu", "mmst", "mms", "wss", "ws",
// HTTP methods
"CONNECT", "DELETE", "GET", "POST", "HEAD", "OPTIONS", "PURGE", "PUT", "TRACE", "PUSH", "QUERY",
// Header extensions
"X-ID", "X-Forwarded-For", "TE", "Strict-Transport-Security", "100-continue",
// RFC-2739
"Forwarded",
// RFC-8470
"Early-Data",
// RFC-7932
"br",
// RFC-8878
"zstd",
// RFC-9213 Targeted Cache Control
"CDN-Cache-Control"};
// MIMEField::m_wks_idx, HTTPHdrImpl's method index and URLImpl's scheme index are all int16_t.
static_assert(std::size(_hdrtoken_strs) <= INT16_MAX, "the well-known string table outgrew the type that indexes it");
constexpr HdrTokenTypeBinding _hdrtoken_strs_type_initializers[] = {
{"file", HdrTokenType::SCHEME },
{"ftp", HdrTokenType::SCHEME },
{"gopher", HdrTokenType::SCHEME },
{"http", HdrTokenType::SCHEME },
{"https", HdrTokenType::SCHEME },
{"mailto", HdrTokenType::SCHEME },
{"news", HdrTokenType::SCHEME },
{"nntp", HdrTokenType::SCHEME },
{"prospero", HdrTokenType::SCHEME },
{"telnet", HdrTokenType::SCHEME },
{"tunnel", HdrTokenType::SCHEME },
{"wais", HdrTokenType::SCHEME },
{"pnm", HdrTokenType::SCHEME },
{"rtsp", HdrTokenType::SCHEME },
{"rtspu", HdrTokenType::SCHEME },
{"mms", HdrTokenType::SCHEME },
{"mmsu", HdrTokenType::SCHEME },
{"mmst", HdrTokenType::SCHEME },
{"wss", HdrTokenType::SCHEME },
{"ws", HdrTokenType::SCHEME },
{"CONNECT", HdrTokenType::METHOD },
{"DELETE", HdrTokenType::METHOD },
{"GET", HdrTokenType::METHOD },
{"HEAD", HdrTokenType::METHOD },
{"OPTIONS", HdrTokenType::METHOD },
{"POST", HdrTokenType::METHOD },
{"PURGE", HdrTokenType::METHOD },
{"PUT", HdrTokenType::METHOD },
{"TRACE", HdrTokenType::METHOD },
{"PUSH", HdrTokenType::METHOD },
{"QUERY", HdrTokenType::METHOD },
{"max-age", HdrTokenType::CACHE_CONTROL },
{"max-stale", HdrTokenType::CACHE_CONTROL },
{"min-fresh", HdrTokenType::CACHE_CONTROL },
{"must-revalidate", HdrTokenType::CACHE_CONTROL },
{"no-cache", HdrTokenType::CACHE_CONTROL },
{"no-store", HdrTokenType::CACHE_CONTROL },
{"no-transform", HdrTokenType::CACHE_CONTROL },
{"only-if-cached", HdrTokenType::CACHE_CONTROL },
{"private", HdrTokenType::CACHE_CONTROL },
{"proxy-revalidate", HdrTokenType::CACHE_CONTROL },
{"public", HdrTokenType::CACHE_CONTROL },
{"s-maxage", HdrTokenType::CACHE_CONTROL },
{"need-revalidate-once", HdrTokenType::CACHE_CONTROL },
{(char *)nullptr, static_cast<HdrTokenType>(0)},
};
constexpr HdrTokenFieldInit _hdrtoken_strs_field_initializers[] = {
{"Accept", MIME_SLOTID_ACCEPT, MIME_PRESENCE_ACCEPT, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Accept-Charset", MIME_SLOTID_ACCEPT_CHARSET, MIME_PRESENCE_ACCEPT_CHARSET,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Accept-Encoding", MIME_SLOTID_ACCEPT_ENCODING, MIME_PRESENCE_ACCEPT_ENCODING,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Accept-Language", MIME_SLOTID_ACCEPT_LANGUAGE, MIME_PRESENCE_ACCEPT_LANGUAGE,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Accept-Ranges", MIME_SLOTID_NONE, MIME_PRESENCE_ACCEPT_RANGES, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Age", MIME_SLOTID_AGE, MIME_PRESENCE_AGE, HdrTokenInfoFlags::NONE },
{"Allow", MIME_SLOTID_NONE, MIME_PRESENCE_ALLOW, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Approved", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Authorization", MIME_SLOTID_AUTHORIZATION, MIME_PRESENCE_AUTHORIZATION, HdrTokenInfoFlags::NONE },
{"Bytes", MIME_SLOTID_NONE, MIME_PRESENCE_BYTES, HdrTokenInfoFlags::NONE },
{"Cache-Control", MIME_SLOTID_CACHE_CONTROL, MIME_PRESENCE_CACHE_CONTROL,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Client-ip", MIME_SLOTID_CLIENT_IP, MIME_PRESENCE_CLIENT_IP, HdrTokenInfoFlags::NONE },
{"Connection", MIME_SLOTID_CONNECTION, MIME_PRESENCE_CONNECTION,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS | HdrTokenInfoFlags::HOPBYHOP) },
{"Content-Base", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Content-Encoding", MIME_SLOTID_CONTENT_ENCODING, MIME_PRESENCE_CONTENT_ENCODING,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Content-Language", MIME_SLOTID_CONTENT_LANGUAGE, MIME_PRESENCE_CONTENT_LANGUAGE,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Content-Length", MIME_SLOTID_CONTENT_LENGTH, MIME_PRESENCE_CONTENT_LENGTH, HdrTokenInfoFlags::NONE },
{"Content-Location", MIME_SLOTID_NONE, MIME_PRESENCE_CONTENT_LOCATION, HdrTokenInfoFlags::NONE },
{"Content-MD5", MIME_SLOTID_NONE, MIME_PRESENCE_CONTENT_MD5, HdrTokenInfoFlags::NONE },
{"Content-Range", MIME_SLOTID_NONE, MIME_PRESENCE_CONTENT_RANGE, HdrTokenInfoFlags::NONE },
{"Content-Type", MIME_SLOTID_CONTENT_TYPE, MIME_PRESENCE_CONTENT_TYPE, HdrTokenInfoFlags::NONE },
{"Control", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Cookie", MIME_SLOTID_COOKIE, MIME_PRESENCE_COOKIE, (HdrTokenInfoFlags::MULTVALS) },
{"Date", MIME_SLOTID_DATE, MIME_PRESENCE_DATE, HdrTokenInfoFlags::NONE },
{"Distribution", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Etag", MIME_SLOTID_NONE, MIME_PRESENCE_ETAG, HdrTokenInfoFlags::NONE },
{"Expires", MIME_SLOTID_EXPIRES, MIME_PRESENCE_EXPIRES, HdrTokenInfoFlags::NONE },
{"Followup-To", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"From", MIME_SLOTID_NONE, MIME_PRESENCE_FROM, HdrTokenInfoFlags::NONE },
{"Host", MIME_SLOTID_NONE, MIME_PRESENCE_HOST, HdrTokenInfoFlags::NONE },
{"If-Match", MIME_SLOTID_IF_MATCH, MIME_PRESENCE_IF_MATCH, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"If-Modified-Since", MIME_SLOTID_IF_MODIFIED_SINCE, MIME_PRESENCE_IF_MODIFIED_SINCE, HdrTokenInfoFlags::NONE },
{"If-None-Match", MIME_SLOTID_IF_NONE_MATCH, MIME_PRESENCE_IF_NONE_MATCH,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"If-Range", MIME_SLOTID_IF_RANGE, MIME_PRESENCE_IF_RANGE, HdrTokenInfoFlags::NONE },
{"If-Unmodified-Since", MIME_SLOTID_IF_UNMODIFIED_SINCE, MIME_PRESENCE_IF_UNMODIFIED_SINCE, HdrTokenInfoFlags::NONE },
{"Keep-Alive", MIME_SLOTID_NONE, MIME_PRESENCE_KEEP_ALIVE, (HdrTokenInfoFlags::HOPBYHOP) },
{"Keywords", MIME_SLOTID_NONE, MIME_PRESENCE_KEYWORDS, HdrTokenInfoFlags::NONE },
{"Last-Modified", MIME_SLOTID_LAST_MODIFIED, MIME_PRESENCE_LAST_MODIFIED, HdrTokenInfoFlags::NONE },
{"Lines", MIME_SLOTID_NONE, MIME_PRESENCE_LINES, HdrTokenInfoFlags::NONE },
{"Location", MIME_SLOTID_NONE, MIME_PRESENCE_LOCATION, (HdrTokenInfoFlags::MULTVALS) },
{"Max-Forwards", MIME_SLOTID_NONE, MIME_PRESENCE_MAX_FORWARDS, HdrTokenInfoFlags::NONE },
{"Message-ID", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Newsgroups", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Organization", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Path", MIME_SLOTID_NONE, MIME_PRESENCE_PATH, HdrTokenInfoFlags::NONE },
{"Pragma", MIME_SLOTID_PRAGMA, MIME_PRESENCE_PRAGMA, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Proxy-Authenticate", MIME_SLOTID_NONE, MIME_PRESENCE_PROXY_AUTHENTICATE,
(HdrTokenInfoFlags::HOPBYHOP | HdrTokenInfoFlags::PROXYAUTH) },
{"Proxy-Authorization", MIME_SLOTID_NONE, MIME_PRESENCE_PROXY_AUTHORIZATION,
(HdrTokenInfoFlags::HOPBYHOP | HdrTokenInfoFlags::PROXYAUTH) },
{"Proxy-Connection", MIME_SLOTID_PROXY_CONNECTION, MIME_PRESENCE_PROXY_CONNECTION,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS | HdrTokenInfoFlags::HOPBYHOP) },
{"Public", MIME_SLOTID_NONE, MIME_PRESENCE_PUBLIC, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Range", MIME_SLOTID_RANGE, MIME_PRESENCE_RANGE, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"References", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Referer", MIME_SLOTID_NONE, MIME_PRESENCE_REFERER, HdrTokenInfoFlags::NONE },
{"Reply-To", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Retry-After", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Sender", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Server", MIME_SLOTID_NONE, MIME_PRESENCE_SERVER, HdrTokenInfoFlags::NONE },
{"Set-Cookie", MIME_SLOTID_SET_COOKIE, MIME_PRESENCE_SET_COOKIE, (HdrTokenInfoFlags::MULTVALS) },
{"Strict-Transport-Security", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HdrTokenInfoFlags::MULTVALS) },
{"Subject", MIME_SLOTID_NONE, MIME_PRESENCE_SUBJECT, HdrTokenInfoFlags::NONE },
{"Summary", MIME_SLOTID_NONE, MIME_PRESENCE_SUMMARY, HdrTokenInfoFlags::NONE },
{"TE", MIME_SLOTID_TE, MIME_PRESENCE_TE, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS | HdrTokenInfoFlags::HOPBYHOP)},
{"Transfer-Encoding", MIME_SLOTID_TRANSFER_ENCODING, MIME_PRESENCE_TRANSFER_ENCODING,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS | HdrTokenInfoFlags::HOPBYHOP) },
{"Upgrade", MIME_SLOTID_NONE, MIME_PRESENCE_UPGRADE,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS | HdrTokenInfoFlags::HOPBYHOP) },
{"User-Agent", MIME_SLOTID_USER_AGENT, MIME_PRESENCE_USER_AGENT, HdrTokenInfoFlags::NONE },
{"Vary", MIME_SLOTID_VARY, MIME_PRESENCE_VARY, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Via", MIME_SLOTID_VIA, MIME_PRESENCE_VIA, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Warning", MIME_SLOTID_NONE, MIME_PRESENCE_WARNING, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Www-Authenticate", MIME_SLOTID_WWW_AUTHENTICATE, MIME_PRESENCE_WWW_AUTHENTICATE, HdrTokenInfoFlags::NONE },
{"Xref", MIME_SLOTID_NONE, MIME_PRESENCE_XREF, HdrTokenInfoFlags::NONE },
{"X-ID", MIME_SLOTID_NONE, MIME_PRESENCE_NONE,
(HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS | HdrTokenInfoFlags::HOPBYHOP) },
{"X-Forwarded-For", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Forwarded", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{"Sec-WebSocket-Key", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"Sec-WebSocket-Version", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, HdrTokenInfoFlags::NONE },
{"CDN-Cache-Control", MIME_SLOTID_NONE, MIME_PRESENCE_NONE, (HdrTokenInfoFlags::COMMAS | HdrTokenInfoFlags::MULTVALS) },
{nullptr, 0, 0, HdrTokenInfoFlags::NONE },
};
struct HdrTokenCacheControlBinding {
const char *name;
uint32_t mask;
};
// The cooked mask for each Cache-Control directive. This is baked into the well-known-string
// table, so it belongs beside the other initializers rather than in MIME.cc.
constexpr HdrTokenCacheControlBinding _hdrtoken_strs_cc_initializers[] = {
{"max-age", MIME_COOKED_MASK_CC_MAX_AGE },
{"no-cache", MIME_COOKED_MASK_CC_NO_CACHE },
{"no-store", MIME_COOKED_MASK_CC_NO_STORE },
{"no-transform", MIME_COOKED_MASK_CC_NO_TRANSFORM },
{"max-stale", MIME_COOKED_MASK_CC_MAX_STALE },
{"min-fresh", MIME_COOKED_MASK_CC_MIN_FRESH },
{"only-if-cached", MIME_COOKED_MASK_CC_ONLY_IF_CACHED },
{"public", MIME_COOKED_MASK_CC_PUBLIC },
{"private", MIME_COOKED_MASK_CC_PRIVATE },
{"must-revalidate", MIME_COOKED_MASK_CC_MUST_REVALIDATE },
{"proxy-revalidate", MIME_COOKED_MASK_CC_PROXY_REVALIDATE },
{"s-maxage", MIME_COOKED_MASK_CC_S_MAXAGE },
{"need-revalidate-once", MIME_COOKED_MASK_CC_NEED_REVALIDATE_ONCE},
{nullptr, 0 },
};
/***********************************************************************
* *
* C O M P I L E - T I M E W K S T A B L E *
* *
***********************************************************************/
// hash_to_slot() folds a hash down to this many bits, so the table needs exactly one bucket per
// value those bits can take.
constexpr uint32_t HDRTOKEN_HASH_SLOT_BITS = 15;
constexpr uint32_t HDRTOKEN_HASH_SLOT_MASK = (1 << HDRTOKEN_HASH_SLOT_BITS) - 1;
constexpr size_t HDRTOKEN_HASH_TABLE_SIZE = static_cast<size_t>(HDRTOKEN_HASH_SLOT_MASK) + 1;
constexpr uint32_t
hash_to_slot(uint32_t hash)
{
return ((hash >> HDRTOKEN_HASH_SLOT_BITS) ^ hash) & HDRTOKEN_HASH_SLOT_MASK;
}
constexpr unsigned char
hdrtoken_ascii_toupper(unsigned char c)
{
return (c >= 'a' && c <= 'z') ? static_cast<unsigned char>(c - ('a' - 'A')) : c;
}
constexpr uint32_t HDRTOKEN_HASH_SEED = 0x811c9dc5u; // FNV-1a 32-bit offset basis
// The one hash function, shared by compile-time table construction and hdrtoken_tokenize(), so the
// two can never disagree. hdrtoken_field_name_scan() folds the same steps inline because it
// discovers the length as it scans; a parity unit test pins it to this function.
constexpr uint32_t
hdrtoken_hash(std::string_view s)
{
uint32_t hval = HDRTOKEN_HASH_SEED;
for (char const c : s) {
hval = (hval ^ hdrtoken_ascii_toupper(static_cast<unsigned char>(c))) * 0x01000193u;
}
return hval;
}
constexpr size_t
hdrtoken_max_literal_length()
{
const auto longest = std::max_element(std::cbegin(_hdrtoken_strs), std::cend(_hdrtoken_strs),
[](std::string_view a, std::string_view b) { return a.length() < b.length(); });
return longest->length();
}
static_assert(hdrtoken_max_literal_length() + 1 <= HDRTOKEN_WKS_STORAGE,
"a well-known string does not fit its entry; raise HDRTOKEN_WKS_STORAGE");
constexpr bool
hdrtoken_literals_equal_nocase(std::string_view a, std::string_view b)
{
if (a.size() != b.size()) {
return false;
}
for (size_t i = 0; i < a.size(); ++i) {
if (hdrtoken_ascii_toupper(static_cast<unsigned char>(a[i])) != hdrtoken_ascii_toupper(static_cast<unsigned char>(b[i]))) {
return false;
}
}
return true;
}
constexpr int
hdrtoken_index_of_literal(std::string_view name)
{
for (size_t i = 0; i < std::size(_hdrtoken_strs); ++i) {
if (hdrtoken_literals_equal_nocase(_hdrtoken_strs[i], name)) {
return static_cast<int>(i);
}
}
return -1;
}
// Each initializer name must be an entry in _hdrtoken_strs, and no two rows of one table may name
// the same entry.
template <typename Table>
constexpr bool
hdrtoken_names_resolve_uniquely(Table const &table)
{
std::array<bool, std::size(_hdrtoken_strs)> seen{};
for (auto const &row : table) {
if (row.name == nullptr) {
continue;
}
int const idx = hdrtoken_index_of_literal(row.name);
if (idx < 0 || seen[idx]) {
return false;
}
seen[idx] = true;
}
return true;
}
static_assert(hdrtoken_names_resolve_uniquely(_hdrtoken_strs_type_initializers),
"a token-type initializer names a string that is missing from _hdrtoken_strs or already claimed");
static_assert(hdrtoken_names_resolve_uniquely(_hdrtoken_strs_field_initializers),
"a field initializer names a string that is missing from _hdrtoken_strs or already claimed");
static_assert(hdrtoken_names_resolve_uniquely(_hdrtoken_strs_cc_initializers),
"a Cache-Control initializer names a string that is missing from _hdrtoken_strs or already claimed");
// Resolution folds ASCII case and matches whole strings only. Exact-length matching is what allows
// a string to be appended to the table even when an existing entry is its prefix.
static_assert(hdrtoken_index_of_literal("cache-control") == hdrtoken_index_of_literal("Cache-Control"),
"resolution must be ASCII case-insensitive");
static_assert(hdrtoken_index_of_literal("Content-Len") == -1, "a prefix of a well-known string must not resolve");
static_assert(hdrtoken_index_of_literal("Accept") >= 0 &&
hdrtoken_index_of_literal("Accept") != hdrtoken_index_of_literal("Accept-Encoding"),
"a well-known string that is a prefix of another must resolve to its own entry");
// The field rows feed the fast MIME slot and presence-bit machinery, so each non-NONE slot id must
// be a valid, unclaimed slot and each nonzero presence mask must be a distinct single bit.
constexpr bool
hdrtoken_field_init_semantics_ok()
{
std::array<bool, 32> slot_seen{}; // MIME_SLOTID_* values are 0..31
uint64_t mask_seen = 0;
for (auto const &f : _hdrtoken_strs_field_initializers) {
if (f.name == nullptr) {
continue;
}
if (f.slotid != MIME_SLOTID_NONE) {
if (f.slotid < 0 || f.slotid >= static_cast<int32_t>(slot_seen.size()) || slot_seen[f.slotid]) {
return false;
}
slot_seen[f.slotid] = true;
}
if (f.mask != 0) {
if ((f.mask & (f.mask - 1)) != 0 || (mask_seen & f.mask) != 0) {
return false;
}
mask_seen |= f.mask;
}
}
return true;
}
static_assert(hdrtoken_field_init_semantics_ok(),
"a field initializer has an out-of-range or duplicate slot id, or a multi-bit or duplicate presence mask");
// Every Cache-Control row must carry a distinct single-bit cooked mask, and its string must be
// typed CACHE_CONTROL to satisfy HTTPHdr::is_cache_control_set(), which asserts that type for any
// directive whose mask it consults.
constexpr bool
hdrtoken_cc_init_semantics_ok()
{
uint32_t mask_seen = 0;
for (auto const &c : _hdrtoken_strs_cc_initializers) {
if (c.name == nullptr) {
continue;
}
if (c.mask == 0 || (c.mask & (c.mask - 1)) != 0 || (mask_seen & c.mask) != 0) {
return false;
}
mask_seen |= c.mask;
bool cc_typed = false;
for (auto const &b : _hdrtoken_strs_type_initializers) {
if (b.name != nullptr && hdrtoken_literals_equal_nocase(b.name, c.name)) {
cc_typed = (b.type == HdrTokenType::CACHE_CONTROL);
break;
}
}
if (!cc_typed) {
return false;
}
}
return true;
}
static_assert(hdrtoken_cc_init_semantics_ok(),
"a Cache-Control initializer has a zero, multi-bit, or duplicate mask, or its entry is not typed CACHE_CONTROL");
constexpr bool
hdrtoken_wks_slots_unique()
{
// std::sort and std::adjacent_find are only constexpr in libstdc++ 12 and later
std::array<bool, HDRTOKEN_HASH_TABLE_SIZE> seen{};
for (std::string_view const s : _hdrtoken_strs) {
uint32_t const slot = hash_to_slot(hdrtoken_hash(s));
if (seen[slot]) {
return false;
}
seen[slot] = true;
}
return true;
}
static_assert(hdrtoken_wks_slots_unique(), "Two well-known strings hash to the same slot. Change the table or the hash!");
constexpr std::array<HdrTokenWksEntry, std::size(_hdrtoken_strs)>
hdrtoken_build_wks_table()
{
std::array<HdrTokenWksEntry, std::size(_hdrtoken_strs)> table{};
for (size_t i = 0; i < std::size(_hdrtoken_strs); ++i) {
HdrTokenWksEntry &e = table[i];
std::string_view const name = _hdrtoken_strs[i];
for (size_t k = 0; k < name.size(); ++k) {
e.str[k] = name[k];
}
e.prefix.wks_idx = static_cast<int>(i);
e.prefix.wks_length = static_cast<int>(name.size());
e.prefix.wks_token_type = HdrTokenType::OTHER;
e.prefix.wks_info.slotid = MIME_SLOTID_NONE;
e.prefix.wks_info.mask = TOK_64_CONST(0);
e.prefix.wks_info.flags = HdrTokenInfoFlags::MULTVALS;
}
for (auto const &b : _hdrtoken_strs_type_initializers) {
if (b.name != nullptr) {
table[hdrtoken_index_of_literal(b.name)].prefix.wks_token_type = b.type;
}
}
for (auto const &f : _hdrtoken_strs_field_initializers) {
if (f.name != nullptr) {
HdrTokenFieldInfo &info = table[hdrtoken_index_of_literal(f.name)].prefix.wks_info;
info.slotid = f.slotid;
info.mask = f.mask;
info.flags = f.flags;
}
}
for (auto const &c : _hdrtoken_strs_cc_initializers) {
if (c.name != nullptr) {
table[hdrtoken_index_of_literal(c.name)].prefix.wks_type_specific.u.cache_control.cc_mask = c.mask;
}
}
return table;
}
constexpr std::array<HdrTokenWksEntry, std::size(_hdrtoken_strs)> hdrtoken_wks_table = hdrtoken_build_wks_table();
// FNV-1a over a canonical byte sequence, independent of native padding and byte order.
// Bump the schema byte when the interpretation of the derived indexes changes.
constexpr uint64_t
hdrtoken_build_wks_identity()
{
uint64_t hash = 14695981039346656037ULL;
auto byte = [&](uint8_t value) { hash = (hash ^ value) * 1099511628211ULL; };
auto integer = [&](uint64_t value) {
for (unsigned i = 0; i < 8; ++i) {
byte(static_cast<uint8_t>(value >> (i * 8)));
}
};
byte(1);
for (size_t i = 0; i < std::size(_hdrtoken_strs); ++i) {
for (unsigned char c : _hdrtoken_strs[i]) {
byte(c);
}
byte(0);
integer(hdrtoken_wks_table[i].prefix.wks_info.slotid);
integer(hdrtoken_wks_table[i].prefix.wks_info.mask);
}
return hash;
}
static_assert(hdrtoken_build_wks_identity() != 0);
/***********************************************************************
* *
* H A S H T A B L E *
* *
***********************************************************************/
struct HdrTokenHashBucket {
uint32_t wks_idx_plus_one; // biased by one so that a value-initialized bucket reads as empty
uint32_t hash;
};
constexpr std::array<HdrTokenHashBucket, HDRTOKEN_HASH_TABLE_SIZE>
hdrtoken_build_hash_table()
{
std::array<HdrTokenHashBucket, HDRTOKEN_HASH_TABLE_SIZE> table{};
// static_assert(hdrtoken_wks_slots_unique()) proves no two strings share a slot, so no bucket is
// assigned twice here.
for (size_t i = 0; i < std::size(_hdrtoken_strs); i++) {
uint32_t const hash = hdrtoken_hash(_hdrtoken_strs[i]);
table[hash_to_slot(hash)] = {static_cast<uint32_t>(i) + 1, hash};
}
return table;
}
constexpr std::array<HdrTokenHashBucket, HDRTOKEN_HASH_TABLE_SIZE> hdrtoken_hash_table = hdrtoken_build_hash_table();
} // end anonymous namespace
const uint64_t hdrtoken_wks_identity = hdrtoken_build_wks_identity();
// hdrtoken_wks_to_prefix() maps a string pointer back to its entry through this table.
const HdrTokenWksEntry *const hdrtoken_wks_entries = hdrtoken_wks_table.data();
// Header string pointers in this range are well-known.
const char *_hdrtoken_strs_heap_f = &hdrtoken_wks_table[0].str[0]; // storage first byte
const char *_hdrtoken_strs_heap_l = &hdrtoken_wks_table[std::size(_hdrtoken_strs) - 1].str[HDRTOKEN_WKS_STORAGE - 1];
int hdrtoken_num_wks = std::size(_hdrtoken_strs); // # of well-known strings
const char *hdrtoken_strs[std::size(_hdrtoken_strs)]; // wks_idx -> string
int hdrtoken_str_lengths[std::size(_hdrtoken_strs)]; // wks_idx -> length
HdrTokenType hdrtoken_str_token_types[std::size(_hdrtoken_strs)]; // wks_idx -> token type
int32_t hdrtoken_str_slotids[std::size(_hdrtoken_strs)]; // wks_idx -> slot id
uint64_t hdrtoken_str_masks[std::size(_hdrtoken_strs)]; // wks_idx -> presence mask
HdrTokenInfoFlags hdrtoken_str_flags[std::size(_hdrtoken_strs)]; // wks_idx -> flags
/***********************************************************************
* *
* M A I N H D R T O K E N C O D E *
* *
***********************************************************************/
/**
*/
void
hdrtoken_init()
{
static int inited = 0;
if (!inited) {
inited = 1;
// hdrtoken_wks_table already holds every string with its prefix, resolved at compile time.
// Copy the hot fields out into the parallel arrays.
for (int i = 0; i < static_cast<int>(std::size(_hdrtoken_strs)); i++) {
HdrTokenHeapPrefix const &prefix = hdrtoken_wks_table[i].prefix;
hdrtoken_strs[i] = hdrtoken_wks_table[i].str;
hdrtoken_str_lengths[i] = prefix.wks_length;
hdrtoken_str_token_types[i] = prefix.wks_token_type;
hdrtoken_str_slotids[i] = prefix.wks_info.slotid;
hdrtoken_str_masks[i] = prefix.wks_info.mask;
hdrtoken_str_flags[i] = prefix.wks_info.flags;
}
}
}
/*-------------------------------------------------------------------------
Have to work around that methods are case sensitive while hdrtoken_tokenize()
is case insensitive.
-------------------------------------------------------------------------*/
int
hdrtoken_method_tokenize(const char *string, int string_len)
{
const char *string_out;
int retval = -1;
if (hdrtoken_is_wks(string)) {
retval = hdrtoken_wks_to_index(string);
return retval;
}
retval = hdrtoken_tokenize(string, string_len, &string_out);
if (retval >= 0) {
if (strncmp(string, string_out, string_len) != 0) {
// Not a case match
retval = -1;
}
}
return retval;
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
// WKS lookup for a name whose FNV-1a hash the caller has already computed
// (e.g. fused into the field-name scan). Matches by slot, hash, and length like
// hdrtoken_tokenize, but skips the interned-pointer test, so it is only valid
// for a non-interned `string`.
int
hdrtoken_tokenize_prehashed(const char *string, int string_len, uint32_t hash, const char **wks_string_out)
{
ink_assert(string != nullptr);
ink_assert(!hdrtoken_is_wks(string));
HdrTokenHashBucket const &bucket = hdrtoken_hash_table[hash_to_slot(hash)];
if ((bucket.wks_idx_plus_one != 0) && (bucket.hash == hash)) {
int const wks_idx = static_cast<int>(bucket.wks_idx_plus_one - 1);
HdrTokenWksEntry const &entry = hdrtoken_wks_table[wks_idx];
if (entry.prefix.wks_length == string_len) {
if (wks_string_out) {
*wks_string_out = entry.str;
}
return wks_idx;
}
}
Dbg(dbg_ctl_hdr_token, "Did not find a WKS for '%.*s'", string_len, string);
return -1;
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
// Single-pass field-name scan for the MIME parser. Scans up to `maxlen` bytes
// of `string` for the ':' delimiter while, in the same pass, accumulating the
// FNV-1a name hash (identical to hdrtoken_hash) and tracking whether every byte
// before ':' is a valid HTTP field-name char. Returns the index of ':' (i.e.
// the field-name length) or -1 if no ':' appears within `maxlen`. `*hash_out`
// and `*all_valid_out` describe the bytes scanned before ':' (or all `maxlen`
// bytes when ':' is absent); both are required.
int
hdrtoken_field_name_scan(const char *string, int maxlen, uint32_t *hash_out, bool *all_valid_out)
{
uint32_t hval = HDRTOKEN_HASH_SEED; // same FNV-1a name hash as hdrtoken_hash
bool all_valid = true;
int i = 0;
for (; i < maxlen; ++i) {
unsigned char const uc = static_cast<unsigned char>(string[i]);
if (uc == ':') {
break;
}
hval = (hval ^ hdrtoken_ascii_toupper(uc)) * 0x01000193u;
all_valid &= (ParseRules::is_http_field_name(static_cast<char>(uc)) != 0);
}
*hash_out = hval;
*all_valid_out = all_valid;
return (i < maxlen) ? i : -1;
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
int
hdrtoken_tokenize(const char *string, int string_len, const char **wks_string_out)
{
ink_assert(string != nullptr);
if (hdrtoken_is_wks(string)) {
int const wks_idx = hdrtoken_wks_to_index(string);
if (wks_string_out) {
*wks_string_out = string;
}
return wks_idx;
}
uint32_t const hash = hdrtoken_hash(std::string_view{string, static_cast<size_t>(string_len)});
return hdrtoken_tokenize_prehashed(string, string_len, hash, wks_string_out);
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
const char *
hdrtoken_string_to_wks(const char *string)
{
const char *wks = nullptr;
hdrtoken_tokenize(string, static_cast<int>(strlen(string)), &wks);
return wks;
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
const char *
hdrtoken_string_to_wks(const char *string, int length)
{
const char *wks = nullptr;
hdrtoken_tokenize(string, length, &wks);
return wks;
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
c_str_view
hdrtoken_string_to_wks_sv(const char *string)
{
const char *wks = nullptr;
auto length{strlen(string)};
hdrtoken_tokenize(string, static_cast<int>(length), &wks);
return c_str_view{wks, length};
}
/*-------------------------------------------------------------------------
-------------------------------------------------------------------------*/
c_str_view
hdrtoken_string_to_wks_sv(const char *string, int length)
{
const char *wks = nullptr;
hdrtoken_tokenize(string, length, &wks);
return c_str_view{wks, static_cast<c_str_view::size_type>(length)};
}