blob: ddc607d4fa4ad7c5d375b4cee69106d48b16c94e [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
35static struct buffer *tmpbuf = &buf_empty;
36
37struct comp_chunk {
38 unsigned int start; /* start of the chunk relative to FLT_FWD offset */
39 unsigned int end; /* end of the chunk relative to FLT_FWD offset */
40 int skip; /* if set to 1, the chunk is skipped. Otherwise it is compressed */
41 int is_last; /* if set, this is the last chunk. Data after this
42 * chunk will be forwarded as it is. */
43 struct list list;
44};
45
46struct comp_state {
47 struct comp_ctx *comp_ctx; /* compression context */
48 struct comp_algo *comp_algo; /* compression algorithm if not NULL */
49 struct list comp_chunks; /* data chunks that should be compressed or skipped */
50 unsigned int first; /* offset of the first chunk. Data before
51 * this offset will be forwarded as it
52 * is. */
53};
54
55static int add_comp_chunk(struct comp_state *st, unsigned int start,
56 unsigned int len, int skip, int is_last);
57static int skip_input_data(struct filter *filter, struct http_msg *msg,
58 unsigned int consumed);
59
60static int select_compression_request_header(struct comp_state *st,
61 struct stream *s,
62 struct http_msg *msg);
63static int select_compression_response_header(struct comp_state *st,
64 struct stream *s,
65 struct http_msg *msg);
66
67static int http_compression_buffer_init(struct buffer *in, struct buffer *out);
68static int http_compression_buffer_add_data(struct comp_state *st,
69 struct buffer *in,
70 struct buffer *out, int sz);
71static int http_compression_buffer_end(struct comp_state *st, struct stream *s,
72 struct buffer **in, struct buffer **out,
73 unsigned int consumed, int end);
74
75/***********************************************************************/
76static int
77comp_flt_init(struct proxy *px, struct filter *filter)
78{
79
80 /* We need a compression buffer in the DATA state to put the output of
81 * compressed data, and in CRLF state to let the TRAILERS state finish
82 * the job of removing the trailing CRLF.
83 */
84 if (!tmpbuf->size) {
85 if (b_alloc(&tmpbuf) == NULL)
86 return -1;
87 }
88 return 0;
89}
90
91static void
92comp_flt_deinit(struct proxy *px, struct filter *filter)
93{
94 if (tmpbuf->size)
95 b_free(&tmpbuf);
96}
97
98static int
99comp_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
100{
101 if (filter->ctx == NULL) {
102 struct comp_state *st;
103
104 if (!(st = malloc(sizeof(*st))))
105 return -1;
106
107 LIST_INIT(&st->comp_chunks);
108 st->comp_algo = NULL;
109 st->comp_ctx = NULL;
110 st->first = 0;
111 filter->ctx = st;
112 }
113 return 1;
114}
115
116static int
117comp_analyze(struct stream *s, struct filter *filter, struct channel *chn,
118 unsigned int an_bit)
119{
120 struct comp_state *st = filter->ctx;
121
122 if (!strm_fe(s)->comp && !s->be->comp)
123 goto end;
124
125 switch (an_bit) {
126 case AN_RES_HTTP_PROCESS_BE:
127 select_compression_response_header(st, s, &s->txn->rsp);
128 break;
129 }
130 end:
131 return 1;
132}
133
134static int
135comp_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
136{
137 struct comp_state *st = filter->ctx;
138 struct comp_chunk *cc, *back;
139
140 if (!st || !(chn->flags & CF_ISRESP))
141 goto end;
142
143 list_for_each_entry_safe(cc, back, &st->comp_chunks, list) {
144 LIST_DEL(&cc->list);
145 free(cc);
146 }
147
148 if (!st->comp_algo || !s->txn->status)
149 goto release_ctx;
150
151 if (strm_fe(s)->mode == PR_MODE_HTTP)
152 strm_fe(s)->fe_counters.p.http.comp_rsp++;
153 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
154 s->be->be_counters.p.http.comp_rsp++;
155
156 /* release any possible compression context */
157 st->comp_algo->end(&st->comp_ctx);
158
159 release_ctx:
160 free(st);
161 filter->ctx = NULL;
162 end:
163 return 1;
164}
165
166static int
167comp_http_headers(struct stream *s, struct filter *filter,
168 struct http_msg *msg)
169{
170 struct comp_state *st = filter->ctx;
171
172 if (strm_fe(s)->comp || s->be->comp) {
173 if (!(msg->chn->flags & CF_ISRESP))
174 select_compression_request_header(st, s, msg);
175 }
176 return 1;
177}
178
179static int
180comp_skip_http_chunk_envelope(struct stream *s, struct filter *filter,
181 struct http_msg *msg)
182{
183 struct comp_state *st = filter->ctx;
184 unsigned int start;
185 int ret;
186
187 if (!(msg->chn->flags & CF_ISRESP) || !st->comp_algo) {
188 flt_set_forward_data(filter, msg->chn);
189 return 1;
190 }
191
192 start = FLT_NXT(filter, msg->chn) - FLT_FWD(filter, msg->chn);
193 /* If this is the last chunk, we flag it */
194 if (msg->chunk_len == 0 && msg->msg_state == HTTP_MSG_CHUNK_SIZE)
195 ret = add_comp_chunk(st, start, 0, 1, 1);
196 else
197 ret = add_comp_chunk(st, start, msg->sol, 1, 0);
198
199 return !ret ? 1 : -1;
200}
201
202static int
203comp_http_data(struct stream *s, struct filter *filter,
204 struct http_msg *msg)
205{
206 struct comp_state *st = filter->ctx;
207 unsigned int start;
208 int is_last, ret;
209
210 ret = MIN(msg->chunk_len + msg->next, msg->chn->buf->i) - FLT_NXT(filter, msg->chn);
211 if (!(msg->chn->flags & CF_ISRESP) || !st->comp_algo) {
212 flt_set_forward_data(filter, msg->chn);
213 goto end;
214 }
215 if (!ret)
216 goto end;
217
218 start = FLT_NXT(filter, msg->chn) - FLT_FWD(filter, msg->chn);
219 is_last = (!(msg->flags & HTTP_MSGF_TE_CHNK) &&
220 (msg->chunk_len == ret - msg->next + FLT_NXT(filter, msg->chn)));
221
222 if (add_comp_chunk(st, start, ret, 0, is_last) == -1)
223 ret = -1;
224 end:
225 return ret;
226}
227
228static int
229comp_http_forward_data(struct stream *s, struct filter *filter,
230 struct http_msg *msg, unsigned int len)
231{
232 struct comp_state *st = filter->ctx;
233 struct comp_chunk *cc, *back;
234 unsigned int sz, consumed = 0, compressed = 0;
235 int is_last = 0, ret = len;
236
237 if (!(msg->chn->flags & CF_ISRESP) || !st->comp_algo) {
238 flt_set_forward_data(filter, msg->chn);
239 goto end;
240 }
241
242 /* no data to forward or no chunk or the first chunk is too far */
243 if (!len || LIST_ISEMPTY(&st->comp_chunks))
244 goto end;
245 if (st->first > len) {
246 consumed = len;
247 goto update_chunks;
248 }
249
250 /* initialize the buffer used to write compressed data */
251 b_adv(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first);
252 ret = http_compression_buffer_init(msg->chn->buf, tmpbuf);
253 b_rew(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first);
254 if (ret < 0) {
255 msg->chn->flags |= CF_WAKE_WRITE;
256 return 0;
257 }
258
259 /* Loop on all chunks */
260 list_for_each_entry_safe(cc, back, &st->comp_chunks, list) {
261 /* current chunk must not be handled yet */
262 if (len <= cc->start) {
263 consumed = len;
264 break;
265 }
266
267 /* Get the number of bytes that must be handled in the current
268 * chunk */
269 sz = MIN(len, cc->end) - cc->start;
270
271 if (cc->skip) {
272 /* No compression for this chunk, data must be
273 * skipped. This happens when the HTTP response is
274 * chunked, the chunk envelope is skipped. */
275 ret = sz;
276 }
277 else {
278 /* Compress the chunk */
279 b_adv(msg->chn->buf, FLT_FWD(filter, msg->chn) + cc->start);
280 ret = http_compression_buffer_add_data(st, msg->chn->buf, tmpbuf, sz);
281 b_rew(msg->chn->buf, FLT_FWD(filter, msg->chn) + cc->start);
282 if (ret < 0)
283 goto end;
284 compressed += ret;
285 }
286
287 /* Update the chunk by removing consumed bytes. If all bytes are
288 * consumed, the chunk is removed from the list and we
289 * loop. Otherwise, we stop here. */
290 cc->start += ret;
291 consumed = cc->start;
292 if (cc->start != cc->end)
293 break;
294
295 /* Remember if this is the last chunk */
296 is_last = cc->is_last;
297 LIST_DEL(&cc->list);
298 free(cc);
299 }
300
301 if (compressed) {
302 /* Some data was compressed so we can switch buffers to replace
303 * uncompressed data by compressed ones. */
304 b_adv(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first);
305 ret = http_compression_buffer_end(st, s, &msg->chn->buf, &tmpbuf,
306 consumed - st->first, is_last);
307 b_rew(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first);
308 }
309 else {
310 /* Here some data was consumed but no compression was
311 * preformed. This means that all consumed data must be
312 * skipped.
313 */
314 ret = skip_input_data(filter, msg, consumed);
315 }
316
317 if (is_last && !(msg->flags & HTTP_MSGF_TE_CHNK)) {
318 /* At the end of data, if the original response was not
319 * chunked-encoded, we must write the empty chunk 0<CRLF>, and
320 * terminate the (empty) trailers section with a last <CRLF>. If
321 * we're forwarding a chunked-encoded response, these parts are
322 * preserved and not rewritten.
323 */
324 char *p = bi_end(msg->chn->buf);
325 memcpy(p, "0\r\n\r\n", 5);
326 msg->chn->buf->i += 5;
327 ret += 5;
328 }
329
330 /* Then, the last step. We need to update state of other filters. */
331 if (ret >= 0) {
332 flt_change_forward_size(filter, msg->chn, -(consumed - st->first - ret));
333 msg->next -= (consumed - st->first - ret);
334 ret += st->first;
335 }
336
337 update_chunks:
338 /* Now, we need to update all remaining chunks to keep them synchronized
339 * with the next position of buf->p. If the chunk list is empty, we
340 * forward remaining data, if any. */
341 st->first -= MIN(st->first, consumed);
342 if (LIST_ISEMPTY(&st->comp_chunks))
343 ret += len - consumed;
344 else {
345 list_for_each_entry(cc, &st->comp_chunks, list) {
346 cc->start -= consumed;
347 cc->end -= consumed;
348 }
349 }
350
351 end:
352 return ret;
353}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100354
355/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100356static int
357add_comp_chunk(struct comp_state *st, unsigned int start, unsigned int len,
358 int skip, int is_last)
359{
360 struct comp_chunk *cc;
361
362 if (!(cc = malloc(sizeof(*cc))))
363 return -1;
364 cc->start = start;
365 cc->end = start + len;
366 cc->skip = skip;
367 cc->is_last = is_last;
368
369 if (LIST_ISEMPTY(&st->comp_chunks))
370 st->first = cc->start;
371
372 LIST_ADDQ(&st->comp_chunks, &cc->list);
373 return 0;
374}
375
376/* This function might be moved in a filter function, probably with others to
377 * add/remove/move/replace buffer data */
378static int
379skip_input_data(struct filter *filter, struct http_msg *msg,
380 unsigned int consumed)
381{
382 struct comp_state *st = filter->ctx;
383 int block1, block2;
384
385 /* 1. Copy input data, skipping consumed ones. */
386 b_adv(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first + consumed);
387 block1 = msg->chn->buf->i;
388 if (block1 > bi_contig_data(msg->chn->buf))
389 block1 = bi_contig_data(msg->chn->buf);
390 block2 = msg->chn->buf->i - block1;
391
392 memcpy(trash.str, bi_ptr(msg->chn->buf), block1);
393 if (block2 > 0)
394 memcpy(trash.str + block1, msg->chn->buf->data, block2);
395 trash.len = block1 + block2;
396 b_rew(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first + consumed);
397
398 /* 2. Then write back these data at the right place in the buffer */
399 b_adv(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first);
400 block1 = trash.len;
401 if (block1 > bi_contig_data(msg->chn->buf))
402 block1 = bi_contig_data(msg->chn->buf);
403 block2 = trash.len - block1;
404
405 memcpy(bi_ptr(msg->chn->buf), trash.str, block1);
406 if (block2 > 0)
407 memcpy(msg->chn->buf->data, trash.str + block1, block2);
408 b_rew(msg->chn->buf, FLT_FWD(filter, msg->chn) + st->first);
409
410 /* Then adjut the input size */
411 msg->chn->buf->i -= consumed;
412 return 0;
413}
414
415/***********************************************************************/
Christopher Faulet3d97c902015-12-09 14:59:38 +0100416/*
417 * Selects a compression algorithm depending on the client request.
418 */
419int
Christopher Faulet92d36382015-11-05 13:35:03 +0100420select_compression_request_header(struct comp_state *st, struct stream *s,
421 struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100422{
423 struct http_txn *txn = s->txn;
Christopher Faulet92d36382015-11-05 13:35:03 +0100424 struct buffer *req = msg->chn->buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100425 struct hdr_ctx ctx;
426 struct comp_algo *comp_algo = NULL;
427 struct comp_algo *comp_algo_back = NULL;
428
429 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
430 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
431 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
432 */
433 ctx.idx = 0;
434 if (http_find_header2("User-Agent", 10, req->p, &txn->hdr_idx, &ctx) &&
435 ctx.vlen >= 9 &&
436 memcmp(ctx.line + ctx.val, "Mozilla/4", 9) == 0 &&
437 (ctx.vlen < 31 ||
438 memcmp(ctx.line + ctx.val + 25, "MSIE ", 5) != 0 ||
439 ctx.line[ctx.val + 30] < '6' ||
440 (ctx.line[ctx.val + 30] == '6' &&
441 (ctx.vlen < 54 || memcmp(ctx.line + 51, "SV1", 3) != 0)))) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100442 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100443 return 0;
444 }
445
446 /* search for the algo in the backend in priority or the frontend */
Christopher Faulet92d36382015-11-05 13:35:03 +0100447 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
448 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100449 int best_q = 0;
450
451 ctx.idx = 0;
452 while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) {
453 const char *qval;
454 int q;
455 int toklen;
456
457 /* try to isolate the token from the optional q-value */
458 toklen = 0;
459 while (toklen < ctx.vlen && http_is_token[(unsigned char)*(ctx.line + ctx.val + toklen)])
460 toklen++;
461
462 qval = ctx.line + ctx.val + toklen;
463 while (1) {
464 while (qval < ctx.line + ctx.val + ctx.vlen && http_is_lws[(unsigned char)*qval])
465 qval++;
466
467 if (qval >= ctx.line + ctx.val + ctx.vlen || *qval != ';') {
468 qval = NULL;
469 break;
470 }
471 qval++;
472
473 while (qval < ctx.line + ctx.val + ctx.vlen && http_is_lws[(unsigned char)*qval])
474 qval++;
475
476 if (qval >= ctx.line + ctx.val + ctx.vlen) {
477 qval = NULL;
478 break;
479 }
480 if (strncmp(qval, "q=", MIN(ctx.line + ctx.val + ctx.vlen - qval, 2)) == 0)
481 break;
482
483 while (qval < ctx.line + ctx.val + ctx.vlen && *qval != ';')
484 qval++;
485 }
486
487 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
488 q = qval ? parse_qvalue(qval + 2, NULL) : 1000;
489
490 if (q <= best_q)
491 continue;
492
493 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
494 if (*(ctx.line + ctx.val) == '*' ||
495 word_match(ctx.line + ctx.val, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100496 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100497 best_q = q;
498 break;
499 }
500 }
501 }
502 }
503
504 /* remove all occurrences of the header when "compression offload" is set */
Christopher Faulet92d36382015-11-05 13:35:03 +0100505 if (st->comp_algo) {
506 if ((s->be->comp && s->be->comp->offload) ||
507 (strm_fe(s)->comp && strm_fe(s)->comp->offload)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100508 http_remove_header2(msg, &txn->hdr_idx, &ctx);
509 ctx.idx = 0;
510 while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) {
511 http_remove_header2(msg, &txn->hdr_idx, &ctx);
512 }
513 }
514 return 1;
515 }
516
517 /* identity is implicit does not require headers */
Christopher Faulet92d36382015-11-05 13:35:03 +0100518 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
519 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100520 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
521 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100522 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100523 return 1;
524 }
525 }
526 }
527
Christopher Faulet92d36382015-11-05 13:35:03 +0100528 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100529 return 0;
530}
531
Christopher Faulet92d36382015-11-05 13:35:03 +0100532
Christopher Faulet3d97c902015-12-09 14:59:38 +0100533/*
534 * Selects a comression algorithm depending of the server response.
535 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100536static int
537select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100538{
539 struct http_txn *txn = s->txn;
Christopher Faulet92d36382015-11-05 13:35:03 +0100540 struct buffer *res = msg->chn->buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100541 struct hdr_ctx ctx;
542 struct comp_type *comp_type;
543
544 /* no common compression algorithm was found in request header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100545 if (st->comp_algo == NULL)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100546 goto fail;
547
548 /* HTTP < 1.1 should not be compressed */
549 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
550 goto fail;
551
Christopher Faulet92d36382015-11-05 13:35:03 +0100552 if (txn->meth == HTTP_METH_HEAD)
553 goto fail;
554
Christopher Faulet3d97c902015-12-09 14:59:38 +0100555 /* compress 200,201,202,203 responses only */
556 if ((txn->status != 200) &&
557 (txn->status != 201) &&
558 (txn->status != 202) &&
559 (txn->status != 203))
560 goto fail;
561
562
563 /* Content-Length is null */
564 if (!(msg->flags & HTTP_MSGF_TE_CHNK) && msg->body_len == 0)
565 goto fail;
566
567 /* content is already compressed */
568 ctx.idx = 0;
569 if (http_find_header2("Content-Encoding", 16, res->p, &txn->hdr_idx, &ctx))
570 goto fail;
571
572 /* no compression when Cache-Control: no-transform is present in the message */
573 ctx.idx = 0;
574 while (http_find_header2("Cache-Control", 13, res->p, &txn->hdr_idx, &ctx)) {
575 if (word_match(ctx.line + ctx.val, ctx.vlen, "no-transform", 12))
576 goto fail;
577 }
578
579 comp_type = NULL;
580
581 /* we don't want to compress multipart content-types, nor content-types that are
582 * not listed in the "compression type" directive if any. If no content-type was
583 * found but configuration requires one, we don't compress either. Backend has
584 * the priority.
585 */
586 ctx.idx = 0;
587 if (http_find_header2("Content-Type", 12, res->p, &txn->hdr_idx, &ctx)) {
588 if (ctx.vlen >= 9 && strncasecmp("multipart", ctx.line+ctx.val, 9) == 0)
589 goto fail;
590
591 if ((s->be->comp && (comp_type = s->be->comp->types)) ||
592 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types))) {
593 for (; comp_type; comp_type = comp_type->next) {
594 if (ctx.vlen >= comp_type->name_len &&
595 strncasecmp(ctx.line+ctx.val, comp_type->name, comp_type->name_len) == 0)
596 /* this Content-Type should be compressed */
597 break;
598 }
599 /* this Content-Type should not be compressed */
600 if (comp_type == NULL)
601 goto fail;
602 }
603 }
604 else { /* no content-type header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100605 if ((s->be->comp && s->be->comp->types) ||
606 (strm_fe(s)->comp && strm_fe(s)->comp->types))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100607 goto fail; /* a content-type was required */
608 }
609
610 /* limit compression rate */
611 if (global.comp_rate_lim > 0)
612 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
613 goto fail;
614
615 /* limit cpu usage */
616 if (idle_pct < compress_min_idle)
617 goto fail;
618
619 /* initialize compression */
Christopher Faulet92d36382015-11-05 13:35:03 +0100620 if (st->comp_algo->init(&st->comp_ctx, global.tune.comp_maxlevel) < 0)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100621 goto fail;
622
Christopher Faulet3d97c902015-12-09 14:59:38 +0100623 /* remove Content-Length header */
624 ctx.idx = 0;
625 if ((msg->flags & HTTP_MSGF_CNT_LEN) && http_find_header2("Content-Length", 14, res->p, &txn->hdr_idx, &ctx))
626 http_remove_header2(msg, &txn->hdr_idx, &ctx);
627
628 /* add Transfer-Encoding header */
629 if (!(msg->flags & HTTP_MSGF_TE_CHNK))
630 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, "Transfer-Encoding: chunked", 26);
631
632 /*
633 * Add Content-Encoding header when it's not identity encoding.
634 * RFC 2616 : Identity encoding: This content-coding is used only in the
635 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
636 * header.
637 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100638 if (st->comp_algo->cfg_name_len != 8 || memcmp(st->comp_algo->cfg_name, "identity", 8) != 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100639 trash.len = 18;
640 memcpy(trash.str, "Content-Encoding: ", trash.len);
Christopher Faulet92d36382015-11-05 13:35:03 +0100641 memcpy(trash.str + trash.len, st->comp_algo->ua_name, st->comp_algo->ua_name_len);
642 trash.len += st->comp_algo->ua_name_len;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100643 trash.str[trash.len] = '\0';
644 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len);
645 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100646 msg->flags |= HTTP_MSGF_COMPRESSING;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100647 return 1;
648
649fail:
Christopher Faulet92d36382015-11-05 13:35:03 +0100650 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100651 return 0;
652}
653
654/***********************************************************************/
655/* emit the chunksize followed by a CRLF on the output and return the number of
656 * bytes written. It goes backwards and starts with the byte before <end>. It
657 * returns the number of bytes written which will not exceed 10 (8 digits, CR,
658 * and LF). The caller is responsible for ensuring there is enough room left in
659 * the output buffer for the string.
660 */
661static int
662http_emit_chunk_size(char *end, unsigned int chksz)
663{
664 char *beg = end;
665
666 *--beg = '\n';
667 *--beg = '\r';
668 do {
669 *--beg = hextab[chksz & 0xF];
670 } while (chksz >>= 4);
671 return end - beg;
672}
673
674/*
675 * Init HTTP compression
676 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100677static int
678http_compression_buffer_init(struct buffer *in, struct buffer *out)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100679{
680 /* output stream requires at least 10 bytes for the gzip header, plus
681 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
682 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
683 */
684 if (in->size - buffer_len(in) < 20 + 5 * ((in->i + 32767) >> 15))
685 return -1;
686
687 /* prepare an empty output buffer in which we reserve enough room for
688 * copying the output bytes from <in>, plus 10 extra bytes to write
689 * the chunk size. We don't copy the bytes yet so that if we have to
690 * cancel the operation later, it's cheap.
691 */
692 b_reset(out);
693 out->o = in->o;
694 out->p += out->o;
695 out->i = 10;
696 return 0;
697}
698
699/*
700 * Add data to compress
701 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100702static int
703http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
704 struct buffer *out, int sz)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100705{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100706 int consumed_data = 0;
707 int data_process_len;
708 int block1, block2;
709
Christopher Faulet92d36382015-11-05 13:35:03 +0100710 if (!sz)
711 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100712
Christopher Faulet92d36382015-11-05 13:35:03 +0100713 /* select the smallest size between the announced chunk size, the input
Christopher Faulet3d97c902015-12-09 14:59:38 +0100714 * data, and the available output buffer size. The compressors are
Christopher Faulet92d36382015-11-05 13:35:03 +0100715 * assumed to be able to process all the bytes we pass to them at
716 * once. */
717 data_process_len = sz;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100718 data_process_len = MIN(out->size - buffer_len(out), data_process_len);
719
Christopher Faulet92d36382015-11-05 13:35:03 +0100720
Christopher Faulet3d97c902015-12-09 14:59:38 +0100721 block1 = data_process_len;
722 if (block1 > bi_contig_data(in))
723 block1 = bi_contig_data(in);
724 block2 = data_process_len - block1;
725
726 /* compressors return < 0 upon error or the amount of bytes read */
Christopher Faulet92d36382015-11-05 13:35:03 +0100727 consumed_data = st->comp_algo->add_data(st->comp_ctx, bi_ptr(in), block1, out);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100728 if (consumed_data >= 0 && block2 > 0) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100729 consumed_data = st->comp_algo->add_data(st->comp_ctx, in->data, block2, out);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100730 if (consumed_data >= 0)
731 consumed_data += block1;
732 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100733 return consumed_data;
734}
735
736/*
737 * Flush data in process, and write the header and footer of the chunk. Upon
738 * success, in and out buffers are swapped to avoid a copy.
739 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100740static int
741http_compression_buffer_end(struct comp_state *st, struct stream *s,
742 struct buffer **in, struct buffer **out,
743 unsigned int consumed, int end)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100744{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100745 struct buffer *ib = *in, *ob = *out;
746 char *tail;
Christopher Faulet92d36382015-11-05 13:35:03 +0100747 int to_forward, left;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100748
749#if defined(USE_SLZ) || defined(USE_ZLIB)
750 int ret;
751
752 /* flush data here */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100753 if (end)
Christopher Faulet92d36382015-11-05 13:35:03 +0100754 ret = st->comp_algo->finish(st->comp_ctx, ob); /* end of data */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100755 else
Christopher Faulet92d36382015-11-05 13:35:03 +0100756 ret = st->comp_algo->flush(st->comp_ctx, ob); /* end of buffer */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100757
758 if (ret < 0)
759 return -1; /* flush failed */
760
761#endif /* USE_ZLIB */
762
763 if (ob->i == 10) {
764 /* No data were appended, let's drop the output buffer and
765 * keep the input buffer unchanged.
766 */
767 return 0;
768 }
769
770 /* OK so at this stage, we have an output buffer <ob> looking like this :
771 *
772 * <-- o --> <------ i ----->
773 * +---------+---+------------+-----------+
774 * | out | c | comp_in | empty |
775 * +---------+---+------------+-----------+
776 * data p size
777 *
778 * <out> is the room reserved to copy ib->o. It starts at ob->data and
779 * has not yet been filled. <c> is the room reserved to write the chunk
780 * size (10 bytes). <comp_in> is the compressed equivalent of the data
781 * part of ib->i. <empty> is the amount of empty bytes at the end of
782 * the buffer, into which we may have to copy the remaining bytes from
783 * ib->i after the data (chunk size, trailers, ...).
784 */
785
786 /* Write real size at the begining of the chunk, no need of wrapping.
787 * We write the chunk using a dynamic length and adjust ob->p and ob->i
788 * accordingly afterwards. That will move <out> away from <data>.
789 */
790 left = 10 - http_emit_chunk_size(ob->p + 10, ob->i - 10);
791 ob->p += left;
792 ob->i -= left;
793
794 /* Copy previous data from ib->o into ob->o */
795 if (ib->o > 0) {
796 left = bo_contig_data(ib);
797 memcpy(ob->p - ob->o, bo_ptr(ib), left);
798 if (ib->o - left) /* second part of the buffer */
799 memcpy(ob->p - ob->o + left, ib->data, ib->o - left);
800 }
801
802 /* chunked encoding requires CRLF after data */
803 tail = ob->p + ob->i;
804 *tail++ = '\r';
805 *tail++ = '\n';
806
Christopher Faulet3d97c902015-12-09 14:59:38 +0100807 ob->i = tail - ob->p;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100808 to_forward = ob->i;
809
810 /* update input rate */
Christopher Faulet92d36382015-11-05 13:35:03 +0100811 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
812 update_freq_ctr(&global.comp_bps_in, consumed);
813 strm_fe(s)->fe_counters.comp_in += consumed;
814 s->be->be_counters.comp_in += consumed;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100815 } else {
Christopher Faulet92d36382015-11-05 13:35:03 +0100816 strm_fe(s)->fe_counters.comp_byp += consumed;
817 s->be->be_counters.comp_byp += consumed;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100818 }
819
820 /* copy the remaining data in the tmp buffer. */
Christopher Faulet92d36382015-11-05 13:35:03 +0100821 b_adv(ib, consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100822 if (ib->i > 0) {
823 left = bi_contig_data(ib);
824 memcpy(ob->p + ob->i, bi_ptr(ib), left);
825 ob->i += left;
826 if (ib->i - left) {
827 memcpy(ob->p + ob->i, ib->data, ib->i - left);
828 ob->i += ib->i - left;
829 }
830 }
831
832 /* swap the buffers */
833 *in = ob;
834 *out = ib;
835
Christopher Faulet92d36382015-11-05 13:35:03 +0100836
837 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100838 update_freq_ctr(&global.comp_bps_out, to_forward);
839 strm_fe(s)->fe_counters.comp_out += to_forward;
840 s->be->be_counters.comp_out += to_forward;
841 }
842
Christopher Faulet3d97c902015-12-09 14:59:38 +0100843 return to_forward;
844}
845
846
847/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100848struct flt_ops comp_ops = {
849 .init = comp_flt_init,
850 .deinit = comp_flt_deinit,
851
852 .channel_start_analyze = comp_start_analyze,
853 .channel_analyze = comp_analyze,
854 .channel_end_analyze = comp_end_analyze,
855
856 .http_headers = comp_http_headers,
857 .http_start_chunk = comp_skip_http_chunk_envelope,
858 .http_end_chunk = comp_skip_http_chunk_envelope,
859 .http_last_chunk = comp_skip_http_chunk_envelope,
860 .http_data = comp_http_data,
861 .http_forward_data = comp_http_forward_data,
862};
863
Christopher Faulet3d97c902015-12-09 14:59:38 +0100864static int
865parse_compression_options(char **args, int section, struct proxy *proxy,
866 struct proxy *defpx, const char *file, int line,
867 char **err)
868{
Christopher Faulet92d36382015-11-05 13:35:03 +0100869 struct comp *comp;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100870
871 if (proxy->comp == NULL) {
872 comp = calloc(1, sizeof(struct comp));
873 proxy->comp = comp;
874 }
875 else
876 comp = proxy->comp;
877
878 if (!strcmp(args[1], "algo")) {
879 struct comp_ctx *ctx;
880 int cur_arg = 2;
881
882 if (!*args[cur_arg]) {
883 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>\n",
884 file, line, args[0]);
885 return -1;
886 }
887 while (*(args[cur_arg])) {
888 if (comp_append_algo(comp, args[cur_arg]) < 0) {
889 memprintf(err, "'%s' : '%s' is not a supported algorithm.\n",
890 args[0], args[cur_arg]);
891 return -1;
892 }
893 if (proxy->comp->algos->init(&ctx, 9) == 0)
894 proxy->comp->algos->end(&ctx);
895 else {
896 memprintf(err, "'%s' : Can't init '%s' algorithm.\n",
897 args[0], args[cur_arg]);
898 return -1;
899 }
900 cur_arg++;
901 continue;
902 }
903 }
904 else if (!strcmp(args[1], "offload"))
905 comp->offload = 1;
906 else if (!strcmp(args[1], "type")) {
907 int cur_arg = 2;
908
909 if (!*args[cur_arg]) {
910 memprintf(err, "'%s' expects <type>\n", args[0]);
911 return -1;
912 }
913 while (*(args[cur_arg])) {
914 comp_append_type(comp, args[cur_arg]);
915 cur_arg++;
916 continue;
917 }
918 }
919 else {
920 memprintf(err, "'%s' expects 'algo', 'type' or 'offload'\n",
921 args[0]);
922 return -1;
923 }
924
925 return 0;
926}
927
Christopher Faulet92d36382015-11-05 13:35:03 +0100928static int
929parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
930 struct filter *filter, char **err)
931{
932 struct filter *flt, *back;
933
934 list_for_each_entry_safe(flt, back, &px->filters, list) {
935 if (flt->id == http_comp_flt_id) {
936 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
937 return -1;
938 }
939 }
940
941 filter->id = http_comp_flt_id;
942 filter->conf = NULL;
943 filter->ops = &comp_ops;
944 (*cur_arg)++;
945
946 return 0;
947}
948
949
950int
951check_legacy_http_comp_flt(struct proxy *proxy)
952{
953 struct filter *filter;
954 int err = 0;
955
956 if (proxy->comp == NULL)
957 goto end;
958 if (!LIST_ISEMPTY(&proxy->filters)) {
959 list_for_each_entry(filter, &proxy->filters, list) {
960 if (filter->id == http_comp_flt_id)
961 goto end;
962 }
963 Alert("config: %s '%s': require an explicit filter declaration to use HTTP compression\n",
964 proxy_type_str(proxy), proxy->id);
965 err++;
966 goto end;
967 }
968
969 filter = pool_alloc2(pool2_filter);
970 if (!filter) {
971 Alert("config: %s '%s': out of memory\n",
972 proxy_type_str(proxy), proxy->id);
973 err++;
974 goto end;
975 }
976 memset(filter, 0, sizeof(*filter));
977 filter->id = http_comp_flt_id;
978 filter->conf = NULL;
979 filter->ops = &comp_ops;
980 LIST_ADDQ(&proxy->filters, &filter->list);
981
982 end:
983 return err;
984}
985
986/*
987 * boolean, returns true if compression is used (either gzip or deflate) in the
988 * response.
989 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100990static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100991smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
992 void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100993{
Christopher Faulet92d36382015-11-05 13:35:03 +0100994 struct http_txn *txn = smp->strm->txn;
995
Christopher Faulet3d97c902015-12-09 14:59:38 +0100996 smp->data.type = SMP_T_BOOL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100997 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100998 return 1;
999}
1000
Christopher Faulet92d36382015-11-05 13:35:03 +01001001/*
1002 * string, returns algo
1003 */
Christopher Faulet3d97c902015-12-09 14:59:38 +01001004static int
Christopher Faulet92d36382015-11-05 13:35:03 +01001005smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
1006 const char *kw, void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001007{
Christopher Faulet92d36382015-11-05 13:35:03 +01001008 struct http_txn *txn = smp->strm->txn;
1009 struct filter *filter;
1010 struct comp_state *st;
1011
1012 if (!(txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING)))
Christopher Faulet3d97c902015-12-09 14:59:38 +01001013 return 0;
1014
Christopher Faulet92d36382015-11-05 13:35:03 +01001015 list_for_each_entry(filter, &smp->strm->strm_flt.filters, list) {
1016 if (filter->id != http_comp_flt_id)
1017 continue;
1018
1019 if (!(st = filter->ctx))
1020 break;
1021
1022 smp->data.type = SMP_T_STR;
1023 smp->flags = SMP_F_CONST;
1024 smp->data.u.str.str = st->comp_algo->cfg_name;
1025 smp->data.u.str.len = st->comp_algo->cfg_name_len;
1026 return 1;
1027 }
1028 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +01001029}
1030
1031/* Declare the config parser for "compression" keyword */
1032static struct cfg_kw_list cfg_kws = {ILH, {
1033 { CFG_LISTEN, "compression", parse_compression_options },
1034 { 0, NULL, NULL },
1035 }
1036};
1037
Christopher Faulet92d36382015-11-05 13:35:03 +01001038/* Declare the filter parser for "compression" keyword */
1039static struct flt_kw_list filter_kws = { "COMP", { }, {
1040 { "compression", parse_http_comp_flt },
1041 { NULL, NULL },
1042 }
1043};
1044
Christopher Faulet3d97c902015-12-09 14:59:38 +01001045/* Note: must not be declared <const> as its list will be overwritten */
1046static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet92d36382015-11-05 13:35:03 +01001047 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
1048 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
1049 { /* END */ },
1050 }
1051};
Christopher Faulet3d97c902015-12-09 14:59:38 +01001052
1053__attribute__((constructor))
Christopher Faulet92d36382015-11-05 13:35:03 +01001054static void
1055__flt_http_comp_init(void)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001056{
1057 cfg_register_keywords(&cfg_kws);
Christopher Faulet92d36382015-11-05 13:35:03 +01001058 flt_register_keywords(&filter_kws);
Christopher Faulet3d97c902015-12-09 14:59:38 +01001059 sample_register_fetches(&sample_fetch_keywords);
1060}