/*
- * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
/* DEBUG: section 65 HTTP Cache Control Header */
#include "squid.h"
+#include "base/LookupTable.h"
#include "HttpHdrCc.h"
#include "HttpHeader.h"
#include "HttpHeaderFieldStat.h"
#include "HttpHeaderStat.h"
#include "HttpHeaderTools.h"
-#include "SBuf.h"
+#include "sbuf/SBuf.h"
#include "StatHist.h"
#include "Store.h"
#include "StrList.h"
#include "util.h"
#include <map>
-
-/* a row in the table used for parsing cache control header and statistics */
-class HttpHeaderCcFields
-{
-public:
- HttpHeaderCcFields() : name(NULL), id(CC_BADHDR), stat() {}
- HttpHeaderCcFields(const char *aName, http_hdr_cc_type aTypeId) : name(aName), id(aTypeId) {}
- HttpHeaderCcFields(const HttpHeaderCcFields &f) : name(f.name), id(f.id) {}
- // nothing to do as name is a pointer to global static string
- ~HttpHeaderCcFields() {}
-
- const char *name;
- http_hdr_cc_type id;
- HttpHeaderFieldStat stat;
-
-private:
- HttpHeaderCcFields &operator =(const HttpHeaderCcFields &); // not implemented
-};
-
-/* order must match that of enum http_hdr_cc_type. The constraint is verified at initialization time */
-static HttpHeaderCcFields CcAttrs[CC_ENUM_END] = {
- HttpHeaderCcFields("public", CC_PUBLIC),
- HttpHeaderCcFields("private", CC_PRIVATE),
- HttpHeaderCcFields("no-cache", CC_NO_CACHE),
- HttpHeaderCcFields("no-store", CC_NO_STORE),
- HttpHeaderCcFields("no-transform", CC_NO_TRANSFORM),
- HttpHeaderCcFields("must-revalidate", CC_MUST_REVALIDATE),
- HttpHeaderCcFields("proxy-revalidate", CC_PROXY_REVALIDATE),
- HttpHeaderCcFields("max-age", CC_MAX_AGE),
- HttpHeaderCcFields("s-maxage", CC_S_MAXAGE),
- HttpHeaderCcFields("max-stale", CC_MAX_STALE),
- HttpHeaderCcFields("min-fresh", CC_MIN_FRESH),
- HttpHeaderCcFields("only-if-cached", CC_ONLY_IF_CACHED),
- HttpHeaderCcFields("stale-if-error", CC_STALE_IF_ERROR),
- HttpHeaderCcFields("Other,", CC_OTHER) /* ',' will protect from matches */
+#include <vector>
+#include <ostream>
+
+// invariant: row[j].id == j
+static LookupTable<HttpHdrCcType>::Record CcAttrs[] = {
+ {"public", HttpHdrCcType::CC_PUBLIC},
+ {"private", HttpHdrCcType::CC_PRIVATE},
+ {"no-cache", HttpHdrCcType::CC_NO_CACHE},
+ {"no-store", HttpHdrCcType::CC_NO_STORE},
+ {"no-transform", HttpHdrCcType::CC_NO_TRANSFORM},
+ {"must-revalidate", HttpHdrCcType::CC_MUST_REVALIDATE},
+ {"proxy-revalidate", HttpHdrCcType::CC_PROXY_REVALIDATE},
+ {"max-age", HttpHdrCcType::CC_MAX_AGE},
+ {"s-maxage", HttpHdrCcType::CC_S_MAXAGE},
+ {"max-stale", HttpHdrCcType::CC_MAX_STALE},
+ {"min-fresh", HttpHdrCcType::CC_MIN_FRESH},
+ {"only-if-cached", HttpHdrCcType::CC_ONLY_IF_CACHED},
+ {"stale-if-error", HttpHdrCcType::CC_STALE_IF_ERROR},
+ {"immutable", HttpHdrCcType::CC_IMMUTABLE},
+ {"Other,", HttpHdrCcType::CC_OTHER}, /* ',' will protect from matches */
+ {nullptr, HttpHdrCcType::CC_ENUM_END}
};
-
-/// Map an header name to its type, to expedite parsing
-typedef std::map<const SBuf,http_hdr_cc_type> CcNameToIdMap_t;
-static CcNameToIdMap_t CcNameToIdMap;
+LookupTable<HttpHdrCcType> ccLookupTable(HttpHdrCcType::CC_OTHER,CcAttrs);
+std::vector<HttpHeaderFieldStat> ccHeaderStats(HttpHdrCcType::CC_ENUM_END);
/// used to walk a table of http_header_cc_type structs
-http_hdr_cc_type &operator++ (http_hdr_cc_type &aHeader)
+static HttpHdrCcType &
+operator++ (HttpHdrCcType &aHeader)
{
int tmp = (int)aHeader;
- aHeader = (http_hdr_cc_type)(++tmp);
+ aHeader = (HttpHdrCcType)(++tmp);
return aHeader;
}
void
httpHdrCcInitModule(void)
{
- /* build lookup and accounting structures */
- for (int32_t i = 0; i < CC_ENUM_END; ++i) {
- const HttpHeaderCcFields &f=CcAttrs[i];
- assert(i == f.id); /* verify assumption: the id is the key into the array */
- const SBuf k(f.name);
- CcNameToIdMap[k]=f.id;
+ // check invariant on initialization table
+ for (unsigned int j = 0; CcAttrs[j].name != nullptr; ++j) {
+ assert(static_cast<decltype(j)>(CcAttrs[j].id) == j);
}
}
-/// Module cleanup hook.
void
-httpHdrCcCleanModule(void)
+HttpHdrCc::clear()
{
- // HdrCcNameToIdMap is self-cleaning
+ *this=HttpHdrCc();
}
+/// set a data member to a new value, and set the corresponding mask-bit.
+/// if setting is false, then the mask-bit is cleared.
void
-HttpHdrCc::clear()
+HttpHdrCc::setValue(int32_t &value, int32_t new_value, HttpHdrCcType hdr, bool setting)
{
- *this=HttpHdrCc();
+ if (setting) {
+ if (new_value < 0) {
+ debugs(65, 3, "rejecting negative-value Cache-Control directive " << hdr
+ << " value " << new_value);
+ return;
+ }
+ } else {
+ new_value = -1; //rely on the convention that "unknown" is -1
+ }
+
+ value = new_value;
+ setMask(hdr,setting);
}
bool
{
const char *item;
const char *p; /* '=' parameter */
- const char *pos = NULL;
- http_hdr_cc_type type;
+ const char *pos = nullptr;
int ilen;
int nlen;
}
/* find type */
- const CcNameToIdMap_t::const_iterator i=CcNameToIdMap.find(SBuf(item,nlen));
- if (i==CcNameToIdMap.end())
- type=CC_OTHER;
- else
- type=i->second;
+ const HttpHdrCcType type = ccLookupTable.lookup(SBuf(item,nlen));
// ignore known duplicate directives
if (isSet(type)) {
- if (type != CC_OTHER) {
+ if (type != HttpHdrCcType::CC_OTHER) {
debugs(65, 2, "hdr cc: ignoring duplicate cache-directive: near '" << item << "' in '" << str << "'");
- ++CcAttrs[type].stat.repCount;
+ ++ ccHeaderStats[type].repCount;
continue;
}
}
/* special-case-parsing and attribute-setting */
switch (type) {
- case CC_MAX_AGE:
+ case HttpHdrCcType::CC_MAX_AGE:
if (!p || !httpHeaderParseInt(p, &max_age) || max_age < 0) {
debugs(65, 2, "cc: invalid max-age specs near '" << item << "'");
clearMaxAge();
}
break;
- case CC_S_MAXAGE:
+ case HttpHdrCcType::CC_S_MAXAGE:
if (!p || !httpHeaderParseInt(p, &s_maxage) || s_maxage < 0) {
debugs(65, 2, "cc: invalid s-maxage specs near '" << item << "'");
clearSMaxAge();
}
break;
- case CC_MAX_STALE:
+ case HttpHdrCcType::CC_MAX_STALE:
if (!p || !httpHeaderParseInt(p, &max_stale) || max_stale < 0) {
debugs(65, 2, "cc: max-stale directive is valid without value");
maxStale(MAX_STALE_ANY);
}
break;
- case CC_MIN_FRESH:
+ case HttpHdrCcType::CC_MIN_FRESH:
if (!p || !httpHeaderParseInt(p, &min_fresh) || min_fresh < 0) {
debugs(65, 2, "cc: invalid min-fresh specs near '" << item << "'");
clearMinFresh();
}
break;
- case CC_STALE_IF_ERROR:
+ case HttpHdrCcType::CC_STALE_IF_ERROR:
if (!p || !httpHeaderParseInt(p, &stale_if_error) || stale_if_error < 0) {
debugs(65, 2, "cc: invalid stale-if-error specs near '" << item << "'");
clearStaleIfError();
}
break;
- case CC_PRIVATE: {
+ case HttpHdrCcType::CC_PRIVATE: {
String temp;
if (!p) {
// Value parameter is optional.
}
break;
- case CC_NO_CACHE: {
+ case HttpHdrCcType::CC_NO_CACHE: {
String temp;
if (!p) {
// On Requests, missing value parameter is expected syntax.
}
break;
- case CC_PUBLIC:
+ case HttpHdrCcType::CC_PUBLIC:
Public(true);
break;
- case CC_NO_STORE:
+ case HttpHdrCcType::CC_NO_STORE:
noStore(true);
break;
- case CC_NO_TRANSFORM:
+ case HttpHdrCcType::CC_NO_TRANSFORM:
noTransform(true);
break;
- case CC_MUST_REVALIDATE:
+ case HttpHdrCcType::CC_MUST_REVALIDATE:
mustRevalidate(true);
break;
- case CC_PROXY_REVALIDATE:
+ case HttpHdrCcType::CC_PROXY_REVALIDATE:
proxyRevalidate(true);
break;
- case CC_ONLY_IF_CACHED:
+ case HttpHdrCcType::CC_ONLY_IF_CACHED:
onlyIfCached(true);
break;
+ case HttpHdrCcType::CC_IMMUTABLE:
+ Immutable(true);
+ break;
- case CC_OTHER:
+ case HttpHdrCcType::CC_OTHER:
if (other.size())
other.append(", ");
}
void
-HttpHdrCc::packInto(Packer * p) const
+HttpHdrCc::packInto(Packable * p) const
{
// optimization: if the mask is empty do nothing
if (mask==0)
return;
- http_hdr_cc_type flag;
+ HttpHdrCcType flag;
int pcount = 0;
assert(p);
- for (flag = CC_PUBLIC; flag < CC_ENUM_END; ++flag) {
- if (isSet(flag) && flag != CC_OTHER) {
+ for (flag = HttpHdrCcType::CC_PUBLIC; flag < HttpHdrCcType::CC_ENUM_END; ++flag) {
+ if (isSet(flag) && flag != HttpHdrCcType::CC_OTHER) {
/* print option name for all options */
- packerPrintf(p, (pcount ? ", %s": "%s") , CcAttrs[flag].name);
+ p->appendf((pcount ? ", %s": "%s"), CcAttrs[flag].name);
/* for all options having values, "=value" after the name */
switch (flag) {
- case CC_MAX_AGE:
- packerPrintf(p, "=%d", (int) maxAge());
+ case HttpHdrCcType::CC_PUBLIC:
break;
- case CC_S_MAXAGE:
- packerPrintf(p, "=%d", (int) sMaxAge());
+ case HttpHdrCcType::CC_PRIVATE:
+ if (private_.size())
+ p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(private_));
break;
- case CC_MAX_STALE:
+
+ case HttpHdrCcType::CC_NO_CACHE:
+ if (no_cache.size())
+ p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(no_cache));
+ break;
+ case HttpHdrCcType::CC_NO_STORE:
+ break;
+ case HttpHdrCcType::CC_NO_TRANSFORM:
+ break;
+ case HttpHdrCcType::CC_MUST_REVALIDATE:
+ break;
+ case HttpHdrCcType::CC_PROXY_REVALIDATE:
+ break;
+ case HttpHdrCcType::CC_MAX_AGE:
+ p->appendf("=%d", max_age);
+ break;
+ case HttpHdrCcType::CC_S_MAXAGE:
+ p->appendf("=%d", s_maxage);
+ break;
+ case HttpHdrCcType::CC_MAX_STALE:
/* max-stale's value is optional.
If we didn't receive it, don't send it */
- if (maxStale()!=MAX_STALE_ANY)
- packerPrintf(p, "=%d", (int) maxStale());
+ if (max_stale != MAX_STALE_ANY)
+ p->appendf("=%d", max_stale);
+ break;
+ case HttpHdrCcType::CC_MIN_FRESH:
+ p->appendf("=%d", min_fresh);
+ break;
+ case HttpHdrCcType::CC_ONLY_IF_CACHED:
break;
- case CC_MIN_FRESH:
- packerPrintf(p, "=%d", (int) minFresh());
+ case HttpHdrCcType::CC_STALE_IF_ERROR:
+ p->appendf("=%d", stale_if_error);
break;
- default:
- /* do nothing, directive was already printed */
+ case HttpHdrCcType::CC_IMMUTABLE:
+ break;
+ case HttpHdrCcType::CC_OTHER:
+ case HttpHdrCcType::CC_ENUM_END:
+ // done below after the loop
break;
}
}
if (other.size() != 0)
- packerPrintf(p, (pcount ? ", " SQUIDSTRINGPH : SQUIDSTRINGPH),
- SQUIDSTRINGPRINT(other));
+ p->appendf((pcount ? ", " SQUIDSTRINGPH : SQUIDSTRINGPH), SQUIDSTRINGPRINT(other));
}
void
httpHdrCcUpdateStats(const HttpHdrCc * cc, StatHist * hist)
{
- http_hdr_cc_type c;
assert(cc);
- for (c = CC_PUBLIC; c < CC_ENUM_END; ++c)
+ for (HttpHdrCcType c = HttpHdrCcType::CC_PUBLIC; c < HttpHdrCcType::CC_ENUM_END; ++c)
if (cc->isSet(c))
hist->count(c);
}
httpHdrCcStatDumper(StoreEntry * sentry, int, double val, double, int count)
{
extern const HttpHeaderStat *dump_stat; /* argh! */
- const int id = (int) val;
- const int valid_id = id >= 0 && id < CC_ENUM_END;
+ const int id = static_cast<int>(val);
+ const bool valid_id = id >= 0 && id < static_cast<int>(HttpHdrCcType::CC_ENUM_END);
const char *name = valid_id ? CcAttrs[id].name : "INVALID";
if (count || valid_id)
id, name, count, xdiv(count, dump_stat->ccParsedCount));
}
-#if !_USE_INLINE_
-#include "HttpHdrCc.cci"
-#endif
+std::ostream &
+operator<< (std::ostream &s, HttpHdrCcType c)
+{
+ const unsigned char ic = static_cast<int>(c);
+ if (c < HttpHdrCcType::CC_ENUM_END)
+ s << CcAttrs[ic].name << '[' << ic << ']' ;
+ else
+ s << "*invalid hdrcc* [" << ic << ']';
+ return s;
+}