/* * 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. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 90 HTTP Cache Control Header */ #include "squid.h" #include "base/LookupTable.h" //#include "HttpHdrSc.h" // pulled in by HttpHdrScTarget.h #include "HttpHdrScTarget.h" #include "HttpHeader.h" #include "HttpHeaderFieldStat.h" #include "HttpHeaderStat.h" #include "HttpHeaderTools.h" #include "Store.h" #include "StrList.h" #include "util.h" #include #include /* this table is used for parsing surrogate control header */ /* order must match that of enum http_hdr_sc_type. The constraint is verified at initialization time */ // TODO: implement constraint static const LookupTable::Record ScAttrs[] { {"no-store", SC_NO_STORE}, {"no-store-remote", SC_NO_STORE_REMOTE}, {"max-age", SC_MAX_AGE}, {"content", SC_CONTENT}, {"Other,", SC_OTHER}, /* ',' will protect from matches */ {nullptr, SC_ENUM_END} /* SC_ENUM_END taken as invalid value */ }; LookupTable scLookupTable(SC_ENUM_END, ScAttrs); std::vector scHeaderStats(SC_ENUM_END); // used when iterating over flags http_hdr_sc_type &operator++ (http_hdr_sc_type &aHeader) { int tmp = static_cast(aHeader); aHeader = static_cast(++tmp); return aHeader; } void httpHdrScInitModule(void) { // check invariant on ScAttrs for (int i = 0; ScAttrs[i].name != nullptr; ++i) assert(i == ScAttrs[i].id); } /* implementation */ /* creates an sc object from a 0-terminating string */ HttpHdrSc * httpHdrScParseCreate(const String & str) { HttpHdrSc *sc = new HttpHdrSc(); if (!sc->parse(&str)) { delete sc; sc = NULL; } return sc; } /* parses a 0-terminating string and inits sc */ bool HttpHdrSc::parse(const String * str) { HttpHdrSc * sc=this; const char *item; const char *p; /* '=' parameter */ const char *pos = NULL; const char *target = NULL; /* ;foo */ const char *temp = NULL; /* temp buffer */ http_hdr_sc_type type; int ilen, vlen; int initiallen; HttpHdrScTarget *sct; assert(str); /* iterate through comma separated list */ while (strListGetItem(str, ',', &item, &ilen, &pos)) { initiallen = ilen; vlen = 0; /* decrease ilen to still match the token for '=' statements */ if ((p = strchr(item, '=')) && (p - item < ilen)) { vlen = ilen - (p + 1 - item); ilen = p - item; ++p; } /* decrease ilen to still match the token for ';' qualified non '=' statments */ else if ((p = strchr(item, ';')) && (p - item < ilen)) { ilen = p - item; ++p; } /* find type */ type = scLookupTable.lookup(SBuf(item,ilen)); if (type == SC_ENUM_END) { debugs(90, 2, "unknown control-directive near '" << item << "' in '" << *str << "'"); type = SC_OTHER; } /* Is this a targeted directive? */ /* TODO: remove the temporary useage and use memrchr and the information we have instead */ temp = xstrndup (item, initiallen + 1); if (!((target = strrchr (temp, ';')) && !strchr (target, '"') && *(target + 1) != '\0')) target = NULL; else ++target; sct = sc->findTarget(target); if (!sct) { sct = new HttpHdrScTarget(target); addTarget(sct); } safe_free (temp); if (sct->isSet(type)) { if (type != SC_OTHER) debugs(90, 2, "ignoring duplicate control-directive near '" << item << "' in '" << *str << "'"); ++ scHeaderStats[type].repCount; continue; } /* process directives */ switch (type) { case SC_NO_STORE: sct->noStore(true); break; case SC_NO_STORE_REMOTE: sct->noStoreRemote(true); break; case SC_MAX_AGE: { int ma; if (p && httpHeaderParseInt(p, &ma)) { sct->maxAge(ma); if ((p = strchr (p, '+'))) { int ms; ++p; //skip the + char if (httpHeaderParseInt(p, &ms)) { sct->maxStale(ms); } else { debugs(90, 2, "sc: invalid max-stale specs near '" << item << "'"); sct->clearMaxStale(); /* leave the max-age alone */ } } } else { debugs(90, 2, "sc: invalid max-age specs near '" << item << "'"); sct->clearMaxAge(); } break; } case SC_CONTENT: if ( p && httpHeaderParseQuotedString(p, vlen, &sct->content_)) { sct->setMask(SC_CONTENT,true); // ugly but saves a copy } else { debugs(90, 2, "sc: invalid content= quoted string near '" << item << "'"); sct->clearContent(); } break; case SC_OTHER: default: break; } } return sc->targets.head != NULL; } HttpHdrSc::~HttpHdrSc() { if (targets.head) { dlink_node *sct = targets.head; while (sct) { HttpHdrScTarget *t = static_cast(sct->data); sct = sct->next; dlinkDelete (&t->node, &targets); delete t; } } } HttpHdrSc::HttpHdrSc(const HttpHdrSc &sc) { dlink_node *node = sc.targets.head; while (node) { HttpHdrScTarget *dupsct = new HttpHdrScTarget(*static_cast(node->data)); addTargetAtTail(dupsct); node = node->next; } } void HttpHdrScTarget::packInto(Packable * p) const { http_hdr_sc_type flag; int pcount = 0; assert (p); for (flag = SC_NO_STORE; flag < SC_ENUM_END; ++flag) { if (isSet(flag) && flag != SC_OTHER) { /* print option name */ p->appendf((pcount ? ", %s" : "%s"), ScAttrs[flag].name); /* handle options with values */ if (flag == SC_MAX_AGE) p->appendf("=%d", (int) max_age); if (flag == SC_CONTENT) p->appendf("=\"" SQUIDSTRINGPH "\"", SQUIDSTRINGPRINT(content_)); ++pcount; } } if (hasTarget()) p->appendf(";" SQUIDSTRINGPH, SQUIDSTRINGPRINT(target)); } void HttpHdrSc::packInto(Packable * p) const { dlink_node *node; assert(p); node = targets.head; while (node) { static_cast(node->data)->packInto(p); node = node->next; } } /* negative max_age will clean old max_Age setting */ void HttpHdrSc::setMaxAge(char const *target, int max_age) { HttpHdrScTarget *sct = findTarget(target); if (!sct) { sct = new HttpHdrScTarget(target); dlinkAddTail (sct, &sct->node, &targets); } sct->maxAge(max_age); } void HttpHdrSc::updateStats(StatHist * hist) const { dlink_node *sct = targets.head; while (sct) { static_cast(sct->data)->updateStats(hist); sct = sct->next; } } void httpHdrScTargetStatDumper(StoreEntry * sentry, int, double val, double, int count) { extern const HttpHeaderStat *dump_stat; /* argh! */ const int id = (int) val; const bool valid_id = id >= 0 && id < SC_ENUM_END; const char *name = valid_id ? ScAttrs[id].name : "INVALID"; if (count || valid_id) storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", id, name, count, xdiv(count, dump_stat->scParsedCount)); } void httpHdrScStatDumper(StoreEntry * sentry, int, double val, double, int count) { extern const HttpHeaderStat *dump_stat; /* argh! */ const int id = (int) val; const bool valid_id = id >= 0 && id < SC_ENUM_END; const char *name = valid_id ? ScAttrs[id].name : "INVALID"; if (count || valid_id) storeAppendPrintf(sentry, "%2d\t %-20s\t %5d\t %6.2f\n", id, name, count, xdiv(count, dump_stat->scParsedCount)); } HttpHdrScTarget * HttpHdrSc::findTarget(const char *target) { dlink_node *node; node = targets.head; while (node) { HttpHdrScTarget *sct = (HttpHdrScTarget *)node->data; if (target && sct->target.size() > 0 && !strcmp(target, sct->target.termedBuf())) return sct; else if (!target && sct->target.size() == 0) return sct; node = node->next; } return NULL; } HttpHdrScTarget * HttpHdrSc::getMergedTarget(const char *ourtarget) { HttpHdrScTarget *sctus = findTarget(ourtarget); HttpHdrScTarget *sctgeneric = findTarget(NULL); /* W3C Edge Architecture Specification 1.0 section 3 * * "If more than one is targeted at a surrogate, the most specific applies. * For example, * Surrogate-Control: max-age=60, no-store;abc * The surrogate that identified itself as 'abc' would apply no-store; * others would apply max-age=60. * * XXX: the if statements below will *merge* the no-store and max-age settings. */ if (sctgeneric || sctus) { HttpHdrScTarget *sctusable = new HttpHdrScTarget(NULL); if (sctgeneric) sctusable->mergeWith(sctgeneric); if (sctus) sctusable->mergeWith(sctus); return sctusable; } return NULL; } void HttpHdrSc::addTarget(HttpHdrScTarget *t) { dlinkAdd(t, &t->node, &targets); } void HttpHdrSc::addTargetAtTail(HttpHdrScTarget *t) { dlinkAddTail (t, &t->node, &targets); }