#include <haproxy/tools.h>
#define DECOMP_STATE_PROCESSING 0x01
+#define DECOMP_STATE_EOM_SEEN 0x02
const char *decomp_req_flt_id = "decomp-req filter";
const char *decomp_res_flt_id = "decomp-res filter";
st->flags = 0;
filter->ctx = st;
+ /* Register post-analyzer on AN_RES_WAIT_HTTP because we need to
+ * analyze response headers before http-response rules execution
+ */
+ filter->post_analyzers |= AN_RES_WAIT_HTTP;
+
return 1;
}
filter->ctx = NULL;
}
+/* tries to write as many pending bytes (decoded bytes) as possible in the stream,
+ * by taking into account the stream buffer space, and already <consumed> bytes
+ * which will be removed first.
+ */
+static int decomp_stream_blk_drain(struct stream *s, struct filter *f, struct channel *chn,
+ struct htx_blk **blk, char *offset, unsigned int consumed)
+{
+ struct decomp_state *st = f->ctx;
+ struct htx *htx = htxbuf(&chn->buf);
+ int drainable = 0;
+ int free_space = htx_free_data_space(htx);
+
+ if (!st->decomp_ctx->drain_len)
+ return 0; // nothing to do
+
+ /* 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.
+ */
+ free_space -= global.tune.maxrewrite / 3;
+
+ /* adjust available bytes according to consumed bytes which are about to be removed
+ * block creation was already taken into account through htx_free_data_space()
+ */
+ free_space += consumed;
+
+ if (free_space < 0)
+ free_space = 0;
+
+ drainable = MIN(free_space, st->decomp_ctx->drain_len);
+
+ if (blk) {
+ struct htx_blk *new_blk;
+
+ new_blk = htx_replace_blk_value(htx, *blk, ist2(offset, consumed), ist2(st->decomp_ctx->drain, drainable));
+ if (!new_blk) {
+ /* The expansion failed. The space was checked above so this
+ * can only be the trash chunk allocation of the defragmenting
+ * path. Leave the pending data untouched, the caller will
+ * retry later: advancing the drain state here would both lose
+ * the decompressed data and hand a NULL block to the caller.
+ */
+ return 0;
+ }
+ *blk = new_blk;
+ }
+ else {
+ /* first we have to convert the htx from buf using htx_from_buf()
+ * since we were given empty htx and we are about to fill it with data
+ */
+ htx = htx_from_buf(&chn->buf);
+ /* We use add_last_data on purpose (instead of add_data()) because we
+ * want to ensure that data is appended at the right place if the htx
+ * wasn't empty (ie: if there is EOT block in the HTX, we want to make
+ * sure the data is inserted before).
+ *
+ * <drainable> number of bytes were checked to fit in the htx above,
+ * but the insertion may still fail on a defragmentation failure
+ */
+ if (!htx_add_last_data(htx, ist2(st->decomp_ctx->drain, drainable)))
+ return 0;
+ }
+
+ flt_update_offsets(f, chn, drainable - consumed);
+ st->decomp_ctx->drain += drainable;
+ st->decomp_ctx->drain_len -= drainable;
+
+ return drainable;
+}
+
+/* Reads input stream and decompress as many bytes as possible which will be removed
+ * from the stream, then try to write as many decoded bytes as possible in the stream.
+ */
+static int decomp_stream_blk(struct stream *s, struct filter *f, struct channel *chn,
+ unsigned int offset, unsigned int len)
+{
+ struct decomp_state *st = f->ctx;
+ struct htx *htx = htx_from_buf(&chn->buf);
+ struct htx_ret htxret;
+ struct htx_blk *blk, *next;
+ struct ist val;
+ uint32_t blk_remain;
+ int ret;
+ unsigned int consumed = 0;
+ int total = 0;
+
+ if (!st->decomp_ctx) {
+ total = len;
+ goto end;
+ }
+
+ htxret = htx_find_offset(htx, offset);
+ offset = htxret.ret;
+ blk = htxret.blk;
+
+ /* Remove EOM flag from HTX message to be sure to not expose it too
+ * early to next filters. But save the information to be able to restore
+ * the flag at the end of the decompression.
+ */
+ if (htx->flags & HTX_FL_EOM) {
+ st->flags |= DECOMP_STATE_EOM_SEEN;
+ htx->flags &= ~HTX_FL_EOM;
+ }
+
+ while (blk && len) {
+ blk_remain = 0;
+ /* next is special variable that must be set to NULL, it will be used at the
+ * end of the loop if != NULL instead of htx_get_next_blk(). This is to
+ * permit inner code (within the loop) to override the next blk when we know
+ * htx_get_next_blk() is not suitable (ie: when current blk is set to NULL because
+ * it was removed)
+ */
+ next = NULL;
+ switch (htx_get_blk_type(blk)) {
+ case HTX_BLK_UNUSED:
+ goto next;
+ case HTX_BLK_DATA:
+ val = htx_get_blk_value(htx, blk);
+ val = istadv(val, offset);
+ blk_remain = val.len;
+
+ if (val.len > len)
+ val.len = len;
+
+ /* check if there remains undrained data from previous call before
+ * attempting to decompress new data. We will potentially increase
+ * blk size but not decrease it
+ */
+ if (st->decomp_ctx->drain_len) {
+ ret = decomp_stream_blk_drain(s, f, chn, &blk, val.ptr, 0);
+ total += ret;
+
+ if (st->decomp_ctx->drain_len) {
+ /* out of space already, not worth decompressing for now */
+ goto end;
+ }
+
+ val = htx_get_blk_value(htx, blk);
+ val = istadv(val, offset + ret);
+ if (val.len > len)
+ val.len = len;
+ }
+
+ if (st->decomp_ctx->flags & DECOMP_CTX_FL_DONE) {
+ /* nothing to do anymore, consume optional
+ * input data, although this is not supposed to
+ * happen if the input is not garbage filled and
+ * decompression library consumed all data
+ */
+ flt_update_offsets(f, chn, -val.len);
+
+ /* skip consumed bytes: remove the block if empty due to skipping
+ * bytes because it is not supported to leave empty htx data block
+ */
+ if (val.len == htx_get_blksz(blk)) {
+ next = htx_remove_blk(htx, blk);
+ blk = NULL; // make sure we don't use the old block
+ }
+ else
+ blk = htx_replace_blk_value(htx, blk, ist2(val.ptr, val.len), ist2("", 0));
+
+ blk_remain -= val.len;
+ len -= val.len;
+ goto next;
+ }
+
+ /* try to decompress new data */
+ ret = st->decomp_algo->add_data(st->decomp_ctx, val.ptr, val.len);
+
+ if (ret == -1)
+ goto error; // fatal error
+
+ /* returned value = consumed bytes */
+ consumed = ret;
+ len -= consumed;
+ blk_remain -= consumed;
+
+ if (st->decomp_ctx->drain_len) {
+ ret = decomp_stream_blk_drain(s, f, chn, &blk, val.ptr, consumed);
+ total += ret;
+
+ if (st->decomp_ctx->drain_len) {
+ /* we need space to finish decompressing, but we surely don't want
+ * new input for now as it will eat up space
+ */
+ channel_dont_read(chn);
+ goto end;
+ }
+ }
+ else {
+ flt_update_offsets(f, chn, -consumed);
+
+ /* skip consumed bytes: remove the block if empty due to skipping
+ * bytes because it is not supported to leave empty htx data block
+ */
+ if (consumed == htx_get_blksz(blk)) {
+ next = htx_remove_blk(htx, blk);
+ blk = NULL; // make sure we don't use the old block
+ }
+ else
+ blk = htx_replace_blk_value(htx, blk, ist2(val.ptr, consumed), ist2("", 0));
+ }
+ break;
+
+ case HTX_BLK_EOT:
+ if (st->decomp_ctx->drain_len) {
+ /* there is still pending data that we need to drain */
+ break;
+ }
+ if (!(st->decomp_ctx->flags & DECOMP_CTX_FL_DONE)) {
+ char *data = "";
+
+ /* ensure there isn't remaining data in the decomp state buffer:
+ * we don't have new input data, yet the library expects valid
+ * input pointer, so we just provide an empty buffer
+ */
+ ret = st->decomp_algo->add_data(st->decomp_ctx, data, 0);
+ if (ret == -1)
+ goto error;
+
+ break; /* if decomp_ctx->drain_len is set it will be taken
+ * care of
+ */
+
+ }
+
+ /* don't forget some out of payload data such as EOT count as forwarded
+ * bytes in HTX API
+ */
+ total += htx_get_blksz(blk) - offset;
+
+ break;
+
+ default:
+ /* nothing to do with that, just forward */
+ total += htx_get_blksz(blk);
+ break;
+ }
+
+ next:
+ if (blk_remain) {
+ /* not worth pursuing, we are not allowed to read past
+ * this point
+ */
+ goto end;
+ }
+
+ if (next)
+ blk = next; // next override requested
+ else
+ blk = htx_get_next_blk(htx, blk);
+ offset = 0;
+ }
+
+ if (total) {
+ /* ask for input data */
+ channel_auto_read(chn);
+ }
+ else {
+ /* nothing was written so far, either there is nothing to write and
+ * the following code will be NO-OP, else it means we were called without
+ * data block so there was no were to drain our data, in which case we
+ * pass NULL as blk argument to decomp_stream_blk_drain() so that it
+ * creates the block itself
+ */
+ ret = decomp_stream_blk_drain(s, f, chn, NULL, NULL, 0);
+ total += ret;
+
+ if (!(st->decomp_ctx->flags & DECOMP_CTX_FL_DONE)) {
+ char *data = "";
+
+ /* ensure there isn't remaining data in the decomp state buffer:
+ * we don't have new input data, yet the library expects valid
+ * input pointer, so we just provide an empty buffer
+ */
+ ret = st->decomp_algo->add_data(st->decomp_ctx, data, 0);
+ if (ret == -1)
+ goto error;
+
+ ret = decomp_stream_blk_drain(s, f, chn, NULL, NULL, 0);
+ total += ret;
+ }
+ }
+
+ if ((st->decomp_ctx->flags & DECOMP_CTX_FL_DONE) && !st->decomp_ctx->drain_len) {
+ st->flags &= ~DECOMP_STATE_PROCESSING;
+
+ /* The decompression is finished and we must now restore the EOM
+ * flag on the HTX message. We must also take care to forward
+ * any EOT block added to support the EOM flag.
+ */
+ if (st->flags & DECOMP_STATE_EOM_SEEN) {
+ unsigned int data = htx->data;
+
+ BUG_ON(!(st->decomp_ctx->flags & DECOMP_CTX_FL_DONE) || st->decomp_ctx->drain_len);
+ htx_set_eom(htx);
+ total += htx->data - data;
+ }
+ }
+ end:
+ htx_to_buf(htx, &chn->buf);
+ return total;
+
+ error:
+ /* On error, restore HTX_FL_EOM flag */
+ if (st->flags & DECOMP_STATE_EOM_SEEN)
+ htx->flags |= HTX_FL_EOM;
+ htx_to_buf(htx, &chn->buf);
+ return -1;
+}
+
+/* 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;
+}
+
+/* checks the input HTTP headers to guess if the body is compressed and if we
+ * support the encoding used. Then if we enabled decompression, add relevant
+ * headers to stream the body in chunked mode since we don't know what will
+ * be the final body size.
+ */
+static int decomp_strm_http_headers(struct stream *s, struct filter *f, struct http_msg *msg)
+{
+ struct decomp_dir *decomp_dir = FLT_CONF(f);
+ struct decomp_state *st = f->ctx;
+ struct htx *htx = htxbuf(&msg->chn->buf);
+ struct htx_sl *sl;
+ struct http_hdr_ctx ctx = { .blk = NULL };
+ int encoded = 0; // set to 1 if the body is encoded
+
+ /* Messages known to carry no payload must be left completely untouched:
+ * there is nothing to decompress, switching them to the chunked encoding
+ * would emit an invalid message (RFC9112#6.1 forbids a body, and thus any
+ * framing header, on 1xx/204/304 and on responses to HEAD) and the data
+ * filter would then wait forever for a body that never comes. Note we
+ * must not remove "Content-Encoding" either: on a 304 it describes the
+ * stored representation and caches rely on it to revalidate their entry.
+ */
+ if (msg->flags & HTTP_MSGF_BODYLESS)
+ goto no_decomp;
+
+ if (msg->chn->flags & CF_ISRESP) {
+ struct http_txn *txn = s->txn.http;
+
+ /* 1xx/204/304 and responses to HEAD have no body. A 2xx reply to
+ * a CONNECT has no body either, what follows is tunneled data.
+ */
+ if (txn->status < 200 || txn->status == 204 || txn->status == 304 ||
+ txn->meth == HTTP_METH_HEAD || txn->meth == HTTP_METH_CONNECT)
+ goto no_decomp;
+ }
+
+ /* we call http_find_header with <full> set to 0, this way we
+ * will iterated over ALL individual values (separated by coma)
+ */
+ while (http_find_header(htx, ist("content-encoding"), &ctx, 0)) {
+ struct decomp_algo *decomp_algo = decomp_dir->algos;
+
+ encoded = 1;
+ while (decomp_algo) {
+ /* content codings are case-insensitive (RFC9110#8.4.1), and the
+ * whole token must match: comparing over ctx.value.len only
+ * would make any prefix of the algo name match it
+ */
+ if (decomp_algo->ua_name_len == ctx.value.len &&
+ strncasecmp(decomp_algo->ua_name, ctx.value.ptr, ctx.value.len) == 0) {
+ /* content is compressed using supported (and selected)
+ * compression algorithm, so let's enable content decompression
+ */
+ st->decomp_algo = decomp_algo;
+ goto decomp;
+ }
+ decomp_algo = decomp_algo->next;
+ }
+ }
+
+ if (!encoded)
+ goto no_decomp;
+
+ /* we arrive here if "content-encoding" is set but we didn't find a matching algo */
+ if (decomp_dir->flags & DECOMP_DIR_FL_MODE_ALWAYS) {
+ /* we're on the response and 'always' mode was requested on the response
+ * or we're on the request and 'always' mode was requested on the request:
+ * this is a hard error, we need to terminate the stream as we are not able
+ * to decompress it and thus not able to guarantee a decompressed output.
+ *
+ * FIXME: returning an error here will cause the stream to be closed right
+ * away, if this not what we intend, we will have to delay the error and
+ * allow some data like the headers to pass through?
+ */
+ goto early_error;
+ }
+
+ /* we cannot decompress the stream, but we are in 'auto' mode, which is
+ * best-effort, so we simply let the stream flow, effectively bypassing the
+ * decompression filter (we didn't enable it on the stream), but we don't
+ * return any error.
+ */
+ no_decomp:
+ return 1;
+
+ decomp:
+
+ /* we need to init the decomp algo because with HTTP it was deferred
+ * until we learned the algo to be used from http headers.
+ */
+ if (st->decomp_algo->init(&st->decomp_ctx) < 0)
+ goto early_error;
+
+ if (st->decomp_algo->stream_new && st->decomp_algo->stream_new(st->decomp_ctx) < 0)
+ goto error;
+
+ sl = http_get_stline(htx);
+ if (!sl)
+ goto error;
+
+ /* add "Transfer-Encoding: chunked" header */
+ if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
+ if (!http_add_header(htx, ist("Transfer-Encoding"), ist("chunked"), 0))
+ goto error;
+ msg->flags |= HTTP_MSGF_TE_CHNK;
+ sl->flags |= (HTX_SL_F_XFER_ENC|HTX_SL_F_CHNK);
+ }
+
+ /* remove Content-Length header */
+ if (msg->flags & HTTP_MSGF_CNT_LEN) {
+ ctx.blk = NULL;
+ while (http_find_header(htx, ist("Content-Length"), &ctx, 1))
+ http_remove_header(htx, &ctx);
+ msg->flags &= ~HTTP_MSGF_CNT_LEN;
+ sl->flags &= ~HTX_SL_F_CLEN;
+ }
+
+ /* remove content-encoding header */
+ ctx.blk = NULL;
+ while (http_find_header(htx, ist("Content-Encoding"), &ctx, 1))
+ http_remove_header(htx, &ctx);
+
+ if (msg->chn->flags & CF_ISRESP) {
+ /* decompression enabled on the response, and we will alter it since
+ * we just engaged decompression. Let's remove any "accept-encoding"
+ * value from vary header
+ */
+ ctx.blk = NULL;
+ /* we call http_find_header with <full> set to 0, this way we
+ * will iterated over ALL individual values (separated by coma)
+ */
+ while (http_find_header(htx, ist("Vary"), &ctx, 0)) {
+ /* the whole token must match: comparing over ctx.value.len only
+ * would also remove any value that is a prefix of
+ * "accept-encoding", and "Vary: Accept" is a common one
+ */
+ if (isteqi(ctx.value, ist("accept-encoding"))) {
+ /* http_remove_header automatically removes the header
+ * if we removed the last value
+ */
+ http_remove_header(htx, &ctx);
+ }
+ }
+ }
+
+ /* convert a strong "ETag" into a weak one: we are about to change the
+ * representation, so the entity tag can no longer be a strong validator
+ * for it. This mirrors what the compression filter does.
+ */
+ ctx.blk = NULL;
+ if (http_find_header(htx, ist("ETag"), &ctx, 1)) {
+ if (ctx.value.len && ctx.value.ptr[0] == '"') {
+ /* This a strong ETag. Convert it to a weak one. */
+ struct ist v = ist2(trash.area, 0);
+
+ if (istcat(&v, ist("W/"), trash.size) == -1 || istcat(&v, ctx.value, trash.size) == -1)
+ goto error;
+
+ if (!http_replace_header_value(htx, &ctx, v, 0))
+ goto error;
+ }
+ }
+
+ chn_prod(msg->chn)->flags |= SC_FL_NO_FASTFWD;
+ st->flags |= DECOMP_STATE_PROCESSING;
+ register_data_filter(s, msg->chn, f);
+ return 1;
+
+ error:
+ st->decomp_algo->end(&st->decomp_ctx);
+ early_error:
+ st->decomp_algo = NULL;
+ return -1;
+}
+
+/* analyze http headers for request oriented stream */
+static int decomp_strm_req_http_headers(struct stream *s, struct filter *f, struct http_msg *msg)
+{
+ if (!(msg->chn->flags & CF_ISRESP))
+ return decomp_strm_http_headers(s, f, msg);
+
+ /* nothing else do on request */
+ return 1;
+}
+
+/* analyze http headers for response oriented stream */
+static int decomp_strm_res_http_headers(struct stream *s, struct filter *f, struct http_msg *msg)
+{
+ struct decomp_dir *decomp_res = FLT_CONF(f);
+
+ if (!(msg->chn->flags & CF_ISRESP)) {
+ struct htx *htx = htxbuf(&msg->chn->buf);
+ struct decomp_algo *algo;
+ struct http_hdr_ctx ctx;
+
+ /* We are only decompressing the response so we don't care
+ * about the request. However, we can tell the endpoint
+ * that we do support compressed responses (with configured
+ * algos).
+ */
+ if (decomp_res->flags & DECOMP_DIR_FL_MODE_ALWAYS) {
+ /* remove any existing accept-encoding header since
+ * we won't let through responses with encodings we
+ * don't support
+ */
+ ctx.blk = NULL;
+ while (http_find_header(htx, ist("Accept-Encoding"), &ctx, 1))
+ http_remove_header(htx, &ctx);
+
+ }
+
+ /* add our own supported algos
+ * FIXME: maybe we could make the effort to group algos within existing/single
+ * "accept-encoding", for now we add a duplicate header for each value.
+ */
+ algo = decomp_res->algos;
+ while (algo) {
+ if (!http_add_header(htx, ist("Accept-Encoding"), ist2(algo->ua_name, algo->ua_name_len), 0))
+ return -1;
+ algo = algo->next;
+ }
+ }
+
+ /* nothing else to do, on response path decomp_strm_http_headers() was alread
+ * handled by decomp_strm_res_http_post_analyze()
+ */
+ return 1;
+
+}
+
+
+/* analyze http headers for response oriented stream, we do this before
+ * http-response rules evaluation because the compression filter also
+ * does it and we need to be in sync with it in case we chain decomp
+ * and comp filters on the same proxy
+ */
+static int decomp_strm_res_http_post_analyze(struct stream *s, struct filter *f,
+ struct channel *chn, unsigned an_bit)
+{
+ struct http_txn *txn = s->txn.http;
+ struct http_msg *msg = &txn->rsp;
+
+ if (!IS_HTX_STRM(s) || an_bit != AN_RES_WAIT_HTTP)
+ return 1; // nothing to do
+
+ return decomp_strm_http_headers(s, f, msg);
+}
+
+/* decompress http payload for request oriented stream */
+int decomp_strm_req_http_payload(struct stream *s, struct filter *f, struct http_msg *msg,
+ unsigned int offset, unsigned int len)
+{
+ if ((msg->chn->flags & CF_ISRESP))
+ return len; // nothing to do
+
+ return decomp_stream_blk(s, f, msg->chn, offset, len);
+}
+
+/* decompress http payload for response oriented stream */
+int decomp_strm_res_http_payload(struct stream *s, struct filter *f, struct http_msg *msg,
+ unsigned int offset, unsigned int len)
+{
+ if (!(msg->chn->flags & CF_ISRESP))
+ return len; // nothing to do
+
+ return decomp_stream_blk(s, f, msg->chn, offset, len);
+}
+
/* 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
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;
- if (st->decomp_algo && st->decomp_algo->finish)
+ if (st && st->decomp_algo && st->decomp_algo->finish)
return st->decomp_algo->finish(st->decomp_ctx);
return 1;
}
.attach = decomp_strm_init,
.detach = decomp_strm_deinit,
.tcp_payload = decomp_strm_req_tcp_payload,
+ .http_headers = decomp_strm_req_http_headers,
+ .http_payload = decomp_strm_req_http_payload,
.channel_start_analyze = decomp_channel_start_req_ana,
.channel_end_analyze = decomp_channel_end_ana,
};
.attach = decomp_strm_init,
.detach = decomp_strm_deinit,
.tcp_payload = decomp_strm_res_tcp_payload,
+ .http_headers = decomp_strm_res_http_headers,
+ .channel_post_analyze = decomp_strm_res_http_post_analyze,
+ .http_payload = decomp_strm_res_http_payload,
.channel_start_analyze = decomp_channel_start_res_ana,
.channel_end_analyze = decomp_channel_end_ana,
};
while (*(args[cur_arg])) {
int retval;
+ if (proxy->mode != PR_MODE_TCP && strcmp(args[cur_arg], "uslz-auto") == 0) {
+ memprintf(err, "'%s %s' : '%s' algorithm only allowed for TCP proxies.",
+ args[0], args[1], args[cur_arg]);
+ ret = -1;
+ goto end;
+ }
+
retval = decomp_append_algo(&decomp->req.algos, args[cur_arg]);
if (retval) {
if (retval < 0)
while (*(args[cur_arg])) {
int retval;
+ if (proxy->mode != PR_MODE_TCP && strcmp(args[cur_arg], "uslz-auto") == 0) {
+ memprintf(err, "'%s %s' : '%s' algorithm only allowed for TCP proxies.",
+ args[0], args[1], args[cur_arg]);
+ ret = -1;
+ goto end;
+ }
+
retval = decomp_append_algo(&decomp->res.algos, args[cur_arg]);
if (retval) {
if (retval < 0)