blob: 54ffeedb1d4f5808402ee4335e709907cf69831b [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;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010036static struct buffer *zbuf = &buf_empty;
Christopher Faulet92d36382015-11-05 13:35:03 +010037
Christopher Faulet92d36382015-11-05 13:35:03 +010038struct comp_state {
39 struct comp_ctx *comp_ctx; /* compression context */
40 struct comp_algo *comp_algo; /* compression algorithm if not NULL */
Christopher Fauletb77c5c22015-12-07 16:48:42 +010041 int hdrs_len;
42 int tlrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +010043 int consumed;
44 int initialized;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010045 int finished;
Christopher Faulet92d36382015-11-05 13:35:03 +010046};
47
Christopher Faulet92d36382015-11-05 13:35:03 +010048static int select_compression_request_header(struct comp_state *st,
49 struct stream *s,
50 struct http_msg *msg);
51static int select_compression_response_header(struct comp_state *st,
52 struct stream *s,
53 struct http_msg *msg);
54
55static int http_compression_buffer_init(struct buffer *in, struct buffer *out);
56static int http_compression_buffer_add_data(struct comp_state *st,
57 struct buffer *in,
58 struct buffer *out, int sz);
59static int http_compression_buffer_end(struct comp_state *st, struct stream *s,
60 struct buffer **in, struct buffer **out,
Christopher Faulet2fb28802015-12-01 10:40:57 +010061 int end);
Christopher Faulet92d36382015-11-05 13:35:03 +010062
63/***********************************************************************/
64static int
Christopher Faulet443ea1a2016-02-04 13:40:26 +010065comp_flt_init(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010066{
67
Christopher Fauletb77c5c22015-12-07 16:48:42 +010068 if (!tmpbuf->size && b_alloc(&tmpbuf) == NULL)
69 return -1;
70 if (!zbuf->size && b_alloc(&zbuf) == NULL)
71 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +010072 return 0;
73}
74
75static void
Christopher Faulet443ea1a2016-02-04 13:40:26 +010076comp_flt_deinit(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010077{
78 if (tmpbuf->size)
79 b_free(&tmpbuf);
Christopher Fauletb77c5c22015-12-07 16:48:42 +010080 if (zbuf->size)
81 b_free(&zbuf);
Christopher Faulet92d36382015-11-05 13:35:03 +010082}
83
84static int
85comp_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
86{
87 if (filter->ctx == NULL) {
88 struct comp_state *st;
89
90 if (!(st = malloc(sizeof(*st))))
91 return -1;
92
Christopher Faulet2fb28802015-12-01 10:40:57 +010093 st->comp_algo = NULL;
94 st->comp_ctx = NULL;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010095 st->hdrs_len = 0;
96 st->tlrs_len = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +010097 st->consumed = 0;
98 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010099 st->finished = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100100 filter->ctx = st;
Christopher Faulet92d36382015-11-05 13:35:03 +0100101 }
102 return 1;
103}
104
105static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100106comp_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
107{
108 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100109
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200110 if (!st)
Christopher Faulet92d36382015-11-05 13:35:03 +0100111 goto end;
112
Christopher Faulet92d36382015-11-05 13:35:03 +0100113 /* release any possible compression context */
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200114 if (st->comp_algo)
115 st->comp_algo->end(&st->comp_ctx);
Christopher Faulet92d36382015-11-05 13:35:03 +0100116 free(st);
117 filter->ctx = NULL;
118 end:
119 return 1;
120}
121
122static int
Christopher Faulet1339d742016-05-11 16:48:33 +0200123comp_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
124{
125 struct comp_state *st = filter->ctx;
126
127 if (!strm_fe(s)->comp && !s->be->comp)
128 goto end;
129
130 if (!(msg->chn->flags & CF_ISRESP))
131 select_compression_request_header(st, s, msg);
132 else {
133 select_compression_response_header(st, s, msg);
134 if (st->comp_algo) {
135 register_data_filter(s, msg->chn, filter);
136 st->hdrs_len = s->txn->rsp.sov;
137 }
138 }
139
140 end:
141 return 1;
142}
143
144static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100145comp_http_data(struct stream *s, struct filter *filter, struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100146{
147 struct comp_state *st = filter->ctx;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100148 struct buffer *buf = msg->chn->buf;
149 unsigned int *nxt = &flt_rsp_nxt(filter);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100150 unsigned int len;
Christopher Faulet92d36382015-11-05 13:35:03 +0100151 int ret;
152
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100153 len = MIN(msg->chunk_len + msg->next, buf->i) - *nxt;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100154 if (!len)
155 return len;
156
157 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100158 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100159
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100160 b_reset(tmpbuf);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100161 b_adv(buf, fwd);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100162 ret = http_compression_buffer_init(buf, zbuf);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100163 b_rew(buf, fwd);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100164 if (ret < 0) {
165 msg->chn->flags |= CF_WAKE_WRITE;
166 return 0;
167 }
168 }
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100169
170 if (msg->flags & HTTP_MSGF_TE_CHNK) {
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200171 int block;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100172
173 len = MIN(tmpbuf->size - buffer_len(tmpbuf), len);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200174
175 b_adv(buf, *nxt);
176 block = bi_contig_data(buf);
177 memcpy(bi_end(tmpbuf), bi_ptr(buf), block);
178 if (len > block)
179 memcpy(bi_end(tmpbuf)+block, buf->data, len-block);
180 b_rew(buf, *nxt);
181
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100182 tmpbuf->i += len;
183 ret = len;
184 }
185 else {
186 b_adv(buf, *nxt);
187 ret = http_compression_buffer_add_data(st, buf, zbuf, len);
188 b_rew(buf, *nxt);
189 if (ret < 0)
190 return ret;
191 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100192
Christopher Faulet2fb28802015-12-01 10:40:57 +0100193 st->initialized = 1;
194 msg->next += ret;
195 msg->chunk_len -= ret;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100196 *nxt = msg->next;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100197 return 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100198}
199
200static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100201comp_http_chunk_trailers(struct stream *s, struct filter *filter,
202 struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100203{
204 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100205
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100206 if (!st->initialized) {
207 if (!st->finished) {
208 struct buffer *buf = msg->chn->buf;
209 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100210
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100211 b_reset(tmpbuf);
212 b_adv(buf, fwd);
213 http_compression_buffer_init(buf, zbuf);
214 b_rew(buf, fwd);
215 st->initialized = 1;
216 }
217 }
218 st->tlrs_len = msg->sol;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100219 return 1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100220}
221
Christopher Faulet2fb28802015-12-01 10:40:57 +0100222
Christopher Faulet92d36382015-11-05 13:35:03 +0100223static int
224comp_http_forward_data(struct stream *s, struct filter *filter,
225 struct http_msg *msg, unsigned int len)
226{
227 struct comp_state *st = filter->ctx;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100228 int ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100229
Christopher Faulet2fb28802015-12-01 10:40:57 +0100230 /* To work, previous filters MUST forward all data */
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100231 if (flt_rsp_fwd(filter) + len != flt_rsp_nxt(filter)) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100232 Warning("HTTP compression failed: unexpected behavior of previous filters\n");
233 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100234 }
235
Christopher Faulet2fb28802015-12-01 10:40:57 +0100236 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100237 if (!len) {
238 /* Nothing to foward */
239 ret = len;
240 }
241 else if (st->hdrs_len > len) {
242 /* Forward part of headers */
243 ret = len;
244 st->hdrs_len -= len;
245 }
246 else if (st->hdrs_len > 0) {
247 /* Forward remaining headers */
248 ret = st->hdrs_len;
249 st->hdrs_len = 0;
250 }
251 else if (msg->msg_state < HTTP_MSG_TRAILERS) {
252 /* Do not forward anything for now. This only happens
253 * with chunk-encoded responses. Waiting data are part
254 * of the chunk envelope (the chunk size or the chunk
255 * CRLF). These data will be skipped during the
256 * compression. */
257 ret = 0;
258 }
259 else {
260 /* Forward trailers data */
261 ret = len;
262 }
Christopher Faulet2fb28802015-12-01 10:40:57 +0100263 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100264 }
265
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100266 if (msg->flags & HTTP_MSGF_TE_CHNK) {
267 ret = http_compression_buffer_add_data(st, tmpbuf, zbuf, tmpbuf->i);
268 if (ret != tmpbuf->i) {
269 Warning("HTTP compression failed: Must consume %d bytes but only %d bytes consumed\n",
270 tmpbuf->i, ret);
271 return -1;
272 }
273 }
274
275 st->consumed = len - st->hdrs_len - st->tlrs_len;
276 b_adv(msg->chn->buf, flt_rsp_fwd(filter) + st->hdrs_len);
277 ret = http_compression_buffer_end(st, s, &msg->chn->buf, &zbuf, msg->msg_state >= HTTP_MSG_TRAILERS);
278 b_rew(msg->chn->buf, flt_rsp_fwd(filter) + st->hdrs_len);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100279 if (ret < 0)
280 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100281
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100282 flt_change_forward_size(filter, msg->chn, ret - st->consumed);
283 msg->next += (ret - st->consumed);
284 ret += st->hdrs_len + st->tlrs_len;
285
Christopher Faulet2fb28802015-12-01 10:40:57 +0100286 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100287 st->finished = (msg->msg_state >= HTTP_MSG_TRAILERS);
288 st->hdrs_len = 0;
289 st->tlrs_len = 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100290 return ret;
291}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100292
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200293static int
294comp_http_end(struct stream *s, struct filter *filter,
295 struct http_msg *msg)
296{
297 struct comp_state *st = filter->ctx;
298
299 if (!(msg->chn->flags & CF_ISRESP) || !st || !st->comp_algo)
300 goto end;
301
302 if (strm_fe(s)->mode == PR_MODE_HTTP)
303 strm_fe(s)->fe_counters.p.http.comp_rsp++;
304 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
305 s->be->be_counters.p.http.comp_rsp++;
306 end:
307 return 1;
308}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100309/***********************************************************************/
310/*
311 * Selects a compression algorithm depending on the client request.
312 */
313int
Christopher Faulet92d36382015-11-05 13:35:03 +0100314select_compression_request_header(struct comp_state *st, struct stream *s,
315 struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100316{
317 struct http_txn *txn = s->txn;
Christopher Faulet92d36382015-11-05 13:35:03 +0100318 struct buffer *req = msg->chn->buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100319 struct hdr_ctx ctx;
320 struct comp_algo *comp_algo = NULL;
321 struct comp_algo *comp_algo_back = NULL;
322
323 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
324 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
325 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
326 */
327 ctx.idx = 0;
328 if (http_find_header2("User-Agent", 10, req->p, &txn->hdr_idx, &ctx) &&
329 ctx.vlen >= 9 &&
330 memcmp(ctx.line + ctx.val, "Mozilla/4", 9) == 0 &&
331 (ctx.vlen < 31 ||
332 memcmp(ctx.line + ctx.val + 25, "MSIE ", 5) != 0 ||
333 ctx.line[ctx.val + 30] < '6' ||
334 (ctx.line[ctx.val + 30] == '6' &&
335 (ctx.vlen < 54 || memcmp(ctx.line + 51, "SV1", 3) != 0)))) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100336 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100337 return 0;
338 }
339
340 /* search for the algo in the backend in priority or the frontend */
Christopher Faulet92d36382015-11-05 13:35:03 +0100341 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
342 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100343 int best_q = 0;
344
345 ctx.idx = 0;
346 while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) {
347 const char *qval;
348 int q;
349 int toklen;
350
351 /* try to isolate the token from the optional q-value */
352 toklen = 0;
Willy Tarreau2235b262016-11-05 15:50:20 +0100353 while (toklen < ctx.vlen && HTTP_IS_TOKEN(*(ctx.line + ctx.val + toklen)))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100354 toklen++;
355
356 qval = ctx.line + ctx.val + toklen;
357 while (1) {
Willy Tarreau2235b262016-11-05 15:50:20 +0100358 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100359 qval++;
360
361 if (qval >= ctx.line + ctx.val + ctx.vlen || *qval != ';') {
362 qval = NULL;
363 break;
364 }
365 qval++;
366
Willy Tarreau2235b262016-11-05 15:50:20 +0100367 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100368 qval++;
369
370 if (qval >= ctx.line + ctx.val + ctx.vlen) {
371 qval = NULL;
372 break;
373 }
374 if (strncmp(qval, "q=", MIN(ctx.line + ctx.val + ctx.vlen - qval, 2)) == 0)
375 break;
376
377 while (qval < ctx.line + ctx.val + ctx.vlen && *qval != ';')
378 qval++;
379 }
380
381 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
382 q = qval ? parse_qvalue(qval + 2, NULL) : 1000;
383
384 if (q <= best_q)
385 continue;
386
387 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
388 if (*(ctx.line + ctx.val) == '*' ||
389 word_match(ctx.line + ctx.val, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100390 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100391 best_q = q;
392 break;
393 }
394 }
395 }
396 }
397
398 /* remove all occurrences of the header when "compression offload" is set */
Christopher Faulet92d36382015-11-05 13:35:03 +0100399 if (st->comp_algo) {
400 if ((s->be->comp && s->be->comp->offload) ||
401 (strm_fe(s)->comp && strm_fe(s)->comp->offload)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100402 http_remove_header2(msg, &txn->hdr_idx, &ctx);
403 ctx.idx = 0;
404 while (http_find_header2("Accept-Encoding", 15, req->p, &txn->hdr_idx, &ctx)) {
405 http_remove_header2(msg, &txn->hdr_idx, &ctx);
406 }
407 }
408 return 1;
409 }
410
411 /* identity is implicit does not require headers */
Christopher Faulet92d36382015-11-05 13:35:03 +0100412 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
413 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100414 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
415 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100416 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100417 return 1;
418 }
419 }
420 }
421
Christopher Faulet92d36382015-11-05 13:35:03 +0100422 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100423 return 0;
424}
425
Christopher Faulet92d36382015-11-05 13:35:03 +0100426
Christopher Faulet3d97c902015-12-09 14:59:38 +0100427/*
428 * Selects a comression algorithm depending of the server response.
429 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100430static int
431select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100432{
433 struct http_txn *txn = s->txn;
Christopher Faulet92d36382015-11-05 13:35:03 +0100434 struct buffer *res = msg->chn->buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100435 struct hdr_ctx ctx;
436 struct comp_type *comp_type;
437
438 /* no common compression algorithm was found in request header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100439 if (st->comp_algo == NULL)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100440 goto fail;
441
442 /* HTTP < 1.1 should not be compressed */
443 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
444 goto fail;
445
Christopher Faulet92d36382015-11-05 13:35:03 +0100446 if (txn->meth == HTTP_METH_HEAD)
447 goto fail;
448
Christopher Faulet3d97c902015-12-09 14:59:38 +0100449 /* compress 200,201,202,203 responses only */
450 if ((txn->status != 200) &&
451 (txn->status != 201) &&
452 (txn->status != 202) &&
453 (txn->status != 203))
454 goto fail;
455
456
457 /* Content-Length is null */
458 if (!(msg->flags & HTTP_MSGF_TE_CHNK) && msg->body_len == 0)
459 goto fail;
460
461 /* content is already compressed */
462 ctx.idx = 0;
463 if (http_find_header2("Content-Encoding", 16, res->p, &txn->hdr_idx, &ctx))
464 goto fail;
465
466 /* no compression when Cache-Control: no-transform is present in the message */
467 ctx.idx = 0;
468 while (http_find_header2("Cache-Control", 13, res->p, &txn->hdr_idx, &ctx)) {
469 if (word_match(ctx.line + ctx.val, ctx.vlen, "no-transform", 12))
470 goto fail;
471 }
472
473 comp_type = NULL;
474
475 /* we don't want to compress multipart content-types, nor content-types that are
476 * not listed in the "compression type" directive if any. If no content-type was
477 * found but configuration requires one, we don't compress either. Backend has
478 * the priority.
479 */
480 ctx.idx = 0;
481 if (http_find_header2("Content-Type", 12, res->p, &txn->hdr_idx, &ctx)) {
482 if (ctx.vlen >= 9 && strncasecmp("multipart", ctx.line+ctx.val, 9) == 0)
483 goto fail;
484
485 if ((s->be->comp && (comp_type = s->be->comp->types)) ||
486 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types))) {
487 for (; comp_type; comp_type = comp_type->next) {
488 if (ctx.vlen >= comp_type->name_len &&
489 strncasecmp(ctx.line+ctx.val, comp_type->name, comp_type->name_len) == 0)
490 /* this Content-Type should be compressed */
491 break;
492 }
493 /* this Content-Type should not be compressed */
494 if (comp_type == NULL)
495 goto fail;
496 }
497 }
498 else { /* no content-type header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100499 if ((s->be->comp && s->be->comp->types) ||
500 (strm_fe(s)->comp && strm_fe(s)->comp->types))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100501 goto fail; /* a content-type was required */
502 }
503
504 /* limit compression rate */
505 if (global.comp_rate_lim > 0)
506 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
507 goto fail;
508
509 /* limit cpu usage */
510 if (idle_pct < compress_min_idle)
511 goto fail;
512
513 /* initialize compression */
Christopher Faulet92d36382015-11-05 13:35:03 +0100514 if (st->comp_algo->init(&st->comp_ctx, global.tune.comp_maxlevel) < 0)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100515 goto fail;
516
Christopher Faulet3d97c902015-12-09 14:59:38 +0100517 /* remove Content-Length header */
518 ctx.idx = 0;
519 if ((msg->flags & HTTP_MSGF_CNT_LEN) && http_find_header2("Content-Length", 14, res->p, &txn->hdr_idx, &ctx))
520 http_remove_header2(msg, &txn->hdr_idx, &ctx);
521
522 /* add Transfer-Encoding header */
523 if (!(msg->flags & HTTP_MSGF_TE_CHNK))
524 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, "Transfer-Encoding: chunked", 26);
525
526 /*
527 * Add Content-Encoding header when it's not identity encoding.
528 * RFC 2616 : Identity encoding: This content-coding is used only in the
529 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
530 * header.
531 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100532 if (st->comp_algo->cfg_name_len != 8 || memcmp(st->comp_algo->cfg_name, "identity", 8) != 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100533 trash.len = 18;
534 memcpy(trash.str, "Content-Encoding: ", trash.len);
Christopher Faulet92d36382015-11-05 13:35:03 +0100535 memcpy(trash.str + trash.len, st->comp_algo->ua_name, st->comp_algo->ua_name_len);
536 trash.len += st->comp_algo->ua_name_len;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100537 trash.str[trash.len] = '\0';
538 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.str, trash.len);
539 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100540 msg->flags |= HTTP_MSGF_COMPRESSING;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100541 return 1;
542
543fail:
Christopher Faulet92d36382015-11-05 13:35:03 +0100544 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100545 return 0;
546}
547
548/***********************************************************************/
549/* emit the chunksize followed by a CRLF on the output and return the number of
550 * bytes written. It goes backwards and starts with the byte before <end>. It
551 * returns the number of bytes written which will not exceed 10 (8 digits, CR,
552 * and LF). The caller is responsible for ensuring there is enough room left in
553 * the output buffer for the string.
554 */
555static int
556http_emit_chunk_size(char *end, unsigned int chksz)
557{
558 char *beg = end;
559
560 *--beg = '\n';
561 *--beg = '\r';
562 do {
563 *--beg = hextab[chksz & 0xF];
564 } while (chksz >>= 4);
565 return end - beg;
566}
567
568/*
569 * Init HTTP compression
570 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100571static int
572http_compression_buffer_init(struct buffer *in, struct buffer *out)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100573{
574 /* output stream requires at least 10 bytes for the gzip header, plus
575 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
576 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
577 */
578 if (in->size - buffer_len(in) < 20 + 5 * ((in->i + 32767) >> 15))
579 return -1;
580
581 /* prepare an empty output buffer in which we reserve enough room for
582 * copying the output bytes from <in>, plus 10 extra bytes to write
583 * the chunk size. We don't copy the bytes yet so that if we have to
584 * cancel the operation later, it's cheap.
585 */
586 b_reset(out);
587 out->o = in->o;
588 out->p += out->o;
589 out->i = 10;
590 return 0;
591}
592
593/*
594 * Add data to compress
595 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100596static int
597http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
598 struct buffer *out, int sz)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100599{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100600 int consumed_data = 0;
601 int data_process_len;
602 int block1, block2;
603
Christopher Faulet92d36382015-11-05 13:35:03 +0100604 if (!sz)
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100605 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100606
Christopher Faulet92d36382015-11-05 13:35:03 +0100607 /* select the smallest size between the announced chunk size, the input
Christopher Faulet3d97c902015-12-09 14:59:38 +0100608 * data, and the available output buffer size. The compressors are
Christopher Faulet92d36382015-11-05 13:35:03 +0100609 * assumed to be able to process all the bytes we pass to them at
610 * once. */
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100611 data_process_len = MIN(out->size - buffer_len(out), sz);
Christopher Faulet92d36382015-11-05 13:35:03 +0100612
Christopher Faulet3d97c902015-12-09 14:59:38 +0100613 block1 = data_process_len;
614 if (block1 > bi_contig_data(in))
615 block1 = bi_contig_data(in);
616 block2 = data_process_len - block1;
617
618 /* compressors return < 0 upon error or the amount of bytes read */
Christopher Faulet92d36382015-11-05 13:35:03 +0100619 consumed_data = st->comp_algo->add_data(st->comp_ctx, bi_ptr(in), block1, out);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100620 if (consumed_data != block1 || !block2)
621 goto end;
622 consumed_data = st->comp_algo->add_data(st->comp_ctx, in->data, block2, out);
623 if (consumed_data < 0)
624 goto end;
625 consumed_data += block1;
626
627 end:
Christopher Faulet3d97c902015-12-09 14:59:38 +0100628 return consumed_data;
629}
630
631/*
632 * Flush data in process, and write the header and footer of the chunk. Upon
633 * success, in and out buffers are swapped to avoid a copy.
634 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100635static int
636http_compression_buffer_end(struct comp_state *st, struct stream *s,
637 struct buffer **in, struct buffer **out,
Christopher Faulet2fb28802015-12-01 10:40:57 +0100638 int end)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100639{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100640 struct buffer *ib = *in, *ob = *out;
641 char *tail;
Christopher Faulet92d36382015-11-05 13:35:03 +0100642 int to_forward, left;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100643
644#if defined(USE_SLZ) || defined(USE_ZLIB)
645 int ret;
646
647 /* flush data here */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100648 if (end)
Christopher Faulet92d36382015-11-05 13:35:03 +0100649 ret = st->comp_algo->finish(st->comp_ctx, ob); /* end of data */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100650 else
Christopher Faulet92d36382015-11-05 13:35:03 +0100651 ret = st->comp_algo->flush(st->comp_ctx, ob); /* end of buffer */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100652
653 if (ret < 0)
654 return -1; /* flush failed */
655
656#endif /* USE_ZLIB */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100657 if (ob->i == 10) {
658 /* No data were appended, let's drop the output buffer and
659 * keep the input buffer unchanged.
660 */
661 return 0;
662 }
663
664 /* OK so at this stage, we have an output buffer <ob> looking like this :
665 *
666 * <-- o --> <------ i ----->
667 * +---------+---+------------+-----------+
668 * | out | c | comp_in | empty |
669 * +---------+---+------------+-----------+
670 * data p size
671 *
672 * <out> is the room reserved to copy ib->o. It starts at ob->data and
673 * has not yet been filled. <c> is the room reserved to write the chunk
674 * size (10 bytes). <comp_in> is the compressed equivalent of the data
675 * part of ib->i. <empty> is the amount of empty bytes at the end of
676 * the buffer, into which we may have to copy the remaining bytes from
677 * ib->i after the data (chunk size, trailers, ...).
678 */
679
680 /* Write real size at the begining of the chunk, no need of wrapping.
681 * We write the chunk using a dynamic length and adjust ob->p and ob->i
682 * accordingly afterwards. That will move <out> away from <data>.
683 */
684 left = 10 - http_emit_chunk_size(ob->p + 10, ob->i - 10);
685 ob->p += left;
686 ob->i -= left;
687
688 /* Copy previous data from ib->o into ob->o */
689 if (ib->o > 0) {
690 left = bo_contig_data(ib);
691 memcpy(ob->p - ob->o, bo_ptr(ib), left);
692 if (ib->o - left) /* second part of the buffer */
693 memcpy(ob->p - ob->o + left, ib->data, ib->o - left);
694 }
695
696 /* chunked encoding requires CRLF after data */
697 tail = ob->p + ob->i;
698 *tail++ = '\r';
699 *tail++ = '\n';
700
Christopher Faulet2fb28802015-12-01 10:40:57 +0100701 /* At the end of data, we must write the empty chunk 0<CRLF>,
702 * and terminate the trailers section with a last <CRLF>. If
703 * we're forwarding a chunked-encoded response, we'll have a
704 * trailers section after the empty chunk which needs to be
705 * forwarded and which will provide the last CRLF. Otherwise
706 * we write it ourselves.
707 */
708 if (end) {
709 struct http_msg *msg = &s->txn->rsp;
710
711 memcpy(tail, "0\r\n", 3);
712 tail += 3;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100713 if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100714 memcpy(tail, "\r\n", 2);
715 tail += 2;
716 }
717 }
718
Christopher Faulet3d97c902015-12-09 14:59:38 +0100719 ob->i = tail - ob->p;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100720 to_forward = ob->i;
721
722 /* update input rate */
Christopher Faulet92d36382015-11-05 13:35:03 +0100723 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100724 update_freq_ctr(&global.comp_bps_in, st->consumed);
725 strm_fe(s)->fe_counters.comp_in += st->consumed;
726 s->be->be_counters.comp_in += st->consumed;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100727 } else {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100728 strm_fe(s)->fe_counters.comp_byp += st->consumed;
729 s->be->be_counters.comp_byp += st->consumed;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100730 }
731
732 /* copy the remaining data in the tmp buffer. */
Christopher Faulet2fb28802015-12-01 10:40:57 +0100733 b_adv(ib, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100734 if (ib->i > 0) {
735 left = bi_contig_data(ib);
736 memcpy(ob->p + ob->i, bi_ptr(ib), left);
737 ob->i += left;
738 if (ib->i - left) {
739 memcpy(ob->p + ob->i, ib->data, ib->i - left);
740 ob->i += ib->i - left;
741 }
742 }
743
744 /* swap the buffers */
745 *in = ob;
746 *out = ib;
747
Christopher Faulet92d36382015-11-05 13:35:03 +0100748
749 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100750 update_freq_ctr(&global.comp_bps_out, to_forward);
751 strm_fe(s)->fe_counters.comp_out += to_forward;
752 s->be->be_counters.comp_out += to_forward;
753 }
754
Christopher Faulet3d97c902015-12-09 14:59:38 +0100755 return to_forward;
756}
757
758
759/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100760struct flt_ops comp_ops = {
761 .init = comp_flt_init,
762 .deinit = comp_flt_deinit,
763
764 .channel_start_analyze = comp_start_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100765 .channel_end_analyze = comp_end_analyze,
766
Christopher Faulet1339d742016-05-11 16:48:33 +0200767 .http_headers = comp_http_headers,
Christopher Faulet309c6412015-12-02 09:57:32 +0100768 .http_data = comp_http_data,
769 .http_chunk_trailers = comp_http_chunk_trailers,
770 .http_forward_data = comp_http_forward_data,
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200771 .http_end = comp_http_end,
Christopher Faulet92d36382015-11-05 13:35:03 +0100772};
773
Christopher Faulet3d97c902015-12-09 14:59:38 +0100774static int
775parse_compression_options(char **args, int section, struct proxy *proxy,
776 struct proxy *defpx, const char *file, int line,
777 char **err)
778{
Christopher Faulet92d36382015-11-05 13:35:03 +0100779 struct comp *comp;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100780
781 if (proxy->comp == NULL) {
Vincent Bernat02779b62016-04-03 13:48:43 +0200782 comp = calloc(1, sizeof(*comp));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100783 proxy->comp = comp;
784 }
785 else
786 comp = proxy->comp;
787
788 if (!strcmp(args[1], "algo")) {
789 struct comp_ctx *ctx;
790 int cur_arg = 2;
791
792 if (!*args[cur_arg]) {
793 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>\n",
794 file, line, args[0]);
795 return -1;
796 }
797 while (*(args[cur_arg])) {
798 if (comp_append_algo(comp, args[cur_arg]) < 0) {
799 memprintf(err, "'%s' : '%s' is not a supported algorithm.\n",
800 args[0], args[cur_arg]);
801 return -1;
802 }
803 if (proxy->comp->algos->init(&ctx, 9) == 0)
804 proxy->comp->algos->end(&ctx);
805 else {
806 memprintf(err, "'%s' : Can't init '%s' algorithm.\n",
807 args[0], args[cur_arg]);
808 return -1;
809 }
810 cur_arg++;
811 continue;
812 }
813 }
814 else if (!strcmp(args[1], "offload"))
815 comp->offload = 1;
816 else if (!strcmp(args[1], "type")) {
817 int cur_arg = 2;
818
819 if (!*args[cur_arg]) {
820 memprintf(err, "'%s' expects <type>\n", args[0]);
821 return -1;
822 }
823 while (*(args[cur_arg])) {
824 comp_append_type(comp, args[cur_arg]);
825 cur_arg++;
826 continue;
827 }
828 }
829 else {
830 memprintf(err, "'%s' expects 'algo', 'type' or 'offload'\n",
831 args[0]);
832 return -1;
833 }
834
835 return 0;
836}
837
Christopher Faulet92d36382015-11-05 13:35:03 +0100838static int
839parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
Thierry Fournier3610c392016-04-13 18:27:51 +0200840 struct flt_conf *fconf, char **err, void *private)
Christopher Faulet92d36382015-11-05 13:35:03 +0100841{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100842 struct flt_conf *fc, *back;
Christopher Faulet92d36382015-11-05 13:35:03 +0100843
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100844 list_for_each_entry_safe(fc, back, &px->filter_configs, list) {
845 if (fc->id == http_comp_flt_id) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100846 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
847 return -1;
848 }
849 }
850
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100851 fconf->id = http_comp_flt_id;
852 fconf->conf = NULL;
853 fconf->ops = &comp_ops;
Christopher Faulet92d36382015-11-05 13:35:03 +0100854 (*cur_arg)++;
855
856 return 0;
857}
858
859
860int
861check_legacy_http_comp_flt(struct proxy *proxy)
862{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100863 struct flt_conf *fconf;
Christopher Faulet92d36382015-11-05 13:35:03 +0100864 int err = 0;
865
866 if (proxy->comp == NULL)
867 goto end;
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100868 if (!LIST_ISEMPTY(&proxy->filter_configs)) {
869 list_for_each_entry(fconf, &proxy->filter_configs, list) {
870 if (fconf->id == http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100871 goto end;
872 }
873 Alert("config: %s '%s': require an explicit filter declaration to use HTTP compression\n",
874 proxy_type_str(proxy), proxy->id);
875 err++;
876 goto end;
877 }
878
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100879 fconf = calloc(1, sizeof(*fconf));
880 if (!fconf) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100881 Alert("config: %s '%s': out of memory\n",
882 proxy_type_str(proxy), proxy->id);
883 err++;
884 goto end;
885 }
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100886 fconf->id = http_comp_flt_id;
887 fconf->conf = NULL;
888 fconf->ops = &comp_ops;
889 LIST_ADDQ(&proxy->filter_configs, &fconf->list);
Christopher Faulet92d36382015-11-05 13:35:03 +0100890
891 end:
892 return err;
893}
894
895/*
896 * boolean, returns true if compression is used (either gzip or deflate) in the
897 * response.
898 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100899static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100900smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
901 void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100902{
Willy Tarreaube508f12016-03-10 11:47:01 +0100903 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100904
Christopher Faulet3d97c902015-12-09 14:59:38 +0100905 smp->data.type = SMP_T_BOOL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100906 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100907 return 1;
908}
909
Christopher Faulet92d36382015-11-05 13:35:03 +0100910/*
911 * string, returns algo
912 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100913static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100914smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
915 const char *kw, void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100916{
Willy Tarreaube508f12016-03-10 11:47:01 +0100917 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100918 struct filter *filter;
919 struct comp_state *st;
920
921 if (!(txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING)))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100922 return 0;
923
Christopher Fauletfcf035c2015-12-03 11:48:03 +0100924 list_for_each_entry(filter, &strm_flt(smp->strm)->filters, list) {
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100925 if (FLT_ID(filter) != http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100926 continue;
927
928 if (!(st = filter->ctx))
929 break;
930
931 smp->data.type = SMP_T_STR;
932 smp->flags = SMP_F_CONST;
933 smp->data.u.str.str = st->comp_algo->cfg_name;
934 smp->data.u.str.len = st->comp_algo->cfg_name_len;
935 return 1;
936 }
937 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100938}
939
940/* Declare the config parser for "compression" keyword */
941static struct cfg_kw_list cfg_kws = {ILH, {
942 { CFG_LISTEN, "compression", parse_compression_options },
943 { 0, NULL, NULL },
944 }
945};
946
Christopher Faulet92d36382015-11-05 13:35:03 +0100947/* Declare the filter parser for "compression" keyword */
948static struct flt_kw_list filter_kws = { "COMP", { }, {
Thierry Fournier3610c392016-04-13 18:27:51 +0200949 { "compression", parse_http_comp_flt, NULL },
950 { NULL, NULL, NULL },
Christopher Faulet92d36382015-11-05 13:35:03 +0100951 }
952};
953
Christopher Faulet3d97c902015-12-09 14:59:38 +0100954/* Note: must not be declared <const> as its list will be overwritten */
955static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet92d36382015-11-05 13:35:03 +0100956 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
957 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
958 { /* END */ },
959 }
960};
Christopher Faulet3d97c902015-12-09 14:59:38 +0100961
962__attribute__((constructor))
Christopher Faulet92d36382015-11-05 13:35:03 +0100963static void
964__flt_http_comp_init(void)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100965{
966 cfg_register_keywords(&cfg_kws);
Christopher Faulet92d36382015-11-05 13:35:03 +0100967 flt_register_keywords(&filter_kws);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100968 sample_register_fetches(&sample_fetch_keywords);
969}