blob: bf8f81ae5641d49eb465756dbcaaaf030948b05a [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
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020035
36/* Pools used to allocate comp_state structs */
Willy Tarreaubafbe012017-11-24 17:34:44 +010037static struct pool_head *pool_head_comp_state = NULL;
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020038
Willy Tarreauc9fa0482018-07-10 17:43:27 +020039static THREAD_LOCAL struct buffer tmpbuf;
40static THREAD_LOCAL struct buffer zbuf;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020041static THREAD_LOCAL unsigned int buf_output;
Christopher Faulet92d36382015-11-05 13:35:03 +010042
Christopher Faulet92d36382015-11-05 13:35:03 +010043struct comp_state {
44 struct comp_ctx *comp_ctx; /* compression context */
45 struct comp_algo *comp_algo; /* compression algorithm if not NULL */
Christopher Fauletb77c5c22015-12-07 16:48:42 +010046 int hdrs_len;
47 int tlrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +010048 int consumed;
49 int initialized;
Christopher Fauletb77c5c22015-12-07 16:48:42 +010050 int finished;
Christopher Faulet92d36382015-11-05 13:35:03 +010051};
52
Christopher Faulet92d36382015-11-05 13:35:03 +010053static int select_compression_request_header(struct comp_state *st,
54 struct stream *s,
55 struct http_msg *msg);
56static int select_compression_response_header(struct comp_state *st,
57 struct stream *s,
58 struct http_msg *msg);
59
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020060static int http_compression_buffer_init(struct channel *inc, struct buffer *out, unsigned int *out_len);
Christopher Faulet92d36382015-11-05 13:35:03 +010061static int http_compression_buffer_add_data(struct comp_state *st,
62 struct buffer *in,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020063 int in_out,
Christopher Faulet92d36382015-11-05 13:35:03 +010064 struct buffer *out, int sz);
65static int http_compression_buffer_end(struct comp_state *st, struct stream *s,
Willy Tarreauc9fa0482018-07-10 17:43:27 +020066 struct channel *chn, struct buffer *out,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +020067 unsigned int *out_len, int end);
Christopher Faulet92d36382015-11-05 13:35:03 +010068
69/***********************************************************************/
70static int
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020071comp_flt_init_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010072{
Willy Tarreauc9fa0482018-07-10 17:43:27 +020073 if (!tmpbuf.size && b_alloc(&tmpbuf) == NULL)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010074 return -1;
Willy Tarreauc9fa0482018-07-10 17:43:27 +020075 if (!zbuf.size && b_alloc(&zbuf) == NULL)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010076 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +010077 return 0;
78}
79
80static void
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020081comp_flt_deinit_per_thread(struct proxy *px, struct flt_conf *fconf)
Christopher Faulet92d36382015-11-05 13:35:03 +010082{
Willy Tarreauc9fa0482018-07-10 17:43:27 +020083 if (tmpbuf.size)
Christopher Faulet92d36382015-11-05 13:35:03 +010084 b_free(&tmpbuf);
Willy Tarreauc9fa0482018-07-10 17:43:27 +020085 if (zbuf.size)
Christopher Fauletb77c5c22015-12-07 16:48:42 +010086 b_free(&zbuf);
Christopher Faulet92d36382015-11-05 13:35:03 +010087}
88
89static int
90comp_start_analyze(struct stream *s, struct filter *filter, struct channel *chn)
91{
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +020092
Christopher Faulet92d36382015-11-05 13:35:03 +010093 if (filter->ctx == NULL) {
94 struct comp_state *st;
95
Willy Tarreaubafbe012017-11-24 17:34:44 +010096 st = pool_alloc_dirty(pool_head_comp_state);
Christopher Fauleta03d4ad2017-06-26 16:53:33 +020097 if (st == NULL)
Christopher Faulet92d36382015-11-05 13:35:03 +010098 return -1;
99
Christopher Faulet2fb28802015-12-01 10:40:57 +0100100 st->comp_algo = NULL;
101 st->comp_ctx = NULL;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100102 st->hdrs_len = 0;
103 st->tlrs_len = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100104 st->consumed = 0;
105 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100106 st->finished = 0;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100107 filter->ctx = st;
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200108
109 /* Register post-analyzer on AN_RES_WAIT_HTTP because we need to
110 * analyze response headers before http-response rules execution
111 * to be sure we can use res.comp and res.comp_algo sample
112 * fetches */
113 filter->post_analyzers |= AN_RES_WAIT_HTTP;
Christopher Faulet92d36382015-11-05 13:35:03 +0100114 }
115 return 1;
116}
117
118static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100119comp_end_analyze(struct stream *s, struct filter *filter, struct channel *chn)
120{
121 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100122
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200123 if (!st)
Christopher Faulet92d36382015-11-05 13:35:03 +0100124 goto end;
125
Christopher Faulet92d36382015-11-05 13:35:03 +0100126 /* release any possible compression context */
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200127 if (st->comp_algo)
128 st->comp_algo->end(&st->comp_ctx);
Willy Tarreaubafbe012017-11-24 17:34:44 +0100129 pool_free(pool_head_comp_state, st);
Christopher Faulet92d36382015-11-05 13:35:03 +0100130 filter->ctx = NULL;
131 end:
132 return 1;
133}
134
135static int
Christopher Faulet1339d742016-05-11 16:48:33 +0200136comp_http_headers(struct stream *s, struct filter *filter, struct http_msg *msg)
137{
138 struct comp_state *st = filter->ctx;
139
140 if (!strm_fe(s)->comp && !s->be->comp)
141 goto end;
142
143 if (!(msg->chn->flags & CF_ISRESP))
144 select_compression_request_header(st, s, msg);
145 else {
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200146 /* Response headers have already been checked in
147 * comp_http_post_analyze callback. */
Christopher Faulet1339d742016-05-11 16:48:33 +0200148 if (st->comp_algo) {
149 register_data_filter(s, msg->chn, filter);
150 st->hdrs_len = s->txn->rsp.sov;
151 }
152 }
153
154 end:
155 return 1;
156}
157
158static int
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200159comp_http_post_analyze(struct stream *s, struct filter *filter,
160 struct channel *chn, unsigned an_bit)
161{
162 struct http_txn *txn = s->txn;
163 struct http_msg *msg = &txn->rsp;
164 struct comp_state *st = filter->ctx;
165
166 if (an_bit != AN_RES_WAIT_HTTP)
167 goto end;
168
169 if (!strm_fe(s)->comp && !s->be->comp)
170 goto end;
171
172 select_compression_response_header(st, s, msg);
173
174 end:
175 return 1;
176}
177
178static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100179comp_http_data(struct stream *s, struct filter *filter, struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100180{
181 struct comp_state *st = filter->ctx;
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200182 struct channel *chn = msg->chn;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100183 unsigned int *nxt = &flt_rsp_nxt(filter);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100184 unsigned int len;
Christopher Faulet92d36382015-11-05 13:35:03 +0100185 int ret;
186
Olivier Houchard0b662842018-06-29 18:16:31 +0200187 len = MIN(msg->chunk_len + msg->next, ci_data(chn)) - *nxt;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100188 if (!len)
189 return len;
190
191 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100192 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100193
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200194 b_reset(&tmpbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200195 c_adv(chn, fwd);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200196 ret = http_compression_buffer_init(chn, &zbuf, &buf_output);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200197 c_rew(chn, fwd);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100198 if (ret < 0) {
199 msg->chn->flags |= CF_WAKE_WRITE;
200 return 0;
201 }
202 }
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100203
204 if (msg->flags & HTTP_MSGF_TE_CHNK) {
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200205 int block;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100206
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200207 len = MIN(b_room(&tmpbuf), len);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200208
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200209 c_adv(chn, *nxt);
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200210 block = ci_contig_data(chn);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200211 memcpy(b_tail(&tmpbuf), ci_head(chn), block);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200212 if (len > block)
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200213 memcpy(b_tail(&tmpbuf)+block, b_orig(&chn->buf), len-block);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200214 c_rew(chn, *nxt);
Christopher Faulet06ecf3a2016-09-22 15:31:43 +0200215
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200216 b_add(&tmpbuf, len);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100217 ret = len;
218 }
219 else {
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200220 c_adv(chn, *nxt);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200221 ret = http_compression_buffer_add_data(st, &chn->buf, co_data(chn), &zbuf, len);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200222 c_rew(chn, *nxt);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100223 if (ret < 0)
224 return ret;
225 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100226
Christopher Faulet2fb28802015-12-01 10:40:57 +0100227 st->initialized = 1;
228 msg->next += ret;
229 msg->chunk_len -= ret;
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100230 *nxt = msg->next;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100231 return 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100232}
233
234static int
Christopher Faulet2fb28802015-12-01 10:40:57 +0100235comp_http_chunk_trailers(struct stream *s, struct filter *filter,
236 struct http_msg *msg)
Christopher Faulet92d36382015-11-05 13:35:03 +0100237{
238 struct comp_state *st = filter->ctx;
Christopher Faulet92d36382015-11-05 13:35:03 +0100239
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100240 if (!st->initialized) {
241 if (!st->finished) {
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200242 struct channel *chn = msg->chn;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100243 unsigned int fwd = flt_rsp_fwd(filter) + st->hdrs_len;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100244
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200245 b_reset(&tmpbuf);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200246 c_adv(chn, fwd);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200247 http_compression_buffer_init(chn, &zbuf, &buf_output);
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200248 c_rew(chn, fwd);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100249 st->initialized = 1;
250 }
251 }
252 st->tlrs_len = msg->sol;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100253 return 1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100254}
255
Christopher Faulet2fb28802015-12-01 10:40:57 +0100256
Christopher Faulet92d36382015-11-05 13:35:03 +0100257static int
258comp_http_forward_data(struct stream *s, struct filter *filter,
259 struct http_msg *msg, unsigned int len)
260{
261 struct comp_state *st = filter->ctx;
Christopher Faulet2fb28802015-12-01 10:40:57 +0100262 int ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100263
Christopher Faulet2fb28802015-12-01 10:40:57 +0100264 /* To work, previous filters MUST forward all data */
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100265 if (flt_rsp_fwd(filter) + len != flt_rsp_nxt(filter)) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100266 ha_warning("HTTP compression failed: unexpected behavior of previous filters\n");
Christopher Faulet2fb28802015-12-01 10:40:57 +0100267 return -1;
Christopher Faulet92d36382015-11-05 13:35:03 +0100268 }
269
Christopher Faulet2fb28802015-12-01 10:40:57 +0100270 if (!st->initialized) {
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100271 if (!len) {
272 /* Nothing to foward */
273 ret = len;
274 }
275 else if (st->hdrs_len > len) {
276 /* Forward part of headers */
277 ret = len;
278 st->hdrs_len -= len;
279 }
280 else if (st->hdrs_len > 0) {
281 /* Forward remaining headers */
282 ret = st->hdrs_len;
283 st->hdrs_len = 0;
284 }
285 else if (msg->msg_state < HTTP_MSG_TRAILERS) {
286 /* Do not forward anything for now. This only happens
287 * with chunk-encoded responses. Waiting data are part
288 * of the chunk envelope (the chunk size or the chunk
289 * CRLF). These data will be skipped during the
290 * compression. */
291 ret = 0;
292 }
293 else {
294 /* Forward trailers data */
295 ret = len;
296 }
Christopher Faulet2fb28802015-12-01 10:40:57 +0100297 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100298 }
299
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100300 if (msg->flags & HTTP_MSGF_TE_CHNK) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200301 ret = http_compression_buffer_add_data(st, &tmpbuf, 0,
302 &zbuf, b_data(&tmpbuf));
303 if (ret != b_data(&tmpbuf)) {
Willy Tarreau506a29a2018-07-18 10:07:58 +0200304 ha_warning("HTTP compression failed: Must consume %u bytes but only %d bytes consumed\n",
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200305 (unsigned int)b_data(&tmpbuf), ret);
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100306 return -1;
307 }
308 }
309
310 st->consumed = len - st->hdrs_len - st->tlrs_len;
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200311 c_adv(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200312 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 +0200313 c_rew(msg->chn, flt_rsp_fwd(filter) + st->hdrs_len);
Christopher Faulet2fb28802015-12-01 10:40:57 +0100314 if (ret < 0)
315 return ret;
Christopher Faulet92d36382015-11-05 13:35:03 +0100316
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100317 flt_change_forward_size(filter, msg->chn, ret - st->consumed);
318 msg->next += (ret - st->consumed);
319 ret += st->hdrs_len + st->tlrs_len;
320
Christopher Faulet2fb28802015-12-01 10:40:57 +0100321 st->initialized = 0;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100322 st->finished = (msg->msg_state >= HTTP_MSG_TRAILERS);
323 st->hdrs_len = 0;
324 st->tlrs_len = 0;
Christopher Faulet92d36382015-11-05 13:35:03 +0100325 return ret;
326}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100327
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200328static int
329comp_http_end(struct stream *s, struct filter *filter,
330 struct http_msg *msg)
331{
332 struct comp_state *st = filter->ctx;
333
334 if (!(msg->chn->flags & CF_ISRESP) || !st || !st->comp_algo)
335 goto end;
336
337 if (strm_fe(s)->mode == PR_MODE_HTTP)
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200338 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.p.http.comp_rsp, 1);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200339 if ((s->flags & SF_BE_ASSIGNED) && (s->be->mode == PR_MODE_HTTP))
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200340 HA_ATOMIC_ADD(&s->be->be_counters.p.http.comp_rsp, 1);
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200341 end:
342 return 1;
343}
Christopher Faulet3d97c902015-12-09 14:59:38 +0100344/***********************************************************************/
345/*
346 * Selects a compression algorithm depending on the client request.
347 */
348int
Christopher Faulet92d36382015-11-05 13:35:03 +0100349select_compression_request_header(struct comp_state *st, struct stream *s,
350 struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100351{
352 struct http_txn *txn = s->txn;
Olivier Houchard0b662842018-06-29 18:16:31 +0200353 struct channel *req = msg->chn;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100354 struct hdr_ctx ctx;
355 struct comp_algo *comp_algo = NULL;
356 struct comp_algo *comp_algo_back = NULL;
357
358 /* Disable compression for older user agents announcing themselves as "Mozilla/4"
359 * unless they are known good (MSIE 6 with XP SP2, or MSIE 7 and later).
360 * See http://zoompf.com/2012/02/lose-the-wait-http-compression for more details.
361 */
362 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200363 if (http_find_header2("User-Agent", 10, ci_head(req), &txn->hdr_idx, &ctx) &&
Christopher Faulet3d97c902015-12-09 14:59:38 +0100364 ctx.vlen >= 9 &&
365 memcmp(ctx.line + ctx.val, "Mozilla/4", 9) == 0 &&
366 (ctx.vlen < 31 ||
367 memcmp(ctx.line + ctx.val + 25, "MSIE ", 5) != 0 ||
368 ctx.line[ctx.val + 30] < '6' ||
369 (ctx.line[ctx.val + 30] == '6' &&
370 (ctx.vlen < 54 || memcmp(ctx.line + 51, "SV1", 3) != 0)))) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100371 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100372 return 0;
373 }
374
375 /* search for the algo in the backend in priority or the frontend */
Christopher Faulet92d36382015-11-05 13:35:03 +0100376 if ((s->be->comp && (comp_algo_back = s->be->comp->algos)) ||
377 (strm_fe(s)->comp && (comp_algo_back = strm_fe(s)->comp->algos))) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100378 int best_q = 0;
379
380 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200381 while (http_find_header2("Accept-Encoding", 15, ci_head(req), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100382 const char *qval;
383 int q;
384 int toklen;
385
386 /* try to isolate the token from the optional q-value */
387 toklen = 0;
Willy Tarreau2235b262016-11-05 15:50:20 +0100388 while (toklen < ctx.vlen && HTTP_IS_TOKEN(*(ctx.line + ctx.val + toklen)))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100389 toklen++;
390
391 qval = ctx.line + ctx.val + toklen;
392 while (1) {
Willy Tarreau2235b262016-11-05 15:50:20 +0100393 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100394 qval++;
395
396 if (qval >= ctx.line + ctx.val + ctx.vlen || *qval != ';') {
397 qval = NULL;
398 break;
399 }
400 qval++;
401
Willy Tarreau2235b262016-11-05 15:50:20 +0100402 while (qval < ctx.line + ctx.val + ctx.vlen && HTTP_IS_LWS(*qval))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100403 qval++;
404
405 if (qval >= ctx.line + ctx.val + ctx.vlen) {
406 qval = NULL;
407 break;
408 }
409 if (strncmp(qval, "q=", MIN(ctx.line + ctx.val + ctx.vlen - qval, 2)) == 0)
410 break;
411
412 while (qval < ctx.line + ctx.val + ctx.vlen && *qval != ';')
413 qval++;
414 }
415
416 /* here we have qval pointing to the first "q=" attribute or NULL if not found */
417 q = qval ? parse_qvalue(qval + 2, NULL) : 1000;
418
419 if (q <= best_q)
420 continue;
421
422 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
423 if (*(ctx.line + ctx.val) == '*' ||
424 word_match(ctx.line + ctx.val, toklen, comp_algo->ua_name, comp_algo->ua_name_len)) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100425 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100426 best_q = q;
427 break;
428 }
429 }
430 }
431 }
432
433 /* remove all occurrences of the header when "compression offload" is set */
Christopher Faulet92d36382015-11-05 13:35:03 +0100434 if (st->comp_algo) {
435 if ((s->be->comp && s->be->comp->offload) ||
436 (strm_fe(s)->comp && strm_fe(s)->comp->offload)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100437 http_remove_header2(msg, &txn->hdr_idx, &ctx);
438 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200439 while (http_find_header2("Accept-Encoding", 15, ci_head(req), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100440 http_remove_header2(msg, &txn->hdr_idx, &ctx);
441 }
442 }
443 return 1;
444 }
445
446 /* identity is implicit does not require headers */
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 for (comp_algo = comp_algo_back; comp_algo; comp_algo = comp_algo->next) {
450 if (comp_algo->cfg_name_len == 8 && memcmp(comp_algo->cfg_name, "identity", 8) == 0) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100451 st->comp_algo = comp_algo;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100452 return 1;
453 }
454 }
455 }
456
Christopher Faulet92d36382015-11-05 13:35:03 +0100457 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100458 return 0;
459}
460
Christopher Faulet92d36382015-11-05 13:35:03 +0100461
Christopher Faulet3d97c902015-12-09 14:59:38 +0100462/*
463 * Selects a comression algorithm depending of the server response.
464 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100465static int
466select_compression_response_header(struct comp_state *st, struct stream *s, struct http_msg *msg)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100467{
468 struct http_txn *txn = s->txn;
Olivier Houchard0b662842018-06-29 18:16:31 +0200469 struct channel *c = msg->chn;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100470 struct hdr_ctx ctx;
471 struct comp_type *comp_type;
472
473 /* no common compression algorithm was found in request header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100474 if (st->comp_algo == NULL)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100475 goto fail;
476
477 /* HTTP < 1.1 should not be compressed */
478 if (!(msg->flags & HTTP_MSGF_VER_11) || !(txn->req.flags & HTTP_MSGF_VER_11))
479 goto fail;
480
Christopher Faulet92d36382015-11-05 13:35:03 +0100481 if (txn->meth == HTTP_METH_HEAD)
482 goto fail;
483
Christopher Faulet3d97c902015-12-09 14:59:38 +0100484 /* compress 200,201,202,203 responses only */
485 if ((txn->status != 200) &&
486 (txn->status != 201) &&
487 (txn->status != 202) &&
488 (txn->status != 203))
489 goto fail;
490
491
492 /* Content-Length is null */
493 if (!(msg->flags & HTTP_MSGF_TE_CHNK) && msg->body_len == 0)
494 goto fail;
495
496 /* content is already compressed */
497 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200498 if (http_find_header2("Content-Encoding", 16, ci_head(c), &txn->hdr_idx, &ctx))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100499 goto fail;
500
501 /* no compression when Cache-Control: no-transform is present in the message */
502 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200503 while (http_find_header2("Cache-Control", 13, ci_head(c), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100504 if (word_match(ctx.line + ctx.val, ctx.vlen, "no-transform", 12))
505 goto fail;
506 }
507
508 comp_type = NULL;
509
510 /* we don't want to compress multipart content-types, nor content-types that are
511 * not listed in the "compression type" directive if any. If no content-type was
512 * found but configuration requires one, we don't compress either. Backend has
513 * the priority.
514 */
515 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200516 if (http_find_header2("Content-Type", 12, ci_head(c), &txn->hdr_idx, &ctx)) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100517 if (ctx.vlen >= 9 && strncasecmp("multipart", ctx.line+ctx.val, 9) == 0)
518 goto fail;
519
520 if ((s->be->comp && (comp_type = s->be->comp->types)) ||
521 (strm_fe(s)->comp && (comp_type = strm_fe(s)->comp->types))) {
522 for (; comp_type; comp_type = comp_type->next) {
523 if (ctx.vlen >= comp_type->name_len &&
524 strncasecmp(ctx.line+ctx.val, comp_type->name, comp_type->name_len) == 0)
525 /* this Content-Type should be compressed */
526 break;
527 }
528 /* this Content-Type should not be compressed */
529 if (comp_type == NULL)
530 goto fail;
531 }
532 }
533 else { /* no content-type header */
Christopher Faulet92d36382015-11-05 13:35:03 +0100534 if ((s->be->comp && s->be->comp->types) ||
535 (strm_fe(s)->comp && strm_fe(s)->comp->types))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100536 goto fail; /* a content-type was required */
537 }
538
539 /* limit compression rate */
540 if (global.comp_rate_lim > 0)
541 if (read_freq_ctr(&global.comp_bps_in) > global.comp_rate_lim)
542 goto fail;
543
544 /* limit cpu usage */
545 if (idle_pct < compress_min_idle)
546 goto fail;
547
548 /* initialize compression */
Christopher Faulet92d36382015-11-05 13:35:03 +0100549 if (st->comp_algo->init(&st->comp_ctx, global.tune.comp_maxlevel) < 0)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100550 goto fail;
551
Christopher Faulet3d97c902015-12-09 14:59:38 +0100552 /* remove Content-Length header */
553 ctx.idx = 0;
Olivier Houchard0b662842018-06-29 18:16:31 +0200554 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 +0100555 http_remove_header2(msg, &txn->hdr_idx, &ctx);
556
557 /* add Transfer-Encoding header */
558 if (!(msg->flags & HTTP_MSGF_TE_CHNK))
559 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, "Transfer-Encoding: chunked", 26);
560
561 /*
562 * Add Content-Encoding header when it's not identity encoding.
563 * RFC 2616 : Identity encoding: This content-coding is used only in the
564 * Accept-Encoding header, and SHOULD NOT be used in the Content-Encoding
565 * header.
566 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100567 if (st->comp_algo->cfg_name_len != 8 || memcmp(st->comp_algo->cfg_name, "identity", 8) != 0) {
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200568 trash.data = 18;
569 memcpy(trash.area, "Content-Encoding: ", trash.data);
570 memcpy(trash.area + trash.data, st->comp_algo->ua_name,
571 st->comp_algo->ua_name_len);
572 trash.data += st->comp_algo->ua_name_len;
573 trash.area[trash.data] = '\0';
574 http_header_add_tail2(&txn->rsp, &txn->hdr_idx, trash.area,
575 trash.data);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100576 }
Christopher Faulet92d36382015-11-05 13:35:03 +0100577 msg->flags |= HTTP_MSGF_COMPRESSING;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100578 return 1;
579
580fail:
Christopher Faulet92d36382015-11-05 13:35:03 +0100581 st->comp_algo = NULL;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100582 return 0;
583}
584
585/***********************************************************************/
586/* emit the chunksize followed by a CRLF on the output and return the number of
587 * bytes written. It goes backwards and starts with the byte before <end>. It
588 * returns the number of bytes written which will not exceed 10 (8 digits, CR,
589 * and LF). The caller is responsible for ensuring there is enough room left in
590 * the output buffer for the string.
591 */
592static int
593http_emit_chunk_size(char *end, unsigned int chksz)
594{
595 char *beg = end;
596
597 *--beg = '\n';
598 *--beg = '\r';
599 do {
600 *--beg = hextab[chksz & 0xF];
601 } while (chksz >>= 4);
602 return end - beg;
603}
604
605/*
606 * Init HTTP compression
607 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100608static int
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200609http_compression_buffer_init(struct channel *inc, struct buffer *out, unsigned int *out_len)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100610{
611 /* output stream requires at least 10 bytes for the gzip header, plus
612 * at least 8 bytes for the gzip trailer (crc+len), plus a possible
613 * plus at most 5 bytes per 32kB block and 2 bytes to close the stream.
614 */
Olivier Houchard0b662842018-06-29 18:16:31 +0200615 if (c_room(inc) < 20 + 5 * ((ci_data(inc) + 32767) >> 15))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100616 return -1;
617
618 /* prepare an empty output buffer in which we reserve enough room for
619 * copying the output bytes from <in>, plus 10 extra bytes to write
620 * the chunk size. We don't copy the bytes yet so that if we have to
621 * cancel the operation later, it's cheap.
622 */
623 b_reset(out);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200624 *out_len = co_data(inc);
625 out->head += *out_len + 10;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100626 return 0;
627}
628
629/*
630 * Add data to compress
631 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100632static int
633http_compression_buffer_add_data(struct comp_state *st, struct buffer *in,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200634 int in_out, struct buffer *out, int sz)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100635{
Christopher Faulet3d97c902015-12-09 14:59:38 +0100636 int consumed_data = 0;
637 int data_process_len;
638 int block1, block2;
639
Christopher Faulet92d36382015-11-05 13:35:03 +0100640 if (!sz)
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100641 goto end;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100642
Christopher Faulet92d36382015-11-05 13:35:03 +0100643 /* select the smallest size between the announced chunk size, the input
Christopher Faulet3d97c902015-12-09 14:59:38 +0100644 * data, and the available output buffer size. The compressors are
Christopher Faulet92d36382015-11-05 13:35:03 +0100645 * assumed to be able to process all the bytes we pass to them at
646 * once. */
Willy Tarreaueac52592018-06-15 13:59:36 +0200647 data_process_len = MIN(b_room(out), sz);
Christopher Faulet92d36382015-11-05 13:35:03 +0100648
Christopher Faulet3d97c902015-12-09 14:59:38 +0100649 block1 = data_process_len;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200650 if (block1 > b_contig_data(in, in_out))
651 block1 = b_contig_data(in, in_out);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100652 block2 = data_process_len - block1;
653
654 /* compressors return < 0 upon error or the amount of bytes read */
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200655 consumed_data = st->comp_algo->add_data(st->comp_ctx, b_head(in) + in_out, block1, out);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100656 if (consumed_data != block1 || !block2)
657 goto end;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200658 consumed_data = st->comp_algo->add_data(st->comp_ctx, b_peek(in, 0), block2, out);
Christopher Faulet3e7bc672015-12-07 13:39:08 +0100659 if (consumed_data < 0)
660 goto end;
661 consumed_data += block1;
662
663 end:
Christopher Faulet3d97c902015-12-09 14:59:38 +0100664 return consumed_data;
665}
666
667/*
668 * Flush data in process, and write the header and footer of the chunk. Upon
669 * success, in and out buffers are swapped to avoid a copy.
670 */
Christopher Faulet92d36382015-11-05 13:35:03 +0100671static int
672http_compression_buffer_end(struct comp_state *st, struct stream *s,
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200673 struct channel *chn, struct buffer *out,
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200674 unsigned int *buf_out, int end)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100675{
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200676 struct buffer tmp_buf;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100677 char *tail;
Christopher Faulet92d36382015-11-05 13:35:03 +0100678 int to_forward, left;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200679 unsigned int tmp_out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100680
681#if defined(USE_SLZ) || defined(USE_ZLIB)
682 int ret;
683
684 /* flush data here */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100685 if (end)
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200686 ret = st->comp_algo->finish(st->comp_ctx, out); /* end of data */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100687 else
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200688 ret = st->comp_algo->flush(st->comp_ctx, out); /* end of buffer */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100689
690 if (ret < 0)
691 return -1; /* flush failed */
692
693#endif /* USE_ZLIB */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200694 if (b_data(out) == 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100695 /* No data were appended, let's drop the output buffer and
696 * keep the input buffer unchanged.
697 */
698 return 0;
699 }
700
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200701 /* OK so at this stage, we have an output buffer <out> looking like this :
Christopher Faulet3d97c902015-12-09 14:59:38 +0100702 *
703 * <-- o --> <------ i ----->
704 * +---------+---+------------+-----------+
705 * | out | c | comp_in | empty |
706 * +---------+---+------------+-----------+
707 * data p size
708 *
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200709 * <out> is the room reserved to copy the channel output. It starts at
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200710 * out->area and has not yet been filled. <c> is the room reserved to
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200711 * write the chunk size (10 bytes). <comp_in> is the compressed
712 * equivalent of the data part of ib->len. <empty> is the amount of
713 * empty bytes at the end of the buffer, into which we may have to
714 * copy the remaining bytes from ib->len after the data
715 * (chunk size, trailers, ...).
Christopher Faulet3d97c902015-12-09 14:59:38 +0100716 */
717
718 /* Write real size at the begining of the chunk, no need of wrapping.
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200719 * We write the chunk using a dynamic length and adjust out->p and out->i
Christopher Faulet3d97c902015-12-09 14:59:38 +0100720 * accordingly afterwards. That will move <out> away from <data>.
721 */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200722 left = http_emit_chunk_size(b_head(out), b_data(out));
723 b_add(out, left);
724 out->head -= *buf_out + (left);
725 /* Copy previous data from chn into out */
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200726 if (co_data(chn) > 0) {
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200727 left = b_contig_data(&chn->buf, 0);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200728 if (left > *buf_out)
729 left = *buf_out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100730
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200731 memcpy(b_head(out), co_head(chn), left);
732 b_add(out, left);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200733 if (co_data(chn) - left) {/* second part of the buffer */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200734 memcpy(b_head(out) + left, b_orig(&chn->buf), co_data(chn) - left);
735 b_add(out, co_data(chn) - left);
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200736 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100737 }
738
739 /* chunked encoding requires CRLF after data */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200740 tail = b_tail(out);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100741 *tail++ = '\r';
742 *tail++ = '\n';
743
Christopher Faulet2fb28802015-12-01 10:40:57 +0100744 /* At the end of data, we must write the empty chunk 0<CRLF>,
745 * and terminate the trailers section with a last <CRLF>. If
746 * we're forwarding a chunked-encoded response, we'll have a
747 * trailers section after the empty chunk which needs to be
748 * forwarded and which will provide the last CRLF. Otherwise
749 * we write it ourselves.
750 */
751 if (end) {
752 struct http_msg *msg = &s->txn->rsp;
753
754 memcpy(tail, "0\r\n", 3);
755 tail += 3;
Christopher Fauletb77c5c22015-12-07 16:48:42 +0100756 if (!(msg->flags & HTTP_MSGF_TE_CHNK)) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100757 memcpy(tail, "\r\n", 2);
758 tail += 2;
759 }
760 }
761
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200762 b_add(out, tail - b_tail(out));
763 to_forward = b_data(out) - *buf_out;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100764
765 /* update input rate */
Christopher Faulet92d36382015-11-05 13:35:03 +0100766 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet2fb28802015-12-01 10:40:57 +0100767 update_freq_ctr(&global.comp_bps_in, st->consumed);
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200768 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_in, st->consumed);
769 HA_ATOMIC_ADD(&s->be->be_counters.comp_in, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100770 } else {
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200771 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_byp, st->consumed);
772 HA_ATOMIC_ADD(&s->be->be_counters.comp_byp, st->consumed);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100773 }
774
775 /* copy the remaining data in the tmp buffer. */
Willy Tarreaubcbd3932018-06-06 07:13:22 +0200776 c_adv(chn, st->consumed);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200777 if (b_data(&chn->buf) - co_data(chn) > 0) {
Willy Tarreau7194d3c2018-06-06 16:55:45 +0200778 left = ci_contig_data(chn);
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200779 memcpy(b_tail(out), ci_head(chn), left);
780 b_add(out, left);
781 if (b_data(&chn->buf) - (co_data(chn) + left)) {
782 memcpy(b_tail(out), b_orig(&chn->buf), b_data(&chn->buf) - left);
783 b_add(out, b_data(&chn->buf) - left);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100784 }
785 }
Christopher Faulet3d97c902015-12-09 14:59:38 +0100786 /* swap the buffers */
Willy Tarreauc9fa0482018-07-10 17:43:27 +0200787 tmp_buf = chn->buf;
788 chn->buf = *out;
789 *out = tmp_buf;
790
Olivier Houchard08afac02018-06-22 19:26:39 +0200791 tmp_out = chn->output;
792 chn->output = *buf_out;
Willy Tarreaud54a8ce2018-06-29 18:42:02 +0200793 *buf_out = tmp_out;
794
Christopher Faulet3d97c902015-12-09 14:59:38 +0100795
Christopher Faulet92d36382015-11-05 13:35:03 +0100796
797 if (st->comp_ctx && st->comp_ctx->cur_lvl > 0) {
Christopher Faulet3d97c902015-12-09 14:59:38 +0100798 update_freq_ctr(&global.comp_bps_out, to_forward);
Christopher Fauletff8abcd2017-06-02 15:33:24 +0200799 HA_ATOMIC_ADD(&strm_fe(s)->fe_counters.comp_out, to_forward);
800 HA_ATOMIC_ADD(&s->be->be_counters.comp_out, to_forward);
Christopher Faulet3d97c902015-12-09 14:59:38 +0100801 }
802
Christopher Faulet3d97c902015-12-09 14:59:38 +0100803 return to_forward;
804}
805
806
807/***********************************************************************/
Christopher Faulet92d36382015-11-05 13:35:03 +0100808struct flt_ops comp_ops = {
Christopher Faulet8ca3b4b2017-07-25 11:07:15 +0200809 .init_per_thread = comp_flt_init_per_thread,
810 .deinit_per_thread = comp_flt_deinit_per_thread,
Christopher Faulet92d36382015-11-05 13:35:03 +0100811
812 .channel_start_analyze = comp_start_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100813 .channel_end_analyze = comp_end_analyze,
Christopher Faulet3dc860d2017-09-15 11:39:36 +0200814 .channel_post_analyze = comp_http_post_analyze,
Christopher Faulet92d36382015-11-05 13:35:03 +0100815
Christopher Faulet1339d742016-05-11 16:48:33 +0200816 .http_headers = comp_http_headers,
Christopher Faulet309c6412015-12-02 09:57:32 +0100817 .http_data = comp_http_data,
818 .http_chunk_trailers = comp_http_chunk_trailers,
819 .http_forward_data = comp_http_forward_data,
Christopher Fauletd60b3cf2017-06-26 11:47:13 +0200820 .http_end = comp_http_end,
Christopher Faulet92d36382015-11-05 13:35:03 +0100821};
822
Christopher Faulet3d97c902015-12-09 14:59:38 +0100823static int
824parse_compression_options(char **args, int section, struct proxy *proxy,
825 struct proxy *defpx, const char *file, int line,
826 char **err)
827{
Christopher Faulet92d36382015-11-05 13:35:03 +0100828 struct comp *comp;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100829
830 if (proxy->comp == NULL) {
Vincent Bernat02779b62016-04-03 13:48:43 +0200831 comp = calloc(1, sizeof(*comp));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100832 proxy->comp = comp;
833 }
834 else
835 comp = proxy->comp;
836
837 if (!strcmp(args[1], "algo")) {
838 struct comp_ctx *ctx;
839 int cur_arg = 2;
840
841 if (!*args[cur_arg]) {
842 memprintf(err, "parsing [%s:%d] : '%s' expects <algorithm>\n",
843 file, line, args[0]);
844 return -1;
845 }
846 while (*(args[cur_arg])) {
847 if (comp_append_algo(comp, args[cur_arg]) < 0) {
848 memprintf(err, "'%s' : '%s' is not a supported algorithm.\n",
849 args[0], args[cur_arg]);
850 return -1;
851 }
852 if (proxy->comp->algos->init(&ctx, 9) == 0)
853 proxy->comp->algos->end(&ctx);
854 else {
855 memprintf(err, "'%s' : Can't init '%s' algorithm.\n",
856 args[0], args[cur_arg]);
857 return -1;
858 }
859 cur_arg++;
860 continue;
861 }
862 }
863 else if (!strcmp(args[1], "offload"))
864 comp->offload = 1;
865 else if (!strcmp(args[1], "type")) {
866 int cur_arg = 2;
867
868 if (!*args[cur_arg]) {
869 memprintf(err, "'%s' expects <type>\n", args[0]);
870 return -1;
871 }
872 while (*(args[cur_arg])) {
873 comp_append_type(comp, args[cur_arg]);
874 cur_arg++;
875 continue;
876 }
877 }
878 else {
879 memprintf(err, "'%s' expects 'algo', 'type' or 'offload'\n",
880 args[0]);
881 return -1;
882 }
883
884 return 0;
885}
886
Christopher Faulet92d36382015-11-05 13:35:03 +0100887static int
888parse_http_comp_flt(char **args, int *cur_arg, struct proxy *px,
Thierry Fournier3610c392016-04-13 18:27:51 +0200889 struct flt_conf *fconf, char **err, void *private)
Christopher Faulet92d36382015-11-05 13:35:03 +0100890{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100891 struct flt_conf *fc, *back;
Christopher Faulet92d36382015-11-05 13:35:03 +0100892
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100893 list_for_each_entry_safe(fc, back, &px->filter_configs, list) {
894 if (fc->id == http_comp_flt_id) {
Christopher Faulet92d36382015-11-05 13:35:03 +0100895 memprintf(err, "%s: Proxy supports only one compression filter\n", px->id);
896 return -1;
897 }
898 }
899
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100900 fconf->id = http_comp_flt_id;
901 fconf->conf = NULL;
902 fconf->ops = &comp_ops;
Christopher Faulet92d36382015-11-05 13:35:03 +0100903 (*cur_arg)++;
904
905 return 0;
906}
907
908
909int
910check_legacy_http_comp_flt(struct proxy *proxy)
911{
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100912 struct flt_conf *fconf;
Christopher Faulet92d36382015-11-05 13:35:03 +0100913 int err = 0;
914
915 if (proxy->comp == NULL)
916 goto end;
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100917 if (!LIST_ISEMPTY(&proxy->filter_configs)) {
918 list_for_each_entry(fconf, &proxy->filter_configs, list) {
919 if (fconf->id == http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100920 goto end;
921 }
Christopher Faulet767a84b2017-11-24 16:50:31 +0100922 ha_alert("config: %s '%s': require an explicit filter declaration to use HTTP compression\n",
923 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100924 err++;
925 goto end;
926 }
927
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100928 fconf = calloc(1, sizeof(*fconf));
929 if (!fconf) {
Christopher Faulet767a84b2017-11-24 16:50:31 +0100930 ha_alert("config: %s '%s': out of memory\n",
931 proxy_type_str(proxy), proxy->id);
Christopher Faulet92d36382015-11-05 13:35:03 +0100932 err++;
933 goto end;
934 }
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100935 fconf->id = http_comp_flt_id;
936 fconf->conf = NULL;
937 fconf->ops = &comp_ops;
938 LIST_ADDQ(&proxy->filter_configs, &fconf->list);
Christopher Faulet92d36382015-11-05 13:35:03 +0100939
940 end:
941 return err;
942}
943
944/*
945 * boolean, returns true if compression is used (either gzip or deflate) in the
946 * response.
947 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100948static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100949smp_fetch_res_comp(const struct arg *args, struct sample *smp, const char *kw,
950 void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100951{
Willy Tarreaube508f12016-03-10 11:47:01 +0100952 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100953
Christopher Faulet3d97c902015-12-09 14:59:38 +0100954 smp->data.type = SMP_T_BOOL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100955 smp->data.u.sint = (txn && (txn->rsp.flags & HTTP_MSGF_COMPRESSING));
Christopher Faulet3d97c902015-12-09 14:59:38 +0100956 return 1;
957}
958
Christopher Faulet92d36382015-11-05 13:35:03 +0100959/*
960 * string, returns algo
961 */
Christopher Faulet3d97c902015-12-09 14:59:38 +0100962static int
Christopher Faulet92d36382015-11-05 13:35:03 +0100963smp_fetch_res_comp_algo(const struct arg *args, struct sample *smp,
964 const char *kw, void *private)
Christopher Faulet3d97c902015-12-09 14:59:38 +0100965{
Willy Tarreaube508f12016-03-10 11:47:01 +0100966 struct http_txn *txn = smp->strm ? smp->strm->txn : NULL;
Christopher Faulet92d36382015-11-05 13:35:03 +0100967 struct filter *filter;
968 struct comp_state *st;
969
Christopher Faulet03d85532017-09-15 10:14:43 +0200970 if (!txn || !(txn->rsp.flags & HTTP_MSGF_COMPRESSING))
Christopher Faulet3d97c902015-12-09 14:59:38 +0100971 return 0;
972
Christopher Fauletfcf035c2015-12-03 11:48:03 +0100973 list_for_each_entry(filter, &strm_flt(smp->strm)->filters, list) {
Christopher Faulet443ea1a2016-02-04 13:40:26 +0100974 if (FLT_ID(filter) != http_comp_flt_id)
Christopher Faulet92d36382015-11-05 13:35:03 +0100975 continue;
976
977 if (!(st = filter->ctx))
978 break;
979
980 smp->data.type = SMP_T_STR;
981 smp->flags = SMP_F_CONST;
Willy Tarreau843b7cb2018-07-13 10:54:26 +0200982 smp->data.u.str.area = st->comp_algo->cfg_name;
983 smp->data.u.str.data = st->comp_algo->cfg_name_len;
Christopher Faulet92d36382015-11-05 13:35:03 +0100984 return 1;
985 }
986 return 0;
Christopher Faulet3d97c902015-12-09 14:59:38 +0100987}
988
989/* Declare the config parser for "compression" keyword */
990static struct cfg_kw_list cfg_kws = {ILH, {
991 { CFG_LISTEN, "compression", parse_compression_options },
992 { 0, NULL, NULL },
993 }
994};
995
Christopher Faulet92d36382015-11-05 13:35:03 +0100996/* Declare the filter parser for "compression" keyword */
997static struct flt_kw_list filter_kws = { "COMP", { }, {
Thierry Fournier3610c392016-04-13 18:27:51 +0200998 { "compression", parse_http_comp_flt, NULL },
999 { NULL, NULL, NULL },
Christopher Faulet92d36382015-11-05 13:35:03 +01001000 }
1001};
1002
Christopher Faulet3d97c902015-12-09 14:59:38 +01001003/* Note: must not be declared <const> as its list will be overwritten */
1004static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet92d36382015-11-05 13:35:03 +01001005 { "res.comp", smp_fetch_res_comp, 0, NULL, SMP_T_BOOL, SMP_USE_HRSHP },
1006 { "res.comp_algo", smp_fetch_res_comp_algo, 0, NULL, SMP_T_STR, SMP_USE_HRSHP },
1007 { /* END */ },
1008 }
1009};
Christopher Faulet3d97c902015-12-09 14:59:38 +01001010
1011__attribute__((constructor))
Christopher Faulet92d36382015-11-05 13:35:03 +01001012static void
1013__flt_http_comp_init(void)
Christopher Faulet3d97c902015-12-09 14:59:38 +01001014{
1015 cfg_register_keywords(&cfg_kws);
Christopher Faulet92d36382015-11-05 13:35:03 +01001016 flt_register_keywords(&filter_kws);
Christopher Faulet3d97c902015-12-09 14:59:38 +01001017 sample_register_fetches(&sample_fetch_keywords);
Willy Tarreaubafbe012017-11-24 17:34:44 +01001018 pool_head_comp_state = create_pool("comp_state", sizeof(struct comp_state), MEM_F_SHARED);
Christopher Faulet3d97c902015-12-09 14:59:38 +01001019}