1 /*
2  * Functions to manipulate HTTP messages using the internal representation.
3  *
4  * Copyright (C) 2018 HAProxy Technologies, Christopher Faulet <cfaulet@haproxy.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/config.h>
14 #include <common/debug.h>
15 #include <common/cfgparse.h>
16 #include <common/h1.h>
17 #include <common/http.h>
18 #include <common/htx.h>
19 
20 #include <proto/http_htx.h>
21 
22 struct buffer htx_err_chunks[HTTP_ERR_SIZE];
23 
24 /* Returns the next unporocessed start line in the HTX message. It returns NULL
25  * if the start-line is undefined (first == -1). Otherwise, it returns the
26  * pointer on the htx_sl structure.
27  */
http_get_stline(struct htx * htx)28 struct htx_sl *http_get_stline(struct htx *htx)
29 {
30 	struct htx_blk *blk;
31 
32 	blk = htx_get_first_blk(htx);
33 	if (!blk || (htx_get_blk_type(blk) != HTX_BLK_REQ_SL && htx_get_blk_type(blk) != HTX_BLK_RES_SL))
34 		return NULL;
35 	return htx_get_blk_ptr(htx, blk);
36 }
37 
38 /* Returns the headers size in the HTX message */
http_get_hdrs_size(struct htx * htx)39 size_t http_get_hdrs_size(struct htx *htx)
40 {
41 	struct htx_blk *blk;
42 	size_t sz = 0;
43 
44 	blk = htx_get_first_blk(htx);
45 	if (!blk || htx_get_blk_type(blk) > HTX_BLK_EOH)
46 		return sz;
47 
48 	for (; blk; blk = htx_get_next_blk(htx, blk)) {
49 		sz += htx_get_blksz(blk);
50 		if (htx_get_blk_type(blk) == HTX_BLK_EOH)
51 			break;
52 	}
53 	return sz;
54 }
55 
56 /* Finds the first or next occurrence of header <name> in the HTX message <htx>
57  * using the context <ctx>. This structure holds everything necessary to use the
58  * header and find next occurrence. If its <blk> member is NULL, the header is
59  * searched from the beginning. Otherwise, the next occurrence is returned. The
60  * function returns 1 when it finds a value, and 0 when there is no more. It is
61  * designed to work with headers defined as comma-separated lists. If <full> is
62  * set, it works on full-line headers in whose comma is not a delimiter but is
63  * part of the syntax. A special case, if ctx->value is NULL when searching for
64  * a new values of a header, the current header is rescanned. This allows
65  * rescanning after a header deletion.
66  */
http_find_header(const struct htx * htx,const struct ist name,struct http_hdr_ctx * ctx,int full)67 int http_find_header(const struct htx *htx, const struct ist name,
68 		    struct http_hdr_ctx *ctx, int full)
69 {
70 	struct htx_blk *blk = ctx->blk;
71 	struct ist n, v;
72 	enum htx_blk_type type;
73 
74 	if (blk) {
75 		char *p;
76 
77 		if (!ctx->value.ptr)
78 			goto rescan_hdr;
79 		if (full)
80 			goto next_blk;
81 		v = htx_get_blk_value(htx, blk);
82 		p = ctx->value.ptr + ctx->value.len + ctx->lws_after;
83 		v.len -= (p - v.ptr);
84 		v.ptr  = p;
85 		if (!v.len)
86 			goto next_blk;
87 		/* Skip comma */
88 		if (*(v.ptr) == ',') {
89 			v.ptr++;
90 			v.len--;
91 		}
92 
93 		goto return_hdr;
94 	}
95 
96 	if (!htx->used)
97 		return 0;
98 
99 	for (blk = htx_get_first_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
100 	  rescan_hdr:
101 		type = htx_get_blk_type(blk);
102 		if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
103 			break;
104 		if (type != HTX_BLK_HDR)
105 			continue;
106 		if (name.len) {
107 			/* If no name was passed, we want any header. So skip the comparison */
108 			n = htx_get_blk_name(htx, blk);
109 			if (!isteqi(n, name))
110 				goto next_blk;
111 		}
112 		v = htx_get_blk_value(htx, blk);
113 
114 	  return_hdr:
115 		ctx->lws_before = 0;
116 		ctx->lws_after = 0;
117 		while (v.len && HTTP_IS_LWS(*v.ptr)) {
118 			v.ptr++;
119 			v.len--;
120 			ctx->lws_before++;
121 		}
122 		if (!full)
123 			v.len = http_find_hdr_value_end(v.ptr, v.ptr + v.len) - v.ptr;
124 		while (v.len && HTTP_IS_LWS(*(v.ptr + v.len - 1))) {
125 			v.len--;
126 			ctx->lws_after++;
127 		}
128 		ctx->blk   = blk;
129 		ctx->value = v;
130 		return 1;
131 
132 	  next_blk:
133 		;
134 	}
135 
136 	ctx->blk   = NULL;
137 	ctx->value = ist("");
138 	ctx->lws_before = ctx->lws_after = 0;
139 	return 0;
140 }
141 
142 /* Adds a header block int the HTX message <htx>, just before the EOH block. It
143  * returns 1 on success, otherwise it returns 0.
144  */
http_add_header(struct htx * htx,const struct ist n,const struct ist v)145 int http_add_header(struct htx *htx, const struct ist n, const struct ist v)
146 {
147 	struct htx_blk *blk;
148 	enum htx_blk_type type = htx_get_tail_type(htx);
149 	int32_t prev;
150 
151 	blk = htx_add_header(htx, n, v);
152 	if (!blk)
153 		return 0;
154 
155 	if (unlikely(type < HTX_BLK_EOH))
156 		return 1;
157 
158 	/* <blk> is the head, swap it iteratively with its predecessor to place
159 	 * it just before the end-of-header block. So blocks remains ordered. */
160 	for (prev = htx_get_prev(htx, htx->tail); prev != htx->first; prev = htx_get_prev(htx, prev)) {
161 		struct htx_blk   *pblk = htx_get_blk(htx, prev);
162 		enum htx_blk_type type = htx_get_blk_type(pblk);
163 
164 		/* Swap .addr and .info fields */
165 		blk->addr ^= pblk->addr; pblk->addr ^= blk->addr; blk->addr ^= pblk->addr;
166 		blk->info ^= pblk->info; pblk->info ^= blk->info; blk->info ^= pblk->info;
167 
168 		if (blk->addr == pblk->addr)
169 			blk->addr += htx_get_blksz(pblk);
170 
171 		/* Stop when end-of-header is reached */
172 		if (type == HTX_BLK_EOH)
173 			break;
174 
175 		blk = pblk;
176 	}
177 
178 	return 1;
179 }
180 
181 /* Replaces parts of the start-line of the HTX message <htx>. It returns 1 on
182  * success, otherwise it returns 0.
183  */
http_replace_stline(struct htx * htx,const struct ist p1,const struct ist p2,const struct ist p3)184 int http_replace_stline(struct htx *htx, const struct ist p1, const struct ist p2, const struct ist p3)
185 {
186 	struct htx_blk *blk;
187 
188 	blk = htx_get_first_blk(htx);
189 	if (!blk || !htx_replace_stline(htx, blk, p1, p2, p3))
190 		return 0;
191 	return 1;
192 }
193 
194 /* Replace the request method in the HTX message <htx> by <meth>. It returns 1
195  * on success, otherwise 0.
196  */
http_replace_req_meth(struct htx * htx,const struct ist meth)197 int http_replace_req_meth(struct htx *htx, const struct ist meth)
198 {
199 	struct buffer *temp = get_trash_chunk();
200 	struct htx_sl *sl = http_get_stline(htx);
201 	struct ist uri, vsn;
202 
203 	if (!sl)
204 		return 0;
205 
206 	/* Start by copying old uri and version */
207 	chunk_memcat(temp, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); /* uri */
208 	uri = ist2(temp->area, HTX_SL_REQ_ULEN(sl));
209 
210 	chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
211 	vsn = ist2(temp->area + uri.len, HTX_SL_REQ_VLEN(sl));
212 
213 	/* create the new start line */
214 	sl->info.req.meth = find_http_meth(meth.ptr, meth.len);
215 	return http_replace_stline(htx, meth, uri, vsn);
216 }
217 
218 /* Replace the request uri in the HTX message <htx> by <uri>. It returns 1 on
219  * success, otherwise 0.
220  */
http_replace_req_uri(struct htx * htx,const struct ist uri)221 int http_replace_req_uri(struct htx *htx, const struct ist uri)
222 {
223 	struct buffer *temp = get_trash_chunk();
224 	struct htx_sl *sl = http_get_stline(htx);
225 	struct ist meth, vsn;
226 
227 	if (!sl)
228 		return 0;
229 
230 	/* Start by copying old method and version */
231 	chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
232 	meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
233 
234 	chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
235 	vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
236 
237 	/* create the new start line */
238 	return http_replace_stline(htx, meth, uri, vsn);
239 }
240 
241 /* Replace the request path in the HTX message <htx> by <path>. The host part
242  * and the query string are preserved. It returns 1 on success, otherwise 0.
243  */
http_replace_req_path(struct htx * htx,const struct ist path)244 int http_replace_req_path(struct htx *htx, const struct ist path)
245 {
246 	struct buffer *temp = get_trash_chunk();
247 	struct htx_sl *sl = http_get_stline(htx);
248 	struct ist meth, uri, vsn, p;
249 	size_t plen = 0;
250 
251 	if (!sl)
252 		return 0;
253 
254 	uri = htx_sl_req_uri(sl);
255 	p = http_get_path(uri);
256 	if (!p.ptr)
257 		p = uri;
258 	while (plen < p.len && *(p.ptr + plen) != '?')
259 		plen++;
260 
261 	/* Start by copying old method and version and create the new uri */
262 	chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
263 	meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
264 
265 	chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
266 	vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
267 
268 	chunk_memcat(temp, uri.ptr, p.ptr - uri.ptr);         /* uri: host part */
269 	chunk_memcat(temp, path.ptr, path.len);               /* uri: new path */
270 	chunk_memcat(temp, p.ptr + plen, p.len - plen);       /* uri: QS part */
271 	uri = ist2(temp->area + meth.len + vsn.len, uri.len - plen + path.len);
272 
273 	/* create the new start line */
274 	return http_replace_stline(htx, meth, uri, vsn);
275 }
276 
277 /* Replace the request query-string in the HTX message <htx> by <query>. The
278  * host part and the path are preserved. It returns 1 on success, otherwise
279  * 0.
280  */
http_replace_req_query(struct htx * htx,const struct ist query)281 int http_replace_req_query(struct htx *htx, const struct ist query)
282 {
283 	struct buffer *temp = get_trash_chunk();
284 	struct htx_sl *sl = http_get_stline(htx);
285 	struct ist meth, uri, vsn, q;
286 	int offset = 1;
287 
288 	if (!sl)
289 		return 0;
290 
291 	uri = htx_sl_req_uri(sl);
292 	q = uri;
293 	while (q.len > 0 && *(q.ptr) != '?') {
294 		q.ptr++;
295 		q.len--;
296 	}
297 
298 	/* skip the question mark or indicate that we must insert it
299 	 * (but only if the format string is not empty then).
300 	 */
301 	if (q.len) {
302 		q.ptr++;
303 		q.len--;
304 	}
305 	else if (query.len > 1)
306 		offset = 0;
307 
308 	/* Start by copying old method and version and create the new uri */
309 	chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
310 	meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
311 
312 	chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
313 	vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
314 
315 	chunk_memcat(temp, uri.ptr, q.ptr - uri.ptr);               /* uri: host + path part */
316 	chunk_memcat(temp, query.ptr + offset, query.len - offset); /* uri: new QS */
317 	uri = ist2(temp->area + meth.len + vsn.len, uri.len - q.len + query.len - offset);
318 
319 	/* create the new start line */
320 	return http_replace_stline(htx, meth, uri, vsn);
321 }
322 
323 /* Replace the response status in the HTX message <htx> by <status>. It returns
324  * 1 on success, otherwise 0.
325 */
http_replace_res_status(struct htx * htx,const struct ist status)326 int http_replace_res_status(struct htx *htx, const struct ist status)
327 {
328 	struct buffer *temp = get_trash_chunk();
329 	struct htx_sl *sl = http_get_stline(htx);
330 	struct ist vsn, reason;
331 
332 	if (!sl)
333 		return 0;
334 
335 	/* Start by copying old uri and version */
336 	chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
337 	vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
338 
339 	chunk_memcat(temp, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); /* reason */
340 	reason = ist2(temp->area + vsn.len, HTX_SL_RES_RLEN(sl));
341 
342 	/* create the new start line */
343 	sl->info.res.status = strl2ui(status.ptr, status.len);
344 	return http_replace_stline(htx, vsn, status, reason);
345 }
346 
347 /* Replace the response reason in the HTX message <htx> by <reason>. It returns
348  * 1 on success, otherwise 0.
349 */
http_replace_res_reason(struct htx * htx,const struct ist reason)350 int http_replace_res_reason(struct htx *htx, const struct ist reason)
351 {
352 	struct buffer *temp = get_trash_chunk();
353 	struct htx_sl *sl = http_get_stline(htx);
354 	struct ist vsn, status;
355 
356 	if (!sl)
357 		return 0;
358 
359 	/* Start by copying old uri and version */
360 	chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
361 	vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
362 
363 	chunk_memcat(temp, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); /* code */
364 	status = ist2(temp->area + vsn.len, HTX_SL_RES_CLEN(sl));
365 
366 	/* create the new start line */
367 	return http_replace_stline(htx, vsn, status, reason);
368 }
369 
370 /* Replaces a part of a header value referenced in the context <ctx> by
371  * <data>. It returns 1 on success, otherwise it returns 0. The context is
372  * updated if necessary.
373  */
http_replace_header_value(struct htx * htx,struct http_hdr_ctx * ctx,const struct ist data)374 int http_replace_header_value(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist data)
375 {
376 	struct htx_blk *blk = ctx->blk;
377 	char *start;
378 	struct ist v;
379 	uint32_t len, off;
380 
381 	if (!blk)
382 		return 0;
383 
384 	v     = htx_get_blk_value(htx, blk);
385 	start = ctx->value.ptr - ctx->lws_before;
386 	len   = ctx->lws_before + ctx->value.len + ctx->lws_after;
387 	off   = start - v.ptr;
388 
389 	blk = htx_replace_blk_value(htx, blk, ist2(start, len), data);
390 	if (!blk)
391 		return 0;
392 
393 	v = htx_get_blk_value(htx, blk);
394 	ctx->blk = blk;
395 	ctx->value.ptr = v.ptr + off;
396 	ctx->value.len = data.len;
397 	ctx->lws_before = ctx->lws_after = 0;
398 
399 	return 1;
400 }
401 
402 /* Fully replaces a header referenced in the context <ctx> by the name <name>
403  * with the value <value>. It returns 1 on success, otherwise it returns 0. The
404  * context is updated if necessary.
405  */
http_replace_header(struct htx * htx,struct http_hdr_ctx * ctx,const struct ist name,const struct ist value)406 int http_replace_header(struct htx *htx, struct http_hdr_ctx *ctx,
407 			const struct ist name, const struct ist value)
408 {
409 	struct htx_blk *blk = ctx->blk;
410 
411 	if (!blk)
412 		return 0;
413 
414 	blk = htx_replace_header(htx, blk, name, value);
415 	if (!blk)
416 		return 0;
417 
418 	ctx->blk = blk;
419 	ctx->value = ist(NULL);
420 	ctx->lws_before = ctx->lws_after = 0;
421 
422 	return 1;
423 }
424 
425 /* Remove one value of a header. This only works on a <ctx> returned by
426  * http_find_header function. The value is removed, as well as surrounding commas
427  * if any. If the removed value was alone, the whole header is removed.  The
428  * <ctx> is always updated accordingly, as well as the HTX message <htx>. It
429  * returns 1 on success. Otherwise, it returns 0. The <ctx> is always left in a
430  * form that can be handled by http_find_header() to find next occurrence.
431  */
http_remove_header(struct htx * htx,struct http_hdr_ctx * ctx)432 int http_remove_header(struct htx *htx, struct http_hdr_ctx *ctx)
433 {
434 	struct htx_blk *blk = ctx->blk;
435 	char *start;
436 	struct ist v;
437 	uint32_t len;
438 
439 	if (!blk)
440 		return 0;
441 
442 	start = ctx->value.ptr - ctx->lws_before;
443 	len   = ctx->lws_before + ctx->value.len + ctx->lws_after;
444 
445 	v = htx_get_blk_value(htx, blk);
446 	if (len == v.len) {
447 		blk = htx_remove_blk(htx, blk);
448 		if (blk || !htx->used) {
449 			ctx->blk = blk;
450 			ctx->value = ist2(NULL, 0);
451 			ctx->lws_before = ctx->lws_after = 0;
452 		}
453 		else {
454 			ctx->blk = htx_get_blk(htx, htx->tail);
455 			ctx->value = htx_get_blk_value(htx, ctx->blk);
456 			ctx->lws_before = ctx->lws_after = 0;
457 		}
458 		return 1;
459 	}
460 
461 	/* This was not the only value of this header. We have to remove the
462 	 * part pointed by ctx->value. If it is the last entry of the list, we
463 	 * remove the last separator.
464 	 */
465 	if (start == v.ptr) {
466 		/* It's the first header part but not the only one. So remove
467 		 * the comma after it. */
468 		len++;
469 	}
470 	else {
471 		/* There is at least one header part before the removed one. So
472 		 * remove the comma between them. */
473 		start--;
474 		len++;
475 	}
476 	/* Update the block content and its len */
477 	memmove(start, start+len, v.len-len);
478 	htx_change_blk_value_len(htx, blk, v.len-len);
479 
480 	/* Finally update the ctx */
481 	ctx->value.ptr = start;
482 	ctx->value.len = 0;
483 	ctx->lws_before = ctx->lws_after = 0;
484 
485 	return 1;
486 }
487 
488 
489 /* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
490  * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
491  * performed over the whole headers. Otherwise it must contain a valid header
492  * context, initialised with ctx->blk=NULL for the first lookup in a series. If
493  * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
494  * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
495  * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
496  * -1. The value fetch stops at commas, so this function is suited for use with
497  * list headers.
498  * The return value is 0 if nothing was found, or non-zero otherwise.
499  */
http_get_htx_hdr(const struct htx * htx,const struct ist hdr,int occ,struct http_hdr_ctx * ctx,char ** vptr,size_t * vlen)500 unsigned int http_get_htx_hdr(const struct htx *htx, const struct ist hdr,
501 			      int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
502 {
503 	struct http_hdr_ctx local_ctx;
504 	struct ist val_hist[MAX_HDR_HISTORY];
505 	unsigned int hist_idx;
506 	int found;
507 
508 	if (!ctx) {
509 		local_ctx.blk = NULL;
510 		ctx = &local_ctx;
511 	}
512 
513 	if (occ >= 0) {
514 		/* search from the beginning */
515 		while (http_find_header(htx, hdr, ctx, 0)) {
516 			occ--;
517 			if (occ <= 0) {
518 				*vptr = ctx->value.ptr;
519 				*vlen = ctx->value.len;
520 				return 1;
521 			}
522 		}
523 		return 0;
524 	}
525 
526 	/* negative occurrence, we scan all the list then walk back */
527 	if (-occ > MAX_HDR_HISTORY)
528 		return 0;
529 
530 	found = hist_idx = 0;
531 	while (http_find_header(htx, hdr, ctx, 0)) {
532 		val_hist[hist_idx] = ctx->value;
533 		if (++hist_idx >= MAX_HDR_HISTORY)
534 			hist_idx = 0;
535 		found++;
536 	}
537 	if (-occ > found)
538 		return 0;
539 
540 	/* OK now we have the last occurrence in [hist_idx-1], and we need to
541 	 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
542 	 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
543 	 * to remain in the 0..9 range.
544 	 */
545 	hist_idx += occ + MAX_HDR_HISTORY;
546 	if (hist_idx >= MAX_HDR_HISTORY)
547 		hist_idx -= MAX_HDR_HISTORY;
548 	*vptr = val_hist[hist_idx].ptr;
549 	*vlen = val_hist[hist_idx].len;
550 	return 1;
551 }
552 
553 /* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
554  * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
555  * performed over the whole headers. Otherwise it must contain a valid header
556  * context, initialised with ctx->blk=NULL for the first lookup in a series. If
557  * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
558  * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
559  * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
560  * -1. This function differs from http_get_hdr() in that it only returns full
561  * line header values and does not stop at commas.
562  * The return value is 0 if nothing was found, or non-zero otherwise.
563  */
http_get_htx_fhdr(const struct htx * htx,const struct ist hdr,int occ,struct http_hdr_ctx * ctx,char ** vptr,size_t * vlen)564 unsigned int http_get_htx_fhdr(const struct htx *htx, const struct ist hdr,
565 			       int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
566 {
567 	struct http_hdr_ctx local_ctx;
568 	struct ist val_hist[MAX_HDR_HISTORY];
569 	unsigned int hist_idx;
570 	int found;
571 
572 	if (!ctx) {
573 		local_ctx.blk = NULL;
574 		ctx = &local_ctx;
575 	}
576 
577 	if (occ >= 0) {
578 		/* search from the beginning */
579 		while (http_find_header(htx, hdr, ctx, 1)) {
580 			occ--;
581 			if (occ <= 0) {
582 				*vptr = ctx->value.ptr;
583 				*vlen = ctx->value.len;
584 				return 1;
585 			}
586 		}
587 		return 0;
588 	}
589 
590 	/* negative occurrence, we scan all the list then walk back */
591 	if (-occ > MAX_HDR_HISTORY)
592 		return 0;
593 
594 	found = hist_idx = 0;
595 	while (http_find_header(htx, hdr, ctx, 1)) {
596 		val_hist[hist_idx] = ctx->value;
597 		if (++hist_idx >= MAX_HDR_HISTORY)
598 			hist_idx = 0;
599 		found++;
600 	}
601 	if (-occ > found)
602 		return 0;
603 
604 	/* OK now we have the last occurrence in [hist_idx-1], and we need to
605 	 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
606 	 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
607 	 * to remain in the 0..9 range.
608 	 */
609 	hist_idx += occ + MAX_HDR_HISTORY;
610 	if (hist_idx >= MAX_HDR_HISTORY)
611 		hist_idx -= MAX_HDR_HISTORY;
612 	*vptr = val_hist[hist_idx].ptr;
613 	*vlen = val_hist[hist_idx].len;
614 	return 1;
615 }
616 
http_str_to_htx(struct buffer * buf,struct ist raw,char ** errmsg)617 int http_str_to_htx(struct buffer *buf, struct ist raw, char **errmsg)
618 {
619 	struct htx *htx;
620 	struct htx_sl *sl;
621 	struct h1m h1m;
622 	struct http_hdr hdrs[global.tune.max_http_hdr];
623 	union h1_sl h1sl;
624 	unsigned int flags = HTX_SL_F_IS_RESP;
625 	int ret = 0;
626 
627 	b_reset(buf);
628 	if (!raw.len) {
629 		buf->size = 0;
630 		buf->area = malloc(raw.len);
631 		return 1;
632 	}
633 
634 	buf->size = global.tune.bufsize;
635 	buf->area = (char *)malloc(buf->size);
636 	if (!buf->area)
637 		goto error;
638 
639 	h1m_init_res(&h1m);
640 	h1m.flags |= H1_MF_NO_PHDR;
641 	ret = h1_headers_to_hdr_list(raw.ptr, raw.ptr + raw.len,
642 				     hdrs, sizeof(hdrs)/sizeof(hdrs[0]), &h1m, &h1sl);
643 	if (ret <= 0) {
644 		memprintf(errmsg, "unabled to parse headers (error offset: %d)", h1m.err_pos);
645 		goto error;
646 	}
647 
648 	if (unlikely(h1sl.st.v.len != 8)) {
649 		memprintf(errmsg, "invalid http version (%.*s)", (int)h1sl.st.v.len, h1sl.st.v.ptr);
650 		goto error;
651 	}
652 	if ((*(h1sl.st.v.ptr + 5) > '1') ||
653 	    ((*(h1sl.st.v.ptr + 5) == '1') && (*(h1sl.st.v.ptr + 7) >= '1')))
654 		h1m.flags |= H1_MF_VER_11;
655 
656 	if (h1sl.st.status < 200 && (h1sl.st.status == 100 || h1sl.st.status >= 102)) {
657 		memprintf(errmsg, "invalid http status code for an error message (%u)",
658 			  h1sl.st.status);
659 		goto error;
660 	}
661 
662 	if (h1sl.st.status == 204 || h1sl.st.status == 304) {
663 		/* Responses known to have no body. */
664 		h1m.flags &= ~(H1_MF_CLEN|H1_MF_CHNK);
665 		h1m.flags |= H1_MF_XFER_LEN;
666 		h1m.curr_len = h1m.body_len = 0;
667 	}
668 	else if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK))
669 		h1m.flags |= H1_MF_XFER_LEN;
670 
671 	if (h1m.flags & H1_MF_VER_11)
672 		flags |= HTX_SL_F_VER_11;
673 	if (h1m.flags & H1_MF_XFER_ENC)
674 		flags |= HTX_SL_F_XFER_ENC;
675 	if (h1m.flags & H1_MF_XFER_LEN) {
676 		flags |= HTX_SL_F_XFER_LEN;
677 		if (h1m.flags & H1_MF_CHNK) {
678 			memprintf(errmsg, "chunk-encoded payload not supported");
679 			goto error;
680 		}
681 		else if (h1m.flags & H1_MF_CLEN) {
682 			flags |= HTX_SL_F_CLEN;
683 			if (h1m.body_len == 0)
684 				flags |= HTX_SL_F_BODYLESS;
685 		}
686 		else
687 			flags |= HTX_SL_F_BODYLESS;
688 	}
689 
690 	if ((flags & HTX_SL_F_BODYLESS) && raw.len > ret) {
691 		memprintf(errmsg, "message payload not expected");
692 		goto error;
693 	}
694 	if ((flags & HTX_SL_F_CLEN) && h1m.body_len != (raw.len - ret)) {
695 		struct ist clen = ist(ultoa(raw.len - ret));
696 		int i;
697 
698 		memprintf(errmsg, "payload size does not match the announced content-length (%lu != %lu)."
699 			  " C-L header is updated accordingly but it should be fixed to avoid any errors on future versions.",
700 			  (unsigned long)(raw.len - ret), (unsigned long)h1m.body_len);
701 
702 		for (i = 0; hdrs[i].n.len; i++) {
703 			if (isteqi(hdrs[i].n, ist("content-length")))
704 				hdrs[i].v = clen;
705 		}
706 	}
707 
708 	htx = htx_from_buf(buf);
709 	sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, h1sl.st.v, h1sl.st.c, h1sl.st.r);
710 	if (!sl || !htx_add_all_headers(htx, hdrs)) {
711 		memprintf(errmsg, "unable to add headers into the HTX message");
712 		goto error;
713 	}
714 	sl->info.res.status = h1sl.st.status;
715 
716 	while (raw.len > ret) {
717 		int sent = htx_add_data(htx, ist2(raw.ptr + ret, raw.len - ret));
718 		if (!sent) {
719 			memprintf(errmsg, "unable to add payload into the HTX message");
720 			goto error;
721 		}
722 		ret += sent;
723 	}
724 
725 	if (!htx_add_endof(htx, HTX_BLK_EOM)) {
726 		memprintf(errmsg, "unable to add EOM into the HTX message");
727 		goto error;
728 	}
729 
730 	return 1;
731 
732 error:
733 	if (buf->size)
734 		free(buf->area);
735 	return 0;
736 }
737 
http_htx_init(void)738 static int http_htx_init(void)
739 {
740 	struct proxy *px;
741 	struct buffer chk;
742 	struct ist raw;
743 	char *errmsg = NULL;
744 	int rc;
745 	int err_code = 0;
746 
747 	for (px = proxies_list; px; px = px->next) {
748 		if (!(px->options2 & PR_O2_USE_HTX))
749 			continue;
750 
751 		for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
752 			if (!b_data(&px->errmsg[rc]))
753 				continue;
754 
755 			raw = ist2(b_head(&px->errmsg[rc]), b_data(&px->errmsg[rc]));
756 			if (!http_str_to_htx(&chk, raw, &errmsg)) {
757 				ha_alert("config: %s '%s': invalid message for HTTP return code %d: %s.\n",
758 					 proxy_type_str(px), px->id, http_err_codes[rc], errmsg);
759 				err_code |= ERR_ALERT | ERR_FATAL;
760 			}
761 			else if (errmsg)
762 				ha_warning("config: %s '%s': invalid default message for HTTP return code %d: %s.\n",
763 					   proxy_type_str(px), px->id, http_err_codes[rc], errmsg);
764 
765 			/* Reset errmsg */
766 			free(errmsg);
767 			errmsg = NULL;
768 
769 			chunk_destroy(&px->errmsg[rc]);
770 			px->errmsg[rc] = chk;
771 		}
772 	}
773 
774 	for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
775 		if (!http_err_msgs[rc]) {
776 			ha_alert("Internal error: no default message defined for HTTP return code %d", rc);
777 			err_code |= ERR_ALERT | ERR_FATAL;
778 			continue;
779 		}
780 
781 		raw = ist2(http_err_msgs[rc], strlen(http_err_msgs[rc]));
782 		if (!http_str_to_htx(&chk, raw, &errmsg)) {
783 			ha_alert("Internal error: invalid default message for HTTP return code %d: %s.\n",
784 				 http_err_codes[rc], errmsg);
785 			err_code |= ERR_ALERT | ERR_FATAL;
786 		}
787 		else if (errmsg)
788 			ha_warning("invalid default message for HTTP return code %d: %s.\n", http_err_codes[rc], errmsg);
789 
790 		/* Reset errmsg */
791 		free(errmsg);
792 		errmsg = NULL;
793 
794 		htx_err_chunks[rc] = chk;
795 	}
796 end:
797 	return err_code;
798 }
799 
800 REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init);
801