]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/refresh.cc
C++11: Remove GnuRegex and all -lregex related code
[thirdparty/squid.git] / src / refresh.cc
index 4a4f08e921c7bf31642badfd308c985497d8b2d7..5342567d7e02db694030d1dc57137fa955d434b4 100644 (file)
@@ -1,39 +1,12 @@
-
 /*
- * DEBUG: section 22    Refresh Calculation
- * AUTHOR: Harvest Derived
- *
- * SQUID Web Proxy Cache          http://www.squid-cache.org/
- * ----------------------------------------------------------
- *
- *  Squid is the result of efforts by numerous individuals from
- *  the Internet community; see the CONTRIBUTORS file for full
- *  details.   Many organizations have provided support for Squid's
- *  development; see the SPONSORS file for full details.  Squid is
- *  Copyrighted (C) 2001 by the Regents of the University of
- *  California; see the COPYRIGHT file for full details.  Squid
- *  incorporates software developed and/or copyrighted by other
- *  sources; see the CREDITS file for full details.
- *
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2 of the License, or
- *  (at your option) any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software
- *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ * Copyright (C) 1996-2016 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.
  */
 
-#ifndef USE_POSIX_REGEX
-#define USE_POSIX_REGEX                /* put before includes; always use POSIX */
-#endif
+/* DEBUG: section 22    Refresh Calculation */
 
 #include "squid.h"
 #include "HttpHdrCc.h"
@@ -46,6 +19,7 @@
 #include "SquidTime.h"
 #include "Store.h"
 #include "URL.h"
+#include "util.h"
 
 typedef enum {
     rcHTTP,
@@ -60,11 +34,14 @@ typedef enum {
     rcCount
 } refreshCountsEnum;
 
+/**
+ * Flags indicating which staleness algorithm has been applied.
+ */
 typedef struct {
-    bool expires;
-    bool min;
-    bool lmfactor;
-    bool max;
+    bool expires;  ///< Expires: header absolute timestamp limit
+    bool min;      ///< Heuristic minimum age limited
+    bool lmfactor; ///< Last-Modified with heuristic determines limit
+    bool max;      ///< Configured maximum age limit
 } stale_flags;
 
 /*
@@ -96,73 +73,77 @@ static struct RefreshCounts {
     const char *proto;
     int total;
     int status[STALE_DEFAULT + 1];
-}
-
-refreshCounts[rcCount];
-
-/*
- * Defaults:
- *      MIN     NONE
- *      PCT     20%
- *      MAX     3 days
- */
-#define REFRESH_DEFAULT_MIN    (time_t)0
-#define REFRESH_DEFAULT_PCT    0.20
-#define REFRESH_DEFAULT_MAX    (time_t)259200
+} refreshCounts[rcCount];
 
 static const RefreshPattern *refreshUncompiledPattern(const char *);
 static OBJH refreshStats;
 static int refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf);
 
-static RefreshPattern DefaultRefresh;
+static RefreshPattern DefaultRefresh(".",std::regex_constants::syntax_option_type());
 
+/** Locate the first refresh_pattern rule that matches the given URL by regex.
+ *
+ * \return A pointer to the refresh_pattern parameters to use, or nullptr if there is no match.
+ */
 const RefreshPattern *
 refreshLimits(const char *url)
 {
-    const RefreshPattern *R;
-
-    for (R = Config.Refresh; R; R = R->next) {
-        if (!regexec(&(R->compiled_pattern), url, 0, 0, 0))
+    for (auto R = Config.Refresh; R; R = R->next) {
+        ++(R->stats.matchTests);
+        if (R->pattern.match(url)) {
+            ++(R->stats.matchCount);
             return R;
+        }
     }
 
-    return NULL;
+    return nullptr;
 }
 
+/** Locate the first refresh_pattern rule that has the given uncompiled regex.
+ *
+ * \note There is only one reference to this function, below. It always passes "." as the pattern.
+ * This function is only ever called if there is no URI. Because a regex match is impossible, Squid
+ * forces the "." rule to apply (if it exists)
+ *
+ * \return A pointer to the refresh_pattern parameters to use, or nullptr if there is no match.
+ */
 static const RefreshPattern *
 refreshUncompiledPattern(const char *pat)
 {
-    const RefreshPattern *R;
-
-    for (R = Config.Refresh; R; R = R->next) {
-        if (0 == strcmp(R->pattern, pat))
+    for (auto R = Config.Refresh; R; R = R->next) {
+        if (0 == strcmp(R->pattern.c_str(), pat))
             return R;
     }
 
-    return NULL;
+    return nullptr;
 }
 
 /**
  * Calculate how stale the response is (or will be at the check_time).
- * Staleness calculation is based on the following: (1) response
- * expiration time, (2) age greater than configured maximum, (3)
- * last-modified factor, and (4) age less than configured minimum.
+ *
+ * We try the following ways until one gives a result:
+ *
+ * 1. response expiration time, if one was set
+ * 2. age greater than configured maximum
+ * 3. last-modified factor algorithm
+ * 4. age less than configured minimum
+ * 5. default (stale)
+ *
+ * \param entry       the StoreEntry being examined
+ * \param check_time  the time (maybe future) at which we want to know whether $
+ * \param age         the age of the entry at check_time
+ * \param R           the refresh_pattern rule that matched this entry
+ * \param sf          small struct to indicate reason for stale/fresh decision
  *
  * \retval -1  If the response is fresh.
- * \retval >0  Otherwise return it's staleness.
+ * \retval >0  The amount of staleness.
  * \retval 0   NOTE return value of 0 means the response is stale.
- *
- * The 'stale_flags' structure is used to tell the calling function
- * _why_ this response is fresh or stale.  Its used, for example,
- * when the admin wants to override expiration and last-modified
- * times.
  */
 static int
 refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age, const RefreshPattern * R, stale_flags * sf)
 {
-    /** \par
-     * Check for an explicit expiration time (Expires: header).
-     */
+    // 1. If the cached object has an explicit expiration time, then we rely on this and
+    //    completely ignore the Min, Percent and Max values in the refresh_pattern.
     if (entry->expires > -1) {
         sf->expires = true;
 
@@ -179,25 +160,26 @@ refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age,
         }
     }
 
-    /** \par
-     * Use local heuristics to determine staleness.  Start with the
-     * max age from the refresh_pattern rule.
-     */
+    debugs(22, 3, "No explicit expiry given, using heuristics to determine freshness");
+
+    // 2. If the entry is older than the maximum age in the refresh_pattern, it is STALE.
     if (age > R->max) {
         debugs(22, 3, "STALE: age " << age << " > max " << R->max << " ");
         sf->max = true;
         return (age - R->max);
     }
 
-    /** \par
-     * Try the last-modified factor algorithm:  refresh_pattern n% percentage of Last-Modified: age.
-     */
-    if (entry->lastmod > -1 && entry->timestamp > entry->lastmod) {
-        /*
-         * stale_age is the Age of the response when it became/becomes
-         * stale according to the last-modified factor algorithm.
+    // 3. If there is a Last-Modified header, try the last-modified factor algorithm.
+    const time_t lastmod_delta = entry->timestamp - entry->lastModified();
+    if (lastmod_delta > 0) {
+        /* stale_age is the age of the response when it became/becomes stale according to
+         * the last-modified factor algorithm. It's how long we can consider the response
+         * fresh from the time we cached it.
          */
-        time_t stale_age = static_cast<time_t>((entry->timestamp - entry->lastmod) * R->pct);
+        time_t stale_age = static_cast<time_t>(lastmod_delta * R->pct);
+
+        debugs(22,3, "Last modified " << lastmod_delta << " sec before we cached it, L-M factor " <<
+               (100.0 * R->pct) << "% = " << stale_age << " sec freshness lifetime");
         sf->lmfactor = true;
 
         if (age >= stale_age) {
@@ -209,55 +191,109 @@ refreshStaleness(const StoreEntry * entry, time_t check_time, const time_t age,
         }
     }
 
-    /** \par
-     * Finally, if all else fails;  staleness is determined by the refresh_pattern
-     * configured minimum age.
-     */
+    // 4. If the entry is not as old as the minimum age in the refresh_pattern, it is FRESH.
     if (age < R->min) {
-        debugs(22, 3, "FRESH: age " << age << " < min " << R->min);
+        debugs(22, 3, "FRESH: age (" << age << " sec) is less than configured minimum (" << R->min << " sec)");
         sf->min = true;
         return -1;
     }
 
-    debugs(22, 3, "STALE: age " << age << " >= min " << R->min);
+    // 5. default is stale, by the amount we missed the minimum by
+    debugs(22, 3, "STALE: No explicit expiry, no last modified, and older than configured minimum.");
     return (age - R->min);
 }
 
-/**
- * \retval 1 if the entry must be revalidated within delta seconds
- * \retval 0 otherwise
+/** Checks whether a store entry is fresh or stale, and why.
+ *
+ * This is where all aspects of request, response and squid configuration
+ * meet to decide whether a response is cacheable or not:
  *
- *  note: request maybe null (e.g. for cache digests build)
+ * 1. Client request headers that affect cacheability, e.g.
+ *  - Cache-Control: no-cache
+ *  - Cache-Control: max-age=N
+ *  - Cache-Control: max-stale[=N]
+ *  - Pragma: no-cache
+ *
+ * 2. Server response headers that affect cacheability, e.g.
+ *  - Age:
+ *  - Cache-Control: proxy-revalidate
+ *  - Cache-Control: must-revalidate
+ *  - Cache-Control: no-cache
+ *  - Cache-Control: max-age=N
+ *  - Cache-Control: s-maxage=N
+ *  - Date:
+ *  - Expires:
+ *  - Last-Modified:
+ *
+ * 3. Configuration options, e.g.
+ *  - reload-into-ims (refresh_pattern)
+ *  - ignore-reload (refresh_pattern)
+ *  - refresh-ims (refresh_pattern)
+ *  - override-lastmod (refresh_pattern)
+ *  - override-expire (refresh_pattern)
+ *  - reload_into_ims (global option)
+ *  - refresh_all_ims (global option)
+ *
+ * \returns a status code (from enum above):
+ *  - FRESH_REQUEST_MAX_STALE_ALL
+ *  - FRESH_REQUEST_MAX_STALE_VALUE
+ *  - FRESH_EXPIRES
+ *  - FRESH_LMFACTOR_RULE
+ *  - FRESH_MIN_RULE
+ *  - FRESH_OVERRIDE_EXPIRES
+ *  - FRESH_OVERRIDE_LASTMOD
+ *  - STALE_MUST_REVALIDATE
+ *  - STALE_RELOAD_INTO_IMS
+ *  - STALE_FORCED_RELOAD
+ *  - STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE
+ *  - STALE_EXPIRES
+ *  - STALE_MAX_RULE
+ *  - STALE_LMFACTOR_RULE
+ *  - STALE_MAX_STALE
+ *  - STALE_DEFAULT
+ *
+ * \note request may be NULL (e.g. for cache digests build)
+ *
+ * \note the store entry being examined is not necessarily cached (e.g. if
+ *       this response is being evaluated for the first time)
  */
 static int
 refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
 {
-    const RefreshPattern *R;
-    const char *uri = NULL;
     time_t age = 0;
     time_t check_time = squid_curtime + delta;
     int staleness;
     stale_flags sf;
 
+    // get the URL of this entry, if there is one
+    static const SBuf nilUri("<none>");
+    SBuf uri = nilUri;
     if (entry->mem_obj)
-        uri = entry->mem_obj->url;
+        uri = entry->mem_obj->storeId();
     else if (request)
-        uri = urlCanonical(request);
+        uri = request->effectiveRequestUri();
 
-    debugs(22, 3, "refreshCheck: '" << (uri ? uri : "<none>") << "'");
+    debugs(22, 3, "checking freshness of URI: " << uri);
 
+    // age is not necessarily the age now, but the age at the given check_time
     if (check_time > entry->timestamp)
         age = check_time - entry->timestamp;
 
     // FIXME: what to do when age < 0 or counter overflow?
     assert(age >= 0);
 
-    R = uri ? refreshLimits(uri) : refreshUncompiledPattern(".");
-
+    /* We need a refresh rule. In order of preference:
+     *
+     *   1. the rule that matches this URI by regex
+     *   2. the "." rule from the config file
+     *   3. the default "." rule
+     */
+    // XXX: performance regression. c_str() reallocates
+    const RefreshPattern *R = (uri != nilUri) ? refreshLimits(uri.c_str()) : refreshUncompiledPattern(".");
     if (NULL == R)
         R = &DefaultRefresh;
 
-    debugs(22, 3, "refreshCheck: Matched '" << R->pattern << " " <<
+    debugs(22, 3, "Matched '" << R->pattern.c_str() << " " <<
            (int) R->min << " " << (int) (100.0 * R->pct) << "%% " <<
            (int) R->max << "'");
 
@@ -287,21 +323,28 @@ refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
 
     debugs(22, 3, "Staleness = " << staleness);
 
+    const auto *reply = (entry->mem_obj && entry->mem_obj->getReply() ? entry->mem_obj->getReply() : nullptr);
+
     // stale-if-error requires any failure be passed thru when its period is over.
-    if (request && entry->mem_obj && entry->mem_obj->getReply() && entry->mem_obj->getReply()->cache_control &&
-            entry->mem_obj->getReply()->cache_control->hasStaleIfError() &&
-            entry->mem_obj->getReply()->cache_control->staleIfError() < staleness) {
+    if (request && reply && reply->cache_control &&
+            reply->cache_control->hasStaleIfError() &&
+            reply->cache_control->staleIfError() < staleness) {
 
-        debugs(22, 3, "refreshCheck: stale-if-error period expired.");
+        debugs(22, 3, "stale-if-error period expired. Will produce error if validation fails.");
         request->flags.failOnValidationError = true;
     }
 
-    if (EBIT_TEST(entry->flags, ENTRY_REVALIDATE) && staleness > -1
-#if USE_HTTP_VIOLATIONS
-            && !R->flags.ignore_must_revalidate
-#endif
-       ) {
-        debugs(22, 3, "refreshCheck: YES: Must revalidate stale response");
+    /* If the origin server specified either of:
+     *   Cache-Control: must-revalidate
+     *   Cache-Control: proxy-revalidate
+     * the spec says the response must always be revalidated if stale.
+     */
+    const bool revalidateAlways = EBIT_TEST(entry->flags, ENTRY_REVALIDATE_ALWAYS);
+    if (revalidateAlways || (staleness > -1 &&
+                             EBIT_TEST(entry->flags, ENTRY_REVALIDATE_STALE))) {
+        debugs(22, 3, "YES: Must revalidate stale object (origin set " <<
+               (revalidateAlways ? "no-cache or private" :
+                "must-revalidate, proxy-revalidate or s-maxage") << ")");
         if (request)
             request->flags.failOnValidationError = true;
         return STALE_MUST_REVALIDATE;
@@ -311,81 +354,114 @@ refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
     if (request && !request->flags.ignoreCc) {
         HttpHdrCc *cc = request->cache_control;
 
+        /* If the request is an IMS request, and squid is configured NOT to service this from cache
+         * (either by 'refresh-ims' in the refresh pattern or 'refresh_all_ims on' globally)
+         * then force a reload from the origin.
+         */
         if (request->flags.ims && (R->flags.refresh_ims || Config.onoff.refresh_all_ims)) {
-            /* The clients no-cache header is changed into a IMS query */
-            debugs(22, 3, "refreshCheck: YES: refresh-ims");
+            // The client's no-cache header is changed into a IMS query
+            debugs(22, 3, "YES: Client IMS request forcing revalidation of object (refresh-ims option)");
             return STALE_FORCED_RELOAD;
         }
 
 #if USE_HTTP_VIOLATIONS
-
-        if (!request->flags.noCacheHack()) {
-            (void) 0;
-        } else if (R->flags.ignore_reload) {
-            /* The clients no-cache header is ignored */
-            debugs(22, 3, "refreshCheck: MAYBE: ignore-reload");
-        } else if (R->flags.reload_into_ims || Config.onoff.reload_into_ims) {
-            /* The clients no-cache header is changed into a IMS query */
-            debugs(22, 3, "refreshCheck: YES: reload-into-ims");
-            return STALE_RELOAD_INTO_IMS;
-        } else {
-            /* The clients no-cache header is not overridden on this request */
-            debugs(22, 3, "refreshCheck: YES: client reload");
-            request->flags.noCache = true;
-            return STALE_FORCED_RELOAD;
+        /* Normally a client reload request ("Cache-Control: no-cache" or "Pragma: no-cache")
+         * means we must treat this reponse as STALE and fetch a new one.
+         *
+         * However, some options exist to override this behaviour. For example, we might just
+         * revalidate our existing response, or even just serve it up without revalidating it.
+         *
+         *     ---- Note on the meaning of nocache_hack -----
+         *
+         * The nocache_hack flag has a very specific and complex meaning:
+         *
+         * (a) this is a reload request ("Cache-Control: no-cache" or "Pragma: no-cache" header)
+         * and (b) the configuration file either has at least one refresh_pattern with
+         * ignore-reload or reload-into-ims (not necessarily the rule matching this request) or
+         * the global reload_into_ims is set to on
+         *
+         * In other words: this is a client reload, and we might need to override
+         * the default behaviour (but we might not).
+         *
+         * "nocache_hack" is a pretty deceptive name for such a complicated meaning.
+         */
+        if (request->flags.noCacheHack()) {
+
+            if (R->flags.ignore_reload) {
+                /* The client's no-cache header is ignored completely - we'll try to serve
+                 * what we have (assuming it's still fresh, etc.)
+                 */
+                debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)");
+            } else if (R->flags.reload_into_ims || Config.onoff.reload_into_ims) {
+                /* The client's no-cache header is not honoured completely - we'll just try
+                 * to revalidate our cached copy (IMS to origin) instead of fetching a new
+                 * copy with an unconditional GET.
+                 */
+                debugs(22, 3, "YES: Client reload request - cheating, only revalidating with origin (reload-into-ims option)");
+                return STALE_RELOAD_INTO_IMS;
+            } else {
+                /* The client's no-cache header is honoured - we fetch a new copy from origin */
+                debugs(22, 3, "YES: Client reload request - fetching new copy from origin");
+                request->flags.noCache = true;
+                return STALE_FORCED_RELOAD;
+            }
         }
-
 #endif
+
+        // Check the Cache-Control client request header
         if (NULL != cc) {
+
+            // max-age directive
             if (cc->hasMaxAge()) {
+
+                // draft-mcmanus-immutable-00: reply contains CC:immutable then ignore client CC:max-age=N
+                if (reply && reply->cache_control && reply->cache_control->Immutable()) {
+                    debugs(22, 3, "MAYBE: Ignoring client CC:max-age=" << cc->maxAge() << " request - 'Cache-Control: immutable'");
+
 #if USE_HTTP_VIOLATIONS
-                if (R->flags.ignore_reload && cc->maxAge() == 0) {
-                    debugs(22, 3, "refreshCheck: MAYBE: client-max-age = 0 and ignore-reload");
-                } else
+                    // Ignore of client "Cache-Control: max-age=0" header
+                } else if (R->flags.ignore_reload && cc->maxAge() == 0) {
+                    debugs(22, 3, "MAYBE: Ignoring client reload request - trying to serve from cache (ignore-reload option)");
 #endif
-                {
-                    if (cc->maxAge() == 0) {
-                        debugs(22, 3, "refreshCheck: YES: client-max-age = 0");
-                        return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE;
-                    }
-
-                    if (age > cc->maxAge()) {
-                        debugs(22, 3, "refreshCheck: YES: age > client-max-age");
-                        return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE;
-                    }
+
+                    // Honour client "Cache-Control: max-age=x" header
+                } else if (age > cc->maxAge() || cc->maxAge() == 0) {
+                    debugs(22, 3, "YES: Revalidating object - client 'Cache-Control: max-age=" << cc->maxAge() << "'");
+                    return STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE;
                 }
             }
 
+            // max-stale directive
             if (cc->hasMaxStale() && staleness > -1) {
                 if (cc->maxStale()==HttpHdrCc::MAX_STALE_ANY) {
-                    /* max-stale directive without a value */
-                    debugs(22, 3, "refreshCheck: NO: max-stale wildcard");
+                    debugs(22, 3, "NO: Client accepts a stale response of any age - 'Cache-Control: max-stale'");
                     return FRESH_REQUEST_MAX_STALE_ALL;
                 } else if (staleness < cc->maxStale()) {
-                    debugs(22, 3, "refreshCheck: NO: staleness < max-stale");
+                    debugs(22, 3, "NO: Client accepts a stale response - 'Cache-Control: max-stale=" << cc->maxStale() << "'");
                     return FRESH_REQUEST_MAX_STALE_VALUE;
                 }
             }
         }
     }
 
+    // If the object is fresh, return the right FRESH_ code
     if (-1 == staleness) {
-        debugs(22, 3, "refreshCheck: object isn't stale..");
+        debugs(22, 3, "Object isn't stale..");
         if (sf.expires) {
-            debugs(22, 3, "refreshCheck: returning FRESH_EXPIRES");
+            debugs(22, 3, "returning FRESH_EXPIRES");
             return FRESH_EXPIRES;
         }
 
         assert(!sf.max);
 
         if (sf.lmfactor) {
-            debugs(22, 3, "refreshCheck: returning FRESH_LMFACTOR_RULE");
+            debugs(22, 3, "returning FRESH_LMFACTOR_RULE");
             return FRESH_LMFACTOR_RULE;
         }
 
         assert(sf.min);
 
-        debugs(22, 3, "refreshCheck: returning FRESH_MIN_RULE");
+        debugs(22, 3, "returning FRESH_MIN_RULE");
         return FRESH_MIN_RULE;
     }
 
@@ -396,7 +472,7 @@ refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
      */
     int max_stale = (R->max_stale >= 0 ? R->max_stale : Config.maxStale);
     if ( max_stale >= 0 && staleness > max_stale) {
-        debugs(22, 3, "refreshCheck: YES: max-stale limit");
+        debugs(22, 3, "YES: refresh_pattern max-stale=N limit from squid.conf");
         if (request)
             request->flags.failOnValidationError = true;
         return STALE_MAX_STALE;
@@ -406,7 +482,7 @@ refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
 #if USE_HTTP_VIOLATIONS
 
         if (R->flags.override_expire && age < R->min) {
-            debugs(22, 3, "refreshCheck: NO: age < min && override-expire");
+            debugs(22, 3, "NO: Serving from cache - even though explicit expiry has passed, we enforce Min value (override-expire option)");
             return FRESH_OVERRIDE_EXPIRES;
         }
 
@@ -419,21 +495,30 @@ refreshCheck(const StoreEntry * entry, HttpRequest * request, time_t delta)
 
     if (sf.lmfactor) {
 #if USE_HTTP_VIOLATIONS
-
         if (R->flags.override_lastmod && age < R->min) {
-            debugs(22, 3, "refreshCheck: NO: age < min && override-lastmod");
+            debugs(22, 3, "NO: Serving from cache - even though L-M factor says the object is stale, we enforce Min value (override-lastmod option)");
             return FRESH_OVERRIDE_LASTMOD;
         }
-
 #endif
+        debugs(22, 3, "YES: L-M factor says the object is stale'");
         return STALE_LMFACTOR_RULE;
     }
 
-    debugs(22, 3, "refreshCheck: returning STALE_DEFAULT");
+    debugs(22, 3, "returning STALE_DEFAULT");
     return STALE_DEFAULT;
 }
 
-int
+/**
+ * This is called by http.cc once it has received and parsed the origin server's
+ * response headers. It uses the result as part of its algorithm to decide whether a
+ * response should be cached.
+ *
+ * \retval true if the entry is cacheable, regardless of whether FRESH or STALE
+ * \retval false if the entry is not cacheable
+ *
+ * TODO: this algorithm seems a bit odd and might not be quite right. Verify against HTTPbis.
+ */
+bool
 refreshIsCachable(const StoreEntry * entry)
 {
     /*
@@ -449,26 +534,26 @@ refreshIsCachable(const StoreEntry * entry)
 
     if (reason < STALE_MUST_REVALIDATE)
         /* Does not need refresh. This is certainly cachable */
-        return 1;
+        return true;
 
-    if (entry->lastmod < 0)
-        /* Last modified is needed to do a refresh */
-        return 0;
+    if (entry->lastModified() < 0)
+        /* We should know entry's modification time to do a refresh */
+        return false;
 
     if (entry->mem_obj == NULL)
         /* no mem_obj? */
-        return 1;
+        return true;
 
     if (entry->getReply() == NULL)
         /* no reply? */
-        return 1;
+        return true;
 
     if (entry->getReply()->content_length == 0)
         /* No use refreshing (caching?) 0 byte objects */
-        return 0;
+        return false;
 
     /* This seems to be refreshable. Cache it */
-    return 1;
+    return true;
 }
 
 /// whether reply is stale if it is a hit
@@ -485,9 +570,14 @@ refreshIsStaleIfHit(const int reason)
     }
 }
 
-/* refreshCheck... functions below are protocol-specific wrappers around
- * refreshCheck() function above */
-
+/**
+ * Protocol-specific wrapper around refreshCheck() function.
+ *
+ * Note the reason for STALE/FRESH then return true/false respectively.
+ *
+ * \retval 1 if STALE
+ * \retval 0 if FRESH
+ */
 int
 refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
 {
@@ -498,6 +588,7 @@ refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
     return (Config.onoff.offline || reason < 200) ? 0 : 1;
 }
 
+/// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
 int
 refreshCheckICP(const StoreEntry * entry, HttpRequest * request)
 {
@@ -508,6 +599,7 @@ refreshCheckICP(const StoreEntry * entry, HttpRequest * request)
 }
 
 #if USE_HTCP
+/// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
 int
 refreshCheckHTCP(const StoreEntry * entry, HttpRequest * request)
 {
@@ -520,6 +612,7 @@ refreshCheckHTCP(const StoreEntry * entry, HttpRequest * request)
 #endif
 
 #if USE_CACHE_DIGESTS
+/// \see int refreshCheckHTTP(const StoreEntry * entry, HttpRequest * request)
 int
 refreshCheckDigest(const StoreEntry * entry, time_t delta)
 {
@@ -530,9 +623,18 @@ refreshCheckDigest(const StoreEntry * entry, time_t delta)
     ++ refreshCounts[rcCDigest].status[reason];
     return (reason < 200) ? 0 : 1;
 }
-
 #endif
 
+/**
+ * Get the configured maximum caching time for objects with this URL
+ * according to refresh_pattern.
+ *
+ * Used by http.cc when generating a upstream requests to ensure that
+ * responses it is given are fresh enough to be worth caching.
+ *
+ * \retval pattern-max if there is a refresh_pattern matching the URL configured.
+ * \retval REFRESH_DEFAULT_MAX if there are no explicit limits configured
+ */
 time_t
 getMaxAge(const char *url)
 {
@@ -545,63 +647,56 @@ getMaxAge(const char *url)
         return REFRESH_DEFAULT_MAX;
 }
 
-static void
+static int
+refreshCountsStatsEntry(StoreEntry * sentry, struct RefreshCounts &rc, int code, const char *desc)
+{
+    storeAppendPrintf(sentry, "%6d\t%6.2f\t%s\n", rc.status[code], xpercent(rc.status[code], rc.total), desc);
+    return rc.status[code];
+}
 
-refreshCountsStats(StoreEntry * sentry, struct RefreshCounts *rc)
+static void
+refreshCountsStats(StoreEntry * sentry, struct RefreshCounts &rc)
 {
-    int sum = 0;
-    int tot = rc->total;
+    if (!rc.total)
+        return;
 
-    storeAppendPrintf(sentry, "\n\n%s histogram:\n", rc->proto);
+    storeAppendPrintf(sentry, "\n\n%s histogram:\n", rc.proto);
     storeAppendPrintf(sentry, "Count\t%%Total\tCategory\n");
 
-#define refreshCountsStatsEntry(code,desc) { \
-       storeAppendPrintf(sentry, "%6d\t%6.2f\t%s\n", \
-           rc->status[code], xpercent(rc->status[code], tot), desc); \
-    sum += rc->status[code]; \
-}
-
-    refreshCountsStatsEntry(FRESH_REQUEST_MAX_STALE_ALL,
-                            "Fresh: request max-stale wildcard");
-    refreshCountsStatsEntry(FRESH_REQUEST_MAX_STALE_VALUE,
-                            "Fresh: request max-stale value");
-    refreshCountsStatsEntry(FRESH_EXPIRES,
-                            "Fresh: expires time not reached");
-    refreshCountsStatsEntry(FRESH_LMFACTOR_RULE,
-                            "Fresh: refresh_pattern last-mod factor percentage");
-    refreshCountsStatsEntry(FRESH_MIN_RULE,
-                            "Fresh: refresh_pattern min value");
-    refreshCountsStatsEntry(FRESH_OVERRIDE_EXPIRES,
-                            "Fresh: refresh_pattern override expires");
-    refreshCountsStatsEntry(FRESH_OVERRIDE_LASTMOD,
-                            "Fresh: refresh_pattern override lastmod");
-    refreshCountsStatsEntry(STALE_MUST_REVALIDATE,
-                            "Stale: response has must-revalidate");
-    refreshCountsStatsEntry(STALE_RELOAD_INTO_IMS,
-                            "Stale: changed reload into IMS");
-    refreshCountsStatsEntry(STALE_FORCED_RELOAD,
-                            "Stale: request has no-cache directive");
-    refreshCountsStatsEntry(STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE,
-                            "Stale: age exceeds request max-age value");
-    refreshCountsStatsEntry(STALE_EXPIRES,
-                            "Stale: expires time reached");
-    refreshCountsStatsEntry(STALE_MAX_RULE,
-                            "Stale: refresh_pattern max age rule");
-    refreshCountsStatsEntry(STALE_LMFACTOR_RULE,
-                            "Stale: refresh_pattern last-mod factor percentage");
-    refreshCountsStatsEntry(STALE_DEFAULT,
-                            "Stale: by default");
-
-    tot = sum;                 /* paranoid: "total" line shows 100% if we forgot nothing */
-    storeAppendPrintf(sentry, "%6d\t%6.2f\tTOTAL\n",
-                      rc->total, xpercent(rc->total, tot));
-    \
+    int sum = 0;
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_REQUEST_MAX_STALE_ALL, "Fresh: request max-stale wildcard");
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_REQUEST_MAX_STALE_VALUE, "Fresh: request max-stale value");
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_EXPIRES, "Fresh: expires time not reached");
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_LMFACTOR_RULE, "Fresh: refresh_pattern last-mod factor percentage");
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_MIN_RULE, "Fresh: refresh_pattern min value");
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_OVERRIDE_EXPIRES, "Fresh: refresh_pattern override-expires");
+    sum += refreshCountsStatsEntry(sentry, rc, FRESH_OVERRIDE_LASTMOD, "Fresh: refresh_pattern override-lastmod");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_MUST_REVALIDATE, "Stale: response has must-revalidate");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_RELOAD_INTO_IMS, "Stale: changed reload into IMS");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_FORCED_RELOAD, "Stale: request has no-cache directive");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_EXCEEDS_REQUEST_MAX_AGE_VALUE, "Stale: age exceeds request max-age value");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_EXPIRES, "Stale: expires time reached");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_MAX_RULE, "Stale: refresh_pattern max age rule");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_LMFACTOR_RULE, "Stale: refresh_pattern last-mod factor percentage");
+    sum += refreshCountsStatsEntry(sentry, rc, STALE_DEFAULT, "Stale: by default");
     storeAppendPrintf(sentry, "\n");
 }
 
 static void
 refreshStats(StoreEntry * sentry)
 {
+    // display per-rule counts of usage and tests
+    storeAppendPrintf(sentry, "\nRefresh pattern usage:\n\n");
+    storeAppendPrintf(sentry, "  Used      \tChecks    \t%% Matches\tPattern\n");
+    for (const RefreshPattern *R = Config.Refresh; R; R = R->next) {
+        storeAppendPrintf(sentry, "  %10" PRIu64 "\t%10" PRIu64 "\t%6.2f\t%s%s\n",
+                          R->stats.matchCount,
+                          R->stats.matchTests,
+                          xpercent(R->stats.matchCount, R->stats.matchTests),
+                          (R->pattern.flags & std::regex::icase ? "-i " : ""),
+                          R->pattern.c_str());
+    }
+
     int i;
     int total = 0;
 
@@ -625,7 +720,7 @@ refreshStats(StoreEntry * sentry)
     storeAppendPrintf(sentry, "\n\nRefreshCheck histograms for various protocols\n");
 
     for (i = 0; i < rcCount; ++i)
-        refreshCountsStats(sentry, &refreshCounts[i]);
+        refreshCountsStats(sentry, refreshCounts[i]);
 }
 
 static void
@@ -651,11 +746,6 @@ refreshInit(void)
     refreshCounts[rcCDigest].proto = "Cache Digests";
 #endif
 
-    memset(&DefaultRefresh, '\0', sizeof(DefaultRefresh));
-    DefaultRefresh.pattern = "<none>";
-    DefaultRefresh.min = REFRESH_DEFAULT_MIN;
-    DefaultRefresh.pct = REFRESH_DEFAULT_PCT;
-    DefaultRefresh.max = REFRESH_DEFAULT_MAX;
-
     refreshRegisterWithCacheManager();
 }
+