1 /*
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/htx.h>
16 #include <common/initcall.h>
17 #include <common/mini-clist.h>
18 #include <common/standard.h>
19
20 #include <types/compression.h>
21 #include <types/filters.h>
22 #include <types/proxy.h>
23 #include <types/sample.h>
24
25 #include <proto/compression.h>
26 #include <proto/filters.h>
27 #include <proto/http_htx.h>
28 #include <proto/http_ana.h>
29 #include <proto/sample.h>
30 #include <proto/stream.h>
31
32 const char *http_comp_flt_id = "compression filter";
33
34 struct flt_ops comp_ops;
35
36 struct comp_state {
37 struct comp_ctx *comp_ctx; /* compression context */
38 struct comp_algo *comp_algo; /* compression algorithm if not NULL */
39 };
40
41 /* Pools used to allocate comp_state structs */
42 DECLARE_STATIC_POOL(pool_head_comp_state, "comp_state", sizeof(struct comp_state));
43
44 static THREAD_LOCAL struct buffer tmpbuf;
45 static THREAD_LOCAL struct buffer zbuf;
46
47 static int select_compression_request_header(struct comp_state *st,
48 struct stream *s,
49 struct http_msg *msg);
50 static int select_compression_response_header(struct comp_state *st,
51 struct stream *s,
52 struct http_msg *msg);
53 static int set_compression_response_header(struct comp_state *st,
54 struct stream *s,
55 struct http_msg *msg);
56
57 static int htx_compression_buffer_init(struct htx *htx, struct buffer *out);
58 static int htx_compression_buffer_add_data(struct comp_state *st, const char *data, size_t len,
59 struct buffer *out);
60 static int htx_compression_buffer_end(struct comp_state *st, struct buffer *out, int end);
61
62 /***********************************************************************/
63 static int
comp_flt_init(struct proxy * px,struct flt_conf * fconf)64 comp_flt_init(struct proxy *px, struct flt_conf *fconf)
65 {
66 fconf->flags |= FLT_CFG_FL_HTX;
67 return 0;
68 }
69
70 static int
comp_flt_init_per_thread(struct proxy * px,struct flt_conf * fconf)71 comp_flt_init_per_thread(struct proxy *px, struct flt_conf *fconf)
72 {
73 if (!tmpbuf.size && b_alloc(&tmpbuf) == NULL)
74 return -1;
75 if (!zbuf.size && b_alloc(&zbuf) == NULL)
76 return -1;
77 return 0;
78 }
79
80 static void
comp_flt_deinit_per_thread(struct proxy * px,struct flt_conf * fconf)81 comp_flt_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
82 {
83 if (tmpbuf.size)
84 b_free(&tmpbuf);
85 if (zbuf.size)
86 b_free(&zbuf);
87 }
88
89 static int
comp_start_analyze(struct stream * s,struct filter * filter,struct channel * chn)90 comp_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
91 {
92
93 if (filter->ctx == NULL) {
94 struct comp_state *st;
95
96 st = pool_alloc_dirty(pool_head_comp_state);
97 if (st == NULL)
98 return -1;
99
100 st->comp_algo = NULL;
101 st->comp_ctx = NULL;
102 filter->ctx = st;
103
104 /* Register post-analyzer on AN_RES_WAIT_HTTP because we need to
105 * analyze response headers before http-response rules execution
106 * to be sure we can use res.comp and res.comp_algo sample
107 * fetches */
108 filter->post_analyzers |= AN_RES_WAIT_HTTP;
109 }
110 return 1;
111 }
112
113 static int
comp_end_analyze(struct stream * s,struct filter * filter,struct channel * chn)114 comp_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
115 {
116 struct comp_state *st = filter->ctx;
117
118 if (!st)
119 goto end;
120
121 /* release any possible compression context */
122 if (st->comp_algo)
123 st->comp_algo->end(&st->comp_ctx);
124 pool_free(pool_head_comp_state, st);
125 filter->ctx = NULL;
126 end:
127 return 1;
128 }
129
130 static int
comp_http_headers(struct stream * s,struct filter * filter,struct http_msg * msg)131 comp_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
132 {
133 struct comp_state *st = filter->ctx;
134
135 if (!strm_fe(s)->comp && !s->be->comp)
136 goto end;
137
138 if (!(msg->chn->flags & CF_ISRESP))
139 select_compression_request_header(st, s, msg);
140 else {
141 /* Response headers have already been checked in
142 * comp_http_post_analyze callback. */
143 if (st->comp_algo) {
144 if (!set_compression_response_header(st, s, msg))
145 goto end;
146 register_data_filter(s, msg->chn, filter);
147 }
148 }
149
150 end:
151 return 1;
152 }
153
154 static int
comp_http_post_analyze(struct stream * s,struct filter * filter,struct channel * chn,unsigned an_bit)155 comp_http_post_analyze(struct stream *s, struct filter *filter,
156 struct channel *chn, unsigned an_bit)
157 {
158 struct http_txn *txn = s->txn;
159 struct http_msg *msg = &txn->rsp;
160 struct comp_state *st = filter->ctx;
161
162 if (an_bit != AN_RES_WAIT_HTTP)
163 goto end;
164
165 if (!strm_fe(s)->comp && !s->be->comp)
166 goto end;
167
168 select_compression_response_header(st, s, msg);
169
170 end:
171 return 1;
172 }
173
174 static int
comp_http_payload(struct stream * s,struct filter * filter,struct http_msg * msg,unsigned int offset,unsigned int len)175 comp_http_payload(struct stream *s, struct filter *filter, struct http_msg *msg,
176 unsigned int offset, unsigned int len)
177 {
178 struct comp_state *st = filter->ctx;
179 struct htx *htx = htxbuf(&msg->chn->buf);
180 struct htx_ret htxret = htx_find_offset(htx, offset);
181 struct htx_blk *blk;
182 int ret, consumed = 0, to_forward = 0;
183
184 blk = htxret.blk;
185 offset = htxret.ret;
186 for (; blk && len; blk = htx_get_next_blk(htx, blk)) {
187 enum htx_blk_type type = htx_get_blk_type(blk);
188 uint32_t sz = htx_get_blksz(blk);
189 struct ist v;
190
191 switch (type) {
192 case HTX_BLK_UNUSED:
193 break;
194
195 case HTX_BLK_DATA:
196 v = htx_get_blk_value(htx, blk);
197 v.ptr += offset;
198 v.len -= offset;
199 if (v.len > len)
200 v.len = len;
201 if (htx_compression_buffer_init(htx, &trash) < 0) {
202 msg->chn->flags |= CF_WAKE_WRITE;
203 goto end;
204 }
205 ret = htx_compression_buffer_add_data(st, v.ptr, v.len, &trash);
206 if (ret < 0)
207 goto error;
208 if (htx_compression_buffer_end(st, &trash, 0) < 0)
209 goto error;
210 len -= ret;
211 consumed += ret;
212 to_forward += b_data(&trash);
213 if (ret == sz && !b_data(&trash)) {
214 offset = 0;
215 blk = htx_remove_blk(htx, blk);
216 continue;
217 }
218 v.len = ret;
219 blk = htx_replace_blk_value(htx, blk, v, ist2(b_head(&trash), b_data(&trash)));
220 break;
221
222 case HTX_BLK_TLR:
223 case HTX_BLK_EOT:
224 case HTX_BLK_EOM:
225 if (msg->flags & HTTP_MSGF_COMPRESSING) {
226 if (htx_compression_buffer_init(htx, &trash) < 0) {
227 msg->chn->flags |= CF_WAKE_WRITE;
228 goto end;
229 }
230 if (htx_compression_buffer_end(st, &trash, 1) < 0)
231 goto error;
232 if (b_data(&trash)) {
233 struct htx_blk *last = htx_add_last_data(htx, ist2(b_head(&trash), b_data(&trash)));
234 if (!last)
235 goto error;
236 blk = htx_get_next_blk(htx, last);
237 if (!blk)
238 goto error;
239 to_forward += b_data(&trash);
240 }
241 msg->flags &= ~HTTP_MSGF_COMPRESSING;
242 /* We let the mux add last empty chunk and empty trailers */
243 }
244 /* fall through */
245
246 default:
247 sz -= offset;
248 if (sz > len)
249 sz = len;
250 consumed += sz;
251 to_forward += sz;
252 len -= sz;
253 break;
254 }
255
256 offset = 0;
257 }
258
259 end:
260 if (to_forward != consumed)
261 flt_update_offsets(filter, msg->chn, to_forward - consumed);
262
263 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
264 update_freq_ctr(&global.comp_bps_in, consumed);
265 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_in, consumed);
266 _HA_ATOMIC_ADD(&s->be->be_counters.comp_in, consumed);
267 update_freq_ctr(&global.comp_bps_out, to_forward);
268 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_out, to_forward);
269 _HA_ATOMIC_ADD(&s->be->be_counters.comp_out, to_forward);
270 } else {
271 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_byp, consumed);
272 _HA_ATOMIC_ADD(&s->be->be_counters.comp_byp, consumed);
273 }
274 return to_forward;
275
276 error:
277 return -1;
278 }
279
280
281 static int
comp_http_end(struct stream * s,struct filter * filter,struct http_msg * msg)282 comp_http_end(struct stream *s, struct filter *filter,
283 struct http_msg *msg)
284 {
285 struct comp_state *st = filter->ctx;
286
287 if (!(msg->chn->flags & CF_ISRESP) || !st || !st->comp_algo)
288 goto end;
289
290 if (strm_fe(s)->mode == PR_MODE_HTTP)
291 _HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.p.http.comp_rsp, 1);
292 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
293 _HA_ATOMIC_ADD(&s->be->be_counters.p.http.comp_rsp, 1);
294 end:
295 return 1;
296 }
297
298 /***********************************************************************/
299 static int
set_compression_response_header(struct comp_state * st,struct stream * s,struct http_msg * msg)300 set_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
301 {
302 struct htx *htx = htxbuf(&msg->chn->buf);
303 struct http_hdr_ctx ctx;
304
305 /*
306 * Add Content-Encoding header when it's not identity encoding.
307 * RFC 2616 : Identity encoding: This content-coding is used only in the
308 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
309 * header.
310 */
311 if (st->comp_algo->cfg_name_len != 8 || memcmp(st->comp_algo->cfg_name, "identity", 8) != 0) {
312 struct ist v = ist2(st->comp_algo->ua_name, st->comp_algo->ua_name_len);
313
314 if (!http_add_header(htx, ist("Content-Encoding"), v))
315 goto error;
316 }
317
318 /* remove Content-Length header */
319 if (msg->flags & HTTP_MSGF_CNT_LEN) {
320 ctx.blk = NULL;
321 while (http_find_header(htx, ist("Content-Length"), &ctx, 1))
322 http_remove_header(htx, &ctx);
323 }
324
325 /* add "Transfer-Encoding: chunked" header */
326 if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
327 if (!http_add_header(htx, ist("Transfer-Encoding"), ist("chunked")))
328 goto error;
329 }
330
331 /* convert "ETag" header to a weak ETag */
332 ctx.blk = NULL;
333 if (http_find_header(htx, ist("ETag"), &ctx, 1)) {
334 if (ctx.value.ptr[0] == '"') {
335 /* This a strong ETag. Convert it to a weak one. */
336 struct ist v = ist2(trash.area, 0);
337 if (istcat(&v, ist("W/"), trash.size) == -1 || istcat(&v, ctx.value, trash.size) == -1)
338 goto error;
339
340 if (!http_replace_header_value(htx, &ctx, v))
341 goto error;
342 }
343 }
344
345 if (!http_add_header(htx, ist("Vary"), ist("Accept-Encoding")))
346 goto error;
347
348 return 1;
349
350 error:
351 st->comp_algo->end(&st->comp_ctx);
352 st->comp_algo = NULL;
353 return 0;
354 }
355
356 /*
357 * Selects a compression algorithm depending on the client request.
358 */
359 static int
select_compression_request_header(struct comp_state * st,struct stream * s,struct http_msg * msg)360 select_compression_request_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
361 {
362 struct htx *htx = htxbuf(&msg->chn->buf);
363 struct http_hdr_ctx ctx;
364 struct comp_algo *comp_algo = NULL;
365 struct comp_algo *comp_algo_back = NULL;
366
367 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
368 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
369 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
370 */
371 ctx.blk = NULL;
372 if (http_find_header(htx, ist("User-Agent"), &ctx, 1) &&
373 ctx.value.len >= 9 &&
374 memcmp(ctx.value.ptr, "Mozilla/4", 9) == 0 &&
375 (ctx.value.len < 31 ||
376 memcmp(ctx.value.ptr + 25, "MSIE ", 5) != 0 ||
377 *(ctx.value.ptr + 30) < '6' ||
378 (*(ctx.value.ptr + 30) == '6' &&
379 (ctx.value.len < 54 || memcmp(ctx.value.ptr + 51, "SV1", 3) != 0)))) {
380 st->comp_algo = NULL;
381 return 0;
382 }
383
384 /* search for the algo in the backend in priority or the frontend */
385 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
386 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
387 int best_q = 0;
388
389 ctx.blk = NULL;
390 while (http_find_header(htx, ist("Accept-Encoding"), &ctx, 0)) {
391 const char *qval;
392 int q;
393 int toklen;
394
395 /* try to isolate the token from the optional q-value */
396 toklen = 0;
397 while (toklen < ctx.value.len && HTTP_IS_TOKEN(*(ctx.value.ptr + toklen)))
398 toklen++;
399
400 qval = ctx.value.ptr + toklen;
401 while (1) {
402 while (qval < ctx.value.ptr + ctx.value.len && HTTP_IS_LWS(*qval))
403 qval++;
404
405 if (qval >= ctx.value.ptr + ctx.value.len || *qval != ';') {
406 qval = NULL;
407 break;
408 }
409 qval++;
410
411 while (qval < ctx.value.ptr + ctx.value.len && HTTP_IS_LWS(*qval))
412 qval++;
413
414 if (qval >= ctx.value.ptr + ctx.value.len) {
415 qval = NULL;
416 break;
417 }
418 if (strncmp(qval, "q=", MIN(ctx.value.ptr + ctx.value.len - qval, 2)) == 0)
419 break;
420
421 while (qval < ctx.value.ptr + ctx.value.len && *qval != ';')
422 qval++;
423 }
424
425 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
426 q = qval ? http_parse_qvalue(qval + 2, NULL) : 1000;
427
428 if (q <= best_q)
429 continue;
430
431 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
432 if (*(ctx.value.ptr) == '*' ||
433 word_match(ctx.value.ptr, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
434 st->comp_algo = comp_algo;
435 best_q = q;
436 break;
437 }
438 }
439 }
440 }
441
442 /* remove all occurrences of the header when "compression offload" is set */
443 if (st->comp_algo) {
444 if ((s->be->comp && s->be->comp->offload) ||
445 (strm_fe(s)->comp && strm_fe(s)->comp->offload)) {
446 http_remove_header(htx, &ctx);
447 ctx.blk = NULL;
448 while (http_find_header(htx, ist("Accept-Encoding"), &ctx, 1))
449 http_remove_header(htx, &ctx);
450 }
451 return 1;
452 }
453
454 /* identity is implicit does not require headers */
455 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
456 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
457 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
458 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
459 st->comp_algo = comp_algo;
460 return 1;
461 }
462 }
463 }
464
465 st->comp_algo = NULL;
466 return 0;
467 }
468
469 /*
470 * Selects a comression algorithm depending of the server response.
471 */
472 static int
select_compression_response_header(struct comp_state * st,struct stream * s,struct http_msg * msg)473 select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
474 {
475 struct htx *htx = htxbuf(&msg->chn->buf);
476 struct http_txn *txn = s->txn;
477 struct http_hdr_ctx ctx;
478 struct comp_type *comp_type;
479
480 /* no common compression algorithm was found in request header */
481 if (st->comp_algo == NULL)
482 goto fail;
483
484 /* compression already in progress */
485 if (msg->flags & HTTP_MSGF_COMPRESSING)
486 goto fail;
487
488 /* HTTP < 1.1 should not be compressed */
489 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
490 goto fail;
491
492 if (txn->meth == HTTP_METH_HEAD)
493 goto fail;
494
495 /* compress 200,201,202,203 responses only */
496 if ((txn->status != 200) &&
497 (txn->status != 201) &&
498 (txn->status != 202) &&
499 (txn->status != 203))
500 goto fail;
501
502 if (!(msg->flags & HTTP_MSGF_XFER_LEN) || msg->flags & HTTP_MSGF_BODYLESS)
503 goto fail;
504
505 /* content is already compressed */
506 ctx.blk = NULL;
507 if (http_find_header(htx, ist("Content-Encoding"), &ctx, 1))
508 goto fail;
509
510 /* no compression when Cache-Control: no-transform is present in the message */
511 ctx.blk = NULL;
512 while (http_find_header(htx, ist("Cache-Control"), &ctx, 0)) {
513 if (word_match(ctx.value.ptr, ctx.value.len, "no-transform", 12))
514 goto fail;
515 }
516
517 /* no compression when ETag is malformed */
518 ctx.blk = NULL;
519 if (http_find_header(htx, ist("ETag"), &ctx, 1)) {
520 if (!(((ctx.value.len >= 4 && memcmp(ctx.value.ptr, "W/\"", 3) == 0) || /* Either a weak ETag */
521 (ctx.value.len >= 2 && ctx.value.ptr[0] == '"')) && /* or strong ETag */
522 ctx.value.ptr[ctx.value.len - 1] == '"')) {
523 goto fail;
524 }
525 }
526 /* no compression when multiple ETags are present
527 * Note: Do not reset ctx.blk!
528 */
529 if (http_find_header(htx, ist("ETag"), &ctx, 1))
530 goto fail;
531
532 comp_type = NULL;
533
534 /* we don't want to compress multipart content-types, nor content-types that are
535 * not listed in the "compression type" directive if any. If no content-type was
536 * found but configuration requires one, we don't compress either. Backend has
537 * the priority.
538 */
539 ctx.blk = NULL;
540 if (http_find_header(htx, ist("Content-Type"), &ctx, 1)) {
541 if (ctx.value.len >= 9 && strncasecmp("multipart", ctx.value.ptr, 9) == 0)
542 goto fail;
543
544 if ((s->be->comp && (comp_type = s->be->comp->types)) ||
545 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types))) {
546 for (; comp_type; comp_type = comp_type->next) {
547 if (ctx.value.len >= comp_type->name_len &&
548 strncasecmp(ctx.value.ptr, comp_type->name, comp_type->name_len) == 0)
549 /* this Content-Type should be compressed */
550 break;
551 }
552 /* this Content-Type should not be compressed */
553 if (comp_type == NULL)
554 goto fail;
555 }
556 }
557 else { /* no content-type header */
558 if ((s->be->comp && s->be->comp->types) ||
559 (strm_fe(s)->comp && strm_fe(s)->comp->types))
560 goto fail; /* a content-type was required */
561 }
562
563 /* limit compression rate */
564 if (global.comp_rate_lim > 0)
565 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
566 goto fail;
567
568 /* limit cpu usage */
569 if (ti->idle_pct < compress_min_idle)
570 goto fail;
571
572 /* initialize compression */
573 if (st->comp_algo->init(&st->comp_ctx, global.tune.comp_maxlevel) < 0)
574 goto fail;
575 msg->flags |= HTTP_MSGF_COMPRESSING;
576 return 1;
577
578 deinit_comp_ctx:
579 st->comp_algo->end(&st->comp_ctx);
580 fail:
581 st->comp_algo = NULL;
582 return 0;
583 }
584
585 /***********************************************************************/
586 static int
htx_compression_buffer_init(struct htx * htx,struct buffer * out)587 htx_compression_buffer_init(struct htx *htx, struct buffer *out)
588 {
589 /* output stream requires at least 10 bytes for the gzip header, plus
590 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
591 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
592 */
593 if (htx_free_space(htx) < 20 + 5 * ((htx->data + 32767) >> 15))
594 return -1;
595 b_reset(out);
596 return 0;
597 }
598
599 static int
htx_compression_buffer_add_data(struct comp_state * st,const char * data,size_t len,struct buffer * out)600 htx_compression_buffer_add_data(struct comp_state *st, const char *data, size_t len,
601 struct buffer *out)
602 {
603 return st->comp_algo->add_data(st->comp_ctx, data, len, out);
604 }
605
606 static int
htx_compression_buffer_end(struct comp_state * st,struct buffer * out,int end)607 htx_compression_buffer_end(struct comp_state *st, struct buffer *out, int end)
608 {
609 if (end)
610 return st->comp_algo->finish(st->comp_ctx, out);
611 else
612 return st->comp_algo->flush(st->comp_ctx, out);
613 }
614
615
616 /***********************************************************************/
617 struct flt_ops comp_ops = {
618 .init = comp_flt_init,
619 .init_per_thread = comp_flt_init_per_thread,
620 .deinit_per_thread = comp_flt_deinit_per_thread,
621
622 .channel_start_analyze = comp_start_analyze,
623 .channel_end_analyze = comp_end_analyze,
624 .channel_post_analyze = comp_http_post_analyze,
625
626 .http_headers = comp_http_headers,
627 .http_payload = comp_http_payload,
628 .http_end = comp_http_end,
629 };
630
631 static int
parse_compression_options(char ** args,int section,struct proxy * proxy,struct proxy * defpx,const char * file,int line,char ** err)632 parse_compression_options(char **args, int section, struct proxy *proxy,
633 struct proxy *defpx, const char *file, int line,
634 char **err)
635 {
636 struct comp *comp;
637
638 if (proxy->comp == NULL) {
639 comp = calloc(1, sizeof(*comp));
640 proxy->comp = comp;
641 }
642 else
643 comp = proxy->comp;
644
645 if (!strcmp(args[1], "algo")) {
646 struct comp_ctx *ctx;
647 int cur_arg = 2;
648
649 if (!*args[cur_arg]) {
650 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>\n",
651 file, line, args[0]);
652 return -1;
653 }
654 while (*(args[cur_arg])) {
655 if (comp_append_algo(comp, args[cur_arg]) < 0) {
656 memprintf(err, "'%s' : '%s' is not a supported algorithm.\n",
657 args[0], args[cur_arg]);
658 return -1;
659 }
660 if (proxy->comp->algos->init(&ctx, 9) == 0)
661 proxy->comp->algos->end(&ctx);
662 else {
663 memprintf(err, "'%s' : Can't init '%s' algorithm.\n",
664 args[0], args[cur_arg]);
665 return -1;
666 }
667 cur_arg++;
668 continue;
669 }
670 }
671 else if (!strcmp(args[1], "offload"))
672 comp->offload = 1;
673 else if (!strcmp(args[1], "type")) {
674 int cur_arg = 2;
675
676 if (!*args[cur_arg]) {
677 memprintf(err, "'%s' expects <type>\n", args[0]);
678 return -1;
679 }
680 while (*(args[cur_arg])) {
681 comp_append_type(comp, args[cur_arg]);
682 cur_arg++;
683 continue;
684 }
685 }
686 else {
687 memprintf(err, "'%s' expects 'algo', 'type' or 'offload'\n",
688 args[0]);
689 return -1;
690 }
691
692 return 0;
693 }
694
695 static int
parse_http_comp_flt(char ** args,int * cur_arg,struct proxy * px,struct flt_conf * fconf,char ** err,void * private)696 parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
697 struct flt_conf *fconf, char **err, void *private)
698 {
699 struct flt_conf *fc, *back;
700
701 list_for_each_entry_safe(fc, back, &px->filter_configs, list) {
702 if (fc->id == http_comp_flt_id) {
703 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
704 return -1;
705 }
706 }
707
708 fconf->id = http_comp_flt_id;
709 fconf->conf = NULL;
710 fconf->ops = &comp_ops;
711 (*cur_arg)++;
712
713 return 0;
714 }
715
716
717 int
check_implicit_http_comp_flt(struct proxy * proxy)718 check_implicit_http_comp_flt(struct proxy *proxy)
719 {
720 struct flt_conf *fconf;
721 int explicit = 0;
722 int comp = 0;
723 int err = 0;
724
725 if (proxy->comp == NULL)
726 goto end;
727 if (!LIST_ISEMPTY(&proxy->filter_configs)) {
728 list_for_each_entry(fconf, &proxy->filter_configs, list) {
729 if (fconf->id == http_comp_flt_id)
730 comp = 1;
731 else if (fconf->id == cache_store_flt_id) {
732 if (comp) {
733 ha_alert("config: %s '%s': unable to enable the compression filter "
734 "before any cache filter.\n",
735 proxy_type_str(proxy), proxy->id);
736 err++;
737 goto end;
738 }
739 }
740 else if (fconf->id == fcgi_flt_id)
741 continue;
742 else
743 explicit = 1;
744 }
745 }
746 if (comp)
747 goto end;
748 else if (explicit) {
749 ha_alert("config: %s '%s': require an explicit filter declaration to use "
750 "HTTP compression\n", proxy_type_str(proxy), proxy->id);
751 err++;
752 goto end;
753 }
754
755 /* Implicit declaration of the compression filter is always the last
756 * one */
757 fconf = calloc(1, sizeof(*fconf));
758 if (!fconf) {
759 ha_alert("config: %s '%s': out of memory\n",
760 proxy_type_str(proxy), proxy->id);
761 err++;
762 goto end;
763 }
764 fconf->id = http_comp_flt_id;
765 fconf->conf = NULL;
766 fconf->ops = &comp_ops;
767 LIST_ADDQ(&proxy->filter_configs, &fconf->list);
768 end:
769 return err;
770 }
771
772 /*
773 * boolean, returns true if compression is used (either gzip or deflate) in the
774 * response.
775 */
776 static int
smp_fetch_res_comp(const struct arg * args,struct sample * smp,const char * kw,void * private)777 smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
778 void *private)
779 {
780 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
781
782 smp->data.type = SMP_T_BOOL;
783 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
784 return 1;
785 }
786
787 /*
788 * string, returns algo
789 */
790 static int
smp_fetch_res_comp_algo(const struct arg * args,struct sample * smp,const char * kw,void * private)791 smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
792 const char *kw, void *private)
793 {
794 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
795 struct filter *filter;
796 struct comp_state *st;
797
798 if (!txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING))
799 return 0;
800
801 list_for_each_entry(filter, &strm_flt(smp->strm)->filters, list) {
802 if (FLT_ID(filter) != http_comp_flt_id)
803 continue;
804
805 if (!(st = filter->ctx))
806 break;
807
808 smp->data.type = SMP_T_STR;
809 smp->flags = SMP_F_CONST;
810 smp->data.u.str.area = st->comp_algo->cfg_name;
811 smp->data.u.str.data = st->comp_algo->cfg_name_len;
812 return 1;
813 }
814 return 0;
815 }
816
817 /* Declare the config parser for "compression" keyword */
818 static struct cfg_kw_list cfg_kws = {ILH, {
819 { CFG_LISTEN, "compression", parse_compression_options },
820 { 0, NULL, NULL },
821 }
822 };
823
824 INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
825
826 /* Declare the filter parser for "compression" keyword */
827 static struct flt_kw_list filter_kws = { "COMP", { }, {
828 { "compression", parse_http_comp_flt, NULL },
829 { NULL, NULL, NULL },
830 }
831 };
832
833 INITCALL1(STG_REGISTER, flt_register_keywords, &filter_kws);
834
835 /* Note: must not be declared <const> as its list will be overwritten */
836 static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
837 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
838 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
839 { /* END */ },
840 }
841 };
842
843 INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
844