This patch is generated from the varyetag branch of HEAD in squid3
Wed Sep 29 00:44:44 2004 GMT
See http://devel.squid-cache.org/

Index: squid3/doc/debug-sections.txt
diff -u squid3/doc/debug-sections.txt:1.6 squid3/doc/debug-sections.txt:1.6.2.1
--- squid3/doc/debug-sections.txt:1.6	Tue Jul 22 19:12:50 2003
+++ squid3/doc/debug-sections.txt	Thu Jun  3 15:50:05 2004
@@ -96,3 +96,4 @@
 section 90    Store Client
 section 91    Http Surrogate-Control Header
 section 92    Store File System
+section 93    MMSquid Project
Index: squid3/src/CharHashTable.cc
diff -u /dev/null squid3/src/CharHashTable.cc:1.1.2.2
--- /dev/null	Tue Sep 28 17:49:22 2004
+++ squid3/src/CharHashTable.cc	Sun Jul  4 09:56:50 2004
@@ -0,0 +1,307 @@
+/*
+ * $Id: squid3-varyetag-HEAD,v 1.3 2004/09/29 00:56:20 hno Exp $
+ *
+ * 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.
+ */
+
+#include "squid.h"
+#include "CharHashTable.h"
+
+/* 
+ * Creates new CharHashTable.
+ * It's contents is packed in the argument.
+ * Format of packed: "L LK LV LK LV ...", 
+ * where L is the length of the whole packed string minus sizeof(int)
+ * (it is the length of "LK LV LK LV ..." part only), 
+ * LK is the length of the key of a pair and the key-string itself,
+ * LV is the length of the value of a pair and the value-string itself.
+ *
+ * Doesn't free packed.
+ */
+CharHashTable::CharHashTable(char *packed) : map(hash_create((HASHCMP *) strcmp,CHARHASHTABLE_DEFAULT_SIZE,hash4)) {
+  
+  debug(93, 9) ("CharHashTable::CharHashTable(%p): constructor\n", packed);
+  
+  // if there are no packed pairs to add to fresh and empty contents
+  if (!packed) 
+    return;
+
+  // counter to traverse packed string
+  int pos = 0;
+  
+  // length of packed string - first sizeof(int) bytes of packed string
+  int length;
+  
+  // temporal length for keys and values
+  int len;
+
+  // temporal...
+  char *key, *element;
+    
+  // get packed string length
+  xmemcpy(&length, &packed[pos], sizeof(int));
+
+  // int was read
+  pos += sizeof(int);
+
+  // read (key, element) as long as the whole packed is read
+  while (pos < length + 4) {
+    
+    // the length of the key
+    xmemcpy(&len, &packed[pos], sizeof(int));
+    pos += sizeof(int);
+    
+    // after the key length should be the key itself or the element LV
+    assert(pos < length + 4);
+    
+    // key itself
+    key = (char*)xmalloc(len+1);
+    xmemcpy(key, &packed[pos], len);
+    key[len] = 0;
+    pos += len;
+
+    // after the key should be the element LV
+    assert(pos < length + 4);
+  
+    // the length of the value
+    xmemcpy(&len, &packed[pos], sizeof(int));
+    pos += sizeof(int);
+    
+    // if value is non empty there should be the element now
+    assert(pos < length + 4 || len == 0);
+
+    // element itself    
+    element = (char*)xmalloc(len+1);
+    xmemcpy(element, &packed[pos], len);
+    element[len] = 0;
+    pos += len;
+
+    // save the pair (copies the strings)
+    put(key, element);
+
+    // freedom for all
+    safe_free(key);
+    safe_free(element);
+  }
+}
+
+/*
+ * Frees table pair... to be used with hashFreeItems.
+ */
+void 
+CharHashTable::elementFree(void *e) {
+  CharHashTableElement *chtel = (CharHashTableElement*)e;
+  assert(chtel);
+
+  debug(93, 9) ("CharHashTable::elementFree(): key: %s, element: %s\n",
+                (char*)chtel->key, (char*)chtel->element);
+  
+  assert(chtel->key);
+  safe_free(chtel->key); 
+  delete chtel;
+}
+
+/*
+ * Frees the whole table.
+ */
+CharHashTable::~CharHashTable() {
+  
+  debug(93, 9) ("~CharHashTable: size=%d\n", map->count);
+  assert(map);
+  
+  hashFreeItems(map, elementFree);
+  hashFreeMemory(map);
+  
+}
+
+/*
+ * Retrieves element identified by the given key or NULL.
+ * Doesn't copy the returned element.
+ */ 
+char *
+CharHashTable::get(char *key) {
+  CharHashTableElement *hte = static_cast<CharHashTableElement *>(hash_lookup(map, key));
+  
+  if (hte) {
+    debug(93, 9) ("CharHashTable::get key=[%s] element=[%s]\n", key, hte->element);
+    return hte->element;
+  }
+  debug(93, 9) ("CharHashTable::get key=[%s] element=[NULL]\n", key);
+  return NULL;
+}
+
+/*
+ * Retrieves concatenation of the keys.
+ */
+char *
+CharHashTable::keys() {
+  CharHashTableElement *hte;
+  hash_first(map);
+  int size = 0;
+  int pos = 0;
+
+  while ((hte = (CharHashTableElement*)hash_next(map))) {
+    size += (strlen((char*)hte->key)+1); // 1 for ,
+    debug(93, 9) ("CharHashTable::keys: key=[%s]\n", 
+                  (char*)hte->key);
+  }
+  if (!size) {
+    debug(93, 9) ("CharHashTable::keys: result is empty!\n");
+    return NULL;
+  }
+  char* result = (char*)xmalloc(size); 
+  
+  hash_first(map);
+
+  while ((hte = (CharHashTableElement*)hash_next(map))) {
+    xmemcpy(result+pos,(char*)hte->key, strlen((char*)hte->key));
+    pos += (strlen((char*)hte->key)); // 1 for ,
+    result[pos++]= ',';
+    debug(93, 9) ("CharHashTable::keys: key=[%s]\n", (char*)hte->key);
+  }
+  result[size-1] = 0;
+  debug(93, 9) ("CharHashTable::keys: result=[%s]\n", result);
+  return result;
+}
+
+/*
+ * Puts the given pair into table.
+ * Copies both key and element.
+ */
+void 
+CharHashTable::put(char *key, char *element) {
+  
+  debug(93, 9) ("CharHashTable::put key=[%s] element=[%s]\n", key, element);
+
+  // new element
+  CharHashTableElement *hte = (CharHashTableElement*)xmalloc(sizeof(CharHashTableElement));
+  hte->key = xstrdup(key);
+  hte->element = xstrdup(element);
+
+  hash_join(map, hte);
+}
+
+/*
+ * Removes pair identified by the given key.
+ * Doesn't free the given key.
+ *
+ * XXX does it free the stored key?
+ */
+void
+CharHashTable::remove(char *key) {
+  CharHashTableElement *hte = static_cast<CharHashTableElement *>(hash_lookup(map, key));
+  
+  debug(93, 9) ("CharHashTable::remove key=[%s], hte=%p\n", key, hte);
+  
+  if (hte) {
+    hash_remove_link(map, hte);
+    delete hte;
+  }
+
+}
+
+/* 
+ * Packs table into char *.
+ * On packed_length returns the length
+ * of the whole char *.
+ *
+ * The resulting string is to be passed to constructor 
+ * in order to restore the table.
+ *
+ * It's format: "L LK LV LK LV ..."
+ */
+char *
+CharHashTable::pack(int *packed_length) {
+
+  // temporal...
+  CharHashTableElement *hte;
+  
+  // length of the "LK LV LK LV ..." to be stored in the first
+  // sizeof(int) bytes of resulting string
+  int length = 0;
+
+  hash_first(map);
+  
+  // sum up all the length needed for each "LK LV"
+  while ((hte = (CharHashTableElement*)hash_next(map))) {
+    length += (2*sizeof(int));
+    length += (strlen((char*)hte->key) + strlen((char*)hte->element));
+  }
+
+  // return the whole length...
+  *packed_length = (length + sizeof(int));
+
+  char *packed = (char*)xmalloc(*packed_length);
+  
+  // the leading "L" 
+  xmemcpy(packed, &length, sizeof(int));
+  
+  // counter... traverses the packed string as the packing proceeds
+  int pos = sizeof(int);
+  
+  hash_first(map);
+
+  // pack all pairs
+  while ((hte = (CharHashTableElement*)hash_next(map))) {
+    // temporal for key's and element's lengths
+    int len;
+
+    // the length of the key
+    len = strlen((char*)hte->key);    
+    xmemcpy(&packed[pos], &len, sizeof(int));
+    pos += sizeof(int);
+
+    // the key itself
+    xmemcpy(&packed[pos], hte->key, len);
+    pos += len;
+
+    // the length of the element
+    len = strlen(hte->element);    
+    xmemcpy(&packed[pos], &len, sizeof(int));
+    pos += sizeof(int);
+
+    // the element itself
+    xmemcpy(&packed[pos], hte->element, len);
+    pos += len;
+  }
+
+  return packed;
+}
+
+/*
+ * Dumps all the pairs with debugs
+ */
+void
+CharHashTable::dump() {
+  CharHashTableElement *hte;
+  hash_first(map);
+
+  while ((hte = (CharHashTableElement*)hash_next(map))) {
+    debug(93, 9) ("CharHashTable::dump: key=[%s] element=[%s]\n", 
+                  (char*)hte->key, (char*)hte->element);
+  }
+}
Index: squid3/src/CharHashTable.h
diff -u /dev/null squid3/src/CharHashTable.h:1.1.2.1
--- /dev/null	Tue Sep 28 17:49:22 2004
+++ squid3/src/CharHashTable.h	Thu Jun  3 15:52:27 2004
@@ -0,0 +1,115 @@
+/*
+ * $Id: squid3-varyetag-HEAD,v 1.3 2004/09/29 00:56:20 hno Exp $
+ *
+ * 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.
+ */
+
+#ifndef SQUID_CHARHASHTABLE_H
+#define SQUID_CHARHASHTABLE_H
+
+#define CHARHASHTABLE_DEFAULT_SIZE 32
+
+/*
+ * Hashtable to store pair of char *.
+ * Based on hash_table from hash_table.cc.
+ */
+class CharHashTable 
+{
+
+public:
+    /* 
+     * Creates empty hashtable. 
+     */
+    CharHashTable() : map(hash_create((HASHCMP *) strcmp,CHARHASHTABLE_DEFAULT_SIZE,hash4)) {}
+
+    /*
+     * Creates new hashtable filled with pairs encoded in the pack argument.
+     */
+    CharHashTable(char *packed);
+
+    /*
+     * Destroys hashtable along with it's elements.
+     */
+    ~CharHashTable();
+
+    /*
+     * Retrieves element associated with the given key.
+     */
+    char * get(char *key);
+
+    /*
+     * Retrieves concatenation of the keys from hash table.
+     */
+    char * keys();
+
+    /*
+     * Associates the given key with the given element.
+     * Copies both of them.
+     */
+    void put(char *key, char *element);
+
+    /*
+     * Encodes all the associations into a string.
+     * On packed_length the length of packed string is returned.
+     */
+    char * pack(int *packed_length);
+
+    /*
+     * Removes pair identified by the given key.
+     * Doesn't free the key.
+     */
+    void remove(char *key);
+    
+    /*
+     * To be used with hashFreeItems.
+     */
+    static void elementFree(void *);
+
+    /*
+     * Debugs all the pairs
+     */
+    void dump();
+
+protected:
+ 
+    /*
+     * Backing hashtable.
+     */
+    hash_table *map;
+
+};
+
+/*
+ * Hashtable element.
+ */
+struct CharHashTableElement : public hash_link 
+{
+  ~CharHashTableElement() { safe_free(element); } 
+  char *element;
+};
+
+#endif /* SQUID_CHARHASHTABLE_H*/
Index: squid3/src/HttpHeader.cc
diff -u squid3/src/HttpHeader.cc:1.14 squid3/src/HttpHeader.cc:1.14.2.1
--- squid3/src/HttpHeader.cc:1.14	Mon Sep 29 19:12:40 2003
+++ squid3/src/HttpHeader.cc	Thu Jun  3 15:50:06 2004
@@ -1162,6 +1162,22 @@
     return etag;
 }
 
+/* 
+ * Extracts body of If-None-Match, returns empty String if there is no If-None-Match
+ */ 
+String
+httpHeaderGetINM(const HttpHeader *hdr) {
+  assert(hdr);
+
+  String inm;  
+
+  inm = httpHeaderGetStrOrList(hdr, HDR_IF_NONE_MATCH);
+
+  debug(93, 5) ("httpHeaderGetINM: INM: %s\n", inm.buf());
+
+  return inm;
+}
+
 TimeOrTag
 httpHeaderGetTimeOrTag(const HttpHeader * hdr, http_hdr_type id)
 {
Index: squid3/src/HttpHeaderTools.cc
diff -u squid3/src/HttpHeaderTools.cc:1.14 squid3/src/HttpHeaderTools.cc:1.14.2.1
--- squid3/src/HttpHeaderTools.cc:1.14	Sat Sep 20 19:13:17 2003
+++ squid3/src/HttpHeaderTools.cc	Thu Jun  3 15:50:06 2004
@@ -318,6 +318,60 @@
     return len > 0;
 }
 
+/* 
+ * creates array of char* and fills it with elements of str 
+ * elements are separated by ','
+ * len becomes number of elements
+ * XXX len and returned number arent the same?
+ */
+int
+fillArrayWithStr(const String *str, char ***array, int *len) {
+
+  assert(str);
+  
+  // for strListGetItem
+  const char *pos = NULL;
+  int ilen;
+  
+  // separate element
+  const char *item;
+
+  // iterates the elements
+  int count = 0;
+
+  // to start with
+  *len = 0;
+
+  
+  // element number 
+  while(strListGetItem(str, ',', &item, &ilen, &pos))
+    (*len)++;
+
+  //XXX shouldn't we safe_free?
+  *array = NULL;
+
+  // if there are elements
+  if (*len)
+    *array = new (char *)[*len];
+ 
+  // for strListGetItem
+  const char *pos2 = NULL;
+  
+  //copy the elements
+  while(strListGetItem(str, ',', &item, &ilen, &pos2)) {
+    
+    debug(93,9) ("fillArrayWithStr: count=%d len=%d\n", count, *len);
+    assert(count < *len);
+    
+    (*array)[count] = (char*)xmalloc(ilen + 1);
+    xstrncpy((*array)[count++], item, ilen + 1); 
+    
+    debug(93,3) ("fillArrayWithStr: item found %s\n", item);
+  }
+
+  return count;
+}
+
 /* handy to printf prefixes of potentially very long buffers */
 const char *
 getStringPrefix(const char *str, const char *end)
Index: squid3/src/HttpReply.cc
diff -u squid3/src/HttpReply.cc:1.17 squid3/src/HttpReply.cc:1.17.2.2
--- squid3/src/HttpReply.cc:1.17	Tue Dec 23 19:12:59 2003
+++ squid3/src/HttpReply.cc	Sat Jun  5 18:18:51 2004
@@ -87,7 +87,7 @@
     return rep;
 }
 
-HttpReply::HttpReply() : hdr_sz (0), content_length (0), date (0), last_modified (0), expires (0), cache_control (NULL), surrogate_control (NULL), content_range (NULL), keep_alive (0), pstate(psReadyToParseStartLine), header (hoReply)
+HttpReply::HttpReply() : hdr_sz (0), content_length (0), date (0), last_modified (0), expires (0), cache_control (NULL), surrogate_control (NULL), content_range (NULL), keep_alive (0), saved304 (0), pstate(psReadyToParseStartLine), header (hoReply)
 {
     assert(this);
     httpBodyInit(&body);
@@ -221,7 +221,7 @@
 HttpReply *
 httpReplyMake304 (const HttpReply * rep)
 {
-    static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, /* eof */ HDR_OTHER};
+    static const http_hdr_type ImsEntries[] = {HDR_DATE, HDR_CONTENT_TYPE, HDR_EXPIRES, HDR_LAST_MODIFIED, HDR_ETAG, /* eof */ HDR_OTHER};
 
     HttpReply *rv;
     int t;
@@ -238,8 +238,11 @@
     /* rv->content_range */
     /* rv->keep_alive */
     HttpVersion ver(1,0);
+
+    // XXX was "" OK?
     httpStatusLineSet(&rv->sline, ver,
-                      HTTP_NOT_MODIFIED, "");
+    //                  HTTP_NOT_MODIFIED, "");
+                        HTTP_NOT_MODIFIED, "Not Modified");
 
     for (t = 0; ImsEntries[t] != HDR_OTHER; ++t)
         if ((e = httpHeaderFindEntry(&rep->header, ImsEntries[t])))
@@ -324,11 +327,13 @@
 httpReplyValidatorsMatch(HttpReply const * rep, HttpReply const * otherRep)
 {
     String one,two;
+    debug(93, 9) ("httpReplyValidatorsMatch: start\n");
     assert (rep && otherRep);
     /* Numbers first - easiest to check */
     /* Content-Length */
     /* TODO: remove -1 bypass */
 
+    debug(93, 9) ("httpReplyValidatorsMatch: %d %d\n", rep->content_length, otherRep->content_length);
     if (rep->content_length != otherRep->content_length
             && rep->content_length > -1 &&
             otherRep->content_length > -1)
@@ -339,12 +344,17 @@
 
     two = httpHeaderGetStrOrList(&otherRep->header, HDR_ETAG);
 
+    debug(93, 9) ("httpReplyValidatorsMatch: %s %s!\n",one.buf(), two.buf());
+    if (one.buf() || two.buf()) {
     if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
+          debug(93, 9) ("httpReplyValidatorsMatch: no etags!\n");
         one.clean();
         two.clean();
         return 0;
     }
+    }
 
+    debug(93, 9) ("httpReplyValidatorsMatch: %ld %ld\n",rep->last_modified, otherRep->last_modified);
     if (rep->last_modified != otherRep->last_modified)
         return 0;
 
@@ -353,11 +363,21 @@
 
     two = httpHeaderGetStrOrList(&otherRep->header, HDR_CONTENT_MD5);
 
+
+
+    debug(93, 9) ("httpReplyValidatorsMatch: md5: %s, %s\n", one.buf(), two.buf());
+    
+    // if any of MD5 is not NULL we are able to compare 'em
+    if (one.buf() || two.buf()) {
+   
     if (!one.buf() || !two.buf() || strcasecmp (one.buf(), two.buf())) {
         one.clean();
         two.clean();
         return 0;
     }
+    }
+
+    debug(93, 9) ("httpReplyValidatorsMatch: true\n");
 
     return 1;
 }
@@ -366,9 +386,11 @@
 void
 httpReplyUpdateOnNotModified(HttpReply * rep, HttpReply const * freshRep)
 {
+    debug(93, 9) ("httpReplyUpdateOnNotModified: start\n");
     assert(rep && freshRep);
     /* Can not update modified headers that don't match! */
     assert (httpReplyValidatorsMatch(rep, freshRep));
+    debug(93, 9) ("httpReplyUpdateOnNotModified: after assert(httpReplyValidatorsMatch)\n");
     /* clean cache */
     httpReplyHdrCacheClean(rep);
     /* update raw headers */
@@ -376,6 +398,7 @@
                      (const HttpHeaderMask *) &Denied304HeadersMask);
     /* init cache */
     httpReplyHdrCacheInit(rep);
+    debug(93, 9) ("httpReplyUpdateOnNotModified: end\n");
 }
 
 
Index: squid3/src/HttpReply.h
diff -u squid3/src/HttpReply.h:1.7 squid3/src/HttpReply.h:1.7.2.1
--- squid3/src/HttpReply.h:1.7	Mon Sep  1 19:12:39 2003
+++ squid3/src/HttpReply.h	Thu Jun  3 15:50:06 2004
@@ -102,6 +102,9 @@
     HttpHdrContRange *content_range;
     short int keep_alive;
 
+    /* for handleConditionalMiss */
+    int saved304;
+
     /* public, readable */
     HttpMsgParseState pstate;	/* the current parsing state */
 
Index: squid3/src/HttpRequest.cc
diff -u squid3/src/HttpRequest.cc:1.14 squid3/src/HttpRequest.cc:1.14.2.2
--- squid3/src/HttpRequest.cc:1.14	Mon Sep  1 19:12:39 2003
+++ squid3/src/HttpRequest.cc	Sat Jun  5 18:18:51 2004
@@ -137,6 +137,8 @@
 
     safe_free(req->vary_headers);
 
+    safe_free(req->real_vary_headers);
+
     req->urlpath.clean();
 
     httpHeaderClean(&req->header);
@@ -155,6 +157,26 @@
 
     req->extacl_log.clean();
 
+    // clear inm information
+    if (req->inmlen) {
+      
+      assert(req->inm);
+      
+      for(int i=0; i < req->inmlen; i++) {
+      
+        assert(req->inm[i]);
+        safe_free(req->inm[i]);
+        
+      }
+      debug(93, 9) ("requestDestroy\n"); 
+      delete req->inm;
+    }
+
+    //clear etag information
+    if (req->etag) {
+      delete req->etag;
+    }
+
     delete req;
 }
 
Index: squid3/src/HttpRequest.h
diff -u squid3/src/HttpRequest.h:1.9 squid3/src/HttpRequest.h:1.9.2.1
--- squid3/src/HttpRequest.h:1.9	Thu Oct 16 19:12:32 2003
+++ squid3/src/HttpRequest.h	Thu Jun  3 15:50:06 2004
@@ -80,6 +80,16 @@
     HttpVersion http_ver;
     time_t ims;
     int imslen;
+
+    // etags from client INM
+    char **inm;
+
+    // number of etags in client INM
+    int inmlen;
+
+    // whole INM string 
+    String *etag;
+
     int max_forwards;
     /* these in_addr's could probably be sockaddr_in's */
 
@@ -96,6 +106,7 @@
     char *peer_login;		/* Configured peer login:password */
     time_t lastmod;		/* Used on refreshes */
     const char *vary_headers;	/* Used when varying entities are detected. Changes how the store key is calculated */
+    const char *real_vary_headers;	/* original vary_headers (before vary mappings) */
     char *peer_domain;		/* Configured peer forceddomain */
     String tag;			/* Internal tag for this request */
     String extacl_user;		/* User name returned by extacl lookup */
Index: squid3/src/Makefile.am
diff -u squid3/src/Makefile.am:1.50 squid3/src/Makefile.am:1.50.2.1
--- squid3/src/Makefile.am:1.50	Mon Oct 20 19:12:44 2003
+++ squid3/src/Makefile.am	Thu Jun  3 15:50:06 2004
@@ -316,6 +316,8 @@
 	client_side_request.h \
 	clientStream.cc \
 	clientStream.h \
+  CharHashTable.cc \
+  CharHashTable.h \
 	comm.cc \
 	comm.h \
 	comm_select.cc \
@@ -552,6 +554,8 @@
 	client_side_request.h \
 	clientStream.cc \
 	clientStream.h \
+  CharHashTable.cc \
+  CharHashTable.h \
 	comm.cc \
 	comm.h \
 	comm_select.cc \
Index: squid3/src/MemObject.h
diff -u squid3/src/MemObject.h:1.8 squid3/src/MemObject.h:1.8.2.1
--- squid3/src/MemObject.h:1.8	Sun Aug 10 19:13:02 2003
+++ squid3/src/MemObject.h	Thu Jun  3 15:50:06 2004
@@ -38,6 +38,8 @@
 #include "stmem.h"
 #include "CommRead.h"
 
+#include "CharHashTable.h"
+
 typedef void STMCB (void *data, StoreIOBuffer wroteBuffer);
 
 class store_client;
@@ -135,8 +137,13 @@
     unsigned int chksum;
 #endif
 
+    // the vary key for variant
     const char *vary_headers;
 
+    // information of variants for base vary entry
+    CharHashTable *etag_mapping;
+    CharHashTable *vary_headers_mapping;
+
     void delayRead(DeferredRead const &);
     void kickReads();
 
Index: squid3/src/StoreMetaVary.cc
diff -u squid3/src/StoreMetaVary.cc:1.4 squid3/src/StoreMetaVary.cc:1.4.2.1
--- squid3/src/StoreMetaVary.cc:1.4	Mon Aug  4 19:12:48 2003
+++ squid3/src/StoreMetaVary.cc	Thu Jun  3 15:50:06 2004
@@ -37,6 +37,7 @@
 #include "StoreMetaVary.h"
 #include "Store.h"
 #include "MemObject.h"
+#include "CharHashTable.h"
 
 MemPool *StoreMetaVary::pool = NULL;
 
@@ -63,15 +64,204 @@
 {
     assert (getType() == STORE_META_VARY_HEADERS);
 
-    if (!e->mem_obj->vary_headers) {
-        /* XXX separate this mutator from the query */
-        /* Assume the object is OK.. remember the vary request headers */
-        e->mem_obj->vary_headers = xstrdup((char *)value);
-        return true;
+    //unpacking 
+    return storeMetaVaryUnpack(e->mem_obj, (char*)value);
+
+}
+
+bool
+storeMetaVaryUnpack(MemObject *mem_obj, char* value) {
+
+  debug(93, 9) ("\t*** storeMetaVaryUnpack: value: %p mem_obj: %p\n", value, mem_obj);
+  assert(value && mem_obj);
+
+  // vary_headers length
+  int vary_headers_len;
+  
+  xmemcpy(&vary_headers_len, value, sizeof(int));
+  
+  debug(93, 9) ("\t** storeMetaVaryUnpack: vary_headers_len: %d\n", vary_headers_len);  
+
+  char *vary_headers = NULL;
+  
+  if (vary_headers_len) {
+    // unpack vary_headers
+    vary_headers = (char*)xmalloc(vary_headers_len);
+
+    xmemcpy(vary_headers, &value[sizeof(int)], vary_headers_len);
+  }
+
+
+  // packed etag_mapping length
+  int etag_mapping_packed_len;
+  
+  xmemcpy(&etag_mapping_packed_len, &value[sizeof(int) + vary_headers_len], sizeof(int));
+  
+  debug(93, 9) ("\t** storeMetaVaryUnpack: etag_mapping_packed_len: %d\n", etag_mapping_packed_len);  
+
+  char *etag_mapping_packed = NULL;
+  
+  if (etag_mapping_packed_len) {
+    
+    // get packed etag_mapping
+    etag_mapping_packed = (char*)xmalloc(etag_mapping_packed_len + sizeof(int));
+  
+    xmemcpy(etag_mapping_packed, 
+            &value[sizeof(int) + vary_headers_len],
+            etag_mapping_packed_len + sizeof(int));
+  }
+
+  // packed vary_headers_mapping length
+  int vary_headers_mapping_packed_len;
+  
+  xmemcpy(&vary_headers_mapping_packed_len, 
+          &value[sizeof(int) + vary_headers_len + sizeof(int) + etag_mapping_packed_len],
+          sizeof(int));
+  
+  debug(93, 9) ("\t** storeMetaVaryUnpack: vary_headers_mapping_packed_len: %d\n", vary_headers_mapping_packed_len);  
+
+  char *vary_headers_mapping_packed = NULL;
+  
+  if (vary_headers_mapping_packed_len) {
+
+    // get packed vary_headers_mapping 
+    vary_headers_mapping_packed = (char*)xmalloc(vary_headers_mapping_packed_len + sizeof(int));
+    
+    xmemcpy(vary_headers_mapping_packed, 
+            &value[sizeof(int) + vary_headers_len + sizeof(int) + etag_mapping_packed_len],
+            vary_headers_mapping_packed_len + sizeof(int));
+
+  }
+
+  debug(93, 9) ("\t*** storeMetaVaryUnpack: %d %d %d\n", vary_headers_mapping_packed_len, etag_mapping_packed_len, vary_headers_len);
+
+
+  // save data in mem_obj
+  
+  if (!mem_obj->vary_headers && vary_headers) {
+    mem_obj->vary_headers = xstrdup(vary_headers);
+  }
+
+  // vary_headers means this is a variant, not a base object
+  if (!vary_headers) {
+    
+    // unpack mappings
+    mem_obj->etag_mapping = new CharHashTable(etag_mapping_packed);
+    mem_obj->vary_headers_mapping = new CharHashTable(vary_headers_mapping_packed);
+    
     }
 
-    if (strcmp(e->mem_obj->vary_headers, (char *)value) != 0)
+  
+  safe_free(etag_mapping_packed);
+  safe_free(vary_headers_mapping_packed);
+  
+  // decide if stuff unpacked correctly
+  // XXX check also mappings
+  if (mem_obj->vary_headers) {
+    
+    if (vary_headers) {
+      
+      if (strcmp(mem_obj->vary_headers, vary_headers) != 0) {
+          safe_free(vary_headers);
+          debug(93, 9) ("storeMetaVaryUnpack: end - false\n");
+        return false;
+      }
+
+    } else {
+      safe_free(vary_headers);
+      debug(93, 9) ("storeMetaVaryUnpack: end - false\n");
         return false;
+    }
+  }
 
+  safe_free(vary_headers);
+
+  debug(93, 9) ("storeMetaVaryUnpack: end - true\n");
     return true;
 }
+
+/* 
+ * Packs vary_headers or mappings
+ * rlen becomes length of the whole packed string (returned)
+ * 
+ * LVH VH LEM VEM LVHM VVHM
+ */
+char *
+storeMetaVaryPack(MemObject *mem_obj, int *rlen) {
+ 
+  assert(mem_obj && rlen);
+ 
+  debug(93, 9) ("storeMetaVaryPack: start\n");
+  
+  // packed string to be returned
+  char *packed = NULL;
+
+  // never all three
+  assert(!(mem_obj->vary_headers && mem_obj->etag_mapping && mem_obj->vary_headers_mapping));
+  
+  debug(93, 9) ("storeMetaVaryPack: %p %p %p\n", mem_obj->vary_headers, mem_obj->etag_mapping, mem_obj->vary_headers_mapping);
+
+  // which do we have? 
+  int hasMappings = mem_obj->vary_headers_mapping && mem_obj->etag_mapping;
+  int hasVaryHeaders = (int)mem_obj->vary_headers; 
+
+  if (hasMappings || hasVaryHeaders) {
+
+    // mappings lengths
+    int etag_mapping_len = sizeof(int);
+    int vary_headers_mapping_len = sizeof(int);
+
+    // strings for packed mappings
+    char *etag_mapping_packed = NULL;
+    char *vary_headers_mapping_packed = NULL;
+
+    // pack if we are to
+    if (hasMappings) {
+      etag_mapping_packed = mem_obj->etag_mapping->pack(&etag_mapping_len);
+      vary_headers_mapping_packed = mem_obj->vary_headers_mapping->pack(&vary_headers_mapping_len);
+    }
+
+    debug(93, 9) ("storeMetaVaryPack: %d %d\n", etag_mapping_len, vary_headers_mapping_len);
+
+    // vary_headers length
+    int vary_headers_len = 0;
+    
+    // pack if we are to
+    if (hasVaryHeaders) 
+      vary_headers_len = strlen(mem_obj->vary_headers) + 1;
+    
+    // total length
+    int len = sizeof(int) + vary_headers_len + etag_mapping_len + vary_headers_mapping_len;
+    
+    // final allocation
+    packed = (char*)xmalloc(len);
+
+    // LVH 
+    xmemcpy(packed, &vary_headers_len, sizeof(int));
+    
+    //VH
+    if (hasVaryHeaders)
+      xmemcpy(&packed[sizeof(int)], mem_obj->vary_headers, vary_headers_len);
+
+     
+    if (hasMappings) {
+      debug(93, 9) ("storeMetaVaryPack: has mappings\n");
+      // LEM VEM
+      xmemcpy(&packed[sizeof(int)+vary_headers_len], etag_mapping_packed, etag_mapping_len);
+      //LVH VVHM
+      xmemcpy(&packed[sizeof(int)+vary_headers_len + etag_mapping_len],
+              vary_headers_mapping_packed, vary_headers_mapping_len);
+    } else {
+      debug(93, 9) ("storeMetaVaryPack: has no mappings\n");
+      int zero = 0;
+      xmemcpy(&packed[sizeof(int) + vary_headers_len], &zero, sizeof(int));
+      xmemcpy(&packed[sizeof(int) + vary_headers_len + sizeof(int)], &zero, sizeof(int));
+    }
+    
+    
+    *rlen = len;  
+  }
+
+  debug(93, 9) ("storeMetaVaryPack: end\n");
+  return packed;
+}
Index: squid3/src/StoreMetaVary.h
diff -u squid3/src/StoreMetaVary.h:1.4 squid3/src/StoreMetaVary.h:1.4.2.1
--- squid3/src/StoreMetaVary.h:1.4	Mon Aug  4 19:12:48 2003
+++ squid3/src/StoreMetaVary.h	Thu Jun  3 15:50:06 2004
@@ -35,6 +35,7 @@
 #define SQUID_STOREMETAVARY_H
 
 #include "StoreMeta.h"
+#include "MemObject.h"
 
 class StoreMetaVary : public StoreMeta
 {
@@ -51,4 +52,7 @@
     static MemPool *pool;
 };
 
+bool storeMetaVaryUnpack(MemObject *, char*);
+char *storeMetaVaryPack(MemObject *, int *);
+
 #endif /* SQUID_STOREMETAVARY_H */
Index: squid3/src/client_side.cc
diff -u squid3/src/client_side.cc:1.60 squid3/src/client_side.cc:1.60.2.2
--- squid3/src/client_side.cc:1.60	Sun Apr  4 19:00:27 2004
+++ squid3/src/client_side.cc	Sat Jun  5 18:18:51 2004
@@ -596,8 +596,11 @@
     openReference = NULL;
     clientdbEstablished(peer.sin_addr, -1);	/* decrement */
     assert(areAllContextsForThisConnection());
+    debug(93, 9) ("ConnStateData::close before freeAllContexts()\n");
     freeAllContexts();
+    debug(93, 9) ("ConnStateData::close before authenticateOnCloseConnection()\n");
     authenticateOnCloseConnection(this);
+    debug(93, 9) ("ConnStateData::close finished\n");
 }
 
 bool
@@ -801,6 +804,8 @@
     if (!multipartRangeRequest()) {
         size_t length = lengthToSend(bodyData.range());
         noteSentBodyBytes (length);
+        if (flags.saved304)
+          length = 0;
         comm_write(fd(), bodyData.data, length,
                    clientWriteBodyComplete, this);
         return;
@@ -1165,10 +1170,14 @@
 void
 ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData)
 {
+    // ranges
     prepareReply(rep);
+    
     /* init mb; put status line and headers if any */
     assert (rep);
+
     MemBuf mb = httpReplyPack(rep);
+    
     /* Save length of headers for persistent conn checks */
     http->out.headers_sz = mb.size;
 #if HEADERS_LOG
@@ -1181,6 +1190,8 @@
             size_t length = lengthToSend(bodyData.range());
             noteSentBodyBytes (length);
 
+            debug(93, 9) ("sendStartOfMessage: length = %d\n", length);
+            if (!flags.saved304)
             memBufAppend(&mb, bodyData.data, length);
         } else {
             packRange(bodyData, &mb);
@@ -1194,7 +1205,8 @@
 }
 
 /*
- * Write a chunk of data to a client socket. If the reply is present, send the reply headers down the wire too,
+ * Write a chunk of data to a client socket. If the reply is present, send 
+ * the reply headers down the wire too,
  * and clean them up when finished.
  * Pre-condition: 
  *   The request is one backed by a connection, not an internal request.
@@ -1231,6 +1243,10 @@
         return;
     }
 
+    int saved304 = rep ? rep->saved304 : 0;
+    if (saved304)
+      context->flags.saved304 = 1;
+    
     if (!context->startOfOutput())
         context->sendBody(rep, recievedData);
     else {
@@ -1522,6 +1538,7 @@
 void
 ClientSocketContext::writeComplete(int fd, char *bufnotused, size_t size, comm_err_t errflag)
 {
+
     StoreEntry *entry = http->storeEntry();
     http->out.size += size;
     assert(fd > -1);
@@ -2292,6 +2309,7 @@
         context->mayUseConnection(true);
 
     clientAccessCheck(http);
+    
 }
 
 static void
@@ -2836,6 +2854,7 @@
     clientdbEstablished(details->peer.sin_addr, 1);
 
     incoming_sockets_accepted++;
+    
 }
 
 #if USE_SSL
@@ -3078,7 +3097,12 @@
 int
 varyEvaluateMatch(StoreEntry * entry, HttpRequest * request)
 {
+  
+    // vary_headers from Vary of base entry and request headers
+    // set after finding of Vary base entry
     const char *vary = request->vary_headers;
+
+    // reply has Vary header
     int has_vary = httpHeaderHas(&entry->getReply()->header, HDR_VARY);
 #if X_ACCELERATOR_VARY
 
@@ -3086,17 +3110,21 @@
         httpHeaderHas(&entry->getReply()->header, HDR_X_ACCELERATOR_VARY);
 #endif
 
+    // mem_obj has vary_headers if reply is a variant
     if (!has_vary || !entry->mem_obj->vary_headers) {
+
+        // we were looking for a variant and we haven't found one 
         if (vary) {
             /* Oops... something odd is going on here.. */
-            debug(33,
-                  1)
+            debug(33, 1)
             ("varyEvaluateMatch: Oops. Not a Vary object on second attempt, '%s' '%s'\n",
              entry->mem_obj->url, vary);
             safe_free(request->vary_headers);
+            safe_free(request->real_vary_headers);
             return VARY_CANCEL;
         }
 
+        // there never was any Vary/variants
         if (!has_vary) {
             /* This is not a varying object */
             return VARY_NONE;
@@ -3107,28 +3135,60 @@
          */
         vary = httpMakeVaryMark(request, entry->getReply());
 
+        assert(entry->mem_obj->vary_headers_mapping);
+        assert(entry->mem_obj->etag_mapping);
+
+        // always should be vary
         if (vary) {
-            request->vary_headers = xstrdup(vary);
+          
+            // saving vary_headers
+            request->real_vary_headers = xstrdup(vary); 
+            request->vary_headers = entry->mem_obj->vary_headers_mapping->get((char *)request->real_vary_headers);
+       
+            // if there is no mapping
+            if (!request->vary_headers)
+              request->vary_headers = xstrdup(request->real_vary_headers);
+            else 
+              request->vary_headers = xstrdup(request->vary_headers);
+            
+       
             return VARY_OTHER;
         } else {
             /* Ouch.. we cannot handle this kind of variance */
             /* XXX This cannot really happen, but just to be complete */
             return VARY_CANCEL;
         }
+
     } else {
+      // reply has Vary header and reply is a variant
+      // XXX we're not sure if each variant has to have Vary header
+
+        // first time we know the reply is a variant
         if (!vary) {
+          
+            // get variants key
             vary = httpMakeVaryMark(request, entry->getReply());
 
-            if (vary)
+            // should always be vary
+            if (vary) {
+
                 request->vary_headers = xstrdup(vary);
-        }
+                request->real_vary_headers = xstrdup(vary);
+            
+            } else {
 
-        if (!vary) {
             /* Ouch.. we cannot handle this kind of variance */
             /* XXX This cannot really happen, but just to be complete */
             return VARY_CANCEL;
-        } else if (strcmp(vary, entry->mem_obj->vary_headers) == 0) {
+            }
+
+        }
+
+        
+        // check if this is the correct variant
+        if (strcmp(vary, entry->mem_obj->vary_headers) == 0) {
             return VARY_MATCH;
+
         } else {
             /* Oops.. we have already been here and still haven't
              * found the requested variant. Bail out
Index: squid3/src/client_side.h
diff -u squid3/src/client_side.h:1.7 squid3/src/client_side.h:1.7.4.1
--- squid3/src/client_side.h:1.7	Thu Aug 14 19:12:38 2003
+++ squid3/src/client_side.h	Thu Jun  3 15:50:06 2004
@@ -71,6 +71,10 @@
 
 int parsed_ok:
         1; /* Was this parsed correctly? */
+
+int saved304:
+        1;  /* handleConditionalMiss path? */
+
     }
 
     flags;
Index: squid3/src/client_side_reply.cc
diff -u squid3/src/client_side_reply.cc:1.48 squid3/src/client_side_reply.cc:1.48.2.4
--- squid3/src/client_side_reply.cc:1.48	Sun Apr  4 19:00:27 2004
+++ squid3/src/client_side_reply.cc	Sun Jul  4 09:56:59 2004
@@ -77,7 +77,7 @@
     cbdataReferenceDone(http);
 }
 
-clientReplyContext::clientReplyContext(clientHttpRequest *clientContext) : http (cbdataReference(clientContext)), old_entry (NULL), old_sc(NULL), deleting(false)
+clientReplyContext::clientReplyContext(clientHttpRequest *clientContext) : http (cbdataReference(clientContext)), old_entry (NULL), old_sc(NULL), vary_entry(NULL), vary_sc(NULL), deleting(false)
 {}
 
 /* create an error in the store awaiting the client side to read it. */
@@ -219,6 +219,25 @@
     storeClientCopy(sc, http->storeEntry(), tempBuffer, SendMoreData, this);
 }
 
+/*
+ * copy of triggerInitialStoreRead but takes also a callback to call back at the end
+ */
+void 
+clientReplyContext::triggerInitialStoreReadClone(STCB * callback)
+{
+
+    StoreIOBuffer otherTempBuffer;
+    /* when confident, 0 becomes reqofs, and then this factors into
+     * startSendProcess 
+     */
+    assert(reqofs == 0);
+    otherTempBuffer.offset = 0;
+    otherTempBuffer.length = next()->readBuffer.length;
+    otherTempBuffer.data = next()->readBuffer.data;
+    debug(93, 9) ("triggerInitialStoreReadClone: sc = %p, callback: %p\n", sc, callback);
+    storeClientCopy(sc, http->storeEntry(), otherTempBuffer, callback, this);
+}
+
 /* there is an expired entry in the store.
  * setup a temporary buffer area and perform an IMS to the origin
  */
@@ -228,7 +247,13 @@
     char *url = http->uri;
     StoreEntry *entry = NULL;
     debug(88, 3)("clientReplyContext::processExpired: '%s'", http->uri);
-    assert(http->storeEntry()->lastmod >= 0);
+
+    // get ETag from reply
+    const HttpHeader *rep_hdr = &http->storeEntry()->getReply()->header;
+    ETag etag = httpHeaderGetETag(rep_hdr, HDR_ETAG);
+    
+    // Last-Modified or ETag
+    assert(http->storeEntry()->lastmod >= 0 || etag.str);
     /*
      * check if we are allowed to contact other servers
      * @?@: Instead of a 504 (Gateway Timeout) reply, we may want to return 
@@ -257,11 +282,68 @@
     sc->setDelayId(DelayId::DelayClient(http));
 #endif
 
+    // old_entry may be a variant
+    const char *vary_headers = old_entry->mem_obj->vary_headers;
+
+    // vary_entry is base vary object 
+    vary_entry = NULL;
+    
+    if (vary_headers) {
+
+      vary_entry = storeGetPublic(old_entry->mem_obj->url, old_entry->mem_obj->method);
+
+    }
+
+    debug(93, 9) ("processExpired: vary_entry = %p\n", vary_entry);
+
+    // if Vary 
+    if (vary_entry) {
+      
+      // collect etags of all the variants to send to server
+      
+      // we should have mem_obj because base object was swapped in in cacheHit
+      assert(vary_entry->mem_obj);
+
+      // get all the etags
+      char * etag_keys = vary_entry->mem_obj->etag_mapping->keys();
+      debug (93, 9) ("processExpired: inm=[%s]\n", etag_keys);
+
+      flags.varyvalidation = 1;
+
+      // save them
+      if (etag_keys) {
+        
+        http->request->lastmod = -1;  
+        http->request->etag = new String(etag_keys);
+      
+      } else {
+      
+        // the variants has no etags -> try to validate with IMS
+        http->request->etag = NULL;
     http->request->lastmod = old_entry->lastmod;
-    debug(88, 5)("clientReplyContext::processExpired : lastmod %ld",
+        
+      }
+         
+    } else {
+      
+      // no vary, just casual single etag
+      
+      if (etag.str)
+        http->request->etag = new String(etag.str);
+      else
+        http->request->etag = NULL;
+      
+      http->request->lastmod = old_entry->lastmod;
+      
+    }
+
+    debug(88, 5)("clientReplyContext::processExpired : lastmod %ld\n",
                  (long int) entry->lastmod);
     http->storeEntry(entry);
     assert(http->out.offset == 0);
+    
+    debug(93, 9) ("processExpired: old_entry = %p old_entry->mem_obj = %p\n", old_entry, old_entry->mem_obj);
+
     fwdStart(http->getConn().getRaw() != NULL ? http->getConn()->fd : -1, http->storeEntry(), http->request);
     /* Register with storage manager to receive updates when data comes in. */
 
@@ -299,24 +381,21 @@
         return false;
     }
 
-    /* If the client did not send IMS in the request, then it
+    /* if client did not send INM in the request, then it 
      * must get the old object, not this "Not Modified" reply 
      * REGARDLESS of validation */
-    if (!http->request->flags.ims) {
-        debug(88, 5) ("clientGetsOldEntry: YES, no client IMS\n");
+    if (!http->request->flags.inm && !http->request->flags.ims) {
+
+      debug(93, 5) ("clientGetsOldEntry: YES, no client INM, nor IMS\n");
         return true;
+
     }
 
-    /* If key metadata in the reply are not consistent with the
-     * old entry, we must use the new reply.
-     * Note: this means that the server is sending garbage replies 
-     * in that it has sent an IMS that is incompatible with our request!?
-     */
-    /* This is a duplicate call through the HandleIMS code path.
-     * Can we guarantee we don't need it elsewhere?
-     */
+    
+    // to sum up: 304 and client's request was conditional
     if (!httpReplyValidatorsMatch(http->storeEntry()->getReply(),
                                   old_entry->getReply())) {
+
         debug(88, 5) ("clientGetsOldEntry: NO, Old object has been invalidated"
                       "by the new one\n");
         return false;
@@ -324,11 +403,42 @@
 
     /* If the client IMS time is prior to the entry LASTMOD time we
      * need to send the old object */
+    
+    if (http->request->flags.ims) {
     if (old_entry->modifiedSince(http->request)) {
         debug(88, 5) ("clientGetsOldEntry: YES, modified since %ld\n",
                       (long int) http->request->ims);
         return true;
     }
+    }
+
+    
+    // IMS(passed)+INM 
+    if (http->request->flags.inm) {
+
+      // check if one of the etags from reply matches client's one
+      
+      const HttpHeader *hdr = &http->storeEntry()->getReply()->header;
+      ETag etag = httpHeaderGetETag(hdr, HDR_ETAG);
+      const char *etag_str = etag.str;
+      int llen = 0;
+      char *etag_inm;
+
+      debug(93,5) ("clientGetsOldEntry: etag from reply %s\n",etag_str);
+      
+      while(etag_str && llen < http->request->inmlen) {
+        etag_inm = http->request->inm[llen++];
+        
+        debug(93,5) ("clientGetsOldEntry: parsing client INM: %s\n",etag_inm);
+        
+        if (strcmp(etag_str,etag_inm)) 
+          continue;
+          
+        return false;
+      }
+
+      return true;
+    }
 
     debug(88, 5) ("clientGetsOldEntry: NO, new one is fine\n");
     return false;
@@ -414,6 +524,10 @@
      * headers have been loaded from disk. */
     http->logType = LOG_TCP_REFRESH_HIT;
 
+    http_status status = http->storeEntry()->getReply()->sline.status;
+   
+   debug(93, 3) ("handleIMSGiveClientUpdatedOldEntry: reply=%d\n", status);
+
     if (httpReplyValidatorsMatch(http->storeEntry()->getReply(),
                                  old_entry->getReply())) {
         int unlink_request = 0;
@@ -452,6 +566,8 @@
      * is a 304 that is incompatible with our cached entities.
      */
 
+    debug(93, 9) ("handleIMSGiveClientNewEntry: newReply = %p oldReply = %p\n",http->storeEntry()->getReply(), old_entry->getReply());
+
     if (http->request->flags.ims) {
         /* The client asked for a IMS, and can deal
          * with any reply
@@ -505,13 +621,118 @@
     }
 }
 
+/*
+ * see variantUpdateSend
+ */
+void
+clientReplyContext::VariantUpdateSend(void *data, StoreIOBuffer result)
+{
+    clientReplyContext *context = (clientReplyContext *)data;
+    context->variantUpdateSend(result);
+}
+
+/*
+ * for now does nothing :(
+ * should update variant headers after revalidation
+ */
+void
+clientReplyContext::variantUpdateSend(StoreIOBuffer result)
+{
+  sendMoreData(result);
+}
+
+/*
+ * see fakeEntryToVariant
+ */
+void
+clientReplyContext::FakeEntryToVariant(void *data, StoreIOBuffer result)
+{
+    clientReplyContext *context = (clientReplyContext *)data;
+    context->fakeEntryToVariant(result);
+}
+
+/*
+ * Decide which variant to send to client upon receiving Not Modified after Vary INM
+ * etag based
+ */
+void
+clientReplyContext::fakeEntryToVariant(StoreIOBuffer result)
+{
+    // assumptions:
+    // - tempBufferVary - result saved in handleIMSReply
+    // - vary_entry - base vary object
+    // - vary_sc
+    // - old_entry is stale entry or save new entry for update
+
+    // 304's headers
+    const HttpHeader *hdr = &old_entry->getReply()->header;
+    ETag etag = httpHeaderGetETag(hdr, HDR_ETAG);
+    const char *etag_str = etag.str;
+
+    assert(vary_entry->mem_obj);
+    
+    // vary_headers of the variant pointed to by etag_str
+
+    const char *vary_headers = vary_entry->mem_obj->etag_mapping->get
+                               ((char*)etag_str);
+    
+    if (vary_headers) {
+      // we have such a variant
+      
+      // update for storeGetPublicByRequest
+      http->request->vary_headers = xstrdup(vary_headers);
+      StoreEntry *e = storeGetPublicByRequest(http->request);
+
+      
+      if (e) {
+        // XXX: what with old_entry??
+        // clearly we don't need it anymore
+        // mmatusiak
+        if (old_sc) {
+          storeUnregister(old_sc, old_entry, this);
+        }
+
+        if (vary_sc) {
+          storeUnregister(vary_sc, vary_entry, this);
+        }
+
+        storeLockObject(e);
+        sc = storeClientListAdd(e, this);
+        reqofs = 0;
+        reqsize = 0;
+        http->storeEntry(e);
+
+        StoreIOBuffer tempBuffer;
+        tempBuffer.offset = reqofs;
+        tempBuffer.length = next()->readBuffer.length;
+        tempBuffer.data = next()->readBuffer.data;
+            // XXX: we should update first
+        storeClientCopy(sc, http->storeEntry(), tempBuffer, SendMoreData, this);
+        
+      } else {
+            // here we didn't find variant, we send server response or stale entry
+        debug(93, 9) ("fakeEntryToVariant: sending supposed garbage - no variant found\n");
+        restoreState();
+        sendMoreData(tempBufferVary);
+        
+      }
+
+    } else {
+        // we haven't found correct etag->vary_headers mapping, so we must send client
+        // server response (probably garbage) or stale entry
+      debug(93, 9) ("fakeEntryToVariant: sending supposed garbage - no mapping\n");
+      restoreState();
+      sendMoreData(tempBufferVary);
+    }
+}
+
 void
 clientReplyContext::handleIMSReply(StoreIOBuffer result)
 {
     if (deleting)
         return;
 
-    debug(88, 3) ("clientHandleIMSReply: %s, %lu bytes\n",
+    debug(88, 3) ("handleIMSReply: %s, %lu bytes\n",
                   storeUrl(http->storeEntry()),
                   (long unsigned) result.length);
 
@@ -525,6 +746,79 @@
     reqsize = result.length + reqofs;
 
     http_status status = http->storeEntry()->getReply()->sline.status;
+    debug(88, 3) ("handleIMSReply: reply=%d\n", status);
+   
+    if (flags.varyvalidation) {
+      debug(88, 9) ("handleIMSReply: variant handling\n");
+      
+        // we don't need old_entry anymore
+        if (old_entry) {
+            storeUnregister(old_sc, old_entry, this);
+
+            // how to drop it?
+            storeUnlockObject(old_entry);
+            old_entry = NULL;
+            old_sc = NULL;
+        }
+
+      // XXX: it's not sufficient (but for now, we can leave this)
+      // mmatusiak
+      // if other than HTTP_NOT_MODIFIED send response to client
+      if (status != HTTP_NOT_MODIFIED) {
+        debug(88, 9) ("handleIMSReply: new variant, proceed to sendMoreData()\n");
+
+        sendMoreData(result);
+
+        return;
+      }
+      
+      // XXX: what if entry got aborted?
+      // mmatusiak
+      StoreEntry *entry = http->storeEntry();
+
+      // we go HTTP_NOT_MODIFIED and we can go on with vary stuff
+
+      // base vary object
+      vary_entry = storeGetPublic(entry->mem_obj->url, entry->mem_obj->method);
+      
+      if (vary_entry) {
+        
+        // save for fakeEntryToVariant
+        saveState();
+        tempBufferVary = result;
+        
+        vary_sc = NULL;
+        
+        if (!vary_entry->mem_obj) {
+          // "fake" (base) has no memobj -> swap it in
+          debug(88, 9) ("handleIMSReply: vary_entry has no mem_obj\n");
+
+          storeCreateMemObject(vary_entry, http->uri, http->log_uri);
+
+          vary_sc = storeClientListAdd(vary_entry, this);
+
+          // XXX: we need this only to swapin mem_obj we don't actualy need the reply
+          // so i thought that small buffer will be sufficient
+          // mmatusiak
+          StoreIOBuffer tempBuffer2(HTTP_REQBUF_SZ, 0, (char*)xmalloc(HTTP_REQBUF_SZ));
+          storeClientCopy(vary_sc, vary_entry, tempBuffer2, FakeEntryToVariant, this);
+        } else {
+          // "fake" (base) has memobj
+          debug(88, 9) ("handleIMSReply: vary_entry has mem_obj\n");
+          fakeEntryToVariant(result);
+        }
+        
+      } else {
+        
+        debug(88, 9) ("handleIMSReply: no base entry found, sendMoreData()\n");
+        sendMoreData(result);
+      }
+
+      return;
+    }
+
+    // when no 'vary' was detected before we can process as always
+    // hovewer this implementation is broken, see httpReplyValidatorsMatch() issues
 
     if (EBIT_TEST(http->storeEntry()->flags, ENTRY_ABORTED))
         cleanUpAfterIMSCheck();
@@ -561,6 +855,8 @@
 
     StoreEntry *e = http->storeEntry();
 
+    debug(93, 9) ("cacheHit: entry = %p\n", e);
+
     HttpRequest *r = http->request;
 
     debug(88, 3) ("clientCacheHit: %s, %ud bytes\n", http->uri, (unsigned int)result.length);
@@ -627,22 +923,35 @@
 
     case VARY_NONE:
         /* No variance detected. Continue as normal */
+        flags.varyother = 0;
         break;
 
     case VARY_MATCH:
         /* This is the correct entity for this request. Continue */
         debug(88, 2) ("clientProcessHit: Vary MATCH!\n");
+        flags.varyother = 0;
         break;
 
     case VARY_OTHER:
+        
         /* This is not the correct entity for this request. We need
          * to requery the cache.
          */
+
+        flags.varyother = 1;
+        
+        // XXX won't it remove base object from memory
         removeClientStoreReference(&sc, http);
+        
         e = NULL;
+        
         /* Note: varyEvalyateMatch updates the request with vary information
          * so we only get here once. (it also takes care of cancelling loops)
          */
+        
+        debug(93, 9) ("cacheHit: r->vary_headers = %s, r->real_vary_headers = %s\n", 
+                      r->vary_headers, r->real_vary_headers);
+
         debug(88, 2) ("clientProcessHit: Vary detected!\n");
         clientGetMoreData(ourNode, http);
         return;
@@ -650,6 +959,7 @@
     case VARY_CANCEL:
         /* varyEvaluateMatch found a object loop. Process as miss */
         debug(88, 1) ("clientProcessHit: Vary object loop!\n");
+        flags.varyother = 0;
         processMiss();
         return;
     }
@@ -661,10 +971,15 @@
         return;
     }
 
+    
     if (storeCheckNegativeHit(e)) {
+   
         http->logType = LOG_TCP_NEGATIVE_HIT;
         sendMoreData(result);
+        
     } else if (!Config.onoff.offline && refreshCheckHTTP(e, r) && !http->flags.internal) {
+
+      
         debug(88, 5) ("clientCacheHit: in refreshCheck() block\n");
         /*
          * We hold a stale copy; it needs to be validated
@@ -678,9 +993,15 @@
          */
         r->flags.need_validation = 1;
 
-        if (e->lastmod < 0) {
+        // ETag check... 
+        const HttpHeader *rep_hdr = &e->getReply()->header;
+        ETag etag = httpHeaderGetETag(rep_hdr, HDR_ETAG);
+        
+        debug(93, 9) ("cacheHit: ETag: %s\n", etag.str);
+       
+        if (e->lastmod < 0 && !etag.str) {
             /*
-             * Previous reply didn't have a Last-Modified header,
+             * Previous reply didn't have a Last-Modified header or ETag,
              * we cannot revalidate it.
              */
             http->logType = LOG_TCP_MISS;
@@ -707,9 +1028,9 @@
             http->logType = LOG_TCP_MISS;
             processMiss();
         }
-    } else if (r->flags.ims) {
+    } else if (r->flags.ims || r->flags.inm) {
         /*
-         * Handle If-Modified-Since requests from the client
+         * Handle If-Modified-Since and If-None-Match requests from the client
          */
 
         if (e->getReply()->sline.status != HTTP_OK) {
@@ -717,17 +1038,67 @@
                           e->getReply()->sline.status);
             http->logType = LOG_TCP_MISS;
             processMiss();
-        } else if (e->modifiedSince(http->request)) {
+            return;
+
+        } 
+
+        // 304 vs 200 decisions
+        
+        // IMS based
+        int modifiedSince = r->flags.ims ? e->modifiedSince(http->request) : false;
+        
+        // INM based
+        int etagNoneMatch = r->flags.inm; 
+        
+        if (r->flags.inm) {
+
+          // r->inm is client ETags array
+          
+          // reply's ETag
+          const HttpHeader *hdr = &e->getReply()->header;
+          ETag etag = httpHeaderGetETag(hdr, HDR_ETAG);
+          const char *etag_str = etag.str;
+
+          debug(93,5) ("cacheHit: etag from stored reply %s\n",etag_str);
+         
+
+          // check if any of client's ETags matches
+          char *etag_inm;
+          int llen = 0;
+          
+          while(etag.str && llen < r->inmlen) {
+           
+            etag_inm = r->inm[llen++];
+            
+            debug(93,5) ("cacheHit: parsing client INM: %s\n",etag_inm);
+            
+            if (!strcmp(etag_str,etag_inm)) {
+              etagNoneMatch = false;
+              break;
+            }
+          }
+        }
+
+        if (modifiedSince || etagNoneMatch) {
+            // one of validators doesn't match -> send 200 OK
             http->logType = LOG_TCP_IMS_HIT;
             sendMoreData(result);
+
         } else {
+            // both validators match -> send 304 Not Modified
+            
             time_t const timestamp = e->timestamp;
+            
             HttpReply *temprep = httpReplyMake304 (e->getReply());
             http->logType = LOG_TCP_IMS_HIT;
+            
             removeClientStoreReference(&sc, http);
+            
             createStoreEntry(http->request->method,
                              request_flags());
+            
             e = http->storeEntry();
+            
             /*
              * Copy timestamp from the original entry so the 304
              * reply has a meaningful Age: header.
@@ -735,6 +1106,7 @@
             e->timestamp = timestamp;
             httpReplySwapOut (temprep, e);
             e->complete();
+            
             /* TODO: why put this in the store and then serialise it and then parse it again.
              * Simply mark the request complete in our context and
              * write the reply struct to the client side
@@ -782,6 +1154,20 @@
         removeClientStoreReference(&sc, http);
     }
 
+    // vary check
+    vary_entry = NULL;
+
+    // there was no variant matching client's request
+    // the flag was set in cacheHit with VARY_OTHER.
+    
+    if (flags.varyother) {
+      // base vary object
+      vary_entry = storeGetPublic(urlCanonical(r), r->method);
+    }
+
+      
+    debug(93,9) ("processMiss:  varyother: %d, vary_entry: %p\n", flags.varyother, vary_entry);
+
     if (r->method == METHOD_PURGE) {
         purgeRequest();
         return;
@@ -805,9 +1191,40 @@
         triggerInitialStoreRead();
         return;
     } else {
+      
         assert(http->out.offset == 0);
+        debug(93, 9) ("processMiss: before createStoreEntry()\n");
         createStoreEntry(r->method, r->flags);
-        triggerInitialStoreRead();
+
+        debug(93, 9) ("processMiss: entry = %p\n", http->storeEntry());
+
+        // vary procesing start... 
+
+        if (vary_entry) {
+          
+          http->request->lastmod = -1; 
+ 
+          assert(vary_entry->mem_obj);
+      
+          // get all the variants' etags
+          char * etag_keys = vary_entry->mem_obj->etag_mapping->keys();
+          debug (93, 9) ("processMiss: inm=[%s]\n", etag_keys);
+          
+          // and use 'em
+          if (etag_keys) {
+
+            http->request->etag = new String(etag_keys);
+
+            triggerInitialStoreReadClone(HandleIMSReply);
+
+            flags.varyvalidation = 1;
+
+          } else 
+            triggerInitialStoreReadClone(HandleConditionalMiss);
+
+        } else 
+          triggerInitialStoreReadClone(HandleConditionalMiss);
+
 
         if (http->redirect.status) {
             HttpReply *rep = httpReplyCreate();
@@ -821,9 +1238,11 @@
                               http->redirect.location);
             httpReplySwapOut(rep, http->storeEntry());
             http->storeEntry()->complete();
+            
             return;
         }
 
+        
         if (http->flags.internal)
             r->protocol = PROTO_INTERNAL;
 
@@ -1459,6 +1878,11 @@
         return;
     }
 
+    // if handleConditionalMiss decided to send 304
+    if (saved304) {
+      holdingReply = saved304;  
+    }
+
     /* enforce 1.0 reply version */
     holdingReply->sline.version = HttpVersion(1,0);
 
@@ -1664,6 +2088,7 @@
         reqofs = 0;
         /* guarantee nothing has been sent yet! */
         assert(http->out.size == 0);
+        storeLockObject(http->storeEntry());
         assert(http->out.offset == 0);
         tempBuffer.offset = reqofs;
         tempBuffer.length = getNextNode()->readBuffer.length;
@@ -1833,6 +2258,7 @@
     }
 
     headers_sz = rep->hdr_sz;
+    debug(93, 9) ("\t --- processReplyAccess: headers_sz: %d\n", headers_sz);
     ACLChecklist *replyChecklist;
     replyChecklist = clientAclChecklistCreate(Config.accessList.reply, http);
     replyChecklist->reply = rep;
@@ -1882,7 +2308,7 @@
     assert(body_size >= 0);
 
     debug(88,3)
-    ("clientReplyContext::sendMoreData: Appending %d bytes after %d bytes of headers\n",
+    ("clientReplyContext::processReplyAccessResult: Appending %d bytes after %d bytes of headers\n",
      (int) body_size, rep->hdr_sz);
 
 #if ESI
@@ -1896,7 +2322,11 @@
     }
 
 #endif
-    if (http->request->method == METHOD_HEAD) {
+
+    debug(93, 9) ("processReplyAccessResult: saved304: %p\n", 
+                  saved304);
+    // method was head or we're sending 304 so we don't have the body
+    if ((http->request->method == METHOD_HEAD) /*|| saved304*/) {
         /* do not forward body for HEAD replies */
         body_size = 0;
         http->flags.done_copying = 1;
@@ -1930,12 +2360,284 @@
     }
 
     /* TODO??: move the data in the buffer back by the request header size */
-    clientStreamCallback((clientStreamNode *)http->client_stream.head->data,
-                         http, rep, tempBuffer);
+    clientStreamCallback((clientStreamNode *)http->client_stream.head->data, http, rep, tempBuffer);
 
     return;
 }
 
+/*
+ * see handleConditionalMiss
+ */
+void
+clientReplyContext::HandleConditionalMiss(void *data, StoreIOBuffer result)
+{
+  clientReplyContext *context = static_cast<clientReplyContext *>(data);
+  context->handleConditionalMiss(result);
+}
+
+/*
+ * decides whether to send 304 or 200 
+ * when there is no cached reply and client's request was conditional
+ *
+ * in case the request isnot conditional passes to sendMoreData
+ */
+void 
+clientReplyContext::handleConditionalMiss(StoreIOBuffer result)
+{
+
+  
+  HttpRequest *r = http->request;
+  StoreEntry *e = http->storeEntry();
+  assert(r);
+  
+  debug(93, 9) ("handleConditionalMiss: ims: %d, inm: %d\n",
+                r->flags.ims, r->flags.inm);
+
+  // if the request isnot conditional
+  if (!r->flags.ims && !r->flags.inm) {
+    sendMoreData(result);
+    return;
+  }
+
+
+  debug(93, 9) ("handleConditionalMiss: Reply code %d\n",
+                e->getReply()->sline.status);
+
+  if (e->getReply()->sline.status != HTTP_OK ) {
+    
+    // not 200... passing
+    
+    sendMoreData(result);
+    return;
+    
+  } 
+
+  // check if validators match,  the same as in cacheHit
+  int modifiedSince = r->flags.ims ? e->modifiedSince(http->request) : false;
+  int etagNoneMatch = r->flags.inm; 
+  
+  if (r->flags.inm) {
+    const HttpHeader *hdr = &e->getReply()->header;
+    ETag etag = httpHeaderGetETag(hdr, HDR_ETAG);
+    const char *etag_str = etag.str;
+    char *etag_inm;
+    int llen = 0;
+
+    debug(93,5) ("handleConditionalMiss: etag from stored reply %s\n",etag_str);
+    
+    while(etag.str && llen < r->inmlen) {
+      etag_inm = r->inm[llen++];
+      if (!strcmp(etag_str,etag_inm)) {
+        etagNoneMatch = false;
+        break;
+      }
+    }
+  }
+
+  debug(93, 9) ("handleConditionalMiss: modifiedSince: %d, etagNoneMatch: %d\n", 
+                modifiedSince, etagNoneMatch);
+
+  if (modifiedSince || etagNoneMatch) {
+      // one of validators doesn't match -> send 200 OK
+      sendMoreData(result);
+      return;
+
+  } 
+  
+  // both validators match -> send 304 Not Modified
+  
+  // magic trick
+  saved304 = httpReplyMake304 (e->getReply());
+  saved304->saved304 = 1;
+  
+  // swap current 200 out -> clearly wrong
+  //e->complete();
+
+  // fill result with new saved304 reply
+  // XXX unnecessary?
+  //Packer p;
+  //packerToStoreIOBufferInit(&p, &result);
+  //httpReplyPackHeadersInto(saved304, &p);
+  //packerClean(&p);
+
+  //saved304->hdr_sz = result.length;
+
+
+  sendMoreData(result);
+}
+
+
+/*
+ * see updateFakeEntry
+ */
+void
+clientReplyContext::UpdateFakeEntry(void *data, StoreIOBuffer result)
+{
+    clientReplyContext *context = static_cast<clientReplyContext *>(data);
+    context->updateFakeEntry(result);
+}
+
+/*
+ * updates Vary mappings when result passes through sendMoreData
+ */
+void clientReplyContext::updateFakeEntry(StoreIOBuffer result)
+{
+  StoreEntry *entry = http->storeEntry();
+
+  ETag etag = httpHeaderGetETag(&entry->mem_obj->getReply()->header, HDR_ETAG); 
+  
+  // this is wrong when Vary has changed
+  const char *vary_headers = http->request->real_vary_headers;
+  
+  assert(vary_entry);
+
+  // get the mappings from base vary object
+  CharHashTable *vhm = vary_entry->mem_obj->vary_headers_mapping;
+  CharHashTable *em = vary_entry->mem_obj->etag_mapping;
+
+  // tells if the code below changes the mappings
+  int changed = 1;
+
+    if (!vhm->get((char*)vary_headers)) {
+    
+    if (etag.str) {
+      // there is ETag
+
+            if (em->get
+                    ((char*)etag.str)) {
+
+                vhm->put((char*)vary_headers, (char*)em->get
+                         ((char*)etag.str));
+            }
+            else {
+        vhm->put((char*)vary_headers, (char*)vary_headers);
+        em->put((char*)etag.str, (char*)vary_headers); 
+      } 
+    } else {
+      // no ETag
+      vhm->put((char*)vary_headers, (char*)vary_headers);
+    }
+    
+    }
+    else {
+    
+    if (etag.str) {
+      // there is ETag
+
+            if (em->get
+                    ((char*)etag.str)) {
+                if (strcmp(vhm->get
+                           ((char*)vary_headers), em->get
+                           ((char*)etag.str))) {
+
+                    vhm->remove
+                    ((char*)vary_headers);
+
+                    vhm->put((char*)vary_headers, em->get
+                             ((char*)etag.str));
+                }
+                else
+          changed = 0;
+            }
+            else {
+
+                vhm->remove
+                ((char*)vary_headers);
+
+        vhm->put((char*)vary_headers, (char*)vary_headers);
+
+        em->put((char*)etag.str, (char*)vary_headers); 
+      } 
+    } else 
+      changed = 0;
+  }
+
+  // XXX if (changed) swap out
+  debug(93, 9) ("updateFakeEntry: changed: %d\n", changed);
+  vhm->dump();
+  em->dump();
+
+  // MM 2004.06.29
+  if (changed) {
+
+    String vary = httpHeaderGetList(&vary_entry->mem_obj->getReply()->header, 
+                                    HDR_VARY);
+
+    // kill old base
+    storeRelease(vary_entry);
+    debug(93, 9) ("\t --> updateFakeEntry: vary_entry: %p, mem: %p\n", 
+                  vary_entry, vary_entry->mem_obj);
+
+    debug(93, 9) ("updateFakeEntry: url: %s, log_url: %s, method: %d\n",
+                  entry->mem_obj->url, entry->mem_obj->log_url, 
+                  http->request->method);
+
+    debug(93, 9) ("updateFakeEntry: url: %s, log_url: %s, method: %d\n",
+                  vary_entry->mem_obj->url, vary_entry->mem_obj->log_url, 
+                  http->request->method);
+
+    // new base
+    vary_entry = storeCreateEntry(vary_entry->mem_obj->url, 
+                                  vary_entry->mem_obj->log_url,
+                                  http->request->flags, 
+                                  http->request->method);
+    debug(93, 9) ("updateFakeEntry: vary_entry: %p\n", vary_entry);
+    HttpVersion version(1, 0);
+    httpReplySetHeaders((HttpReply *)vary_entry->getReply(), version, HTTP_OK, "Internal marker object", 
+                        "x-squid-internal/vary", -1, -1, squid_curtime + 100000);
+
+    debug(93, 9) ("updateFakeEntry: Internal marker object set\n");
+    debug(93, 9) ("updateFakeEntry: vary: %s\n", vary.buf());
+    if (vary.size()) {
+      httpHeaderPutStr((HttpHeader *)&vary_entry->getReply()->header, HDR_VARY, vary.buf());
+      vary.clean();
+    }
+
+    // mappings
+    vary_entry->mem_obj->etag_mapping = em;
+    vary_entry->mem_obj->vary_headers_mapping = vhm;
+
+    debug(93, 9) ("updateFakeEntry: etag_mapping and vary_headers_mapping set\n");
+    
+#if X_ACCELERATOR_VARY
+            vary = httpHeaderGetList(&mem->getReply()->header, HDR_X_ACCELERATOR_VARY);
+
+            if (vary.buf()) {
+                /* Again, we own this structure layout */
+                httpHeaderPutStr((HttpHeader *)&vary_entry->getReply()->header, HDR_X_ACCELERATOR_VARY, 
+                                 vary.buf());
+                vary.clean();
+            }
+
+#endif
+            storeSetPublicKey(vary_entry);
+            debug(93, 9) ("updateFakeEntry: storeSetPublicKey\n");
+
+            storeBuffer(vary_entry);
+            
+            debug(93, 9) ("updateFakeEntry: storeBuffer\n");
+
+            /* TODO: remove this when the metadata is separated */
+            {
+                Packer p;
+                packerToStoreInit(&p, vary_entry);
+                httpReplyPackHeadersInto(vary_entry->getReply(), &p);
+                packerClean(&p);
+            }
+
+            storeBufferFlush(vary_entry);
+            storeTimestampsSet(vary_entry);
+            vary_entry->complete();
+            storeUnlockObject(vary_entry);
+    
+  }
+
+  // set in sendMoreData to avoid reentering
+  if (flags.varycomplete == 1) 
+    sendMoreData(tempBufferVary);
+}
+
+
 void
 clientReplyContext::sendMoreData (StoreIOBuffer result)
 {
@@ -1944,6 +2646,69 @@
 
     StoreEntry *entry = http->storeEntry();
 
+
+    // this is wrong when Vary has changed
+    const char *vary_headers = http->request->real_vary_headers;
+
+    // if base vary object should be updated 
+    if (vary_headers && !flags.varycomplete) {
+      
+      // get base
+      vary_entry = storeGetPublic(entry->mem_obj->url, entry->mem_obj->method);
+      
+
+      if (vary_entry) {
+       
+        if (!vary_entry->mem_obj) {
+          debug(93, 9) ("sendMoreData: base vary object has no mem_obj\n");
+
+          // swap the mem_obj in
+          
+          storeCreateMemObject(vary_entry, http->uri, http->log_uri);
+          
+          vary_sc = storeClientListAdd(vary_entry, this);
+          
+          tempBufferVary = result;
+          StoreIOBuffer tempBuffer2(HTTP_REQBUF_SZ, 0, (char*)xmalloc(HTTP_REQBUF_SZ));
+          
+          flags.varycomplete = 1;
+          
+          // swap mem_obj in and go to updateFakeEntry
+          storeClientCopy(vary_sc, vary_entry, tempBuffer2, UpdateFakeEntry, this);
+          
+          return;
+        } else {
+          
+          flags.varycomplete = 2;
+
+          // go to updateFakeEntry
+          updateFakeEntry(result);
+        }
+        
+      } else {
+        
+        flags.varycomplete = 2;
+            debug(93, 9) ("sendMoreData: no base vary entry found!\n");
+            ;
+        
+      }
+      
+    } else if (vary_headers && flags.varycomplete == 1) {
+      
+      // after updateFakeEntry
+      
+      // unregister store client from base vary entry
+      // and continue
+      
+      assert(vary_entry);
+      
+      flags.varycomplete = 2;
+      
+      storeUnregister(vary_sc, vary_entry, this);
+    }
+    
+    debug(93, 9) ("sendMoreData: after vary processing\n");      
+
     ConnStateData::Pointer conn = http->getConn();
 
     int fd = conn.getRaw() != NULL ? conn->fd : -1;
@@ -2091,6 +2856,7 @@
     if (http->request == NULL)
         http->request = requestLink(requestCreate(m, PROTO_NONE, null_string));
 
+    debug(93, 9) ("createStoreEntry: before storeCreateEntry()\n");
     StoreEntry *e = storeCreateEntry(http->uri, http->log_uri, flags, m);
 
     sc = storeClientListAdd(e, this);
Index: squid3/src/client_side_reply.h
diff -u squid3/src/client_side_reply.h:1.6 squid3/src/client_side_reply.h:1.6.4.2
--- squid3/src/client_side_reply.h:1.6	Sun Aug 10 19:13:02 2003
+++ squid3/src/client_side_reply.h	Sat Jun  5 18:18:52 2004
@@ -51,7 +51,11 @@
     void operator delete (void *address);
     static STCB CacheHit;
     static STCB HandleIMSReply;
+    static STCB HandleConditionalMiss;
     static STCB SendMoreData;
+    static STCB UpdateFakeEntry;
+    static STCB FakeEntryToVariant;
+    static STCB VariantUpdateSend;
 
     clientReplyContext(clientHttpRequest *);
     ~clientReplyContext();
@@ -108,7 +112,12 @@
 
 int storelogiccomplete:
         1;
-
+int varycomplete:
+        2;
+int varyother: 
+        1;
+int varyvalidation: 
+        1;
 int complete:
         1;		/* we have read all we can from upstream */
         bool headersSent;
@@ -129,6 +138,8 @@
     void startSendProcess();
     StoreIOBuffer holdingBuffer;
     HttpReply *holdingReply;
+    
+    HttpReply *saved304;
     void processReplyAccess();
     static PF ProcessReplyAccessResult;
     void processReplyAccessResult(bool accessAllowed);
@@ -141,8 +152,15 @@
     void processOnlyIfCachedMiss();
     void cacheHit(StoreIOBuffer result);
     void handleIMSReply(StoreIOBuffer result);
+    
+    void updateFakeEntry(StoreIOBuffer result);
+    void fakeEntryToVariant(StoreIOBuffer result);
+    void variantUpdateSend(StoreIOBuffer result);
+    void handleConditionalMiss(StoreIOBuffer result);
+
     void sendMoreData(StoreIOBuffer result);
     void triggerInitialStoreRead();
+    void triggerInitialStoreReadClone(STCB* callback);
     void cleanUpAfterIMSCheck();
     void handlePartialIMSHeaders();
     void handleIMSGiveClientUpdatedOldEntry();
@@ -153,6 +171,13 @@
 
     StoreEntry *old_entry;
     store_client *old_sc;	/* ... for entry to be validated */
+
+    //base vary StoreEntry
+    StoreEntry *vary_entry;
+    store_client *vary_sc;
+    // buffer for base vary object updates
+    StoreIOBuffer tempBufferVary;
+    
     bool deleting;
 };
 
Index: squid3/src/client_side_request.cc
diff -u squid3/src/client_side_request.cc:1.28 squid3/src/client_side_request.cc:1.28.2.1
--- squid3/src/client_side_request.cc:1.28	Wed Apr  7 18:59:44 2004
+++ squid3/src/client_side_request.cc	Thu Jun  3 15:50:07 2004
@@ -548,6 +548,19 @@
     const char *str;
 #endif
 
+
+    // check for INM
+    String inm = httpHeaderGetINM(req_hdr);
+    debug(93, 1) ("clientInterpretRequestHeaders: INM: %s\n", inm.buf());
+
+    // parse INM into ETag array
+    if (fillArrayWithStr(&inm, &request->inm, &request->inmlen)) 
+      request->flags.inm = 1;
+    
+    debug(93, 9) ("clientInterpretRequestHeaders: inmlen=%d flags.inm=%d\n",request->inmlen, request->flags.inm);
+
+    
+    // check for IMS
     request->imslen = -1;
     request->ims = httpHeaderGetTime(req_hdr, HDR_IF_MODIFIED_SINCE);
 
Index: squid3/src/http.cc
diff -u squid3/src/http.cc:1.33 squid3/src/http.cc:1.33.2.3
--- squid3/src/http.cc:1.33	Sun Apr  4 19:00:28 2004
+++ squid3/src/http.cc	Sun Jul  4 09:56:59 2004
@@ -221,9 +221,14 @@
 
     assert(e->mem_obj);
 
-    if (e->mem_obj->request)
+    // XXX: here we deal with bad situation
+    // mmatusiak
+    if (e->mem_obj->request) {
+        const char *old_vary_headers = e->mem_obj->request->vary_headers;
+        e->mem_obj->request->vary_headers = e->mem_obj->request->real_vary_headers; 
         pe = storeGetPublicByRequest(e->mem_obj->request);
-    else
+        e->mem_obj->request->vary_headers = old_vary_headers;
+    } else
         pe = storeGetPublic(e->mem_obj->url, e->mem_obj->method);
 
     if (pe != NULL) {
@@ -235,6 +240,10 @@
      * Also remove any cached HEAD response in case the object has
      * changed.
      */
+
+    // XXX: here we deal with bad situation
+    // but for this time we just ignore HEAD...
+    // mmatusiak
     if (e->mem_obj->request)
         pe = storeGetPublicByRequestMethod(e->mem_obj->request, METHOD_HEAD);
     else
@@ -601,6 +610,8 @@
     debug(11, 3) ("httpProcessReplyHeader: key '%s'\n",
                   entry->getMD5Text());
 
+    debug(93, 9) ("processReplyHeader: reply got, header being parsed\n");
+    
     if (reply_hdr == NULL)
         reply_hdr = (char *)memAllocate(MEM_8K_BUF);
 
@@ -698,6 +709,8 @@
 
             if (vary) {
                 entry->mem_obj->vary_headers = xstrdup(vary);
+                /* XXX: ^^^ possible memory leak */
+
                 /* Kill the old base object if a change in variance is detected */
                 httpMakePublic(entry);
             } else {
@@ -937,9 +950,12 @@
              * we want to process the reply headers.
              */
             /* doesn't return */
+        {
             processReplyHeader(buf, len);
+        }
         else {
             fwdComplete(fwd);
+
             do_next_read = 0;
             comm_close(fd);
         }
@@ -972,6 +988,7 @@
 void
 HttpStateData::processReplyData(const char *buf, size_t len)
 {
+
     if (reply_hdr_state < 2) {
         do_next_read = 1;
         maybeReadData();
@@ -1002,7 +1019,12 @@
         tempBuffer.length = len;
         tempBuffer.offset = currentOffset;
         currentOffset += len;
+        
+        // store_status may be STORE_OK if we passed through handleConditionalMiss 
+        // and decided to send 304 there
+        //if (entry->store_status == STORE_PENDING)
         entry->write(tempBuffer);
+
     }
 
     if (EBIT_TEST(entry->flags, ENTRY_ABORTED)) {
@@ -1016,11 +1038,13 @@
         switch (persistentConnStatus()) {
 
         case INCOMPLETE_MSG:
+            debug(93, 9) ("processReplyData: persistentConnStatus(): INCOMPLETE_MSG\n");
             /* Wait for EOF condition */
             do_next_read = 1;
             break;
 
         case COMPLETE_PERSISTENT_MSG:
+            debug(93, 9) ("processReplyData: persistentConnStatus(): COMPLETE_PERSISTENT_MSG\n");
             /* yes we have to clear all these! */
             commSetTimeout(fd, -1, NULL, NULL);
             do_next_read = 0;
@@ -1043,6 +1067,7 @@
             return;
 
         case COMPLETE_NONPERSISTENT_MSG:
+            debug(93, 9) ("processReplyData: persistentConnStatus(): COMPLETE_NONPERSISTENT_MSG\n");
             /* close the connection ourselves */
             /* yes - same as for a complete persistent conn here */
             commSetTimeout(fd, -1, NULL, NULL);
@@ -1057,6 +1082,7 @@
             return;
         }
 
+    debug(93, 9) ("processReplyData: before maybeReadData\n");
     maybeReadData();
 }
 
@@ -1137,9 +1163,15 @@
     assert (hdr_out->owner == hoRequest);
     /* append our IMS header */
 
+    // add If-Modified-Since  if needed
     if (request->lastmod > -1 && request->method == METHOD_GET)
         httpHeaderPutTime(hdr_out, HDR_IF_MODIFIED_SINCE, request->lastmod);
 
+    // add If-None-Match if needed, etags prepared earlier
+    if (request->etag && request->method == METHOD_GET) {
+      httpHeaderPutStr(hdr_out, HDR_IF_NONE_MATCH, request->etag->buf());
+    }
+    
     bool we_do_ranges = decideIfWeDoRanges (orig_request);
 
     String strConnection (httpHeaderGetList(hdr_in, HDR_CONNECTION));
@@ -1406,12 +1438,9 @@
 
         break;
 
+    // don't pass clients IMS or INM
     case HDR_IF_MODIFIED_SINCE:
-        /* append unless we added our own;
-         * note: at most one client's ims header can pass through */
-
-        if (!httpHeaderHas(hdr_out, HDR_IF_MODIFIED_SINCE))
-            httpHeaderAddEntry(hdr_out, httpHeaderEntryClone(e));
+    case HDR_IF_NONE_MATCH:
 
         break;
 
Index: squid3/src/protos.h
diff -u squid3/src/protos.h:1.41 squid3/src/protos.h:1.41.2.2
--- squid3/src/protos.h:1.41	Sun Apr  4 19:00:28 2004
+++ squid3/src/protos.h	Sun Jun  6 17:35:41 2004
@@ -163,6 +163,7 @@
 
 SQUIDCEXTERN void packerToStoreInit(Packer * p, StoreEntry * e);
 SQUIDCEXTERN void packerToMemInit(Packer * p, MemBuf * mb);
+
 SQUIDCEXTERN void packerClean(Packer * p);
 SQUIDCEXTERN void packerAppend(Packer * p, const char *buf, int size);
 #if STDC_HEADERS
@@ -415,6 +416,10 @@
 SQUIDCEXTERN const char *httpHeaderGetStr(const HttpHeader * hdr, http_hdr_type id);
 SQUIDCEXTERN const char *httpHeaderGetLastStr(const HttpHeader * hdr, http_hdr_type id);
 SQUIDCEXTERN const char *httpHeaderGetAuth(const HttpHeader * hdr, http_hdr_type id, const char *auth_scheme);
+
+extern int fillArrayWithStr(const String *, char ***, int *);
+extern String httpHeaderGetINM(const HttpHeader * hdr);
+
 extern String httpHeaderGetList(const HttpHeader * hdr, http_hdr_type id);
 extern String httpHeaderGetStrOrList(const HttpHeader * hdr, http_hdr_type id);
 extern String httpHeaderGetByName(const HttpHeader * hdr, const char *name);
Index: squid3/src/store.cc
diff -u squid3/src/store.cc:1.25 squid3/src/store.cc:1.25.2.2
--- squid3/src/store.cc:1.25	Mon Sep 29 19:12:40 2003
+++ squid3/src/store.cc	Sat Jun  5 18:18:52 2004
@@ -310,8 +310,19 @@
 {
     storeSetMemStatus(e, NOT_IN_MEMORY);
     MemObject *mem = e->mem_obj;
+    
+    // since MemObject holds additional vary information, this has to be done
+    if (mem) {
+      
+      if (mem->etag_mapping)
+        delete mem->etag_mapping;
+        
+      if (mem->vary_headers_mapping)
+        delete mem->vary_headers_mapping;
+        
     e->mem_obj = NULL;
     delete mem;
+    }
 }
 
 static void
@@ -565,6 +576,8 @@
     const cache_key *newkey;
     MemObject *mem = e->mem_obj;
 
+    debug(93, 9) ("storeSetPrivateKey: start\n");
+
     if (e->key && EBIT_TEST(e->flags, KEY_PRIVATE))
         return;			/* is already private */
 
@@ -594,6 +607,8 @@
     const cache_key *newkey;
     MemObject *mem = e->mem_obj;
 
+    debug(93, 9) ("storeSetPublicKey: start\n");
+
     if (e->key && !EBIT_TEST(e->flags, KEY_PRIVATE))
         return;			/* is already public */
 
@@ -619,6 +634,7 @@
 
     assert(!EBIT_TEST(e->flags, RELEASE_REQUEST));
 
+    debug(93, 9) ("storeSetPublicKey: later...\n");
     if (mem->request) {
         StoreEntry *pe;
         HttpRequest *request = mem->request;
@@ -626,12 +642,17 @@
         if (!mem->vary_headers) {
             /* First handle the case where the object no longer varies */
             safe_free(request->vary_headers);
+            safe_free(request->real_vary_headers);
+            
         } else {
-            if (request->vary_headers && strcmp(request->vary_headers, mem->vary_headers) != 0) {
+          
+            if (request->real_vary_headers && strcmp(request->real_vary_headers, mem->vary_headers) != 0) {
                 /* Oops.. the variance has changed. Kill the base object
                  * to record the new variance key
                  */
                 safe_free(request->vary_headers);	/* free old "bad" variance key */
+                safe_free(request->real_vary_headers);	/* free old "bad" variance key */
+
                 pe = storeGetPublic(mem->url, mem->method);
 
                 if (pe)
@@ -662,6 +683,19 @@
                 vary.clean();
             }
 
+            // add mappings
+            pe->mem_obj->etag_mapping = new CharHashTable();
+            pe->mem_obj->vary_headers_mapping = new CharHashTable();
+
+            // fill the mappings
+            pe->mem_obj->vary_headers_mapping->put((char*)request->vary_headers, (char*)request->vary_headers);
+            ETag etag = httpHeaderGetETag(&mem->getReply()->header, HDR_ETAG); 
+            // XXX ^^^ memory leak?
+          
+            if (etag.str) {
+              pe->mem_obj->etag_mapping->put((char*)etag.str, (char*)request->vary_headers);
+            }
+
 #if X_ACCELERATOR_VARY
             vary = httpHeaderGetList(&mem->getReply()->header, HDR_X_ACCELERATOR_VARY);
 
@@ -690,11 +724,25 @@
             storeUnlockObject(pe);
         }
 
+
+        debug(93, 9) ("storeSetPublicKey: mem has request\n");
+
+        const char *old_vary_headers = mem->request->vary_headers;
+
+        if (mem->request->real_vary_headers) {
+          mem->request->vary_headers = mem->request->real_vary_headers;
+        }
+        debug(93, 9) ("storeSetPublicKey: vary_headers = %s\n", mem->request->vary_headers);
         newkey = storeKeyPublicByRequest(mem->request);
-    } else
+
+        mem->request->vary_headers = old_vary_headers;
+    } else {
+        debug(93, 9) ("storeSetPublicKey: mem has no request\n");
         newkey = storeKeyPublic(mem->url, mem->method);
+    }
 
     if ((e2 = (StoreEntry *) hash_lookup(store_table, newkey))) {
+        debug(93, 9) ("storeSetPublicKey: entry found = %p\n", e2);
         debug(20, 3) ("storeSetPublicKey: Making old '%s' private.\n", mem->url);
         storeSetPrivateKey(e2);
         storeRelease(e2);
Index: squid3/src/store_client.cc
diff -u squid3/src/store_client.cc:1.18 squid3/src/store_client.cc:1.18.2.1
--- squid3/src/store_client.cc:1.18	Mon Oct 20 19:12:45 2003
+++ squid3/src/store_client.cc	Sat Jun  5 18:18:52 2004
@@ -636,6 +636,9 @@
 int
 storeUnregister(store_client * sc, StoreEntry * e, void *data)
 {
+
+    debug(93, 9) ("storeUnregister: entry = %p\n", e);
+
     MemObject *mem = e->mem_obj;
 #if STORE_CLIENT_LIST_DEBUG
 
Index: squid3/src/store_key_md5.cc
diff -u squid3/src/store_key_md5.cc:1.5 squid3/src/store_key_md5.cc:1.5.2.1
--- squid3/src/store_key_md5.cc:1.5	Sun Aug 10 19:13:03 2003
+++ squid3/src/store_key_md5.cc	Sat Jun  5 18:18:52 2004
@@ -144,6 +144,9 @@
     MD5Update(&M, &m, sizeof(m));
     MD5Update(&M, (unsigned char *) url, strlen(url));
 
+    // XXX: suspection, should be real_vary_headers?
+    // no, this breaks some other stuff
+    // mmatusiak
     if (request->vary_headers)
         MD5Update(&M, (unsigned char *) request->vary_headers, strlen(request->vary_headers));
 
Index: squid3/src/store_swapmeta.cc
diff -u squid3/src/store_swapmeta.cc:1.5 squid3/src/store_swapmeta.cc:1.5.2.1
--- squid3/src/store_swapmeta.cc:1.5	Mon Aug  4 19:12:49 2003
+++ squid3/src/store_swapmeta.cc	Thu Jun  3 15:50:07 2004
@@ -39,6 +39,8 @@
 #include "StoreMeta.h"
 #include "StoreMetaUnpacker.h"
 
+#include "StoreMetaVary.h"
+
 void
 storeSwapTLVFree(tlv * n)
 {
@@ -60,7 +62,11 @@
     tlv *TLV = NULL;		/* we'll return this */
     tlv **T = &TLV;
     const char *url;
-    const char *vary;
+    
+    // packed vary stuff
+    const char *packed_vary;
+    int packed_vary_len = 0;
+
     assert(e->mem_obj != NULL);
     assert(e->swap_status == SWAPOUT_WRITING);
     url = storeUrl(e);
@@ -89,10 +95,11 @@
     }
 
     T = StoreMeta::Add(T, t);
-    vary = e->mem_obj->vary_headers;
+    // pack vary stuff 
+    packed_vary = storeMetaVaryPack(e->mem_obj, &packed_vary_len);
 
-    if (vary) {
-        t =StoreMeta::Factory(STORE_META_VARY_HEADERS, strlen(vary) + 1, vary);
+    if (packed_vary) {
+        t =StoreMeta::Factory(STORE_META_VARY_HEADERS, packed_vary_len, packed_vary);
 
         if (!t) {
             storeSwapTLVFree(TLV);
Index: squid3/src/store_swapout.cc
diff -u squid3/src/store_swapout.cc:1.10 squid3/src/store_swapout.cc:1.10.2.1
--- squid3/src/store_swapout.cc:1.10	Wed Feb 11 19:09:05 2004
+++ squid3/src/store_swapout.cc	Thu Jun  3 15:50:07 2004
@@ -172,6 +172,7 @@
 void
 storeSwapOut(StoreEntry * e)
 {
+    
     if (!e->mem_obj)
         return;
 
Index: squid3/src/structs.h
diff -u squid3/src/structs.h:1.51 squid3/src/structs.h:1.51.2.1
--- squid3/src/structs.h:1.51	Sun Apr  4 19:00:28 2004
+++ squid3/src/structs.h	Thu Jun  3 15:50:07 2004
@@ -1412,6 +1412,9 @@
 unsigned int ims:
     1;
 
+unsigned int inm:
+    1;
+
 unsigned int auth:
     1;
 
