MEDIUM: cache/compression: Add a way to safely combined compression and cache

This is only true for HTX proxies. On legacy HTTP proxy, if the compression and
the cache are both enabled, an error during HAProxy startup is triggered.

With the HTX, now you can use both in any order. If the compression is defined
before the cache, then the responses will be stored compressed. If the
compression is defined after the cache, then the responses will be stored
uncompressed. So in the last case, when a response is served from the cache, it
will compressed too like any response.
diff --git a/src/cache.c b/src/cache.c
index 834efc1..fd8a00a 100644
--- a/src/cache.c
+++ b/src/cache.c
@@ -43,6 +43,9 @@
 					* messages (legacy implementation) */
 #define CACHE_F_HTX         0x00000002 /* The cache is used to store HTX messages */
 
+#define CACHE_FLT_F_IGNORE_CNT_ENC 0x00000001 /* Ignore 'Content-Encoding' header when response is cached
+					       * if compression is already started */
+
 const char *cache_store_flt_id = "cache store filter";
 
 struct applet http_cache_applet;
@@ -154,7 +157,9 @@
 cache_store_check(struct proxy *px, struct flt_conf *fconf)
 {
 	struct cache_flt_conf *cconf = fconf->conf;
+	struct flt_conf *f;
 	struct cache *cache;
+	int ignore = 0;
 
 	/* resolve the cache name to a ptr in the filter config */
 	list_for_each_entry(cache, &caches, list) {
@@ -175,6 +180,34 @@
 	return 1;
 
   found:
+	/* Here <cache> points on the cache the filter must use and <cconf>
+	 * points on the cache filter configuration. */
+
+	/* Check all filters for proxy <px> to know if the compression is
+	 * enabled and if it is before or after the cache. When the compression
+	 * is before the cache, nothing special is done. The response is stored
+	 * compressed in the cache. When the compression is after the cache, the
+	 * 'Content-encoding' header must be ignored because the response will
+	 * be stored uncompressed. The compression will be done on the cached
+	 * response too. */
+	list_for_each_entry(f, &px->filter_configs, list) {
+		if (f == fconf) {
+			ignore = 1;
+			continue;
+		}
+
+		if (f->id == http_comp_flt_id) {
+			if (!(px->options2 & PR_O2_USE_HTX)) {
+				ha_alert("config: %s '%s' : compression and cache filters cannot be "
+					 "both enabled on non HTX proxy.\n",
+					 proxy_type_str(px), px->id);
+				return 1;
+			}
+			if (ignore)
+				cconf->flags |= CACHE_FLT_F_IGNORE_CNT_ENC;
+			break;
+		}
+	}
 	return 0;
 }
 
@@ -630,10 +663,26 @@
 		chunk_reset(&trash);
 		for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) {
 			struct htx_blk *blk = htx_get_blk(htx, pos);
+			enum htx_blk_type type = htx_get_blk_type(blk);
 			uint32_t sz = htx_get_blksz(blk);
 
+			/* Check if we need to skip 'Content-encoding' header or not */
+			if ((msg->flags & HTTP_MSGF_COMPRESSING) && /* Compression in progress */
+			    (cconf->flags & CACHE_FLT_F_IGNORE_CNT_ENC) &&  /* Compression before the cache */
+			    (type == HTX_BLK_HDR)) {
+				struct ist n = htx_get_blk_name(htx, blk);
+				struct ist v = htx_get_blk_value(htx, blk);
+
+				if (isteq(n, ist("content-encoding")))
+					continue;
+				if (!(msg->flags & HTTP_MSGF_TE_CHNK) &&
+				    isteq(n, ist("transfer-encoding")) &&
+				    isteqi(v, ist("chunked")))
+				    continue;
+			}
+
 			chunk_memcat(&trash, (char *)&blk->info, sizeof(blk->info));
-			if (htx_get_blk_type(blk) == HTX_BLK_EOH)
+			if (type == HTX_BLK_EOH)
 				break;
 			chunk_memcat(&trash, htx_get_blk_ptr(htx, blk), sz);
 		}