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