blob: e0cee75b4f479fb0df4fca92df1fdbc0d54d7365 [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>
Willy Tarreau0108d902018-11-25 19:14:37 +010015#include <common/initcall.h>
Christopher Faulet3d97c902015-12-09 14:59:38 +010016#include <common/mini-clist.h>
17#include <common/standard.h>
18
19#include <types/compression.h>
20#include <types/filters.h>
Willy Tarreau35b51c62018-09-10 15:38:55 +020021#include <types/h1.h>
Christopher Faulet3d97c902015-12-09 14:59:38 +010022#include <types/proxy.h>
23#include <types/sample.h>
24
25#include <proto/compression.h>
Christopher Faulet92d36382015-11-05 13:35:03 +010026#include <proto/filters.h>
Christopher Faulet3d97c902015-12-09 14:59:38 +010027#include <proto/hdr_idx.h>
28#include <proto/proto_http.h>
29#include <proto/sample.h>
30#include <proto/stream.h>
31
Christopher Faulet92d36382015-11-05 13:35:03 +010032static const char *http_comp_flt_id = "compression filter";
33
34struct flt_ops comp_ops;
35
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020036
37/* Pools used to allocate comp_state structs */
Willy Tarreaubafbe012017-11-24 17:34:44 +010038static struct pool_head *pool_head_comp_state = NULL;
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020039
Willy Tarreauc9fa0482018-07-10 17:43:27 +020040static THREAD_LOCAL struct buffer tmpbuf;
41static THREAD_LOCAL struct buffer zbuf;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020042static THREAD_LOCAL unsigned int buf_output;
Christopher Faulet92d36382015-11-05 13:35:03 +010043
Christopher Faulet92d36382015-11-05 13:35:03 +010044struct comp_state {
45 struct comp_ctx *comp_ctx; /* compression context */
46 struct comp_algo *comp_algo; /* compression algorithm if not NULL */
Christopher Fauletb77c5c22015-12-07 16:48:42 +010047 int hdrs_len;
48 int tlrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +010049 int consumed;
50 int initialized;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010051 int finished;
Christopher Faulet92d36382015-11-05 13:35:03 +010052};
53
Christopher Faulet92d36382015-11-05 13:35:03 +010054static int select_compression_request_header(struct comp_state *st,
55 struct stream *s,
56 struct http_msg *msg);
57static int select_compression_response_header(struct comp_state *st,
58 struct stream *s,
59 struct http_msg *msg);
60
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020061static int http_compression_buffer_init(struct channel *inc, struct buffer *out, unsigned int *out_len);
Christopher Faulet92d36382015-11-05 13:35:03 +010062static int http_compression_buffer_add_data(struct comp_state *st,
63 struct buffer *in,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020064 int in_out,
Christopher Faulet92d36382015-11-05 13:35:03 +010065 struct buffer *out, int sz);
66static int http_compression_buffer_end(struct comp_state *st, struct stream *s,
Willy Tarreauc9fa0482018-07-10 17:43:27 +020067 struct channel *chn, struct buffer *out,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020068 unsigned int *out_len, int end);
Christopher Faulet92d36382015-11-05 13:35:03 +010069
70/***********************************************************************/
71static int
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020072comp_flt_init_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010073{
Willy Tarreauc9fa0482018-07-10 17:43:27 +020074 if (!tmpbuf.size && b_alloc(&tmpbuf) == NULL)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010075 return -1;
Willy Tarreauc9fa0482018-07-10 17:43:27 +020076 if (!zbuf.size && b_alloc(&zbuf) == NULL)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010077 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +010078 return 0;
79}
80
81static void
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020082comp_flt_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010083{
Willy Tarreauc9fa0482018-07-10 17:43:27 +020084 if (tmpbuf.size)
Christopher Faulet92d36382015-11-05 13:35:03 +010085 b_free(&tmpbuf);
Willy Tarreauc9fa0482018-07-10 17:43:27 +020086 if (zbuf.size)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010087 b_free(&zbuf);
Christopher Faulet92d36382015-11-05 13:35:03 +010088}
89
90static int
91comp_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
92{
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020093
Christopher Faulet92d36382015-11-05 13:35:03 +010094 if (filter->ctx == NULL) {
95 struct comp_state *st;
96
Willy Tarreaubafbe012017-11-24 17:34:44 +010097 st = pool_alloc_dirty(pool_head_comp_state);
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020098 if (st == NULL)
Christopher Faulet92d36382015-11-05 13:35:03 +010099 return -1;
100
Christopher Faulet2fb28802015-12-01 10:40:57 +0100101 st->comp_algo = NULL;
102 st->comp_ctx = NULL;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100103 st->hdrs_len = 0;
104 st->tlrs_len = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100105 st->consumed = 0;
106 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100107 st->finished = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100108 filter->ctx = st;
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200109
110 /* Register post-analyzer on AN_RES_WAIT_HTTP because we need to
111 * analyze response headers before http-response rules execution
112 * to be sure we can use res.comp and res.comp_algo sample
113 * fetches */
114 filter->post_analyzers |= AN_RES_WAIT_HTTP;
Christopher Faulet92d36382015-11-05 13:35:03 +0100115 }
116 return 1;
117}
118
119static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100120comp_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
121{
122 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100123
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200124 if (!st)
Christopher Faulet92d36382015-11-05 13:35:03 +0100125 goto end;
126
Christopher Faulet92d36382015-11-05 13:35:03 +0100127 /* release any possible compression context */
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200128 if (st->comp_algo)
129 st->comp_algo->end(&st->comp_ctx);
Willy Tarreaubafbe012017-11-24 17:34:44 +0100130 pool_free(pool_head_comp_state, st);
Christopher Faulet92d36382015-11-05 13:35:03 +0100131 filter->ctx = NULL;
132 end:
133 return 1;
134}
135
136static int
Christopher Faulet1339d742016-05-11 16:48:33 +0200137comp_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
138{
139 struct comp_state *st = filter->ctx;
140
141 if (!strm_fe(s)->comp && !s->be->comp)
142 goto end;
143
144 if (!(msg->chn->flags & CF_ISRESP))
145 select_compression_request_header(st, s, msg);
146 else {
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200147 /* Response headers have already been checked in
148 * comp_http_post_analyze callback. */
Christopher Faulet1339d742016-05-11 16:48:33 +0200149 if (st->comp_algo) {
150 register_data_filter(s, msg->chn, filter);
151 st->hdrs_len = s->txn->rsp.sov;
152 }
153 }
154
155 end:
156 return 1;
157}
158
159static int
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200160comp_http_post_analyze(struct stream *s, struct filter *filter,
161 struct channel *chn, unsigned an_bit)
162{
163 struct http_txn *txn = s->txn;
164 struct http_msg *msg = &txn->rsp;
165 struct comp_state *st = filter->ctx;
166
167 if (an_bit != AN_RES_WAIT_HTTP)
168 goto end;
169
170 if (!strm_fe(s)->comp && !s->be->comp)
171 goto end;
172
173 select_compression_response_header(st, s, msg);
174
175 end:
176 return 1;
177}
178
179static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100180comp_http_data(struct stream *s, struct filter *filter, struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100181{
182 struct comp_state *st = filter->ctx;
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200183 struct channel *chn = msg->chn;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100184 unsigned int *nxt = &flt_rsp_nxt(filter);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100185 unsigned int len;
Christopher Faulet92d36382015-11-05 13:35:03 +0100186 int ret;
187
Olivier Houchard0b662842018-06-29 18:16:31 +0200188 len = MIN(msg->chunk_len + msg->next, ci_data(chn)) - *nxt;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100189 if (!len)
190 return len;
191
192 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100193 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100194
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200195 b_reset(&tmpbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200196 c_adv(chn, fwd);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200197 ret = http_compression_buffer_init(chn, &zbuf, &buf_output);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200198 c_rew(chn, fwd);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100199 if (ret < 0) {
200 msg->chn->flags |= CF_WAKE_WRITE;
201 return 0;
202 }
203 }
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100204
205 if (msg->flags & HTTP_MSGF_TE_CHNK) {
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200206 int block;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100207
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200208 len = MIN(b_room(&tmpbuf), len);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200209
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200210 c_adv(chn, *nxt);
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200211 block = ci_contig_data(chn);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200212 memcpy(b_tail(&tmpbuf), ci_head(chn), block);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200213 if (len > block)
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200214 memcpy(b_tail(&tmpbuf)+block, b_orig(&chn->buf), len-block);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200215 c_rew(chn, *nxt);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200216
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200217 b_add(&tmpbuf, len);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100218 ret = len;
219 }
220 else {
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200221 c_adv(chn, *nxt);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200222 ret = http_compression_buffer_add_data(st, &chn->buf, co_data(chn), &zbuf, len);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200223 c_rew(chn, *nxt);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100224 if (ret < 0)
225 return ret;
226 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100227
Christopher Faulet2fb28802015-12-01 10:40:57 +0100228 st->initialized = 1;
229 msg->next += ret;
230 msg->chunk_len -= ret;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100231 *nxt = msg->next;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100232 return 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100233}
234
235static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100236comp_http_chunk_trailers(struct stream *s, struct filter *filter,
237 struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100238{
239 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100240
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100241 if (!st->initialized) {
242 if (!st->finished) {
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200243 struct channel *chn = msg->chn;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100244 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100245
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200246 b_reset(&tmpbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200247 c_adv(chn, fwd);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200248 http_compression_buffer_init(chn, &zbuf, &buf_output);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200249 c_rew(chn, fwd);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100250 st->initialized = 1;
251 }
252 }
253 st->tlrs_len = msg->sol;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100254 return 1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100255}
256
Christopher Faulet2fb28802015-12-01 10:40:57 +0100257
Christopher Faulet92d36382015-11-05 13:35:03 +0100258static int
259comp_http_forward_data(struct stream *s, struct filter *filter,
260 struct http_msg *msg, unsigned int len)
261{
262 struct comp_state *st = filter->ctx;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100263 int ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100264
Christopher Faulet2fb28802015-12-01 10:40:57 +0100265 /* To work, previous filters MUST forward all data */
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100266 if (flt_rsp_fwd(filter) + len != flt_rsp_nxt(filter)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100267 ha_warning("HTTP compression failed: unexpected behavior of previous filters\n");
Christopher Faulet2fb28802015-12-01 10:40:57 +0100268 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100269 }
270
Christopher Faulet2fb28802015-12-01 10:40:57 +0100271 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100272 if (!len) {
Joseph Herlant942eea32018-11-15 13:57:22 -0800273 /* Nothing to forward */
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100274 ret = len;
275 }
276 else if (st->hdrs_len > len) {
277 /* Forward part of headers */
278 ret = len;
279 st->hdrs_len -= len;
280 }
281 else if (st->hdrs_len > 0) {
282 /* Forward remaining headers */
283 ret = st->hdrs_len;
284 st->hdrs_len = 0;
285 }
286 else if (msg->msg_state < HTTP_MSG_TRAILERS) {
287 /* Do not forward anything for now. This only happens
288 * with chunk-encoded responses. Waiting data are part
289 * of the chunk envelope (the chunk size or the chunk
290 * CRLF). These data will be skipped during the
291 * compression. */
292 ret = 0;
293 }
294 else {
295 /* Forward trailers data */
296 ret = len;
297 }
Christopher Faulet2fb28802015-12-01 10:40:57 +0100298 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100299 }
300
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100301 if (msg->flags & HTTP_MSGF_TE_CHNK) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200302 ret = http_compression_buffer_add_data(st, &tmpbuf, 0,
303 &zbuf, b_data(&tmpbuf));
304 if (ret != b_data(&tmpbuf)) {
Willy Tarreau506a29a2018-07-18 10:07:58 +0200305 ha_warning("HTTP compression failed: Must consume %u bytes but only %d bytes consumed\n",
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200306 (unsigned int)b_data(&tmpbuf), ret);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100307 return -1;
308 }
309 }
310
311 st->consumed = len - st->hdrs_len - st->tlrs_len;
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200312 c_adv(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200313 ret = http_compression_buffer_end(st, s, msg->chn, &zbuf, &buf_output, msg->msg_state >= HTTP_MSG_TRAILERS);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200314 c_rew(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100315 if (ret < 0)
316 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100317
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100318 flt_change_forward_size(filter, msg->chn, ret - st->consumed);
319 msg->next += (ret - st->consumed);
320 ret += st->hdrs_len + st->tlrs_len;
321
Christopher Faulet2fb28802015-12-01 10:40:57 +0100322 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100323 st->finished = (msg->msg_state >= HTTP_MSG_TRAILERS);
324 st->hdrs_len = 0;
325 st->tlrs_len = 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100326 return ret;
327}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100328
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200329static int
330comp_http_end(struct stream *s, struct filter *filter,
331 struct http_msg *msg)
332{
333 struct comp_state *st = filter->ctx;
334
335 if (!(msg->chn->flags & CF_ISRESP) || !st || !st->comp_algo)
336 goto end;
337
338 if (strm_fe(s)->mode == PR_MODE_HTTP)
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200339 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.p.http.comp_rsp, 1);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200340 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200341 HA_ATOMIC_ADD(&s->be->be_counters.p.http.comp_rsp, 1);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200342 end:
343 return 1;
344}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100345/***********************************************************************/
346/*
347 * Selects a compression algorithm depending on the client request.
348 */
349int
Christopher Faulet92d36382015-11-05 13:35:03 +0100350select_compression_request_header(struct comp_state *st, struct stream *s,
351 struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100352{
353 struct http_txn *txn = s->txn;
Olivier Houchard0b662842018-06-29 18:16:31 +0200354 struct channel *req = msg->chn;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100355 struct hdr_ctx ctx;
356 struct comp_algo *comp_algo = NULL;
357 struct comp_algo *comp_algo_back = NULL;
358
359 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
360 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
361 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
362 */
363 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200364 if (http_find_header2("User-Agent", 10, ci_head(req), &txn->hdr_idx, &ctx) &&
Christopher Faulet3d97c902015-12-09 14:59:38 +0100365 ctx.vlen >= 9 &&
366 memcmp(ctx.line + ctx.val, "Mozilla/4", 9) == 0 &&
367 (ctx.vlen < 31 ||
368 memcmp(ctx.line + ctx.val + 25, "MSIE ", 5) != 0 ||
369 ctx.line[ctx.val + 30] < '6' ||
370 (ctx.line[ctx.val + 30] == '6' &&
371 (ctx.vlen < 54 || memcmp(ctx.line + 51, "SV1", 3) != 0)))) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100372 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100373 return 0;
374 }
375
376 /* search for the algo in the backend in priority or the frontend */
Christopher Faulet92d36382015-11-05 13:35:03 +0100377 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
378 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100379 int best_q = 0;
380
381 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200382 while (http_find_header2("Accept-Encoding", 15, ci_head(req), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100383 const char *qval;
384 int q;
385 int toklen;
386
387 /* try to isolate the token from the optional q-value */
388 toklen = 0;
Willy Tarreau2235b262016-11-05 15:50:20 +0100389 while (toklen < ctx.vlen && HTTP_IS_TOKEN(*(ctx.line + ctx.val + toklen)))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100390 toklen++;
391
392 qval = ctx.line + ctx.val + toklen;
393 while (1) {
Willy Tarreau2235b262016-11-05 15:50:20 +0100394 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100395 qval++;
396
397 if (qval >= ctx.line + ctx.val + ctx.vlen || *qval != ';') {
398 qval = NULL;
399 break;
400 }
401 qval++;
402
Willy Tarreau2235b262016-11-05 15:50:20 +0100403 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100404 qval++;
405
406 if (qval >= ctx.line + ctx.val + ctx.vlen) {
407 qval = NULL;
408 break;
409 }
410 if (strncmp(qval, "q=", MIN(ctx.line + ctx.val + ctx.vlen - qval, 2)) == 0)
411 break;
412
413 while (qval < ctx.line + ctx.val + ctx.vlen && *qval != ';')
414 qval++;
415 }
416
417 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
Willy Tarreauab813a42018-09-10 18:41:28 +0200418 q = qval ? http_parse_qvalue(qval + 2, NULL) : 1000;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100419
420 if (q <= best_q)
421 continue;
422
423 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
424 if (*(ctx.line + ctx.val) == '*' ||
425 word_match(ctx.line + ctx.val, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100426 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100427 best_q = q;
428 break;
429 }
430 }
431 }
432 }
433
434 /* remove all occurrences of the header when "compression offload" is set */
Christopher Faulet92d36382015-11-05 13:35:03 +0100435 if (st->comp_algo) {
436 if ((s->be->comp && s->be->comp->offload) ||
437 (strm_fe(s)->comp && strm_fe(s)->comp->offload)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100438 http_remove_header2(msg, &txn->hdr_idx, &ctx);
439 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200440 while (http_find_header2("Accept-Encoding", 15, ci_head(req), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100441 http_remove_header2(msg, &txn->hdr_idx, &ctx);
442 }
443 }
444 return 1;
445 }
446
447 /* identity is implicit does not require headers */
Christopher Faulet92d36382015-11-05 13:35:03 +0100448 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
449 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100450 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
451 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100452 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100453 return 1;
454 }
455 }
456 }
457
Christopher Faulet92d36382015-11-05 13:35:03 +0100458 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100459 return 0;
460}
461
Christopher Faulet92d36382015-11-05 13:35:03 +0100462
Christopher Faulet3d97c902015-12-09 14:59:38 +0100463/*
464 * Selects a comression algorithm depending of the server response.
465 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100466static int
467select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100468{
469 struct http_txn *txn = s->txn;
Olivier Houchard0b662842018-06-29 18:16:31 +0200470 struct channel *c = msg->chn;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100471 struct hdr_ctx ctx;
472 struct comp_type *comp_type;
473
474 /* no common compression algorithm was found in request header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100475 if (st->comp_algo == NULL)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100476 goto fail;
477
478 /* HTTP < 1.1 should not be compressed */
479 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
480 goto fail;
481
Christopher Faulet92d36382015-11-05 13:35:03 +0100482 if (txn->meth == HTTP_METH_HEAD)
483 goto fail;
484
Christopher Faulet3d97c902015-12-09 14:59:38 +0100485 /* compress 200,201,202,203 responses only */
486 if ((txn->status != 200) &&
487 (txn->status != 201) &&
488 (txn->status != 202) &&
489 (txn->status != 203))
490 goto fail;
491
492
493 /* Content-Length is null */
494 if (!(msg->flags & HTTP_MSGF_TE_CHNK) && msg->body_len == 0)
495 goto fail;
496
497 /* content is already compressed */
498 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200499 if (http_find_header2("Content-Encoding", 16, ci_head(c), &txn->hdr_idx, &ctx))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100500 goto fail;
501
502 /* no compression when Cache-Control: no-transform is present in the message */
503 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200504 while (http_find_header2("Cache-Control", 13, ci_head(c), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100505 if (word_match(ctx.line + ctx.val, ctx.vlen, "no-transform", 12))
506 goto fail;
507 }
508
509 comp_type = NULL;
510
511 /* we don't want to compress multipart content-types, nor content-types that are
512 * not listed in the "compression type" directive if any. If no content-type was
513 * found but configuration requires one, we don't compress either. Backend has
514 * the priority.
515 */
516 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200517 if (http_find_header2("Content-Type", 12, ci_head(c), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100518 if (ctx.vlen >= 9 && strncasecmp("multipart", ctx.line+ctx.val, 9) == 0)
519 goto fail;
520
521 if ((s->be->comp && (comp_type = s->be->comp->types)) ||
522 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types))) {
523 for (; comp_type; comp_type = comp_type->next) {
524 if (ctx.vlen >= comp_type->name_len &&
525 strncasecmp(ctx.line+ctx.val, comp_type->name, comp_type->name_len) == 0)
526 /* this Content-Type should be compressed */
527 break;
528 }
529 /* this Content-Type should not be compressed */
530 if (comp_type == NULL)
531 goto fail;
532 }
533 }
534 else { /* no content-type header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100535 if ((s->be->comp && s->be->comp->types) ||
536 (strm_fe(s)->comp && strm_fe(s)->comp->types))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100537 goto fail; /* a content-type was required */
538 }
539
540 /* limit compression rate */
541 if (global.comp_rate_lim > 0)
542 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
543 goto fail;
544
545 /* limit cpu usage */
546 if (idle_pct < compress_min_idle)
547 goto fail;
548
549 /* initialize compression */
Christopher Faulet92d36382015-11-05 13:35:03 +0100550 if (st->comp_algo->init(&st->comp_ctx, global.tune.comp_maxlevel) < 0)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100551 goto fail;
552
Christopher Faulet3d97c902015-12-09 14:59:38 +0100553 /* remove Content-Length header */
554 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200555 if ((msg->flags & HTTP_MSGF_CNT_LEN) && http_find_header2("Content-Length", 14, ci_head(c), &txn->hdr_idx, &ctx))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100556 http_remove_header2(msg, &txn->hdr_idx, &ctx);
557
558 /* add Transfer-Encoding header */
559 if (!(msg->flags & HTTP_MSGF_TE_CHNK))
560 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, "Transfer-Encoding: chunked", 26);
561
562 /*
563 * Add Content-Encoding header when it's not identity encoding.
564 * RFC 2616 : Identity encoding: This content-coding is used only in the
565 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
566 * header.
567 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100568 if (st->comp_algo->cfg_name_len != 8 || memcmp(st->comp_algo->cfg_name, "identity", 8) != 0) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200569 trash.data = 18;
570 memcpy(trash.area, "Content-Encoding: ", trash.data);
571 memcpy(trash.area + trash.data, st->comp_algo->ua_name,
572 st->comp_algo->ua_name_len);
573 trash.data += st->comp_algo->ua_name_len;
574 trash.area[trash.data] = '\0';
575 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.area,
576 trash.data);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100577 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100578 msg->flags |= HTTP_MSGF_COMPRESSING;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100579 return 1;
580
581fail:
Christopher Faulet92d36382015-11-05 13:35:03 +0100582 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100583 return 0;
584}
585
586/***********************************************************************/
587/* emit the chunksize followed by a CRLF on the output and return the number of
588 * bytes written. It goes backwards and starts with the byte before <end>. It
589 * returns the number of bytes written which will not exceed 10 (8 digits, CR,
590 * and LF). The caller is responsible for ensuring there is enough room left in
591 * the output buffer for the string.
592 */
593static int
594http_emit_chunk_size(char *end, unsigned int chksz)
595{
596 char *beg = end;
597
598 *--beg = '\n';
599 *--beg = '\r';
600 do {
601 *--beg = hextab[chksz & 0xF];
602 } while (chksz >>= 4);
603 return end - beg;
604}
605
606/*
607 * Init HTTP compression
608 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100609static int
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200610http_compression_buffer_init(struct channel *inc, struct buffer *out, unsigned int *out_len)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100611{
612 /* output stream requires at least 10 bytes for the gzip header, plus
613 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
614 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
615 */
Olivier Houchard0b662842018-06-29 18:16:31 +0200616 if (c_room(inc) < 20 + 5 * ((ci_data(inc) + 32767) >> 15))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100617 return -1;
618
619 /* prepare an empty output buffer in which we reserve enough room for
620 * copying the output bytes from <in>, plus 10 extra bytes to write
621 * the chunk size. We don't copy the bytes yet so that if we have to
622 * cancel the operation later, it's cheap.
623 */
624 b_reset(out);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200625 *out_len = co_data(inc);
626 out->head += *out_len + 10;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100627 return 0;
628}
629
630/*
631 * Add data to compress
632 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100633static int
634http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200635 int in_out, struct buffer *out, int sz)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100636{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100637 int consumed_data = 0;
638 int data_process_len;
639 int block1, block2;
640
Christopher Faulet92d36382015-11-05 13:35:03 +0100641 if (!sz)
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100642 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100643
Christopher Faulet92d36382015-11-05 13:35:03 +0100644 /* select the smallest size between the announced chunk size, the input
Christopher Faulet3d97c902015-12-09 14:59:38 +0100645 * data, and the available output buffer size. The compressors are
Christopher Faulet92d36382015-11-05 13:35:03 +0100646 * assumed to be able to process all the bytes we pass to them at
647 * once. */
Willy Tarreaueac52592018-06-15 13:59:36 +0200648 data_process_len = MIN(b_room(out), sz);
Christopher Faulet92d36382015-11-05 13:35:03 +0100649
Christopher Faulet3d97c902015-12-09 14:59:38 +0100650 block1 = data_process_len;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200651 if (block1 > b_contig_data(in, in_out))
652 block1 = b_contig_data(in, in_out);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100653 block2 = data_process_len - block1;
654
655 /* compressors return < 0 upon error or the amount of bytes read */
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200656 consumed_data = st->comp_algo->add_data(st->comp_ctx, b_head(in) + in_out, block1, out);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100657 if (consumed_data != block1 || !block2)
658 goto end;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200659 consumed_data = st->comp_algo->add_data(st->comp_ctx, b_peek(in, 0), block2, out);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100660 if (consumed_data < 0)
661 goto end;
662 consumed_data += block1;
663
664 end:
Christopher Faulet3d97c902015-12-09 14:59:38 +0100665 return consumed_data;
666}
667
668/*
669 * Flush data in process, and write the header and footer of the chunk. Upon
670 * success, in and out buffers are swapped to avoid a copy.
671 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100672static int
673http_compression_buffer_end(struct comp_state *st, struct stream *s,
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200674 struct channel *chn, struct buffer *out,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200675 unsigned int *buf_out, int end)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100676{
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200677 struct buffer tmp_buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100678 char *tail;
Christopher Faulet92d36382015-11-05 13:35:03 +0100679 int to_forward, left;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200680 unsigned int tmp_out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100681
682#if defined(USE_SLZ) || defined(USE_ZLIB)
683 int ret;
684
685 /* flush data here */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100686 if (end)
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200687 ret = st->comp_algo->finish(st->comp_ctx, out); /* end of data */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100688 else
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200689 ret = st->comp_algo->flush(st->comp_ctx, out); /* end of buffer */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100690
691 if (ret < 0)
692 return -1; /* flush failed */
693
694#endif /* USE_ZLIB */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200695 if (b_data(out) == 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100696 /* No data were appended, let's drop the output buffer and
697 * keep the input buffer unchanged.
698 */
699 return 0;
700 }
701
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200702 /* OK so at this stage, we have an output buffer <out> looking like this :
Christopher Faulet3d97c902015-12-09 14:59:38 +0100703 *
704 * <-- o --> <------ i ----->
705 * +---------+---+------------+-----------+
706 * | out | c | comp_in | empty |
707 * +---------+---+------------+-----------+
708 * data p size
709 *
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200710 * <out> is the room reserved to copy the channel output. It starts at
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200711 * out->area and has not yet been filled. <c> is the room reserved to
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200712 * write the chunk size (10 bytes). <comp_in> is the compressed
713 * equivalent of the data part of ib->len. <empty> is the amount of
714 * empty bytes at the end of the buffer, into which we may have to
715 * copy the remaining bytes from ib->len after the data
716 * (chunk size, trailers, ...).
Christopher Faulet3d97c902015-12-09 14:59:38 +0100717 */
718
Joseph Herlant942eea32018-11-15 13:57:22 -0800719 /* Write real size at the beginning of the chunk, no need of wrapping.
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200720 * We write the chunk using a dynamic length and adjust out->p and out->i
Christopher Faulet3d97c902015-12-09 14:59:38 +0100721 * accordingly afterwards. That will move <out> away from <data>.
722 */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200723 left = http_emit_chunk_size(b_head(out), b_data(out));
724 b_add(out, left);
725 out->head -= *buf_out + (left);
726 /* Copy previous data from chn into out */
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200727 if (co_data(chn) > 0) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200728 left = b_contig_data(&chn->buf, 0);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200729 if (left > *buf_out)
730 left = *buf_out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100731
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200732 memcpy(b_head(out), co_head(chn), left);
733 b_add(out, left);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200734 if (co_data(chn) - left) {/* second part of the buffer */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200735 memcpy(b_head(out) + left, b_orig(&chn->buf), co_data(chn) - left);
736 b_add(out, co_data(chn) - left);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200737 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100738 }
739
740 /* chunked encoding requires CRLF after data */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200741 tail = b_tail(out);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100742 *tail++ = '\r';
743 *tail++ = '\n';
744
Christopher Faulet2fb28802015-12-01 10:40:57 +0100745 /* At the end of data, we must write the empty chunk 0<CRLF>,
746 * and terminate the trailers section with a last <CRLF>. If
747 * we're forwarding a chunked-encoded response, we'll have a
748 * trailers section after the empty chunk which needs to be
749 * forwarded and which will provide the last CRLF. Otherwise
750 * we write it ourselves.
751 */
752 if (end) {
753 struct http_msg *msg = &s->txn->rsp;
754
755 memcpy(tail, "0\r\n", 3);
756 tail += 3;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100757 if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100758 memcpy(tail, "\r\n", 2);
759 tail += 2;
760 }
761 }
762
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200763 b_add(out, tail - b_tail(out));
764 to_forward = b_data(out) - *buf_out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100765
766 /* update input rate */
Christopher Faulet92d36382015-11-05 13:35:03 +0100767 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100768 update_freq_ctr(&global.comp_bps_in, st->consumed);
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200769 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_in, st->consumed);
770 HA_ATOMIC_ADD(&s->be->be_counters.comp_in, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100771 } else {
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200772 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_byp, st->consumed);
773 HA_ATOMIC_ADD(&s->be->be_counters.comp_byp, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100774 }
775
776 /* copy the remaining data in the tmp buffer. */
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200777 c_adv(chn, st->consumed);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200778 if (b_data(&chn->buf) - co_data(chn) > 0) {
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200779 left = ci_contig_data(chn);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200780 memcpy(b_tail(out), ci_head(chn), left);
781 b_add(out, left);
782 if (b_data(&chn->buf) - (co_data(chn) + left)) {
783 memcpy(b_tail(out), b_orig(&chn->buf), b_data(&chn->buf) - left);
784 b_add(out, b_data(&chn->buf) - left);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100785 }
786 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100787 /* swap the buffers */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200788 tmp_buf = chn->buf;
789 chn->buf = *out;
790 *out = tmp_buf;
791
Olivier Houchard08afac02018-06-22 19:26:39 +0200792 tmp_out = chn->output;
793 chn->output = *buf_out;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200794 *buf_out = tmp_out;
795
Christopher Faulet3d97c902015-12-09 14:59:38 +0100796
Christopher Faulet92d36382015-11-05 13:35:03 +0100797
798 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100799 update_freq_ctr(&global.comp_bps_out, to_forward);
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200800 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_out, to_forward);
801 HA_ATOMIC_ADD(&s->be->be_counters.comp_out, to_forward);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100802 }
803
Christopher Faulet3d97c902015-12-09 14:59:38 +0100804 return to_forward;
805}
806
807
808/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100809struct flt_ops comp_ops = {
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +0200810 .init_per_thread = comp_flt_init_per_thread,
811 .deinit_per_thread = comp_flt_deinit_per_thread,
Christopher Faulet92d36382015-11-05 13:35:03 +0100812
813 .channel_start_analyze = comp_start_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100814 .channel_end_analyze = comp_end_analyze,
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200815 .channel_post_analyze = comp_http_post_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100816
Christopher Faulet1339d742016-05-11 16:48:33 +0200817 .http_headers = comp_http_headers,
Christopher Faulet309c6412015-12-02 09:57:32 +0100818 .http_data = comp_http_data,
819 .http_chunk_trailers = comp_http_chunk_trailers,
820 .http_forward_data = comp_http_forward_data,
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200821 .http_end = comp_http_end,
Christopher Faulet92d36382015-11-05 13:35:03 +0100822};
823
Christopher Faulet3d97c902015-12-09 14:59:38 +0100824static int
825parse_compression_options(char **args, int section, struct proxy *proxy,
826 struct proxy *defpx, const char *file, int line,
827 char **err)
828{
Christopher Faulet92d36382015-11-05 13:35:03 +0100829 struct comp *comp;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100830
831 if (proxy->comp == NULL) {
Vincent Bernat02779b62016-04-03 13:48:43 +0200832 comp = calloc(1, sizeof(*comp));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100833 proxy->comp = comp;
834 }
835 else
836 comp = proxy->comp;
837
838 if (!strcmp(args[1], "algo")) {
839 struct comp_ctx *ctx;
840 int cur_arg = 2;
841
842 if (!*args[cur_arg]) {
843 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>\n",
844 file, line, args[0]);
845 return -1;
846 }
847 while (*(args[cur_arg])) {
848 if (comp_append_algo(comp, args[cur_arg]) < 0) {
849 memprintf(err, "'%s' : '%s' is not a supported algorithm.\n",
850 args[0], args[cur_arg]);
851 return -1;
852 }
853 if (proxy->comp->algos->init(&ctx, 9) == 0)
854 proxy->comp->algos->end(&ctx);
855 else {
856 memprintf(err, "'%s' : Can't init '%s' algorithm.\n",
857 args[0], args[cur_arg]);
858 return -1;
859 }
860 cur_arg++;
861 continue;
862 }
863 }
864 else if (!strcmp(args[1], "offload"))
865 comp->offload = 1;
866 else if (!strcmp(args[1], "type")) {
867 int cur_arg = 2;
868
869 if (!*args[cur_arg]) {
870 memprintf(err, "'%s' expects <type>\n", args[0]);
871 return -1;
872 }
873 while (*(args[cur_arg])) {
874 comp_append_type(comp, args[cur_arg]);
875 cur_arg++;
876 continue;
877 }
878 }
879 else {
880 memprintf(err, "'%s' expects 'algo', 'type' or 'offload'\n",
881 args[0]);
882 return -1;
883 }
884
885 return 0;
886}
887
Christopher Faulet92d36382015-11-05 13:35:03 +0100888static int
889parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
Thierry Fournier3610c392016-04-13 18:27:51 +0200890 struct flt_conf *fconf, char **err, void *private)
Christopher Faulet92d36382015-11-05 13:35:03 +0100891{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100892 struct flt_conf *fc, *back;
Christopher Faulet92d36382015-11-05 13:35:03 +0100893
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100894 list_for_each_entry_safe(fc, back, &px->filter_configs, list) {
895 if (fc->id == http_comp_flt_id) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100896 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
897 return -1;
898 }
899 }
900
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100901 fconf->id = http_comp_flt_id;
902 fconf->conf = NULL;
903 fconf->ops = &comp_ops;
Christopher Faulet92d36382015-11-05 13:35:03 +0100904 (*cur_arg)++;
905
906 return 0;
907}
908
909
910int
911check_legacy_http_comp_flt(struct proxy *proxy)
912{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100913 struct flt_conf *fconf;
Christopher Faulet92d36382015-11-05 13:35:03 +0100914 int err = 0;
915
916 if (proxy->comp == NULL)
917 goto end;
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100918 if (!LIST_ISEMPTY(&proxy->filter_configs)) {
919 list_for_each_entry(fconf, &proxy->filter_configs, list) {
920 if (fconf->id == http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100921 goto end;
922 }
Christopher Faulet767a84b2017-11-24 16:50:31 +0100923 ha_alert("config: %s '%s': require an explicit filter declaration to use HTTP compression\n",
924 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100925 err++;
926 goto end;
927 }
928
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100929 fconf = calloc(1, sizeof(*fconf));
930 if (!fconf) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100931 ha_alert("config: %s '%s': out of memory\n",
932 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100933 err++;
934 goto end;
935 }
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100936 fconf->id = http_comp_flt_id;
937 fconf->conf = NULL;
938 fconf->ops = &comp_ops;
939 LIST_ADDQ(&proxy->filter_configs, &fconf->list);
Christopher Faulet92d36382015-11-05 13:35:03 +0100940
941 end:
942 return err;
943}
944
945/*
946 * boolean, returns true if compression is used (either gzip or deflate) in the
947 * response.
948 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100949static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100950smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
951 void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100952{
Willy Tarreaube508f12016-03-10 11:47:01 +0100953 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100954
Christopher Faulet3d97c902015-12-09 14:59:38 +0100955 smp->data.type = SMP_T_BOOL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100956 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100957 return 1;
958}
959
Christopher Faulet92d36382015-11-05 13:35:03 +0100960/*
961 * string, returns algo
962 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100963static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100964smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
965 const char *kw, void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100966{
Willy Tarreaube508f12016-03-10 11:47:01 +0100967 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100968 struct filter *filter;
969 struct comp_state *st;
970
Christopher Faulet03d85532017-09-15 10:14:43 +0200971 if (!txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100972 return 0;
973
Christopher Fauletfcf035c2015-12-03 11:48:03 +0100974 list_for_each_entry(filter, &strm_flt(smp->strm)->filters, list) {
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100975 if (FLT_ID(filter) != http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100976 continue;
977
978 if (!(st = filter->ctx))
979 break;
980
981 smp->data.type = SMP_T_STR;
982 smp->flags = SMP_F_CONST;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200983 smp->data.u.str.area = st->comp_algo->cfg_name;
984 smp->data.u.str.data = st->comp_algo->cfg_name_len;
Christopher Faulet92d36382015-11-05 13:35:03 +0100985 return 1;
986 }
987 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100988}
989
990/* Declare the config parser for "compression" keyword */
991static struct cfg_kw_list cfg_kws = {ILH, {
992 { CFG_LISTEN, "compression", parse_compression_options },
993 { 0, NULL, NULL },
994 }
995};
996
Willy Tarreau0108d902018-11-25 19:14:37 +0100997INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
998
Christopher Faulet92d36382015-11-05 13:35:03 +0100999/* Declare the filter parser for "compression" keyword */
1000static struct flt_kw_list filter_kws = { "COMP", { }, {
Thierry Fournier3610c392016-04-13 18:27:51 +02001001 { "compression", parse_http_comp_flt, NULL },
1002 { NULL, NULL, NULL },
Christopher Faulet92d36382015-11-05 13:35:03 +01001003 }
1004};
1005
Willy Tarreau0108d902018-11-25 19:14:37 +01001006INITCALL1(STG_REGISTER, flt_register_keywords, &filter_kws);
1007
Christopher Faulet3d97c902015-12-09 14:59:38 +01001008/* Note: must not be declared <const> as its list will be overwritten */
1009static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet92d36382015-11-05 13:35:03 +01001010 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
1011 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
1012 { /* END */ },
1013 }
1014};
Christopher Faulet3d97c902015-12-09 14:59:38 +01001015
Willy Tarreau0108d902018-11-25 19:14:37 +01001016INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);
1017
Christopher Faulet3d97c902015-12-09 14:59:38 +01001018__attribute__((constructor))
Christopher Faulet92d36382015-11-05 13:35:03 +01001019static void
1020__flt_http_comp_init(void)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001021{
Willy Tarreaubafbe012017-11-24 17:34:44 +01001022 pool_head_comp_state = create_pool("comp_state", sizeof(struct comp_state), MEM_F_SHARED);
Christopher Faulet3d97c902015-12-09 14:59:38 +01001023}