------------------------------------------------------------
revno: 12597
revision-id: squid3@treenet.co.nz-20130726122120-tq8lgc5au5q5tq41
parent: squid3@treenet.co.nz-20130726114748-i7l2deukqpkeyp9d
fixes bug(s): http://bugs.squid-cache.org/show_bug.cgi?id=2112
author: Christos Tsantilas <chtsanti@users.sourceforge.net>
committer: Amos Jeffries <squid3@treenet.co.nz>
branch nick: 3.3
timestamp: Fri 2013-07-26 06:21:20 -0600
message:
  Bug 2112: Reload into If-None-Match
  
  This patch sends an If-None-Match  request, when we need to re-validate
  if a cached object which has a strong ETag is still valid.
  
  This is also done in the cases an HTTP client request contains HTTP
  headers prohibiting a from-cache response (i.e., a "reload" request).
  The use of If-None-Match request in this context violates RFC 2616 and
  requires using reload-into-ims option within refresh_pattern squid.conf
  directive.
  
  The exact definition of a "reload request" and the adjustment/removal of
  "reload" headers is the same as currently used for reload-into-ims
  option support. This patch is not modifying that code/logic, just adding
  an If-None-Match header in addition to the IMS header that Squid already
  adds.
  
  This is a Measurement Factory Project
------------------------------------------------------------
# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: squid3@treenet.co.nz-20130726122120-tq8lgc5au5q5tq41
# target_branch: http://bzr.squid-cache.org/bzr/squid3/3.3
# testament_sha1: f52d451d51b7cd045fd6a8d37983d9d2024d5d7c
# timestamp: 2013-07-26 12:35:39 +0000
# source_branch: http://bzr.squid-cache.org/bzr/squid3/3.3
# base_revision_id: squid3@treenet.co.nz-20130726114748-\
#   i7l2deukqpkeyp9d
# 
# Begin patch
=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc	2013-07-26 11:47:48 +0000
+++ src/HttpRequest.cc	2013-07-26 12:21:20 +0000
@@ -172,6 +172,8 @@
 
     extacl_message.clean();
 
+    etag.clean();
+
 #if USE_ADAPTATION
     adaptHistory_ = NULL;
 #endif
@@ -217,6 +219,7 @@
     // XXX: what to do with copy->peer_login?
 
     copy->lastmod = lastmod;
+    copy->etag = etag;
     copy->vary_headers = vary_headers ? xstrdup(vary_headers) : NULL;
     // XXX: what to do with copy->peer_domain?
 

=== modified file 'src/HttpRequest.h'
--- src/HttpRequest.h	2012-10-04 09:14:06 +0000
+++ src/HttpRequest.h	2013-07-26 12:21:20 +0000
@@ -211,6 +211,9 @@
     String x_forwarded_for_iterator; /* XXX a list of IP addresses */
 #endif /* FOLLOW_X_FORWARDED_FOR */
 
+    /// A strong etag of the cached entry. Used for refreshing that entry.
+    String etag;
+
 public:
     bool multipartRangeRequest() const;
 

=== modified file 'src/Store.h'
--- src/Store.h	2012-10-16 23:43:54 +0000
+++ src/Store.h	2013-07-26 12:21:20 +0000
@@ -140,6 +140,8 @@
     bool hasIfMatchEtag(const HttpRequest &request) const;
     /// has ETag matching at least one of the If-None-Match etags
     bool hasIfNoneMatchEtag(const HttpRequest &request) const;
+    /// whether this entry has an ETag; if yes, puts ETag value into parameter
+    bool hasEtag(ETag &etag) const;
 
     /** What store does this entry belong too ? */
     virtual RefCount<SwapDir> store() const;

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2013-06-11 13:47:06 +0000
+++ src/cf.data.pre	2013-07-26 12:21:20 +0000
@@ -4466,10 +4466,12 @@
 		override-lastmod enforces min age even on objects
 		that were modified recently.
 
-		reload-into-ims changes client no-cache or ``reload''
-		to If-Modified-Since requests. Doing this VIOLATES the
-		HTTP standard. Enabling this feature could make you
-		liable for problems which it causes.
+		reload-into-ims changes a client no-cache or ``reload''
+		request for a cached entry into a conditional request using
+		If-Modified-Since and/or If-None-Match headers, provided the
+		cached entry has a Last-Modified and/or a strong ETag header.
+		Doing this VIOLATES the HTTP standard. Enabling this feature
+		could make you liable for problems which it causes.
 
 		ignore-reload ignores a client no-cache or ``reload''
 		header. Doing this VIOLATES the HTTP standard. Enabling

=== modified file 'src/client_side_reply.cc'
--- src/client_side_reply.cc	2013-01-28 04:11:25 +0000
+++ src/client_side_reply.cc	2013-07-26 12:21:20 +0000
@@ -38,6 +38,7 @@
 #include "clientStream.h"
 #include "dlink.h"
 #include "errorpage.h"
+#include "ETag.h"
 #include "fd.h"
 #include "fde.h"
 #include "format/Token.h"
@@ -291,6 +292,13 @@
 #endif
 
     http->request->lastmod = old_entry->lastmod;
+
+    if (!http->request->header.has(HDR_IF_NONE_MATCH)) {
+        ETag etag = {NULL, -1}; // TODO: make that a default ETag constructor
+        if (old_entry->hasEtag(etag) && !etag.weak)
+            http->request->etag = etag.str;
+    }
+
     debugs(88, 5, "clientReplyContext::processExpired : lastmod " << entry->lastmod );
     http->storeEntry(entry);
     assert(http->out.offset == 0);

=== modified file 'src/http.cc'
--- src/http.cc	2013-03-29 05:55:45 +0000
+++ src/http.cc	2013-07-26 12:21:20 +0000
@@ -1715,10 +1715,17 @@
     HttpHeaderPos pos = HttpHeaderInitPos;
     assert (hdr_out->owner == hoRequest);
 
-    /* append our IMS header */
+    /* use our IMS header if the cached entry has Last-Modified time */
     if (request->lastmod > -1)
         hdr_out->putTime(HDR_IF_MODIFIED_SINCE, request->lastmod);
 
+    // Add our own If-None-Match field if the cached entry has a strong ETag.
+    // copyOneHeaderFromClientsideRequestToUpstreamRequest() adds client ones.
+    if (request->etag.defined()) {
+        hdr_out->addEntry(new HttpHeaderEntry(HDR_IF_NONE_MATCH, NULL,
+                                              request->etag.termedBuf()));
+    }
+
     bool we_do_ranges = decideIfWeDoRanges (request);
 
     String strConnection (hdr_in->getList(HDR_CONNECTION));

=== modified file 'src/store.cc'
--- src/store.cc	2013-02-14 07:34:42 +0000
+++ src/store.cc	2013-07-26 12:21:20 +0000
@@ -1952,6 +1952,17 @@
 }
 
 bool
+StoreEntry::hasEtag(ETag &etag) const
+{
+    if (const HttpReply *reply = getReply()) {
+        etag = reply->header.getETag(HDR_ETAG);
+        if (etag.str)
+            return true;
+    }
+    return false;
+}
+
+bool
 StoreEntry::hasIfMatchEtag(const HttpRequest &request) const
 {
     const String reqETags = request.header.getList(HDR_IF_MATCH);

