blob: 30f9d2a6281b1791a1dd8eb46dfdadb53fd502e0 [file] [log] [blame]
Christopher Faulet3d97c902015-12-09 14:59:38 +01001/*
2 * Stream filters related variables and functions.
3 *
4 * Copyright (C) 2015 Qualys Inc., Christopher Faulet <cfaulet@qualys.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version
9 * 2 of the License, or (at your option) any later version.
10 *
11 */
12
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020013#include <haproxy/api.h>
Willy Tarreau6be78492020-06-05 00:00:29 +020014#include <haproxy/cfgparse.h>
Willy Tarreau0a3bd392020-06-04 08:52:38 +020015#include <haproxy/compression.h>
Willy Tarreau2741c8c2020-06-02 11:28:02 +020016#include <haproxy/dynbuf.h>
Willy Tarreauc7babd82020-06-04 21:29:29 +020017#include <haproxy/filters.h>
Willy Tarreaucd72d8c2020-06-02 19:11:26 +020018#include <haproxy/http.h>
Willy Tarreauc2b1ff02020-06-04 21:21:03 +020019#include <haproxy/http_ana-t.h>
Willy Tarreau87735332020-06-04 09:08:41 +020020#include <haproxy/http_htx.h>
Willy Tarreau16f958c2020-06-03 08:44:35 +020021#include <haproxy/htx.h>
Willy Tarreau853b2972020-05-27 18:01:47 +020022#include <haproxy/list.h>
Willy Tarreau202f93d2021-05-08 20:34:16 +020023#include <haproxy/proxy.h>
Willy Tarreaue6ce10b2020-06-04 15:33:47 +020024#include <haproxy/sample.h>
Willy Tarreaudfd3de82020-06-04 23:46:14 +020025#include <haproxy/stream.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020026#include <haproxy/tools.h>
Christopher Faulet3d97c902015-12-09 14:59:38 +010027
Christopher Faulet12554d02021-06-09 17:12:44 +020028#define COMP_STATE_PROCESSING 0x01
29
Christopher Fauletf4a4ef72018-12-07 17:39:53 +010030const char *http_comp_flt_id = "compression filter";
Christopher Faulet92d36382015-11-05 13:35:03 +010031
32struct flt_ops comp_ops;
33
Christopher Faulet92d36382015-11-05 13:35:03 +010034struct comp_state {
Olivier Houcharddfc11da2023-04-05 16:25:57 +020035 /*
36 * For both comp_ctx and comp_algo, COMP_DIR_REQ is the index
37 * for requests, and COMP_DIR_RES for responses
38 */
39 struct comp_ctx *comp_ctx[2]; /* compression context */
40 struct comp_algo *comp_algo[2]; /* compression algorithm if not NULL */
Christopher Faulet12554d02021-06-09 17:12:44 +020041 unsigned int flags; /* COMP_STATE_* */
Christopher Faulet92d36382015-11-05 13:35:03 +010042};
43
Willy Tarreau8ceae722018-11-26 11:58:30 +010044/* Pools used to allocate comp_state structs */
45DECLARE_STATIC_POOL(pool_head_comp_state, "comp_state", sizeof(struct comp_state));
46
47static THREAD_LOCAL struct buffer tmpbuf;
48static THREAD_LOCAL struct buffer zbuf;
Willy Tarreau8ceae722018-11-26 11:58:30 +010049
Christopher Faulet92d36382015-11-05 13:35:03 +010050static int select_compression_request_header(struct comp_state *st,
51 struct stream *s,
52 struct http_msg *msg);
53static int select_compression_response_header(struct comp_state *st,
54 struct stream *s,
55 struct http_msg *msg);
Olivier Houcharddfc11da2023-04-05 16:25:57 +020056static int set_compression_header(struct comp_state *st,
57 struct stream *s,
58 struct http_msg *msg);
Christopher Faulet92d36382015-11-05 13:35:03 +010059
Christopher Faulete6902cd2018-11-30 22:29:48 +010060static int htx_compression_buffer_init(struct htx *htx, struct buffer *out);
61static int htx_compression_buffer_add_data(struct comp_state *st, const char *data, size_t len,
Olivier Houcharddfc11da2023-04-05 16:25:57 +020062 struct buffer *out, int dir);
63static int htx_compression_buffer_end(struct comp_state *st, struct buffer *out, int end, int dir);
Christopher Faulete6902cd2018-11-30 22:29:48 +010064
Christopher Faulet92d36382015-11-05 13:35:03 +010065/***********************************************************************/
66static int
Christopher Faulete6902cd2018-11-30 22:29:48 +010067comp_flt_init(struct proxy *px, struct flt_conf *fconf)
68{
Christopher Faulet6e540952018-12-03 22:43:41 +010069 fconf->flags |= FLT_CFG_FL_HTX;
Christopher Faulete6902cd2018-11-30 22:29:48 +010070 return 0;
71}
72
73static int
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020074comp_flt_init_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010075{
Willy Tarreau862ad822021-03-22 16:16:22 +010076 if (b_alloc(&tmpbuf) == NULL)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010077 return -1;
Willy Tarreau862ad822021-03-22 16:16:22 +010078 if (b_alloc(&zbuf) == NULL)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010079 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +010080 return 0;
81}
82
83static void
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020084comp_flt_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010085{
Willy Tarreauc9fa0482018-07-10 17:43:27 +020086 if (tmpbuf.size)
Christopher Faulet92d36382015-11-05 13:35:03 +010087 b_free(&tmpbuf);
Willy Tarreauc9fa0482018-07-10 17:43:27 +020088 if (zbuf.size)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010089 b_free(&zbuf);
Christopher Faulet92d36382015-11-05 13:35:03 +010090}
91
92static int
Christopher Faulet5e896512020-03-06 14:59:05 +010093comp_strm_init(struct stream *s, struct filter *filter)
Christopher Faulet92d36382015-11-05 13:35:03 +010094{
Christopher Faulet5e896512020-03-06 14:59:05 +010095 struct comp_state *st;
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020096
Willy Tarreau5bfeb212021-03-22 15:08:17 +010097 st = pool_alloc(pool_head_comp_state);
Christopher Faulet5e896512020-03-06 14:59:05 +010098 if (st == NULL)
99 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100100
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200101 st->comp_algo[COMP_DIR_REQ] = NULL;
102 st->comp_algo[COMP_DIR_RES] = NULL;
103 st->comp_ctx[COMP_DIR_REQ] = NULL;
104 st->comp_ctx[COMP_DIR_RES] = NULL;
Christopher Faulet12554d02021-06-09 17:12:44 +0200105 st->flags = 0;
Christopher Faulet5e896512020-03-06 14:59:05 +0100106 filter->ctx = st;
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200107
Christopher Faulet5e896512020-03-06 14:59:05 +0100108 /* Register post-analyzer on AN_RES_WAIT_HTTP because we need to
109 * analyze response headers before http-response rules execution
110 * to be sure we can use res.comp and res.comp_algo sample
111 * fetches */
112 filter->post_analyzers |= AN_RES_WAIT_HTTP;
Christopher Faulet92d36382015-11-05 13:35:03 +0100113 return 1;
114}
115
Christopher Faulet5e896512020-03-06 14:59:05 +0100116static void
117comp_strm_deinit(struct stream *s, struct filter *filter)
Christopher Faulet92d36382015-11-05 13:35:03 +0100118{
119 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100120
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200121 if (!st)
Christopher Faulet5e896512020-03-06 14:59:05 +0100122 return;
Christopher Faulet92d36382015-11-05 13:35:03 +0100123
Christopher Faulet92d36382015-11-05 13:35:03 +0100124 /* release any possible compression context */
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200125 if (st->comp_algo[COMP_DIR_REQ])
126 st->comp_algo[COMP_DIR_REQ]->end(&st->comp_ctx[COMP_DIR_REQ]);
127 if (st->comp_algo[COMP_DIR_RES])
128 st->comp_algo[COMP_DIR_RES]->end(&st->comp_ctx[COMP_DIR_RES]);
Willy Tarreaubafbe012017-11-24 17:34:44 +0100129 pool_free(pool_head_comp_state, st);
Christopher Faulet92d36382015-11-05 13:35:03 +0100130 filter->ctx = NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100131}
132
Olivier Houchardead43fe2023-04-06 00:33:48 +0200133static void
134comp_prepare_compress_request(struct comp_state *st, struct stream *s, struct http_msg *msg)
135{
136 struct htx *htx = htxbuf(&msg->chn->buf);
137 struct http_txn *txn = s->txn;
138 struct http_hdr_ctx ctx;
139 struct comp_type *comp_type;
140
141 ctx.blk = NULL;
142 /* Already compressed, don't bother */
143 if (http_find_header(htx, ist("Content-Encoding"), &ctx, 1))
144 return;
145 /* HTTP < 1.1 should not be compressed */
146 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
147 return;
148 comp_type = NULL;
149
150 /*
151 * We don't want to compress content-types not listed in the "compression type" directive if any. If no content-type was found but configuration
152 * requires one, we don't compress either. Backend has the priority.
153 */
154 ctx.blk = NULL;
155 if (http_find_header(htx, ist("Content-Type"), &ctx, 1)) {
156 if ((s->be->comp && (comp_type = s->be->comp->types_req)) ||
157 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types_req))) {
158 for (; comp_type; comp_type = comp_type->next) {
159 if (ctx.value.len >= comp_type->name_len &&
160 strncasecmp(ctx.value.ptr, comp_type->name, comp_type->name_len) == 0)
161 /* this Content-Type should be compressed */
162 break;
163 }
164 /* this Content-Type should not be compressed */
165 if (comp_type == NULL)
166 goto fail;
167 }
168 }
169 else { /* no content-type header */
170 if ((s->be->comp && s->be->comp->types_req) ||
171 (strm_fe(s)->comp && strm_fe(s)->comp->types_req))
172 goto fail; /* a content-type was required */
173 }
174
175 /* limit compression rate */
176 if (global.comp_rate_lim > 0)
177 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
178 goto fail;
179
180 /* limit cpu usage */
181 if (th_ctx->idle_pct < compress_min_idle)
182 goto fail;
183
184 if (txn->meth == HTTP_METH_HEAD)
185 return;
Aurelien DARRAGON765725a2023-11-28 15:47:25 +0100186 if (s->be->comp && s->be->comp->algo_req != NULL)
Olivier Houchardead43fe2023-04-06 00:33:48 +0200187 st->comp_algo[COMP_DIR_REQ] = s->be->comp->algo_req;
Aurelien DARRAGON765725a2023-11-28 15:47:25 +0100188 else if (strm_fe(s)->comp && strm_fe(s)->comp->algo_req != NULL)
Olivier Houchardead43fe2023-04-06 00:33:48 +0200189 st->comp_algo[COMP_DIR_REQ] = strm_fe(s)->comp->algo_req;
Aurelien DARRAGON765725a2023-11-28 15:47:25 +0100190 else
191 goto fail; /* no algo selected: nothing to do */
Olivier Houchardead43fe2023-04-06 00:33:48 +0200192
193
194 /* limit compression rate */
195 if (global.comp_rate_lim > 0)
196 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
197 goto fail;
198
199 /* limit cpu usage */
200 if (th_ctx->idle_pct < compress_min_idle)
201 goto fail;
202
203 /* initialize compression */
204 if (st->comp_algo[COMP_DIR_REQ]->init(&st->comp_ctx[COMP_DIR_REQ], global.tune.comp_maxlevel) < 0)
205 goto fail;
206
207 return;
208fail:
209 st->comp_algo[COMP_DIR_REQ] = NULL;
210}
211
Christopher Faulet92d36382015-11-05 13:35:03 +0100212static int
Christopher Faulet1339d742016-05-11 16:48:33 +0200213comp_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
214{
215 struct comp_state *st = filter->ctx;
Olivier Houchardead43fe2023-04-06 00:33:48 +0200216 int comp_flags = 0;
Christopher Faulet1339d742016-05-11 16:48:33 +0200217
218 if (!strm_fe(s)->comp && !s->be->comp)
219 goto end;
Olivier Houchardead43fe2023-04-06 00:33:48 +0200220 if (strm_fe(s)->comp)
221 comp_flags |= strm_fe(s)->comp->flags;
222 if (s->be->comp)
223 comp_flags |= s->be->comp->flags;
Christopher Faulet1339d742016-05-11 16:48:33 +0200224
Olivier Houchardead43fe2023-04-06 00:33:48 +0200225 if (!(msg->chn->flags & CF_ISRESP)) {
226 if (comp_flags & COMP_FL_DIR_REQ) {
227 comp_prepare_compress_request(st, s, msg);
228 if (st->comp_algo[COMP_DIR_REQ]) {
229 if (!set_compression_header(st, s, msg))
230 goto end;
231 register_data_filter(s, msg->chn, filter);
232 st->flags |= COMP_STATE_PROCESSING;
233 }
234 }
235 if (comp_flags & COMP_FL_DIR_RES)
236 select_compression_request_header(st, s, msg);
237 } else if (comp_flags & COMP_FL_DIR_RES) {
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200238 /* Response headers have already been checked in
239 * comp_http_post_analyze callback. */
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200240 if (st->comp_algo[COMP_DIR_RES]) {
241 if (!set_compression_header(st, s, msg))
Christopher Faulet27d93c32018-12-15 22:32:02 +0100242 goto end;
Christopher Faulet1339d742016-05-11 16:48:33 +0200243 register_data_filter(s, msg->chn, filter);
Christopher Faulet12554d02021-06-09 17:12:44 +0200244 st->flags |= COMP_STATE_PROCESSING;
Christopher Faulet1339d742016-05-11 16:48:33 +0200245 }
246 }
247
248 end:
249 return 1;
250}
251
252static int
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200253comp_http_post_analyze(struct stream *s, struct filter *filter,
254 struct channel *chn, unsigned an_bit)
255{
256 struct http_txn *txn = s->txn;
257 struct http_msg *msg = &txn->rsp;
258 struct comp_state *st = filter->ctx;
259
260 if (an_bit != AN_RES_WAIT_HTTP)
261 goto end;
262
263 if (!strm_fe(s)->comp && !s->be->comp)
264 goto end;
265
266 select_compression_response_header(st, s, msg);
267
268 end:
269 return 1;
270}
271
272static int
Christopher Faulete6902cd2018-11-30 22:29:48 +0100273comp_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg,
274 unsigned int offset, unsigned int len)
275{
276 struct comp_state *st = filter->ctx;
Christopher Faulet27ba2dc2018-12-05 11:53:24 +0100277 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulete6a62bf2020-03-02 16:20:05 +0100278 struct htx_ret htxret = htx_find_offset(htx, offset);
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100279 struct htx_blk *blk, *next;
280 int ret, consumed = 0, to_forward = 0, last = 0;
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200281 int dir;
282
283 if (msg->chn->flags & CF_ISRESP)
284 dir = COMP_DIR_RES;
285 else
286 dir = COMP_DIR_REQ;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100287
Christopher Faulete6a62bf2020-03-02 16:20:05 +0100288 blk = htxret.blk;
289 offset = htxret.ret;
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100290 for (next = NULL; blk && len; blk = next) {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100291 enum htx_blk_type type = htx_get_blk_type(blk);
292 uint32_t sz = htx_get_blksz(blk);
293 struct ist v;
294
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100295 next = htx_get_next_blk(htx, blk);
296 while (next && htx_get_blk_type(next) == HTX_BLK_UNUSED)
Christopher Faulet86ca0e52021-06-09 16:59:02 +0200297 next = htx_get_next_blk(htx, next);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100298
Christopher Faulet12554d02021-06-09 17:12:44 +0200299 if (!(st->flags & COMP_STATE_PROCESSING))
300 goto consume;
301
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100302 if (htx_compression_buffer_init(htx, &trash) < 0) {
303 msg->chn->flags |= CF_WAKE_WRITE;
304 goto end;
305 }
306
307 switch (type) {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100308 case HTX_BLK_DATA:
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100309 /* it is the last data block */
310 last = ((!next && (htx->flags & HTX_FL_EOM)) || (next && htx_get_blk_type(next) != HTX_BLK_DATA));
Christopher Faulete6902cd2018-11-30 22:29:48 +0100311 v = htx_get_blk_value(htx, blk);
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100312 v = istadv(v, offset);
313 if (v.len > len) {
314 last = 0;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100315 v.len = len;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100316 }
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100317
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200318 ret = htx_compression_buffer_add_data(st, v.ptr, v.len, &trash, dir);
319 if (ret < 0 || htx_compression_buffer_end(st, &trash, last, dir) < 0)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100320 goto error;
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100321 BUG_ON(v.len != ret);
322
323 if (ret == sz && !b_data(&trash))
324 next = htx_remove_blk(htx, blk);
Christopher Faulet402740c2021-06-09 17:04:37 +0200325 else {
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100326 blk = htx_replace_blk_value(htx, blk, v, ist2(b_head(&trash), b_data(&trash)));
Christopher Faulet402740c2021-06-09 17:04:37 +0200327 next = htx_get_next_blk(htx, blk);
328 }
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100329
Christopher Faulete6902cd2018-11-30 22:29:48 +0100330 len -= ret;
331 consumed += ret;
332 to_forward += b_data(&trash);
Christopher Faulet12554d02021-06-09 17:12:44 +0200333 if (last)
334 st->flags &= ~COMP_STATE_PROCESSING;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100335 break;
336
Christopher Faulete6902cd2018-11-30 22:29:48 +0100337 case HTX_BLK_TLR:
Christopher Faulet2d7c5392019-06-03 10:41:26 +0200338 case HTX_BLK_EOT:
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200339 if (htx_compression_buffer_end(st, &trash, 1, dir) < 0)
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100340 goto error;
341 if (b_data(&trash)) {
342 struct htx_blk *last = htx_add_last_data(htx, ist2(b_head(&trash), b_data(&trash)));
343 if (!last)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100344 goto error;
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100345 blk = htx_get_next_blk(htx, last);
346 if (!blk)
347 goto error;
Christopher Faulet402740c2021-06-09 17:04:37 +0200348 next = htx_get_next_blk(htx, blk);
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100349 to_forward += b_data(&trash);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100350 }
Christopher Faulet12554d02021-06-09 17:12:44 +0200351 st->flags &= ~COMP_STATE_PROCESSING;
Willy Tarreau91d398c2022-11-14 07:36:05 +0100352 __fallthrough;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100353
354 default:
Christopher Fauletd1ac2b92020-12-02 19:12:22 +0100355 consume:
Christopher Faulete6902cd2018-11-30 22:29:48 +0100356 sz -= offset;
357 if (sz > len)
358 sz = len;
359 consumed += sz;
360 to_forward += sz;
361 len -= sz;
362 break;
363 }
364
365 offset = 0;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100366 }
367
368 end:
369 if (to_forward != consumed)
370 flt_update_offsets(filter, msg->chn, to_forward - consumed);
371
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200372 if (st->comp_ctx[dir] && st->comp_ctx[dir]->cur_lvl > 0) {
Willy Tarreauef6fd852019-02-04 11:48:03 +0100373 update_freq_ctr(&global.comp_bps_in, consumed);
Olivier Houcharddea25f52023-04-06 00:33:01 +0200374 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_in[dir], consumed);
375 _HA_ATOMIC_ADD(&s->be->be_counters.comp_in[dir], consumed);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100376 update_freq_ctr(&global.comp_bps_out, to_forward);
Olivier Houcharddea25f52023-04-06 00:33:01 +0200377 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_out[dir], to_forward);
378 _HA_ATOMIC_ADD(&s->be->be_counters.comp_out[dir], to_forward);
Willy Tarreauef6fd852019-02-04 11:48:03 +0100379 } else {
Olivier Houcharddea25f52023-04-06 00:33:01 +0200380 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_byp[dir], consumed);
381 _HA_ATOMIC_ADD(&s->be->be_counters.comp_byp[dir], consumed);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100382 }
383 return to_forward;
384
385 error:
386 return -1;
387}
388
Christopher Faulet2fb28802015-12-01 10:40:57 +0100389
Christopher Faulet92d36382015-11-05 13:35:03 +0100390static int
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200391comp_http_end(struct stream *s, struct filter *filter,
392 struct http_msg *msg)
393{
394 struct comp_state *st = filter->ctx;
395
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200396 if (!(msg->chn->flags & CF_ISRESP) || !st || !st->comp_algo[COMP_DIR_RES])
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200397 goto end;
398
399 if (strm_fe(s)->mode == PR_MODE_HTTP)
Willy Tarreau4781b152021-04-06 13:53:36 +0200400 _HA_ATOMIC_INC(&strm_fe(s)->fe_counters.p.http.comp_rsp);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200401 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
Willy Tarreau4781b152021-04-06 13:53:36 +0200402 _HA_ATOMIC_INC(&s->be->be_counters.p.http.comp_rsp);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200403 end:
404 return 1;
405}
Christopher Faulet27d93c32018-12-15 22:32:02 +0100406
Christopher Faulet89f2b162019-07-15 21:16:04 +0200407/***********************************************************************/
Christopher Faulet27d93c32018-12-15 22:32:02 +0100408static int
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200409set_compression_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulet27d93c32018-12-15 22:32:02 +0100410{
411 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulet39e436e2022-04-15 15:32:03 +0200412 struct htx_sl *sl;
Christopher Faulet535dd922023-05-25 11:18:21 +0200413 struct http_hdr_ctx ctx, last_vary;
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200414 struct comp_algo *comp_algo;
415 int comp_index;
416
417 if (msg->chn->flags & CF_ISRESP)
418 comp_index = COMP_DIR_RES;
419 else
420 comp_index = COMP_DIR_REQ;
Christopher Faulet27d93c32018-12-15 22:32:02 +0100421
Christopher Faulet39e436e2022-04-15 15:32:03 +0200422 sl = http_get_stline(htx);
423 if (!sl)
424 goto error;
425
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200426 comp_algo = st->comp_algo[comp_index];
427
Christopher Faulet910b7572022-10-24 08:39:29 +0200428 /* add "Transfer-Encoding: chunked" header */
429 if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
430 if (!http_add_header(htx, ist("Transfer-Encoding"), ist("chunked")))
431 goto error;
432 msg->flags |= HTTP_MSGF_TE_CHNK;
433 sl->flags |= (HTX_SL_F_XFER_ENC|HTX_SL_F_CHNK);
434 }
435
Christopher Faulet27d93c32018-12-15 22:32:02 +0100436 /* remove Content-Length header */
437 if (msg->flags & HTTP_MSGF_CNT_LEN) {
Christopher Faulet27d93c32018-12-15 22:32:02 +0100438 ctx.blk = NULL;
439 while (http_find_header(htx, ist("Content-Length"), &ctx, 1))
440 http_remove_header(htx, &ctx);
Christopher Faulet39e436e2022-04-15 15:32:03 +0200441 msg->flags &= ~HTTP_MSGF_CNT_LEN;
442 sl->flags &= ~HTX_SL_F_CLEN;
Christopher Faulet27d93c32018-12-15 22:32:02 +0100443 }
444
Tim Duesterhusb229f012019-01-29 16:38:56 +0100445 /* convert "ETag" header to a weak ETag */
446 ctx.blk = NULL;
447 if (http_find_header(htx, ist("ETag"), &ctx, 1)) {
448 if (ctx.value.ptr[0] == '"') {
449 /* This a strong ETag. Convert it to a weak one. */
450 struct ist v = ist2(trash.area, 0);
451 if (istcat(&v, ist("W/"), trash.size) == -1 || istcat(&v, ctx.value, trash.size) == -1)
452 goto error;
453
454 if (!http_replace_header_value(htx, &ctx, v))
455 goto error;
456 }
457 }
458
Christopher Faulet535dd922023-05-25 11:18:21 +0200459 /* Add "Vary: Accept-Encoding" header but only if it is not found. */
460 ctx.blk = NULL;
461 last_vary.blk = NULL;
462 while (http_find_header(htx, ist("Vary"), &ctx, 0)) {
463 if (isteqi(ctx.value, ist("Accept-Encoding")))
464 break;
465 last_vary = ctx;
466 }
467 /* No "Accept-Encoding" value found. */
468 if (ctx.blk == NULL) {
469 if (last_vary.blk == NULL) {
470 /* No Vary header found at all. Add our header */
471 if (!http_add_header(htx, ist("Vary"), ist("Accept-Encoding")))
472 goto error;
473 }
474 else {
475 /* At least one Vary header found. Append the value to
476 * the last one.
477 */
478 if (!http_append_header_value(htx, &last_vary, ist("Accept-Encoding")))
479 goto error;
480 }
481 }
Tim Duesterhus721d6862019-06-17 16:10:07 +0200482
Christopher Faulet910b7572022-10-24 08:39:29 +0200483 /*
484 * Add Content-Encoding header when it's not identity encoding.
485 * RFC 2616 : Identity encoding: This content-coding is used only in the
486 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
487 * header.
488 */
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200489 if (comp_algo->cfg_name_len != 8 || memcmp(comp_algo->cfg_name, "identity", 8) != 0) {
490 struct ist v = ist2(comp_algo->ua_name, comp_algo->ua_name_len);
Christopher Faulet910b7572022-10-24 08:39:29 +0200491
492 if (!http_add_header(htx, ist("Content-Encoding"), v))
493 goto error;
494 }
495
Christopher Faulet27d93c32018-12-15 22:32:02 +0100496 return 1;
497
498 error:
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200499 st->comp_algo[comp_index]->end(&st->comp_ctx[comp_index]);
500 st->comp_algo[comp_index] = NULL;
Christopher Faulet27d93c32018-12-15 22:32:02 +0100501 return 0;
502}
503
Christopher Faulet3d97c902015-12-09 14:59:38 +0100504/*
505 * Selects a compression algorithm depending on the client request.
506 */
Christopher Faulete6902cd2018-11-30 22:29:48 +0100507static int
Christopher Faulet89f2b162019-07-15 21:16:04 +0200508select_compression_request_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100509{
Christopher Faulet27ba2dc2018-12-05 11:53:24 +0100510 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100511 struct http_hdr_ctx ctx;
512 struct comp_algo *comp_algo = NULL;
513 struct comp_algo *comp_algo_back = NULL;
514
515 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
516 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
517 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
518 */
519 ctx.blk = NULL;
520 if (http_find_header(htx, ist("User-Agent"), &ctx, 1) &&
521 ctx.value.len >= 9 &&
522 memcmp(ctx.value.ptr, "Mozilla/4", 9) == 0 &&
523 (ctx.value.len < 31 ||
524 memcmp(ctx.value.ptr + 25, "MSIE ", 5) != 0 ||
525 *(ctx.value.ptr + 30) < '6' ||
526 (*(ctx.value.ptr + 30) == '6' &&
527 (ctx.value.len < 54 || memcmp(ctx.value.ptr + 51, "SV1", 3) != 0)))) {
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200528 st->comp_algo[COMP_DIR_RES] = NULL;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100529 return 0;
530 }
531
532 /* search for the algo in the backend in priority or the frontend */
Olivier Houcharddb573e92023-04-05 17:32:36 +0200533 if ((s->be->comp && (comp_algo_back = s->be->comp->algos_res)) ||
534 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos_res))) {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100535 int best_q = 0;
536
537 ctx.blk = NULL;
538 while (http_find_header(htx, ist("Accept-Encoding"), &ctx, 0)) {
539 const char *qval;
540 int q;
541 int toklen;
542
543 /* try to isolate the token from the optional q-value */
544 toklen = 0;
545 while (toklen < ctx.value.len && HTTP_IS_TOKEN(*(ctx.value.ptr + toklen)))
546 toklen++;
547
548 qval = ctx.value.ptr + toklen;
549 while (1) {
Tim Duesterhus77508502022-03-15 13:11:06 +0100550 while (qval < istend(ctx.value) && HTTP_IS_LWS(*qval))
Christopher Faulete6902cd2018-11-30 22:29:48 +0100551 qval++;
552
Tim Duesterhus77508502022-03-15 13:11:06 +0100553 if (qval >= istend(ctx.value) || *qval != ';') {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100554 qval = NULL;
555 break;
556 }
557 qval++;
558
Tim Duesterhus77508502022-03-15 13:11:06 +0100559 while (qval < istend(ctx.value) && HTTP_IS_LWS(*qval))
Christopher Faulete6902cd2018-11-30 22:29:48 +0100560 qval++;
561
Tim Duesterhus77508502022-03-15 13:11:06 +0100562 if (qval >= istend(ctx.value)) {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100563 qval = NULL;
564 break;
565 }
Tim Duesterhus77508502022-03-15 13:11:06 +0100566 if (strncmp(qval, "q=", MIN(istend(ctx.value) - qval, 2)) == 0)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100567 break;
568
Tim Duesterhus77508502022-03-15 13:11:06 +0100569 while (qval < istend(ctx.value) && *qval != ';')
Christopher Faulete6902cd2018-11-30 22:29:48 +0100570 qval++;
571 }
572
573 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
574 q = qval ? http_parse_qvalue(qval + 2, NULL) : 1000;
575
576 if (q <= best_q)
577 continue;
578
579 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
580 if (*(ctx.value.ptr) == '*' ||
581 word_match(ctx.value.ptr, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200582 st->comp_algo[COMP_DIR_RES] = comp_algo;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100583 best_q = q;
584 break;
585 }
586 }
587 }
588 }
589
590 /* remove all occurrences of the header when "compression offload" is set */
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200591 if (st->comp_algo[COMP_DIR_RES]) {
Olivier Houchard3ce0f012023-04-03 22:22:24 +0200592 if ((s->be->comp && (s->be->comp->flags & COMP_FL_OFFLOAD)) ||
593 (strm_fe(s)->comp && (strm_fe(s)->comp->flags & COMP_FL_OFFLOAD))) {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100594 http_remove_header(htx, &ctx);
595 ctx.blk = NULL;
596 while (http_find_header(htx, ist("Accept-Encoding"), &ctx, 1))
597 http_remove_header(htx, &ctx);
598 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100599 return 1;
600 }
601
602 /* identity is implicit does not require headers */
Olivier Houcharddb573e92023-04-05 17:32:36 +0200603 if ((s->be->comp && (comp_algo_back = s->be->comp->algos_res)) ||
604 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos_res))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100605 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
606 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200607 st->comp_algo[COMP_DIR_RES] = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100608 return 1;
609 }
610 }
611 }
612
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200613 st->comp_algo[COMP_DIR_RES] = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100614 return 0;
615}
616
617/*
Ilya Shipitsin46a030c2020-07-05 16:36:08 +0500618 * Selects a compression algorithm depending of the server response.
Christopher Faulet3d97c902015-12-09 14:59:38 +0100619 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100620static int
Christopher Faulet89f2b162019-07-15 21:16:04 +0200621select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100622{
Christopher Faulet27ba2dc2018-12-05 11:53:24 +0100623 struct htx *htx = htxbuf(&msg->chn->buf);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100624 struct http_txn *txn = s->txn;
625 struct http_hdr_ctx ctx;
626 struct comp_type *comp_type;
627
628 /* no common compression algorithm was found in request header */
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200629 if (st->comp_algo[COMP_DIR_RES] == NULL)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100630 goto fail;
631
Christopher Faulet1d3613a2019-01-07 14:41:59 +0100632 /* compression already in progress */
633 if (msg->flags & HTTP_MSGF_COMPRESSING)
634 goto fail;
635
Christopher Faulete6902cd2018-11-30 22:29:48 +0100636 /* HTTP < 1.1 should not be compressed */
637 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
638 goto fail;
639
640 if (txn->meth == HTTP_METH_HEAD)
641 goto fail;
642
643 /* compress 200,201,202,203 responses only */
644 if ((txn->status != 200) &&
645 (txn->status != 201) &&
646 (txn->status != 202) &&
647 (txn->status != 203))
648 goto fail;
649
Christopher Fauletc963eb22018-12-21 14:53:54 +0100650 if (!(msg->flags & HTTP_MSGF_XFER_LEN) || msg->flags & HTTP_MSGF_BODYLESS)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100651 goto fail;
652
653 /* content is already compressed */
654 ctx.blk = NULL;
655 if (http_find_header(htx, ist("Content-Encoding"), &ctx, 1))
656 goto fail;
657
658 /* no compression when Cache-Control: no-transform is present in the message */
659 ctx.blk = NULL;
660 while (http_find_header(htx, ist("Cache-Control"), &ctx, 0)) {
661 if (word_match(ctx.value.ptr, ctx.value.len, "no-transform", 12))
662 goto fail;
663 }
664
Tim Duesterhusb229f012019-01-29 16:38:56 +0100665 /* no compression when ETag is malformed */
666 ctx.blk = NULL;
667 if (http_find_header(htx, ist("ETag"), &ctx, 1)) {
Tim Duesterhus6414cd12020-09-01 18:32:35 +0200668 if (http_get_etag_type(ctx.value) == ETAG_INVALID)
Tim Duesterhusb229f012019-01-29 16:38:56 +0100669 goto fail;
Tim Duesterhusb229f012019-01-29 16:38:56 +0100670 }
671 /* no compression when multiple ETags are present
672 * Note: Do not reset ctx.blk!
673 */
674 if (http_find_header(htx, ist("ETag"), &ctx, 1))
675 goto fail;
676
Christopher Faulete6902cd2018-11-30 22:29:48 +0100677 comp_type = NULL;
678
679 /* we don't want to compress multipart content-types, nor content-types that are
680 * not listed in the "compression type" directive if any. If no content-type was
681 * found but configuration requires one, we don't compress either. Backend has
682 * the priority.
683 */
684 ctx.blk = NULL;
685 if (http_find_header(htx, ist("Content-Type"), &ctx, 1)) {
686 if (ctx.value.len >= 9 && strncasecmp("multipart", ctx.value.ptr, 9) == 0)
687 goto fail;
688
Olivier Houcharddb573e92023-04-05 17:32:36 +0200689 if ((s->be->comp && (comp_type = s->be->comp->types_res)) ||
690 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types_res))) {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100691 for (; comp_type; comp_type = comp_type->next) {
692 if (ctx.value.len >= comp_type->name_len &&
693 strncasecmp(ctx.value.ptr, comp_type->name, comp_type->name_len) == 0)
694 /* this Content-Type should be compressed */
695 break;
696 }
697 /* this Content-Type should not be compressed */
698 if (comp_type == NULL)
699 goto fail;
700 }
701 }
702 else { /* no content-type header */
Olivier Houcharddb573e92023-04-05 17:32:36 +0200703 if ((s->be->comp && s->be->comp->types_res) ||
704 (strm_fe(s)->comp && strm_fe(s)->comp->types_res))
Christopher Faulete6902cd2018-11-30 22:29:48 +0100705 goto fail; /* a content-type was required */
706 }
707
708 /* limit compression rate */
709 if (global.comp_rate_lim > 0)
710 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
711 goto fail;
712
713 /* limit cpu usage */
Willy Tarreau45c38e22021-09-30 18:28:49 +0200714 if (th_ctx->idle_pct < compress_min_idle)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100715 goto fail;
716
717 /* initialize compression */
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200718 if (st->comp_algo[COMP_DIR_RES]->init(&st->comp_ctx[COMP_DIR_RES], global.tune.comp_maxlevel) < 0)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100719 goto fail;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100720 msg->flags |= HTTP_MSGF_COMPRESSING;
721 return 1;
722
Christopher Faulete6902cd2018-11-30 22:29:48 +0100723 fail:
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200724 st->comp_algo[COMP_DIR_RES] = NULL;
Christopher Faulete6902cd2018-11-30 22:29:48 +0100725 return 0;
726}
727
Christopher Faulet3d97c902015-12-09 14:59:38 +0100728/***********************************************************************/
Christopher Faulete6902cd2018-11-30 22:29:48 +0100729static int
730htx_compression_buffer_init(struct htx *htx, struct buffer *out)
731{
732 /* output stream requires at least 10 bytes for the gzip header, plus
733 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
734 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
735 */
736 if (htx_free_space(htx) < 20 + 5 * ((htx->data + 32767) >> 15))
737 return -1;
738 b_reset(out);
739 return 0;
740}
741
Christopher Faulete6902cd2018-11-30 22:29:48 +0100742static int
743htx_compression_buffer_add_data(struct comp_state *st, const char *data, size_t len,
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200744 struct buffer *out, int dir)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100745{
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200746
747 return st->comp_algo[dir]->add_data(st->comp_ctx[dir], data, len, out);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100748}
749
Christopher Faulete6902cd2018-11-30 22:29:48 +0100750static int
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200751htx_compression_buffer_end(struct comp_state *st, struct buffer *out, int end, int dir)
Christopher Faulete6902cd2018-11-30 22:29:48 +0100752{
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200753
Christopher Faulete6902cd2018-11-30 22:29:48 +0100754 if (end)
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200755 return st->comp_algo[dir]->finish(st->comp_ctx[dir], out);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100756 else
Olivier Houcharddfc11da2023-04-05 16:25:57 +0200757 return st->comp_algo[dir]->flush(st->comp_ctx[dir], out);
Christopher Faulete6902cd2018-11-30 22:29:48 +0100758}
759
Christopher Faulet3d97c902015-12-09 14:59:38 +0100760
761/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100762struct flt_ops comp_ops = {
Christopher Faulete6902cd2018-11-30 22:29:48 +0100763 .init = comp_flt_init,
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +0200764 .init_per_thread = comp_flt_init_per_thread,
765 .deinit_per_thread = comp_flt_deinit_per_thread,
Christopher Faulet92d36382015-11-05 13:35:03 +0100766
Christopher Faulet5e896512020-03-06 14:59:05 +0100767 .attach = comp_strm_init,
768 .detach = comp_strm_deinit,
769
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200770 .channel_post_analyze = comp_http_post_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100771
Christopher Faulet1339d742016-05-11 16:48:33 +0200772 .http_headers = comp_http_headers,
Christopher Faulete6902cd2018-11-30 22:29:48 +0100773 .http_payload = comp_http_payload,
774 .http_end = comp_http_end,
Christopher Faulet92d36382015-11-05 13:35:03 +0100775};
776
Christopher Faulet3d97c902015-12-09 14:59:38 +0100777static int
778parse_compression_options(char **args, int section, struct proxy *proxy,
Willy Tarreau01825162021-03-09 09:53:46 +0100779 const struct proxy *defpx, const char *file, int line,
Christopher Faulet3d97c902015-12-09 14:59:38 +0100780 char **err)
781{
Christopher Faulet92d36382015-11-05 13:35:03 +0100782 struct comp *comp;
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100783 int ret = 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100784
785 if (proxy->comp == NULL) {
Vincent Bernat02779b62016-04-03 13:48:43 +0200786 comp = calloc(1, sizeof(*comp));
Olivier Houchardead43fe2023-04-06 00:33:48 +0200787 /* Always default to compress responses */
788 comp->flags = COMP_FL_DIR_RES;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100789 proxy->comp = comp;
790 }
791 else
792 comp = proxy->comp;
793
Olivier Houcharddb573e92023-04-05 17:32:36 +0200794 if (strcmp(args[1], "algo") == 0 || strcmp(args[1], "algo-res") == 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100795 struct comp_ctx *ctx;
796 int cur_arg = 2;
797
798 if (!*args[cur_arg]) {
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100799 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>.",
Christopher Faulet3d97c902015-12-09 14:59:38 +0100800 file, line, args[0]);
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100801 ret = -1;
802 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100803 }
804 while (*(args[cur_arg])) {
Olivier Houcharddb573e92023-04-05 17:32:36 +0200805 int retval = comp_append_algo(&comp->algos_res, args[cur_arg]);
Remi Tricot-Le Breton6443bcc2021-05-17 10:35:08 +0200806 if (retval) {
807 if (retval < 0)
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100808 memprintf(err, "'%s' : '%s' is not a supported algorithm.",
Remi Tricot-Le Breton6443bcc2021-05-17 10:35:08 +0200809 args[0], args[cur_arg]);
810 else
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100811 memprintf(err, "'%s' : out of memory while parsing algo '%s'.",
Remi Tricot-Le Breton6443bcc2021-05-17 10:35:08 +0200812 args[0], args[cur_arg]);
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100813 ret = -1;
814 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100815 }
Remi Tricot-Le Breton6443bcc2021-05-17 10:35:08 +0200816
Olivier Houcharddb573e92023-04-05 17:32:36 +0200817 if (proxy->comp->algos_res->init(&ctx, 9) == 0)
818 proxy->comp->algos_res->end(&ctx);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100819 else {
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100820 memprintf(err, "'%s' : Can't init '%s' algorithm.",
Christopher Faulet3d97c902015-12-09 14:59:38 +0100821 args[0], args[cur_arg]);
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100822 ret = -1;
823 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100824 }
825 cur_arg++;
826 continue;
827 }
828 }
Olivier Houchardead43fe2023-04-06 00:33:48 +0200829 else if (strcmp(args[1], "algo-req") == 0) {
830 struct comp_ctx *ctx;
831 int retval = comp_append_algo(&comp->algo_req, args[2]);
832
833 if (retval) {
834 if (retval < 0)
835 memprintf(err, "'%s' : '%s' is not a supported algorithm.",
836 args[0], args[2]);
837 else
838 memprintf(err, "'%s' : out of memory while parsing algo '%s'.",
839 args[0], args[2]);
840 ret = -1;
841 goto end;
842 }
843
844 if (proxy->comp->algo_req->init(&ctx, 9) == 0)
845 proxy->comp->algo_req->end(&ctx);
846 else {
847 memprintf(err, "'%s' : Can't init '%s' algorithm.",
848 args[0], args[2]);
849 ret = -1;
850 goto end;
851 }
852 }
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100853 else if (strcmp(args[1], "offload") == 0) {
854 if (proxy->cap & PR_CAP_DEF) {
855 memprintf(err, "'%s' : '%s' ignored in 'defaults' section.",
856 args[0], args[1]);
857 ret = 1;
858 }
Olivier Houchard3ce0f012023-04-03 22:22:24 +0200859 comp->flags |= COMP_FL_OFFLOAD;
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100860 }
Olivier Houcharddb573e92023-04-05 17:32:36 +0200861 else if (strcmp(args[1], "type") == 0 || strcmp(args[1], "type-res") == 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100862 int cur_arg = 2;
863
864 if (!*args[cur_arg]) {
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100865 memprintf(err, "'%s' expects <type>.", args[0]);
866 ret = -1;
867 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100868 }
869 while (*(args[cur_arg])) {
Olivier Houcharddb573e92023-04-05 17:32:36 +0200870 if (comp_append_type(&comp->types_res, args[cur_arg])) {
Remi Tricot-Le Breton6443bcc2021-05-17 10:35:08 +0200871 memprintf(err, "'%s': out of memory.", args[0]);
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100872 ret = -1;
873 goto end;
Remi Tricot-Le Breton6443bcc2021-05-17 10:35:08 +0200874 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100875 cur_arg++;
876 continue;
877 }
878 }
Olivier Houchardead43fe2023-04-06 00:33:48 +0200879 else if (strcmp(args[1], "type-req") == 0) {
880 int cur_arg = 2;
881
882 if (!*args[cur_arg]) {
883 memprintf(err, "'%s' expects <type>.", args[0]);
884 ret = -1;
885 goto end;
886 }
887 while (*(args[cur_arg])) {
888 if (comp_append_type(&comp->types_req, args[cur_arg])) {
889 memprintf(err, "'%s': out of memory.", args[0]);
890 ret = -1;
891 goto end;
892 }
893 cur_arg++;
894 continue;
895 }
896 }
897 else if (strcmp(args[1], "direction") == 0) {
898 if (!args[2]) {
899 memprintf(err, "'%s' expects 'request', 'response', or 'both'.", args[0]);
900 ret = -1;
901 goto end;
902 }
903 if (strcmp(args[2], "request") == 0) {
904 comp->flags &= ~COMP_FL_DIR_RES;
905 comp->flags |= COMP_FL_DIR_REQ;
906 } else if (strcmp(args[2], "response") == 0) {
907 comp->flags &= COMP_FL_DIR_REQ;
908 comp->flags |= COMP_FL_DIR_RES;
909 } else if (strcmp(args[2], "both") == 0)
910 comp->flags |= COMP_FL_DIR_REQ | COMP_FL_DIR_RES;
911 else {
912 memprintf(err, "'%s' expects 'request', 'response', or 'both'.", args[0]);
913 ret = -1;
914 goto end;
915 }
916 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100917 else {
Olivier Houchardead43fe2023-04-06 00:33:48 +0200918 memprintf(err, "'%s' expects 'algo', 'type' 'direction' or 'offload'",
Christopher Faulet3d97c902015-12-09 14:59:38 +0100919 args[0]);
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100920 ret = -1;
921 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100922 }
923
Christopher Faulet44d34bf2021-11-05 12:06:14 +0100924 end:
925 return ret;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100926}
927
Christopher Faulet92d36382015-11-05 13:35:03 +0100928static int
929parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
Thierry Fournier3610c392016-04-13 18:27:51 +0200930 struct flt_conf *fconf, char **err, void *private)
Christopher Faulet92d36382015-11-05 13:35:03 +0100931{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100932 struct flt_conf *fc, *back;
Christopher Faulet92d36382015-11-05 13:35:03 +0100933
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100934 list_for_each_entry_safe(fc, back, &px->filter_configs, list) {
935 if (fc->id == http_comp_flt_id) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100936 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
937 return -1;
938 }
939 }
940
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100941 fconf->id = http_comp_flt_id;
942 fconf->conf = NULL;
943 fconf->ops = &comp_ops;
Christopher Faulet92d36382015-11-05 13:35:03 +0100944 (*cur_arg)++;
945
946 return 0;
947}
948
949
950int
Christopher Fauletc9df7f72018-12-10 16:14:04 +0100951check_implicit_http_comp_flt(struct proxy *proxy)
Christopher Faulet92d36382015-11-05 13:35:03 +0100952{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100953 struct flt_conf *fconf;
Christopher Faulet27d93c32018-12-15 22:32:02 +0100954 int explicit = 0;
955 int comp = 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100956 int err = 0;
957
958 if (proxy->comp == NULL)
959 goto end;
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100960 if (!LIST_ISEMPTY(&proxy->filter_configs)) {
961 list_for_each_entry(fconf, &proxy->filter_configs, list) {
962 if (fconf->id == http_comp_flt_id)
Christopher Faulet27d93c32018-12-15 22:32:02 +0100963 comp = 1;
964 else if (fconf->id == cache_store_flt_id) {
965 if (comp) {
966 ha_alert("config: %s '%s': unable to enable the compression filter "
967 "before any cache filter.\n",
968 proxy_type_str(proxy), proxy->id);
969 err++;
970 goto end;
971 }
972 }
Christopher Faulet78fbb9f2019-08-11 23:11:03 +0200973 else if (fconf->id == fcgi_flt_id)
974 continue;
Christopher Faulet27d93c32018-12-15 22:32:02 +0100975 else
976 explicit = 1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100977 }
Christopher Faulet27d93c32018-12-15 22:32:02 +0100978 }
979 if (comp)
980 goto end;
981 else if (explicit) {
982 ha_alert("config: %s '%s': require an explicit filter declaration to use "
983 "HTTP compression\n", proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100984 err++;
985 goto end;
986 }
987
Christopher Faulet27d93c32018-12-15 22:32:02 +0100988 /* Implicit declaration of the compression filter is always the last
989 * one */
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100990 fconf = calloc(1, sizeof(*fconf));
991 if (!fconf) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100992 ha_alert("config: %s '%s': out of memory\n",
993 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100994 err++;
995 goto end;
996 }
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100997 fconf->id = http_comp_flt_id;
998 fconf->conf = NULL;
999 fconf->ops = &comp_ops;
Willy Tarreau2b718102021-04-21 07:32:39 +02001000 LIST_APPEND(&proxy->filter_configs, &fconf->list);
Christopher Faulet92d36382015-11-05 13:35:03 +01001001 end:
1002 return err;
1003}
1004
1005/*
1006 * boolean, returns true if compression is used (either gzip or deflate) in the
1007 * response.
1008 */
Christopher Faulet3d97c902015-12-09 14:59:38 +01001009static int
Christopher Faulet92d36382015-11-05 13:35:03 +01001010smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
1011 void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001012{
Willy Tarreaube508f12016-03-10 11:47:01 +01001013 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +01001014
Christopher Faulet3d97c902015-12-09 14:59:38 +01001015 smp->data.type = SMP_T_BOOL;
Christopher Faulet92d36382015-11-05 13:35:03 +01001016 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
Christopher Faulet3d97c902015-12-09 14:59:38 +01001017 return 1;
1018}
1019
Christopher Faulet92d36382015-11-05 13:35:03 +01001020/*
1021 * string, returns algo
1022 */
Christopher Faulet3d97c902015-12-09 14:59:38 +01001023static int
Christopher Faulet92d36382015-11-05 13:35:03 +01001024smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
1025 const char *kw, void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001026{
Willy Tarreaube508f12016-03-10 11:47:01 +01001027 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +01001028 struct filter *filter;
1029 struct comp_state *st;
1030
Christopher Faulet03d85532017-09-15 10:14:43 +02001031 if (!txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING))
Christopher Faulet3d97c902015-12-09 14:59:38 +01001032 return 0;
1033
Christopher Fauletfcf035c2015-12-03 11:48:03 +01001034 list_for_each_entry(filter, &strm_flt(smp->strm)->filters, list) {
Christopher Faulet443ea1a2016-02-04 13:40:26 +01001035 if (FLT_ID(filter) != http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +01001036 continue;
1037
1038 if (!(st = filter->ctx))
1039 break;
1040
1041 smp->data.type = SMP_T_STR;
1042 smp->flags = SMP_F_CONST;
Olivier Houcharddfc11da2023-04-05 16:25:57 +02001043 smp->data.u.str.area = st->comp_algo[COMP_DIR_RES]->cfg_name;
1044 smp->data.u.str.data = st->comp_algo[COMP_DIR_RES]->cfg_name_len;
Christopher Faulet92d36382015-11-05 13:35:03 +01001045 return 1;
1046 }
1047 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +01001048}
1049
1050/* Declare the config parser for "compression" keyword */
1051static struct cfg_kw_list cfg_kws = {ILH, {
1052 { CFG_LISTEN, "compression", parse_compression_options },
1053 { 0, NULL, NULL },
1054 }
1055};
1056
Willy Tarreau0108d902018-11-25 19:14:37 +01001057INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
1058
Christopher Faulet92d36382015-11-05 13:35:03 +01001059/* Declare the filter parser for "compression" keyword */
1060static struct flt_kw_list filter_kws = { "COMP", { }, {
Thierry Fournier3610c392016-04-13 18:27:51 +02001061 { "compression", parse_http_comp_flt, NULL },
1062 { NULL, NULL, NULL },
Christopher Faulet92d36382015-11-05 13:35:03 +01001063 }
1064};
1065
Willy Tarreau0108d902018-11-25 19:14:37 +01001066INITCALL1(STG_REGISTER, flt_register_keywords, &filter_kws);
1067
Christopher Faulet3d97c902015-12-09 14:59:38 +01001068/* Note: must not be declared <const> as its list will be overwritten */
1069static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet92d36382015-11-05 13:35:03 +01001070 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
1071 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
1072 { /* END */ },
1073 }
1074};
Christopher Faulet3d97c902015-12-09 14:59:38 +01001075
Willy Tarreau0108d902018-11-25 19:14:37 +01001076INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);