]> git.kaiwu.me - haproxy.git/commitdiff
MEDIUM: flt-decomp: implement decomp-req and decomp-res filters
authorAurelien DARRAGON <adarragon@haproxy.com>
Wed, 12 Aug 2026 15:11:31 +0000 (17:11 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 28 Aug 2026 15:26:38 +0000 (17:26 +0200)
Initial filter implementation for decompressing gzip/zlib streams by
leveraging libslz/uslz API.

For now both request and response filters only work for TCP proxies
(with "mode tcp" set). While this is currently not possible to
compress TCP streams using the existing compression filters, thus
making the TCP decompression less useful than the upcoming HTTP
decompression, basic tcp decompression implementation helped during
development to stress test the libslz decompression part without
falling in the haproxy HTX API pitfalls straight away. However it
turns out the TCP decompression may find some use-cases, so let's
keep that for now.

With TCP decompression now supported, we have all the code
infrastructure needed to perform HTTP decompression, which will come
in a later patch.

Makefile
include/haproxy/decompression-t.h
include/haproxy/filters.h
include/haproxy/proxy-t.h
src/flt_decomp.c [new file with mode: 0644]
src/proxy.c

index bb12af2ec56a4674ec1dcb2511428146f322de28..579349c07a8254b1a034fb47f25ba094a7e84cf5 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -985,7 +985,7 @@ OBJS += src/mux_h1.o src/log.o                                              \
         src/http_acl.o src/dict.o src/dgram.o src/pipe.o               \
         src/hpack-huff.o src/hpack-enc.o src/ebtree.o src/hash.o       \
         src/httpclient_cli.o src/version.o src/ncbmbuf.o src/ech.o     \
-        src/cfgparse-peers.o src/haterm.o
+        src/flt_decomp.o src/cfgparse-peers.o src/haterm.o
 
 ifneq ($(TRACE),)
   OBJS += src/calltrace.o
index 700896359d061441e651fae53122467dfcb15ffb..35c7a466a3ca48ea0dafc806b3a2e100dd3f5bef 100644 (file)
@@ -44,7 +44,7 @@
 /* DECOMP_DIR_FL_MODE modes are mutually exclusive */
 #define DECOMP_DIR_FL_MODE_MASK     0x0007
 #define DECOMP_DIR_FL_MODE_NONE     0x0001 /* default: do nothing */
-#define DECOMP_DIR_FL_MODE_AUTO     0x0002 /* best effort */
+#define DECOMP_DIR_FL_MODE_AUTO     0x0002 /* best effort, only available for HTTP decomp */
 #define DECOMP_DIR_FL_MODE_ALWAYS   0x0004 /* guarantees the output stream will be decompressed */
 
 /* Decompression ctx flags */
index c59fed3c89f26442d643cab1e05742d44d84f534..636cb85db292a4ebe81ea5a99331974371d47b3f 100644 (file)
@@ -30,6 +30,8 @@
 extern const char *trace_flt_id;
 extern const char *http_comp_req_flt_id;
 extern const char *http_comp_res_flt_id;
+extern const char *decomp_req_flt_id;
+extern const char *decomp_res_flt_id;
 
 extern const char *cache_store_flt_id;
 extern const char *spoe_filter_id;
index 7b65e67a2d3dbfd5239d55c32de545f3baa504a3..aca81a192225286bc63290b1503132e7cd489e92 100644 (file)
@@ -33,6 +33,7 @@
 #include <haproxy/arg-t.h>
 #include <haproxy/backend-t.h>
 #include <haproxy/compression-t.h>
+#include <haproxy/decompression-t.h>
 #include <haproxy/counters-t.h>
 #include <haproxy/guid-t.h>
 #include <haproxy/obj_type-t.h>
@@ -494,6 +495,7 @@ struct proxy {
        struct ceb_root *used_server_addr;      /* list of server addresses in use */
        void *parent;                           /* parent of the proxy when applicable */
        struct comp *comp;                      /* http compression */
+       struct decomp *decomp;                  /* tcp/http decompression */
 
        struct {
                union {
diff --git a/src/flt_decomp.c b/src/flt_decomp.c
new file mode 100644 (file)
index 0000000..7fd8852
--- /dev/null
@@ -0,0 +1,694 @@
+/*
+ * Stream filters related to decompression.
+ *
+ * Copyright 2022 HAProxy Technologies
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <haproxy/api.h>
+#include <haproxy/cfgparse.h>
+#include <haproxy/compression.h>
+#include <haproxy/dynbuf.h>
+#include <haproxy/filters.h>
+#include <haproxy/http.h>
+#include <haproxy/http_ana-t.h>
+#include <haproxy/http_htx.h>
+#include <haproxy/htx.h>
+#include <haproxy/list.h>
+#include <haproxy/proxy.h>
+#include <haproxy/sample.h>
+#include <haproxy/stream.h>
+#include <haproxy/tools.h>
+
+#define DECOMP_STATE_PROCESSING 0x01
+
+const char *decomp_req_flt_id = "decomp-req filter";
+const char *decomp_res_flt_id = "decomp-res filter";
+
+struct flt_ops decomp_req_ops;
+struct flt_ops decomp_res_ops;
+
+struct decomp_state {
+       struct decomp_ctx  *decomp_ctx;   /* decompression context */
+       struct decomp_algo *decomp_algo;  /* decompression algorithm if not NULL */
+       unsigned int      flags;          /* DECOMP_STATE_* */
+};
+
+/* Pools used to allocate comp_state structs */
+DECLARE_STATIC_TYPED_POOL(pool_head_decomp_state, "decomp_state", struct decomp_state);
+
+/***********************************************************************/
+static int
+decomp_flt_init(struct proxy *px, struct flt_conf *fconf)
+{
+       fconf->flags |= FLT_CFG_FL_HTX;
+       return 0;
+}
+
+static int
+decomp_strm_init(struct stream *s, struct filter *filter)
+{
+       struct decomp_dir *decomp_dir = FLT_CONF(filter);
+       struct decomp_state *st;
+
+       BUG_ON(!decomp_dir);
+
+       if ((decomp_dir->flags & DECOMP_DIR_FL_MODE_NONE) || !decomp_dir->algos)
+               return 0;
+
+       st = pool_alloc(pool_head_decomp_state);
+       if (st == NULL)
+               return -1;
+
+       st->decomp_algo = NULL;
+       st->decomp_ctx = NULL;
+       st->flags     = 0;
+       filter->ctx   = st;
+
+       return 1;
+}
+
+static void
+decomp_strm_deinit(struct stream *s, struct filter *filter)
+{
+       struct decomp_state *st = filter->ctx;
+
+       if (!st)
+               return;
+
+       /* release any possible decompression context */
+       if (st->decomp_algo)
+               st->decomp_algo->end(&st->decomp_ctx);
+       pool_free(pool_head_decomp_state, st);
+       filter->ctx = NULL;
+}
+
+/* for the non-htx decompression, returns the max number of bytes that
+ * can be drained in the channel at a current time, depending on both
+ * the channel buffer space available, the number of bytes about to be
+ * consumed, and the number of bytes ready to be drained.
+ */
+static uint decomp_drainable_bytes(struct filter *f, struct channel *chn,
+                                   uint consumed)
+{
+       struct decomp_state *st = f->ctx;
+       int available;
+       unsigned int max_drain;
+
+       /* we take the responsibility to use some of the reserved space
+        * (global.tune.maxrewrite) as we work on output path and there is
+        * the possibility that we are called with no room left except for
+        * the reserve. As there may be another transformation filter right
+        * after us (ie: compression filter), we cannot simply use all the
+        * reserve space because this could cause such filters to stall. So
+        * instead we use up to one third of the reserve, which ensures that
+        * a potential compression filter coming right after us would work
+        * without any issue, at minima.
+        *
+        * We may need to compute this dynamically by taking into account
+        * the number of remaining filters in the chain some day FIXME.
+        *
+        * Note we rely on b_room() and not on b_contig_space() because
+        * b_rep_blk() is wrapping-proof, so a wrapping insertion is fine and
+        * the whole free space is usable.
+        */
+       available = b_room(&chn->buf) - (global.tune.maxrewrite / 3);
+
+       /* adjust available bytes according to consumed bytes which are about to be removed */
+       available += consumed;
+
+       if (available < 0)
+               available = 0;
+
+       max_drain = MIN(available, st->decomp_ctx->drain_len);
+
+       return max_drain;
+}
+
+/* helper for decomp_strm_tcp_payload() function. It consumes as much input as
+ * possible from <offset> in the channel buffer (up to <len> bytes), decompresses
+ * it and writes the resulting bytes in place, where the consumed input was.
+ *
+ * It loops as long as it makes progress so that a single call flushes as much
+ * data as the channel can take. This matters because the decompressed data may
+ * be far bigger than the input: stopping after the first block would leave
+ * pending data behind with nothing to trigger a new call once the input is
+ * exhausted.
+ *
+ * The number of bytes ready to be forwarded is returned, or -1 on error.
+ */
+static int decomp_add_data(struct stream *s, struct filter *f, struct channel *chn,
+                           unsigned int offset, unsigned int len)
+{
+       struct decomp_state *st = f->ctx;
+       unsigned int total = 0; /* decompressed bytes written in the channel */
+       int ret;
+
+       while (1) {
+               char *data = b_peek(&chn->buf, offset + total);
+               unsigned int contig = b_contig_data(&chn->buf, offset + total);
+               unsigned int consumed = 0;
+               unsigned int data_len;
+               uint drainable;
+
+               /* we only look at contiguous input because b_rep_blk() requires
+                * the replaced block to not be interrupted by the wrapping
+                */
+               data_len = MIN(len, contig);
+               BUG_ON(data > b_wrap(&chn->buf));
+
+               /* Refill the decompression buffer once the previous output was
+                * fully drained. An empty input is valid, it just asks the
+                * library to flush what it may still hold.
+                */
+               if (!st->decomp_ctx->drain_len && !(st->decomp_ctx->flags & DECOMP_CTX_FL_DONE)) {
+                       ret = st->decomp_algo->add_data(st->decomp_ctx, data, data_len);
+                       if (ret == -1)
+                               goto error; /* fatal error */
+                       BUG_ON(ret > data_len);
+                       consumed = ret;
+               }
+
+               drainable = decomp_drainable_bytes(f, chn, consumed);
+
+               if (!drainable && !consumed) {
+                       /* nothing moved: either no room left for the pending
+                        * decompressed data, or the library needs more input
+                        */
+                       break;
+               }
+
+               /* replace the consumed input with the decompressed data */
+               b_rep_blk(&chn->buf, data, data + consumed,
+                         (drainable ? st->decomp_ctx->drain : NULL), drainable);
+               flt_update_offsets(f, chn, drainable - consumed);
+               st->decomp_ctx->drain += drainable;
+               st->decomp_ctx->drain_len -= drainable;
+               total += drainable;
+               len -= consumed;
+       }
+
+       if (len && !st->decomp_ctx->drain_len &&
+           (st->decomp_ctx->flags & DECOMP_CTX_FL_DONE)) {
+               /* Decompression is over, drop any remaining input: it is not
+                * part of the compressed stream. This is not supposed to happen
+                * unless the input is garbage filled.
+                */
+               char *data = b_peek(&chn->buf, offset + total);
+               unsigned int drop = MIN(len, b_contig_data(&chn->buf, offset + total));
+
+               b_rep_blk(&chn->buf, data, data + drop, NULL, 0);
+               flt_update_offsets(f, chn, -drop);
+               len -= drop;
+       }
+
+       if (st->decomp_ctx->drain_len) {
+               /* We still have decompressed data to deliver but no room left
+                * for it. We surely don't want new input for now as it would
+                * eat up the space we need.
+                */
+               channel_dont_read(chn);
+       }
+       else
+               channel_auto_read(chn);
+
+       return total;
+  error:
+       return -1;
+}
+
+static int decomp_strm_tcp_payload(struct stream *s, struct filter *f, struct channel *chn,
+                                   unsigned int offset, unsigned int len)
+{
+       struct decomp_state *st = f->ctx;
+
+       if (IS_HTX_STRM(s)) {
+               /* TCP data within HTTP stream (ie: tunnel data) but it means
+                * the proxy was in HTTP mode, while we only care about TCP stream
+                * so let's ignore data.
+                */
+               return len;
+       }
+
+       if (!chn->buf.area) {
+               /* no buffer allocated for the channel, if we still have
+                * data to drain we actually need a buffer to write pending
+                * data
+                */
+               if (!st->decomp_ctx->drain_len || channel_alloc_buffer(chn, &s->buffer_wait) == 0)
+                       return 0; /* either no data to drain, or failed to alloc buffer */
+               /* we now have a buffer, let's continue */
+       }
+
+       return decomp_add_data(s, f, chn, offset, len);
+}
+
+/* decompress request oriented stream */
+int decomp_strm_req_tcp_payload(struct stream *s, struct filter *f, struct channel *chn,
+                                unsigned int offset, unsigned int len)
+{
+       if ((chn->flags & CF_ISRESP))
+               return len; // nothing to do
+
+       return decomp_strm_tcp_payload(s, f, chn, offset, len);
+}
+
+/* decompress response oriented stream */
+int decomp_strm_res_tcp_payload(struct stream *s, struct filter *f, struct channel *chn,
+                                unsigned int offset, unsigned int len)
+{
+       if (!(chn->flags & CF_ISRESP))
+               return len; // nothing to do
+
+       return decomp_strm_tcp_payload(s, f, chn, offset, len);
+}
+
+/* Returns decompression options to be used for <s> stream. Only
+ * call this function when you know for sure that decompression filter
+ * was declared on the proxy.
+ * This function will never return NULL.
+ */
+static inline struct decomp *stream_get_decomp(struct stream *s)
+{
+       struct decomp *decomp;
+
+       /* we consider be decompression options in over frontend ones
+        * unless it is not set at all on the be
+        */
+       if (s->be->decomp && s->be->decomp->res.algos != NULL)
+               decomp = s->be->decomp;
+       else
+               decomp = strm_fe(s)->decomp;
+
+       /* we end up there via a filter callback in decompression context, thus
+        * we are not supposed to be called w/o decomp options allocated.
+        */
+       BUG_ON(decomp == NULL);
+
+       return decomp;
+}
+
+/* initialize decompression for decomp-req filter */
+int decomp_channel_start_req_ana(struct stream *s, struct filter *f, struct channel *chn)
+{
+       struct decomp_state *st = f->ctx;
+       struct decomp_dir *decomp_req = FLT_CONF(f);
+
+       if ((chn->flags & CF_ISRESP))
+               return 1; // nothing to do
+
+       if (s->be->mode == PR_MODE_HTTP) {
+               /* algo will be decided later based on HTTP headers */
+               goto skip_algo;
+       }
+
+       /* in TCP, we only consider the first algo in the list, since the stream
+        * does not tell us which algo was used to compress it.
+        */
+       st->decomp_algo = decomp_req->algos;
+       if (st->decomp_algo->init(&st->decomp_ctx) < 0)
+               goto fail;
+
+       if (st->decomp_algo->stream_new && st->decomp_algo->stream_new(st->decomp_ctx) < 0)
+               goto fail;
+
+       st->flags |= DECOMP_STATE_PROCESSING;
+       register_data_filter(s, chn, f);
+
+ skip_algo:
+       return 1;
+ fail:
+       return -1;
+}
+
+/* initialize decompression for decomp-res filter */
+int decomp_channel_start_res_ana(struct stream *s, struct filter *f, struct channel *chn)
+{
+       struct decomp_state *st = f->ctx;
+       struct decomp_dir *decomp_res = FLT_CONF(f);
+
+       if (!(chn->flags & CF_ISRESP))
+               return 1; // nothing to do
+
+       if (s->be->mode == PR_MODE_HTTP) {
+               /* algo will be decided later based on HTTP headers */
+               goto skip_algo;
+       }
+
+       /* in TCP, we only consider the first algo in the list, since the stream
+        * does not tell us which algo was used to compress it.
+        */
+       st->decomp_algo = decomp_res->algos;
+       if (st->decomp_algo->init(&st->decomp_ctx) < 0)
+               goto fail;
+
+       if (st->decomp_algo->stream_new && st->decomp_algo->stream_new(st->decomp_ctx) < 0)
+               goto fail;
+
+       st->flags |= DECOMP_STATE_PROCESSING;
+       register_data_filter(s, chn, f);
+
+ skip_algo:
+       return 1;
+ fail:
+       return -1;
+}
+
+int decomp_channel_end_ana(struct stream *s, struct filter *f, struct channel *chn)
+{
+       struct decomp_state *st = f->ctx;
+
+       if (st->decomp_algo && st->decomp_algo->finish)
+               return st->decomp_algo->finish(st->decomp_ctx);
+       return 1;
+}
+/***********************************************************************/
+
+struct flt_ops decomp_req_ops = {
+       .init              = decomp_flt_init,
+
+       .attach = decomp_strm_init,
+       .detach = decomp_strm_deinit,
+       .tcp_payload = decomp_strm_req_tcp_payload,
+       .channel_start_analyze = decomp_channel_start_req_ana,
+       .channel_end_analyze = decomp_channel_end_ana,
+};
+
+struct flt_ops decomp_res_ops = {
+       .init              = decomp_flt_init,
+
+       .attach = decomp_strm_init,
+       .detach = decomp_strm_deinit,
+       .tcp_payload = decomp_strm_res_tcp_payload,
+       .channel_start_analyze = decomp_channel_start_res_ana,
+       .channel_end_analyze = decomp_channel_end_ana,
+};
+
+/* returns decompression options from <proxy> proxy or allocates them if
+ * needed
+ *
+ * When decompression options are created, flags will be set to <defaults>
+ *
+ * Returns NULL in case of memory error
+ */
+static inline struct decomp *proxy_get_decomp(struct proxy *proxy, int defaults)
+{
+       struct decomp    *decomp;
+
+       if (proxy->decomp == NULL) {
+               decomp = calloc(1, sizeof(*decomp));
+               if (unlikely(!decomp))
+                       return NULL;
+               decomp->flags = defaults;
+               decomp->req.flags = DECOMP_DIR_FL_MODE_NONE;
+               decomp->res.flags = DECOMP_DIR_FL_MODE_NONE;
+               proxy->decomp = decomp;
+       }
+       return proxy->decomp;
+}
+
+static int
+parse_decompression_options(char **args, int section, struct proxy *proxy,
+                           const struct proxy *defpx, const char *file, int line,
+                           char **err)
+{
+       struct decomp    *decomp;
+       int ret = 0;
+
+       decomp = proxy_get_decomp(proxy, DECOMP_FL_NONE);
+       if (decomp == NULL) {
+               memprintf(err, "'%s': out of memory.", args[0]);
+               ret = -1;
+               goto end;
+       }
+
+       if (strcmp(args[1], "algo-req") == 0) {
+               struct decomp_ctx *ctx;
+               int              cur_arg = 2;
+
+               if (failifnotcap(proxy, PR_CAP_FE, file, line, args[1], NULL)) {
+                       ret = -1;
+                       goto end;
+               }
+
+               if (!*args[2]) {
+                       memprintf(err, "'%s %s' expects <algorithm>.", args[0], args[1]);
+                       ret = -1;
+                       goto end;
+               }
+               while (*(args[cur_arg])) {
+                       int retval;
+
+                       retval = decomp_append_algo(&decomp->req.algos, args[cur_arg]);
+                       if (retval) {
+                               if (retval < 0)
+                                       memprintf(err, "'%s %s' : '%s' is not a supported algorithm.",
+                                                 args[0], args[1], args[cur_arg]);
+                               else
+                                       memprintf(err, "'%s %s' : out of memory while parsing algo '%s'.",
+                                                 args[0], args[1], args[cur_arg]);
+                               ret = -1;
+                               goto end;
+                       }
+
+                       if (decomp->req.algos->init(&ctx) == 0)
+                               decomp->req.algos->end(&ctx);
+                       else {
+                               memprintf(err, "'%s %s' : Can't init '%s' algorithm.",
+                                         args[0], args[1], args[cur_arg]);
+                               ret = -1;
+                               goto end;
+                       }
+                       cur_arg++;
+                       continue;
+               }
+       }
+       else if (strcmp(args[1], "algo-res") == 0) {
+               struct decomp_ctx *ctx;
+               int              cur_arg = 2;
+
+               if (failifnotcap(proxy, PR_CAP_BE, file, line, args[1], NULL)) {
+                       ret = -1;
+                       goto end;
+               }
+
+               if (!*args[2]) {
+                       memprintf(err, "'%s %s' expects <algorithm>.", args[0], args[1]);
+                       ret = -1;
+                       goto end;
+               }
+               while (*(args[cur_arg])) {
+                       int retval;
+
+                       retval = decomp_append_algo(&decomp->res.algos, args[cur_arg]);
+                       if (retval) {
+                               if (retval < 0)
+                                       memprintf(err, "'%s %s' : '%s' is not a supported algorithm.",
+                                                 args[0], args[1], args[cur_arg]);
+                               else
+                                       memprintf(err, "'%s %s' : out of memory while parsing algo '%s'.",
+                                                 args[0], args[1], args[cur_arg]);
+                               ret = -1;
+                               goto end;
+                       }
+
+                       if (decomp->res.algos->init(&ctx) == 0)
+                               decomp->res.algos->end(&ctx);
+                       else {
+                               memprintf(err, "'%s %s' : Can't init '%s' algorithm.",
+                                         args[0], args[1], args[cur_arg]);
+                               ret = -1;
+                               goto end;
+                       }
+                       cur_arg++;
+                       continue;
+               }
+       }
+       else if (strcmp(args[1], "mode-req") == 0 || strcmp(args[1], "mode-res") == 0) {
+               int mode = 0;
+               int req = 0;
+               int err_code = 0;
+
+               if (!*args[2]) {
+                       memprintf(err, "'%s %s' : expects 'none', 'auto' or 'always'.",
+                                 args[0], args[1]);
+                       ret = -1;
+                       goto end;
+               }
+
+               if (alertif_too_many_args_idx(1, 1, file, line, args, &err_code)) {
+                        ret = -1;
+                       goto end;
+                }
+
+               if (strcmp(args[1] + 5, "req") == 0) {
+                       if (failifnotcap(proxy, PR_CAP_FE, file, line, args[1], NULL)) {
+                               ret = -1;
+                               goto end;
+                       }
+                       req = 1;
+               }
+               else if (failifnotcap(proxy, PR_CAP_BE, file, line, args[1], NULL)) {
+                       ret = -1;
+                       goto end;
+               }
+
+
+               if (strcmp(args[2], "none") == 0)
+                       mode = DECOMP_DIR_FL_MODE_NONE;
+               else if (strcmp(args[2], "auto") == 0)
+                       mode = DECOMP_DIR_FL_MODE_AUTO;
+               else if (strcmp(args[2], "always") == 0)
+                       mode = DECOMP_DIR_FL_MODE_ALWAYS;
+               else {
+                       memprintf(err, "'%s %s' : expects 'none', 'auto' or 'always' (got '%s').",
+                                 args[0], args[1], args[2]);
+                       ret = -1;
+                       goto end;
+               }
+
+               if (req) {
+                       decomp->req.flags &= ~DECOMP_DIR_FL_MODE_MASK;
+                       decomp->req.flags |= mode;
+               }
+               else {
+                       decomp->res.flags &= ~DECOMP_DIR_FL_MODE_MASK;
+                       decomp->res.flags |= mode;
+               }
+       }
+       else {
+               memprintf(err, "'%s' : expects 'algo-req', 'algo-res', 'mode-req' or 'mode-res' (got '%s).",
+                         args[0], args[1]);
+               ret = -1;
+               goto end;
+       }
+
+  end:
+       return ret;
+}
+
+/* ensure decompression options are compatible with proxy configuration
+ * during postparsing step.
+ */
+static int postcheck_decompression_options(struct proxy *px)
+{
+       if (px->decomp && px->mode != PR_MODE_HTTP &&
+           (((px->cap & PR_CAP_FE) && (px->decomp->req.flags & DECOMP_DIR_FL_MODE_AUTO)) ||
+            ((px->cap & PR_CAP_BE) && (px->decomp->res.flags & DECOMP_DIR_FL_MODE_AUTO)))) {
+               ha_alert("%s '%s': cannot use 'auto' decompression mode with non HTTP proxy. Please review decompression options on the proxy.\n",  proxy_type_str(px), px->id);
+
+               return ERR_ALERT | ERR_FATAL;
+       }
+       return ERR_NONE;
+}
+REGISTER_POST_PROXY_CHECK(postcheck_decompression_options);
+
+static int
+parse_decomp_req_flt(char **args, int *cur_arg, struct proxy *px,
+                     struct flt_conf *fconf, char **err, void *private)
+{
+       struct flt_conf *fc;
+       struct decomp *decomp;
+
+       if (!(px->cap & PR_CAP_FE)) {
+               memprintf(err, "'decomp-req' filter not allowed because %s '%s' has no frontend capability\n",
+                         proxy_type_str(px), px->id);
+               return -1;
+       }
+
+       list_for_each_entry(fc, &px->filter_configs, list) {
+               if (fc->id == decomp_req_flt_id) {
+                       memprintf(err, "%s: Proxy supports only one decomp-req filter\n", px->id);
+                       return -1;
+               }
+       }
+
+       decomp = proxy_get_decomp(px, 0);
+       if (decomp == NULL) {
+               memprintf(err, "memory failure\n");
+               return -1;
+       }
+       decomp->flags |= DECOMP_FL_DIR_REQ;
+
+       fconf->id   = decomp_req_flt_id;
+       fconf->conf = &decomp->req;
+       fconf->ops  = &decomp_req_ops;
+       (*cur_arg)++;
+
+       return 0;
+}
+
+static int
+parse_decomp_res_flt(char **args, int *cur_arg, struct proxy *px,
+                     struct flt_conf *fconf, char **err, void *private)
+{
+       struct flt_conf *fc;
+       struct decomp *decomp;
+
+       if (!(px->cap & PR_CAP_BE)) {
+               memprintf(err, "'decomp-res' filter not allowed because %s '%s' has no backend capability\n",
+                         proxy_type_str(px), px->id);
+               return -1;
+       }
+
+       list_for_each_entry(fc, &px->filter_configs, list) {
+               if (fc->id == decomp_res_flt_id) {
+                       memprintf(err, "%s: Proxy supports only one decomp-res filter\n", px->id);
+                       return -1;
+               }
+       }
+
+       decomp = proxy_get_decomp(px, 0);
+       if (decomp == NULL) {
+               memprintf(err, "memory failure\n");
+               return -1;
+       }
+       decomp->flags |= DECOMP_FL_DIR_RES;
+
+       fconf->id   = decomp_res_flt_id;
+       fconf->conf = &decomp->res;
+       fconf->ops  = &decomp_res_ops;
+       (*cur_arg)++;
+
+       return 0;
+}
+
+/* Declare the config parser for "decompression" keyword */
+static struct cfg_kw_list cfg_kws = {ILH, {
+               { CFG_LISTEN, "decompression", parse_decompression_options },
+               { 0, NULL, NULL },
+       }
+};
+
+INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
+
+/* Declare the filter parser for "compression" keyword */
+static struct flt_kw_list filter_kws = { "COMP", { }, {
+               { "decomp-req", parse_decomp_req_flt, NULL },
+               { "decomp-res", parse_decomp_res_flt, NULL },
+               { NULL, NULL, NULL },
+       }
+};
+
+INITCALL1(STG_REGISTER, flt_register_keywords, &filter_kws);
+
+/* Note: must not be declared <const> as its list will be overwritten */
+static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
+               { /* END */ },
+       }
+};
+
+INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
index d08cbd0d981ddd33b3cced327b3bc5c29867c9e3..00b3b0c19498f9f3575bb14c6f13d83d9ed2166a 100644 (file)
@@ -3532,6 +3532,20 @@ static int proxy_defproxy_cpy(struct proxy *curproxy, const struct proxy *defpro
                curproxy->comp->flags = defproxy->comp->flags;
        }
 
+       /* default decompression options */
+       if (defproxy->decomp != NULL) {
+               curproxy->decomp = calloc(1, sizeof(*curproxy->decomp));
+               if (!curproxy->decomp) {
+                       memprintf(errmsg, "proxy '%s': out of memory for default decompression options", curproxy->id);
+                       return 1;
+               }
+               curproxy->decomp->req.algos = defproxy->decomp->req.algos;
+               curproxy->decomp->req.flags = defproxy->decomp->req.flags;
+               curproxy->decomp->res.algos = defproxy->decomp->res.algos;
+               curproxy->decomp->res.flags = defproxy->decomp->res.flags;
+               curproxy->decomp->flags = defproxy->decomp->flags;
+       }
+
        if (defproxy->check_path)
                curproxy->check_path = strdup(defproxy->check_path);
        if (defproxy->check_command)