blob: 85abb8466d4907e75fed8e5d33a857564ad84790 [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
13#include <common/buffer.h>
14#include <common/cfgparse.h>
15#include <common/mini-clist.h>
16#include <common/standard.h>
17
18#include <types/compression.h>
19#include <types/filters.h>
20#include <types/proto_http.h>
21#include <types/proxy.h>
22#include <types/sample.h>
23
24#include <proto/compression.h>
Christopher Faulet92d36382015-11-05 13:35:03 +010025#include <proto/filters.h>
Christopher Faulet3d97c902015-12-09 14:59:38 +010026#include <proto/hdr_idx.h>
27#include <proto/proto_http.h>
28#include <proto/sample.h>
29#include <proto/stream.h>
30
Christopher Faulet92d36382015-11-05 13:35:03 +010031static const char *http_comp_flt_id = "compression filter";
32
33struct flt_ops comp_ops;
34
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020035
36/* Pools used to allocate comp_state structs */
Willy Tarreaubafbe012017-11-24 17:34:44 +010037static struct pool_head *pool_head_comp_state = NULL;
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020038
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020039static THREAD_LOCAL struct buffer *tmpbuf = &buf_empty;
40static THREAD_LOCAL struct buffer *zbuf = &buf_empty;
Christopher Faulet92d36382015-11-05 13:35:03 +010041
Christopher Faulet92d36382015-11-05 13:35:03 +010042struct comp_state {
43 struct comp_ctx *comp_ctx; /* compression context */
44 struct comp_algo *comp_algo; /* compression algorithm if not NULL */
Christopher Fauletb77c5c22015-12-07 16:48:42 +010045 int hdrs_len;
46 int tlrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +010047 int consumed;
48 int initialized;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010049 int finished;
Christopher Faulet92d36382015-11-05 13:35:03 +010050};
51
Christopher Faulet92d36382015-11-05 13:35:03 +010052static int select_compression_request_header(struct comp_state *st,
53 struct stream *s,
54 struct http_msg *msg);
55static int select_compression_response_header(struct comp_state *st,
56 struct stream *s,
57 struct http_msg *msg);
58
59static int http_compression_buffer_init(struct buffer *in, struct buffer *out);
60static int http_compression_buffer_add_data(struct comp_state *st,
61 struct buffer *in,
62 struct buffer *out, int sz);
63static int http_compression_buffer_end(struct comp_state *st, struct stream *s,
Willy Tarreau4d452382018-06-06 07:15:47 +020064 struct channel *chn, struct buffer **out,
Christopher Faulet2fb28802015-12-01 10:40:57 +010065 int end);
Christopher Faulet92d36382015-11-05 13:35:03 +010066
67/***********************************************************************/
68static int
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020069comp_flt_init_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010070{
Christopher Fauletb77c5c22015-12-07 16:48:42 +010071 if (!tmpbuf->size && b_alloc(&tmpbuf) == NULL)
72 return -1;
73 if (!zbuf->size && b_alloc(&zbuf) == NULL)
74 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +010075 return 0;
76}
77
78static void
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020079comp_flt_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010080{
81 if (tmpbuf->size)
82 b_free(&tmpbuf);
Christopher Fauletb77c5c22015-12-07 16:48:42 +010083 if (zbuf->size)
84 b_free(&zbuf);
Christopher Faulet92d36382015-11-05 13:35:03 +010085}
86
87static int
88comp_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
89{
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020090
Christopher Faulet92d36382015-11-05 13:35:03 +010091 if (filter->ctx == NULL) {
92 struct comp_state *st;
93
Willy Tarreaubafbe012017-11-24 17:34:44 +010094 st = pool_alloc_dirty(pool_head_comp_state);
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020095 if (st == NULL)
Christopher Faulet92d36382015-11-05 13:35:03 +010096 return -1;
97
Christopher Faulet2fb28802015-12-01 10:40:57 +010098 st->comp_algo = NULL;
99 st->comp_ctx = NULL;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100100 st->hdrs_len = 0;
101 st->tlrs_len = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100102 st->consumed = 0;
103 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100104 st->finished = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100105 filter->ctx = st;
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200106
107 /* Register post-analyzer on AN_RES_WAIT_HTTP because we need to
108 * analyze response headers before http-response rules execution
109 * to be sure we can use res.comp and res.comp_algo sample
110 * fetches */
111 filter->post_analyzers |= AN_RES_WAIT_HTTP;
Christopher Faulet92d36382015-11-05 13:35:03 +0100112 }
113 return 1;
114}
115
116static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100117comp_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
118{
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 Faulet92d36382015-11-05 13:35:03 +0100122 goto end;
123
Christopher Faulet92d36382015-11-05 13:35:03 +0100124 /* release any possible compression context */
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200125 if (st->comp_algo)
126 st->comp_algo->end(&st->comp_ctx);
Willy Tarreaubafbe012017-11-24 17:34:44 +0100127 pool_free(pool_head_comp_state, st);
Christopher Faulet92d36382015-11-05 13:35:03 +0100128 filter->ctx = NULL;
129 end:
130 return 1;
131}
132
133static int
Christopher Faulet1339d742016-05-11 16:48:33 +0200134comp_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
135{
136 struct comp_state *st = filter->ctx;
137
138 if (!strm_fe(s)->comp && !s->be->comp)
139 goto end;
140
141 if (!(msg->chn->flags & CF_ISRESP))
142 select_compression_request_header(st, s, msg);
143 else {
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200144 /* Response headers have already been checked in
145 * comp_http_post_analyze callback. */
Christopher Faulet1339d742016-05-11 16:48:33 +0200146 if (st->comp_algo) {
147 register_data_filter(s, msg->chn, filter);
148 st->hdrs_len = s->txn->rsp.sov;
149 }
150 }
151
152 end:
153 return 1;
154}
155
156static int
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200157comp_http_post_analyze(struct stream *s, struct filter *filter,
158 struct channel *chn, unsigned an_bit)
159{
160 struct http_txn *txn = s->txn;
161 struct http_msg *msg = &txn->rsp;
162 struct comp_state *st = filter->ctx;
163
164 if (an_bit != AN_RES_WAIT_HTTP)
165 goto end;
166
167 if (!strm_fe(s)->comp && !s->be->comp)
168 goto end;
169
170 select_compression_response_header(st, s, msg);
171
172 end:
173 return 1;
174}
175
176static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100177comp_http_data(struct stream *s, struct filter *filter, struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100178{
179 struct comp_state *st = filter->ctx;
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200180 struct channel *chn = msg->chn;
181 struct buffer *buf = chn->buf;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100182 unsigned int *nxt = &flt_rsp_nxt(filter);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100183 unsigned int len;
Christopher Faulet92d36382015-11-05 13:35:03 +0100184 int ret;
185
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100186 len = MIN(msg->chunk_len + msg->next, buf->i) - *nxt;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100187 if (!len)
188 return len;
189
190 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100191 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100192
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100193 b_reset(tmpbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200194 c_adv(chn, fwd);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100195 ret = http_compression_buffer_init(buf, zbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200196 c_rew(chn, fwd);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100197 if (ret < 0) {
198 msg->chn->flags |= CF_WAKE_WRITE;
199 return 0;
200 }
201 }
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100202
203 if (msg->flags & HTTP_MSGF_TE_CHNK) {
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200204 int block;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100205
Willy Tarreaueac52592018-06-15 13:59:36 +0200206 len = MIN(b_room(tmpbuf), len);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200207
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200208 c_adv(chn, *nxt);
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200209 block = ci_contig_data(chn);
Willy Tarreau8f9c72d2018-06-07 18:46:28 +0200210 memcpy(b_tail(tmpbuf), ci_head(chn), block);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200211 if (len > block)
Willy Tarreau8f9c72d2018-06-07 18:46:28 +0200212 memcpy(b_tail(tmpbuf)+block, buf->data, len-block);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200213 c_rew(chn, *nxt);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200214
Olivier Houchardacd14032018-06-28 18:17:23 +0200215 b_add(tmpbuf, len);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100216 ret = len;
217 }
218 else {
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200219 c_adv(chn, *nxt);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100220 ret = http_compression_buffer_add_data(st, buf, zbuf, len);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200221 c_rew(chn, *nxt);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100222 if (ret < 0)
223 return ret;
224 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100225
Christopher Faulet2fb28802015-12-01 10:40:57 +0100226 st->initialized = 1;
227 msg->next += ret;
228 msg->chunk_len -= ret;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100229 *nxt = msg->next;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100230 return 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100231}
232
233static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100234comp_http_chunk_trailers(struct stream *s, struct filter *filter,
235 struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100236{
237 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100238
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100239 if (!st->initialized) {
240 if (!st->finished) {
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200241 struct channel *chn = msg->chn;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100242 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100243
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100244 b_reset(tmpbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200245 c_adv(chn, fwd);
246 http_compression_buffer_init(chn->buf, zbuf);
247 c_rew(chn, fwd);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100248 st->initialized = 1;
249 }
250 }
251 st->tlrs_len = msg->sol;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100252 return 1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100253}
254
Christopher Faulet2fb28802015-12-01 10:40:57 +0100255
Christopher Faulet92d36382015-11-05 13:35:03 +0100256static int
257comp_http_forward_data(struct stream *s, struct filter *filter,
258 struct http_msg *msg, unsigned int len)
259{
260 struct comp_state *st = filter->ctx;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100261 int ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100262
Christopher Faulet2fb28802015-12-01 10:40:57 +0100263 /* To work, previous filters MUST forward all data */
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100264 if (flt_rsp_fwd(filter) + len != flt_rsp_nxt(filter)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100265 ha_warning("HTTP compression failed: unexpected behavior of previous filters\n");
Christopher Faulet2fb28802015-12-01 10:40:57 +0100266 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100267 }
268
Christopher Faulet2fb28802015-12-01 10:40:57 +0100269 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100270 if (!len) {
271 /* Nothing to foward */
272 ret = len;
273 }
274 else if (st->hdrs_len > len) {
275 /* Forward part of headers */
276 ret = len;
277 st->hdrs_len -= len;
278 }
279 else if (st->hdrs_len > 0) {
280 /* Forward remaining headers */
281 ret = st->hdrs_len;
282 st->hdrs_len = 0;
283 }
284 else if (msg->msg_state < HTTP_MSG_TRAILERS) {
285 /* Do not forward anything for now. This only happens
286 * with chunk-encoded responses. Waiting data are part
287 * of the chunk envelope (the chunk size or the chunk
288 * CRLF). These data will be skipped during the
289 * compression. */
290 ret = 0;
291 }
292 else {
293 /* Forward trailers data */
294 ret = len;
295 }
Christopher Faulet2fb28802015-12-01 10:40:57 +0100296 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100297 }
298
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100299 if (msg->flags & HTTP_MSGF_TE_CHNK) {
300 ret = http_compression_buffer_add_data(st, tmpbuf, zbuf, tmpbuf->i);
301 if (ret != tmpbuf->i) {
Willy Tarreau506a29a2018-07-18 10:07:58 +0200302 ha_warning("HTTP compression failed: Must consume %u bytes but only %d bytes consumed\n",
303 (unsigned int)tmpbuf->i, ret);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100304 return -1;
305 }
306 }
307
308 st->consumed = len - st->hdrs_len - st->tlrs_len;
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200309 c_adv(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
Willy Tarreau4d452382018-06-06 07:15:47 +0200310 ret = http_compression_buffer_end(st, s, msg->chn, &zbuf, msg->msg_state >= HTTP_MSG_TRAILERS);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200311 c_rew(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100312 if (ret < 0)
313 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100314
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100315 flt_change_forward_size(filter, msg->chn, ret - st->consumed);
316 msg->next += (ret - st->consumed);
317 ret += st->hdrs_len + st->tlrs_len;
318
Christopher Faulet2fb28802015-12-01 10:40:57 +0100319 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100320 st->finished = (msg->msg_state >= HTTP_MSG_TRAILERS);
321 st->hdrs_len = 0;
322 st->tlrs_len = 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100323 return ret;
324}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100325
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200326static int
327comp_http_end(struct stream *s, struct filter *filter,
328 struct http_msg *msg)
329{
330 struct comp_state *st = filter->ctx;
331
332 if (!(msg->chn->flags & CF_ISRESP) || !st || !st->comp_algo)
333 goto end;
334
335 if (strm_fe(s)->mode == PR_MODE_HTTP)
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200336 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.p.http.comp_rsp, 1);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200337 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200338 HA_ATOMIC_ADD(&s->be->be_counters.p.http.comp_rsp, 1);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200339 end:
340 return 1;
341}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100342/***********************************************************************/
343/*
344 * Selects a compression algorithm depending on the client request.
345 */
346int
Christopher Faulet92d36382015-11-05 13:35:03 +0100347select_compression_request_header(struct comp_state *st, struct stream *s,
348 struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100349{
350 struct http_txn *txn = s->txn;
Christopher Faulet92d36382015-11-05 13:35:03 +0100351 struct buffer *req = msg->chn->buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100352 struct hdr_ctx ctx;
353 struct comp_algo *comp_algo = NULL;
354 struct comp_algo *comp_algo_back = NULL;
355
356 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
357 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
358 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
359 */
360 ctx.idx = 0;
361 if (http_find_header2("User-Agent", 10, req->p, &txn->hdr_idx, &ctx) &&
362 ctx.vlen >= 9 &&
363 memcmp(ctx.line + ctx.val, "Mozilla/4", 9) == 0 &&
364 (ctx.vlen < 31 ||
365 memcmp(ctx.line + ctx.val + 25, "MSIE ", 5) != 0 ||
366 ctx.line[ctx.val + 30] < '6' ||
367 (ctx.line[ctx.val + 30] == '6' &&
368 (ctx.vlen < 54 || memcmp(ctx.line + 51, "SV1", 3) != 0)))) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100369 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100370 return 0;
371 }
372
373 /* search for the algo in the backend in priority or the frontend */
Christopher Faulet92d36382015-11-05 13:35:03 +0100374 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
375 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100376 int best_q = 0;
377
378 ctx.idx = 0;
379 while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) {
380 const char *qval;
381 int q;
382 int toklen;
383
384 /* try to isolate the token from the optional q-value */
385 toklen = 0;
Willy Tarreau2235b262016-11-05 15:50:20 +0100386 while (toklen < ctx.vlen && HTTP_IS_TOKEN(*(ctx.line + ctx.val + toklen)))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100387 toklen++;
388
389 qval = ctx.line + ctx.val + toklen;
390 while (1) {
Willy Tarreau2235b262016-11-05 15:50:20 +0100391 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100392 qval++;
393
394 if (qval >= ctx.line + ctx.val + ctx.vlen || *qval != ';') {
395 qval = NULL;
396 break;
397 }
398 qval++;
399
Willy Tarreau2235b262016-11-05 15:50:20 +0100400 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100401 qval++;
402
403 if (qval >= ctx.line + ctx.val + ctx.vlen) {
404 qval = NULL;
405 break;
406 }
407 if (strncmp(qval, "q=", MIN(ctx.line + ctx.val + ctx.vlen - qval, 2)) == 0)
408 break;
409
410 while (qval < ctx.line + ctx.val + ctx.vlen && *qval != ';')
411 qval++;
412 }
413
414 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
415 q = qval ? parse_qvalue(qval + 2, NULL) : 1000;
416
417 if (q <= best_q)
418 continue;
419
420 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
421 if (*(ctx.line + ctx.val) == '*' ||
422 word_match(ctx.line + ctx.val, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100423 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100424 best_q = q;
425 break;
426 }
427 }
428 }
429 }
430
431 /* remove all occurrences of the header when "compression offload" is set */
Christopher Faulet92d36382015-11-05 13:35:03 +0100432 if (st->comp_algo) {
433 if ((s->be->comp && s->be->comp->offload) ||
434 (strm_fe(s)->comp && strm_fe(s)->comp->offload)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100435 http_remove_header2(msg, &txn->hdr_idx, &ctx);
436 ctx.idx = 0;
437 while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) {
438 http_remove_header2(msg, &txn->hdr_idx, &ctx);
439 }
440 }
441 return 1;
442 }
443
444 /* identity is implicit does not require headers */
Christopher Faulet92d36382015-11-05 13:35:03 +0100445 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
446 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100447 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
448 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100449 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100450 return 1;
451 }
452 }
453 }
454
Christopher Faulet92d36382015-11-05 13:35:03 +0100455 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100456 return 0;
457}
458
Christopher Faulet92d36382015-11-05 13:35:03 +0100459
Christopher Faulet3d97c902015-12-09 14:59:38 +0100460/*
461 * Selects a comression algorithm depending of the server response.
462 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100463static int
464select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100465{
466 struct http_txn *txn = s->txn;
Christopher Faulet92d36382015-11-05 13:35:03 +0100467 struct buffer *res = msg->chn->buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100468 struct hdr_ctx ctx;
469 struct comp_type *comp_type;
470
471 /* no common compression algorithm was found in request header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100472 if (st->comp_algo == NULL)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100473 goto fail;
474
475 /* HTTP < 1.1 should not be compressed */
476 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
477 goto fail;
478
Christopher Faulet92d36382015-11-05 13:35:03 +0100479 if (txn->meth == HTTP_METH_HEAD)
480 goto fail;
481
Christopher Faulet3d97c902015-12-09 14:59:38 +0100482 /* compress 200,201,202,203 responses only */
483 if ((txn->status != 200) &&
484 (txn->status != 201) &&
485 (txn->status != 202) &&
486 (txn->status != 203))
487 goto fail;
488
489
490 /* Content-Length is null */
491 if (!(msg->flags & HTTP_MSGF_TE_CHNK) && msg->body_len == 0)
492 goto fail;
493
494 /* content is already compressed */
495 ctx.idx = 0;
496 if (http_find_header2("Content-Encoding", 16, res->p, &txn->hdr_idx, &ctx))
497 goto fail;
498
499 /* no compression when Cache-Control: no-transform is present in the message */
500 ctx.idx = 0;
501 while (http_find_header2("Cache-Control", 13, res->p, &txn->hdr_idx, &ctx)) {
502 if (word_match(ctx.line + ctx.val, ctx.vlen, "no-transform", 12))
503 goto fail;
504 }
505
506 comp_type = NULL;
507
508 /* we don't want to compress multipart content-types, nor content-types that are
509 * not listed in the "compression type" directive if any. If no content-type was
510 * found but configuration requires one, we don't compress either. Backend has
511 * the priority.
512 */
513 ctx.idx = 0;
514 if (http_find_header2("Content-Type", 12, res->p, &txn->hdr_idx, &ctx)) {
515 if (ctx.vlen >= 9 && strncasecmp("multipart", ctx.line+ctx.val, 9) == 0)
516 goto fail;
517
518 if ((s->be->comp && (comp_type = s->be->comp->types)) ||
519 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types))) {
520 for (; comp_type; comp_type = comp_type->next) {
521 if (ctx.vlen >= comp_type->name_len &&
522 strncasecmp(ctx.line+ctx.val, comp_type->name, comp_type->name_len) == 0)
523 /* this Content-Type should be compressed */
524 break;
525 }
526 /* this Content-Type should not be compressed */
527 if (comp_type == NULL)
528 goto fail;
529 }
530 }
531 else { /* no content-type header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100532 if ((s->be->comp && s->be->comp->types) ||
533 (strm_fe(s)->comp && strm_fe(s)->comp->types))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100534 goto fail; /* a content-type was required */
535 }
536
537 /* limit compression rate */
538 if (global.comp_rate_lim > 0)
539 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
540 goto fail;
541
542 /* limit cpu usage */
543 if (idle_pct < compress_min_idle)
544 goto fail;
545
546 /* initialize compression */
Christopher Faulet92d36382015-11-05 13:35:03 +0100547 if (st->comp_algo->init(&st->comp_ctx, global.tune.comp_maxlevel) < 0)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100548 goto fail;
549
Christopher Faulet3d97c902015-12-09 14:59:38 +0100550 /* remove Content-Length header */
551 ctx.idx = 0;
552 if ((msg->flags & HTTP_MSGF_CNT_LEN) && http_find_header2("Content-Length", 14, res->p, &txn->hdr_idx, &ctx))
553 http_remove_header2(msg, &txn->hdr_idx, &ctx);
554
555 /* add Transfer-Encoding header */
556 if (!(msg->flags & HTTP_MSGF_TE_CHNK))
557 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, "Transfer-Encoding: chunked", 26);
558
559 /*
560 * Add Content-Encoding header when it's not identity encoding.
561 * RFC 2616 : Identity encoding: This content-coding is used only in the
562 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
563 * header.
564 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100565 if (st->comp_algo->cfg_name_len != 8 || memcmp(st->comp_algo->cfg_name, "identity", 8) != 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100566 trash.len = 18;
567 memcpy(trash.str, "Content-Encoding: ", trash.len);
Christopher Faulet92d36382015-11-05 13:35:03 +0100568 memcpy(trash.str + trash.len, st->comp_algo->ua_name, st->comp_algo->ua_name_len);
569 trash.len += st->comp_algo->ua_name_len;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100570 trash.str[trash.len] = '\0';
571 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len);
572 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100573 msg->flags |= HTTP_MSGF_COMPRESSING;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100574 return 1;
575
576fail:
Christopher Faulet92d36382015-11-05 13:35:03 +0100577 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100578 return 0;
579}
580
581/***********************************************************************/
582/* emit the chunksize followed by a CRLF on the output and return the number of
583 * bytes written. It goes backwards and starts with the byte before <end>. It
584 * returns the number of bytes written which will not exceed 10 (8 digits, CR,
585 * and LF). The caller is responsible for ensuring there is enough room left in
586 * the output buffer for the string.
587 */
588static int
589http_emit_chunk_size(char *end, unsigned int chksz)
590{
591 char *beg = end;
592
593 *--beg = '\n';
594 *--beg = '\r';
595 do {
596 *--beg = hextab[chksz & 0xF];
597 } while (chksz >>= 4);
598 return end - beg;
599}
600
601/*
602 * Init HTTP compression
603 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100604static int
605http_compression_buffer_init(struct buffer *in, struct buffer *out)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100606{
607 /* output stream requires at least 10 bytes for the gzip header, plus
608 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
609 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
610 */
Willy Tarreaueac52592018-06-15 13:59:36 +0200611 if (b_room(in) < 20 + 5 * ((in->i + 32767) >> 15))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100612 return -1;
613
614 /* prepare an empty output buffer in which we reserve enough room for
615 * copying the output bytes from <in>, plus 10 extra bytes to write
616 * the chunk size. We don't copy the bytes yet so that if we have to
617 * cancel the operation later, it's cheap.
618 */
619 b_reset(out);
620 out->o = in->o;
621 out->p += out->o;
622 out->i = 10;
623 return 0;
624}
625
626/*
627 * Add data to compress
628 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100629static int
630http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
631 struct buffer *out, int sz)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100632{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100633 int consumed_data = 0;
634 int data_process_len;
635 int block1, block2;
636
Christopher Faulet92d36382015-11-05 13:35:03 +0100637 if (!sz)
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100638 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100639
Christopher Faulet92d36382015-11-05 13:35:03 +0100640 /* select the smallest size between the announced chunk size, the input
Christopher Faulet3d97c902015-12-09 14:59:38 +0100641 * data, and the available output buffer size. The compressors are
Christopher Faulet92d36382015-11-05 13:35:03 +0100642 * assumed to be able to process all the bytes we pass to them at
643 * once. */
Willy Tarreaueac52592018-06-15 13:59:36 +0200644 data_process_len = MIN(b_room(out), sz);
Christopher Faulet92d36382015-11-05 13:35:03 +0100645
Christopher Faulet3d97c902015-12-09 14:59:38 +0100646 block1 = data_process_len;
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200647 if (block1 > b_contig_data(in, in->o))
648 block1 = b_contig_data(in, in->o);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100649 block2 = data_process_len - block1;
650
651 /* compressors return < 0 upon error or the amount of bytes read */
Willy Tarreaudda2e412018-06-07 18:08:04 +0200652 consumed_data = st->comp_algo->add_data(st->comp_ctx, b_peek(in, in->o), block1, out);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100653 if (consumed_data != block1 || !block2)
654 goto end;
655 consumed_data = st->comp_algo->add_data(st->comp_ctx, in->data, block2, out);
656 if (consumed_data < 0)
657 goto end;
658 consumed_data += block1;
659
660 end:
Christopher Faulet3d97c902015-12-09 14:59:38 +0100661 return consumed_data;
662}
663
664/*
665 * Flush data in process, and write the header and footer of the chunk. Upon
666 * success, in and out buffers are swapped to avoid a copy.
667 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100668static int
669http_compression_buffer_end(struct comp_state *st, struct stream *s,
Willy Tarreau4d452382018-06-06 07:15:47 +0200670 struct channel *chn, struct buffer **out,
Christopher Faulet2fb28802015-12-01 10:40:57 +0100671 int end)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100672{
Willy Tarreau4d452382018-06-06 07:15:47 +0200673 struct buffer *ib = chn->buf, *ob = *out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100674 char *tail;
Christopher Faulet92d36382015-11-05 13:35:03 +0100675 int to_forward, left;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100676
677#if defined(USE_SLZ) || defined(USE_ZLIB)
678 int ret;
679
680 /* flush data here */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100681 if (end)
Christopher Faulet92d36382015-11-05 13:35:03 +0100682 ret = st->comp_algo->finish(st->comp_ctx, ob); /* end of data */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100683 else
Christopher Faulet92d36382015-11-05 13:35:03 +0100684 ret = st->comp_algo->flush(st->comp_ctx, ob); /* end of buffer */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100685
686 if (ret < 0)
687 return -1; /* flush failed */
688
689#endif /* USE_ZLIB */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100690 if (ob->i == 10) {
691 /* No data were appended, let's drop the output buffer and
692 * keep the input buffer unchanged.
693 */
694 return 0;
695 }
696
697 /* OK so at this stage, we have an output buffer <ob> looking like this :
698 *
699 * <-- o --> <------ i ----->
700 * +---------+---+------------+-----------+
701 * | out | c | comp_in | empty |
702 * +---------+---+------------+-----------+
703 * data p size
704 *
705 * <out> is the room reserved to copy ib->o. It starts at ob->data and
706 * has not yet been filled. <c> is the room reserved to write the chunk
707 * size (10 bytes). <comp_in> is the compressed equivalent of the data
708 * part of ib->i. <empty> is the amount of empty bytes at the end of
709 * the buffer, into which we may have to copy the remaining bytes from
710 * ib->i after the data (chunk size, trailers, ...).
711 */
712
713 /* Write real size at the begining of the chunk, no need of wrapping.
714 * We write the chunk using a dynamic length and adjust ob->p and ob->i
715 * accordingly afterwards. That will move <out> away from <data>.
716 */
717 left = 10 - http_emit_chunk_size(ob->p + 10, ob->i - 10);
718 ob->p += left;
719 ob->i -= left;
720
721 /* Copy previous data from ib->o into ob->o */
722 if (ib->o > 0) {
Willy Tarreau0e11d592018-06-07 19:03:40 +0200723 left = b_contig_data(ib, 0);
724 if (left > ib->o)
725 left = ib->o;
726
Willy Tarreau89faf5d2018-06-07 18:16:48 +0200727 memcpy(ob->p - ob->o, b_head(ib), left);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100728 if (ib->o - left) /* second part of the buffer */
729 memcpy(ob->p - ob->o + left, ib->data, ib->o - left);
730 }
731
732 /* chunked encoding requires CRLF after data */
733 tail = ob->p + ob->i;
734 *tail++ = '\r';
735 *tail++ = '\n';
736
Christopher Faulet2fb28802015-12-01 10:40:57 +0100737 /* At the end of data, we must write the empty chunk 0<CRLF>,
738 * and terminate the trailers section with a last <CRLF>. If
739 * we're forwarding a chunked-encoded response, we'll have a
740 * trailers section after the empty chunk which needs to be
741 * forwarded and which will provide the last CRLF. Otherwise
742 * we write it ourselves.
743 */
744 if (end) {
745 struct http_msg *msg = &s->txn->rsp;
746
747 memcpy(tail, "0\r\n", 3);
748 tail += 3;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100749 if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100750 memcpy(tail, "\r\n", 2);
751 tail += 2;
752 }
753 }
754
Christopher Faulet3d97c902015-12-09 14:59:38 +0100755 ob->i = tail - ob->p;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100756 to_forward = ob->i;
757
758 /* update input rate */
Christopher Faulet92d36382015-11-05 13:35:03 +0100759 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100760 update_freq_ctr(&global.comp_bps_in, st->consumed);
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200761 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_in, st->consumed);
762 HA_ATOMIC_ADD(&s->be->be_counters.comp_in, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100763 } else {
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200764 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_byp, st->consumed);
765 HA_ATOMIC_ADD(&s->be->be_counters.comp_byp, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100766 }
767
768 /* copy the remaining data in the tmp buffer. */
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200769 c_adv(chn, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100770 if (ib->i > 0) {
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200771 left = ci_contig_data(chn);
Willy Tarreaudda2e412018-06-07 18:08:04 +0200772 memcpy(ob->p + ob->i, ci_head(chn), left);
Olivier Houchardacd14032018-06-28 18:17:23 +0200773 b_add(ob, left);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100774 if (ib->i - left) {
775 memcpy(ob->p + ob->i, ib->data, ib->i - left);
Olivier Houchardacd14032018-06-28 18:17:23 +0200776 b_add(ob, ib->i - left);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100777 }
778 }
779
780 /* swap the buffers */
Willy Tarreau4d452382018-06-06 07:15:47 +0200781 chn->buf = ob;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100782 *out = ib;
783
Christopher Faulet92d36382015-11-05 13:35:03 +0100784
785 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100786 update_freq_ctr(&global.comp_bps_out, to_forward);
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200787 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_out, to_forward);
788 HA_ATOMIC_ADD(&s->be->be_counters.comp_out, to_forward);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100789 }
790
Christopher Faulet3d97c902015-12-09 14:59:38 +0100791 return to_forward;
792}
793
794
795/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100796struct flt_ops comp_ops = {
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +0200797 .init_per_thread = comp_flt_init_per_thread,
798 .deinit_per_thread = comp_flt_deinit_per_thread,
Christopher Faulet92d36382015-11-05 13:35:03 +0100799
800 .channel_start_analyze = comp_start_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100801 .channel_end_analyze = comp_end_analyze,
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200802 .channel_post_analyze = comp_http_post_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100803
Christopher Faulet1339d742016-05-11 16:48:33 +0200804 .http_headers = comp_http_headers,
Christopher Faulet309c6412015-12-02 09:57:32 +0100805 .http_data = comp_http_data,
806 .http_chunk_trailers = comp_http_chunk_trailers,
807 .http_forward_data = comp_http_forward_data,
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200808 .http_end = comp_http_end,
Christopher Faulet92d36382015-11-05 13:35:03 +0100809};
810
Christopher Faulet3d97c902015-12-09 14:59:38 +0100811static int
812parse_compression_options(char **args, int section, struct proxy *proxy,
813 struct proxy *defpx, const char *file, int line,
814 char **err)
815{
Christopher Faulet92d36382015-11-05 13:35:03 +0100816 struct comp *comp;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100817
818 if (proxy->comp == NULL) {
Vincent Bernat02779b62016-04-03 13:48:43 +0200819 comp = calloc(1, sizeof(*comp));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100820 proxy->comp = comp;
821 }
822 else
823 comp = proxy->comp;
824
825 if (!strcmp(args[1], "algo")) {
826 struct comp_ctx *ctx;
827 int cur_arg = 2;
828
829 if (!*args[cur_arg]) {
830 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>\n",
831 file, line, args[0]);
832 return -1;
833 }
834 while (*(args[cur_arg])) {
835 if (comp_append_algo(comp, args[cur_arg]) < 0) {
836 memprintf(err, "'%s' : '%s' is not a supported algorithm.\n",
837 args[0], args[cur_arg]);
838 return -1;
839 }
840 if (proxy->comp->algos->init(&ctx, 9) == 0)
841 proxy->comp->algos->end(&ctx);
842 else {
843 memprintf(err, "'%s' : Can't init '%s' algorithm.\n",
844 args[0], args[cur_arg]);
845 return -1;
846 }
847 cur_arg++;
848 continue;
849 }
850 }
851 else if (!strcmp(args[1], "offload"))
852 comp->offload = 1;
853 else if (!strcmp(args[1], "type")) {
854 int cur_arg = 2;
855
856 if (!*args[cur_arg]) {
857 memprintf(err, "'%s' expects <type>\n", args[0]);
858 return -1;
859 }
860 while (*(args[cur_arg])) {
861 comp_append_type(comp, args[cur_arg]);
862 cur_arg++;
863 continue;
864 }
865 }
866 else {
867 memprintf(err, "'%s' expects 'algo', 'type' or 'offload'\n",
868 args[0]);
869 return -1;
870 }
871
872 return 0;
873}
874
Christopher Faulet92d36382015-11-05 13:35:03 +0100875static int
876parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
Thierry Fournier3610c392016-04-13 18:27:51 +0200877 struct flt_conf *fconf, char **err, void *private)
Christopher Faulet92d36382015-11-05 13:35:03 +0100878{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100879 struct flt_conf *fc, *back;
Christopher Faulet92d36382015-11-05 13:35:03 +0100880
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100881 list_for_each_entry_safe(fc, back, &px->filter_configs, list) {
882 if (fc->id == http_comp_flt_id) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100883 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
884 return -1;
885 }
886 }
887
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100888 fconf->id = http_comp_flt_id;
889 fconf->conf = NULL;
890 fconf->ops = &comp_ops;
Christopher Faulet92d36382015-11-05 13:35:03 +0100891 (*cur_arg)++;
892
893 return 0;
894}
895
896
897int
898check_legacy_http_comp_flt(struct proxy *proxy)
899{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100900 struct flt_conf *fconf;
Christopher Faulet92d36382015-11-05 13:35:03 +0100901 int err = 0;
902
903 if (proxy->comp == NULL)
904 goto end;
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100905 if (!LIST_ISEMPTY(&proxy->filter_configs)) {
906 list_for_each_entry(fconf, &proxy->filter_configs, list) {
907 if (fconf->id == http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100908 goto end;
909 }
Christopher Faulet767a84b2017-11-24 16:50:31 +0100910 ha_alert("config: %s '%s': require an explicit filter declaration to use HTTP compression\n",
911 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100912 err++;
913 goto end;
914 }
915
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100916 fconf = calloc(1, sizeof(*fconf));
917 if (!fconf) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100918 ha_alert("config: %s '%s': out of memory\n",
919 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100920 err++;
921 goto end;
922 }
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100923 fconf->id = http_comp_flt_id;
924 fconf->conf = NULL;
925 fconf->ops = &comp_ops;
926 LIST_ADDQ(&proxy->filter_configs, &fconf->list);
Christopher Faulet92d36382015-11-05 13:35:03 +0100927
928 end:
929 return err;
930}
931
932/*
933 * boolean, returns true if compression is used (either gzip or deflate) in the
934 * response.
935 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100936static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100937smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
938 void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100939{
Willy Tarreaube508f12016-03-10 11:47:01 +0100940 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100941
Christopher Faulet3d97c902015-12-09 14:59:38 +0100942 smp->data.type = SMP_T_BOOL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100943 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100944 return 1;
945}
946
Christopher Faulet92d36382015-11-05 13:35:03 +0100947/*
948 * string, returns algo
949 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100950static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100951smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
952 const char *kw, void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100953{
Willy Tarreaube508f12016-03-10 11:47:01 +0100954 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100955 struct filter *filter;
956 struct comp_state *st;
957
Christopher Faulet03d85532017-09-15 10:14:43 +0200958 if (!txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100959 return 0;
960
Christopher Fauletfcf035c2015-12-03 11:48:03 +0100961 list_for_each_entry(filter, &strm_flt(smp->strm)->filters, list) {
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100962 if (FLT_ID(filter) != http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100963 continue;
964
965 if (!(st = filter->ctx))
966 break;
967
968 smp->data.type = SMP_T_STR;
969 smp->flags = SMP_F_CONST;
970 smp->data.u.str.str = st->comp_algo->cfg_name;
971 smp->data.u.str.len = st->comp_algo->cfg_name_len;
972 return 1;
973 }
974 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100975}
976
977/* Declare the config parser for "compression" keyword */
978static struct cfg_kw_list cfg_kws = {ILH, {
979 { CFG_LISTEN, "compression", parse_compression_options },
980 { 0, NULL, NULL },
981 }
982};
983
Christopher Faulet92d36382015-11-05 13:35:03 +0100984/* Declare the filter parser for "compression" keyword */
985static struct flt_kw_list filter_kws = { "COMP", { }, {
Thierry Fournier3610c392016-04-13 18:27:51 +0200986 { "compression", parse_http_comp_flt, NULL },
987 { NULL, NULL, NULL },
Christopher Faulet92d36382015-11-05 13:35:03 +0100988 }
989};
990
Christopher Faulet3d97c902015-12-09 14:59:38 +0100991/* Note: must not be declared <const> as its list will be overwritten */
992static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet92d36382015-11-05 13:35:03 +0100993 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
994 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
995 { /* END */ },
996 }
997};
Christopher Faulet3d97c902015-12-09 14:59:38 +0100998
999__attribute__((constructor))
Christopher Faulet92d36382015-11-05 13:35:03 +01001000static void
1001__flt_http_comp_init(void)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001002{
1003 cfg_register_keywords(&cfg_kws);
Christopher Faulet92d36382015-11-05 13:35:03 +01001004 flt_register_keywords(&filter_kws);
Christopher Faulet3d97c902015-12-09 14:59:38 +01001005 sample_register_fetches(&sample_fetch_keywords);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001006 pool_head_comp_state = create_pool("comp_state", sizeof(struct comp_state), MEM_F_SHARED);
Christopher Faulet3d97c902015-12-09 14:59:38 +01001007}