blob: 75883fe3cf5e9540689e400f531657fa1860b5eb [file] [log] [blame]
Christopher Faulet47596d32018-10-22 09:17:28 +02001/*
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>
Christopher Faulet29f17582019-05-23 11:03:26 +020014#include <common/debug.h>
Christopher Fauleta7b677c2018-11-29 16:48:49 +010015#include <common/cfgparse.h>
Willy Tarreauafba57a2018-12-11 13:44:24 +010016#include <common/h1.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020017#include <common/http.h>
Willy Tarreaub96b77e2018-12-11 10:22:41 +010018#include <common/htx.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020019
20#include <proto/http_htx.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020021
Christopher Fauleta7b677c2018-11-29 16:48:49 +010022struct buffer htx_err_chunks[HTTP_ERR_SIZE];
23
Christopher Faulet297fbb42019-05-13 14:41:27 +020024/* Returns the next unporocessed start line in the HTX message. It returns NULL
Christopher Faulet29f17582019-05-23 11:03:26 +020025 * if the start-line is undefined (first == -1). Otherwise, it returns the
Christopher Faulet297fbb42019-05-13 14:41:27 +020026 * pointer on the htx_sl structure.
Christopher Faulet47596d32018-10-22 09:17:28 +020027 */
Christopher Faulet297fbb42019-05-13 14:41:27 +020028struct htx_sl *http_get_stline(struct htx *htx)
Christopher Faulet47596d32018-10-22 09:17:28 +020029{
Christopher Faulet297fbb42019-05-13 14:41:27 +020030 struct htx_blk *blk;
Christopher Faulet573fe732018-11-28 16:55:12 +010031
Christopher Faulet29f17582019-05-23 11:03:26 +020032 blk = htx_get_first_blk(htx);
Christopher Faulet84aa9ba2021-04-15 10:25:35 +020033 if (!blk || (htx_get_blk_type(blk) != HTX_BLK_REQ_SL && htx_get_blk_type(blk) != HTX_BLK_RES_SL))
Christopher Faulet297fbb42019-05-13 14:41:27 +020034 return NULL;
35 return htx_get_blk_ptr(htx, blk);
Christopher Faulet47596d32018-10-22 09:17:28 +020036}
37
Christopher Faulet733d3852020-02-07 16:39:41 +010038/* Returns the headers size in the HTX message */
39size_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
Christopher Faulet47596d32018-10-22 09:17:28 +020056/* 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 */
67int 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;
Christopher Faulet47596d32018-10-22 09:17:28 +020073
74 if (blk) {
75 char *p;
76
Christopher Faulet47596d32018-10-22 09:17:28 +020077 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
Christopher Fauleta3f15502019-05-13 15:27:23 +020099 for (blk = htx_get_first_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
Christopher Faulet47596d32018-10-22 09:17:28 +0200100 rescan_hdr:
Christopher Faulet47596d32018-10-22 09:17:28 +0200101 type = htx_get_blk_type(blk);
Christopher Faulet573fe732018-11-28 16:55:12 +0100102 if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
103 break;
Christopher Faulet47596d32018-10-22 09:17:28 +0200104 if (type != HTX_BLK_HDR)
Christopher Faulet28f29c72019-04-30 17:55:45 +0200105 continue;
Christopher Faulet47596d32018-10-22 09:17:28 +0200106 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 }
Christopher Faulet47596d32018-10-22 09:17:28 +0200128 ctx->blk = blk;
129 ctx->value = v;
130 return 1;
131
132 next_blk:
Christopher Faulet28f29c72019-04-30 17:55:45 +0200133 ;
Christopher Faulet47596d32018-10-22 09:17:28 +0200134 }
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 */
145int 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. */
Christopher Faulet29f17582019-05-23 11:03:26 +0200160 for (prev = htx_get_prev(htx, htx->tail); prev != htx->first; prev = htx_get_prev(htx, prev)) {
Christopher Faulet47596d32018-10-22 09:17:28 +0200161 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);
Christopher Faulet47596d32018-10-22 09:17:28 +0200170
171 /* Stop when end-of-header is reached */
172 if (type == HTX_BLK_EOH)
173 break;
174
175 blk = pblk;
176 }
Christopher Faulet05aab642019-04-11 13:43:57 +0200177
Christopher Faulet47596d32018-10-22 09:17:28 +0200178 return 1;
179}
180
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100181/* Replaces parts of the start-line of the HTX message <htx>. It returns 1 on
Christopher Faulet29f17582019-05-23 11:03:26 +0200182 * success, otherwise it returns 0.
Christopher Faulet47596d32018-10-22 09:17:28 +0200183 */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100184int http_replace_stline(struct htx *htx, const struct ist p1, const struct ist p2, const struct ist p3)
Christopher Faulet47596d32018-10-22 09:17:28 +0200185{
Christopher Faulet7b7d5072019-05-13 15:22:59 +0200186 struct htx_blk *blk;
Christopher Faulet47596d32018-10-22 09:17:28 +0200187
Christopher Faulet29f17582019-05-23 11:03:26 +0200188 blk = htx_get_first_blk(htx);
189 if (!blk || !htx_replace_stline(htx, blk, p1, p2, p3))
Christopher Faulet7b7d5072019-05-13 15:22:59 +0200190 return 0;
191 return 1;
Christopher Faulet47596d32018-10-22 09:17:28 +0200192}
193
Christopher Faulete010c802018-10-24 10:36:45 +0200194/* Replace the request method in the HTX message <htx> by <meth>. It returns 1
195 * on success, otherwise 0.
196 */
197int http_replace_req_meth(struct htx *htx, const struct ist meth)
198{
199 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200200 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100201 struct ist uri, vsn;
Christopher Faulete010c802018-10-24 10:36:45 +0200202
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100203 if (!sl)
204 return 0;
205
Christopher Faulete010c802018-10-24 10:36:45 +0200206 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100207 chunk_memcat(temp, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); /* uri */
208 uri = ist2(temp->area, HTX_SL_REQ_ULEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200209
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100210 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));
Christopher Faulete010c802018-10-24 10:36:45 +0200212
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100213 /* 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);
Christopher Faulete010c802018-10-24 10:36:45 +0200216}
217
218/* Replace the request uri in the HTX message <htx> by <uri>. It returns 1 on
219 * success, otherwise 0.
220 */
221int http_replace_req_uri(struct htx *htx, const struct ist uri)
222{
223 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200224 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100225 struct ist meth, vsn;
Christopher Faulete010c802018-10-24 10:36:45 +0200226
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100227 if (!sl)
228 return 0;
229
Christopher Faulete010c802018-10-24 10:36:45 +0200230 /* Start by copying old method and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100231 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
232 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200233
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100234 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));
Christopher Faulete010c802018-10-24 10:36:45 +0200236
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100237 /* create the new start line */
238 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200239}
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 */
244int http_replace_req_path(struct htx *htx, const struct ist path)
245{
246 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200247 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100248 struct ist meth, uri, vsn, p;
Christopher Faulete010c802018-10-24 10:36:45 +0200249 size_t plen = 0;
250
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100251 if (!sl)
252 return 0;
253
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100254 uri = htx_sl_req_uri(sl);
255 p = http_get_path(uri);
Christopher Faulete010c802018-10-24 10:36:45 +0200256 if (!p.ptr)
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100257 p = uri;
Christopher Faulete010c802018-10-24 10:36:45 +0200258 while (plen < p.len && *(p.ptr + plen) != '?')
259 plen++;
260
261 /* Start by copying old method and version and create the new uri */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100262 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
263 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200264
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100265 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 */
Christopher Faulete010c802018-10-24 10:36:45 +0200269 chunk_memcat(temp, path.ptr, path.len); /* uri: new path */
270 chunk_memcat(temp, p.ptr + plen, p.len - plen); /* uri: QS part */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100271 uri = ist2(temp->area + meth.len + vsn.len, uri.len - plen + path.len);
Christopher Faulete010c802018-10-24 10:36:45 +0200272
273 /* create the new start line */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100274 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200275}
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 */
281int http_replace_req_query(struct htx *htx, const struct ist query)
282{
283 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200284 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100285 struct ist meth, uri, vsn, q;
Christopher Faulete010c802018-10-24 10:36:45 +0200286 int offset = 1;
287
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100288 if (!sl)
289 return 0;
290
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100291 uri = htx_sl_req_uri(sl);
292 q = uri;
Christopher Faulete010c802018-10-24 10:36:45 +0200293 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 */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100309 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
310 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200311
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100312 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));
Christopher Faulete010c802018-10-24 10:36:45 +0200314
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100315 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);
Christopher Faulete010c802018-10-24 10:36:45 +0200318
319 /* create the new start line */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100320 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200321}
322
323/* Replace the response status in the HTX message <htx> by <status>. It returns
324 * 1 on success, otherwise 0.
325*/
326int http_replace_res_status(struct htx *htx, const struct ist status)
327{
328 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200329 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100330 struct ist vsn, reason;
Christopher Faulete010c802018-10-24 10:36:45 +0200331
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100332 if (!sl)
333 return 0;
334
Christopher Faulete010c802018-10-24 10:36:45 +0200335 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100336 chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
337 vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200338
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100339 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));
Christopher Faulete010c802018-10-24 10:36:45 +0200341
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100342 /* create the new start line */
343 sl->info.res.status = strl2ui(status.ptr, status.len);
344 return http_replace_stline(htx, vsn, status, reason);
Christopher Faulete010c802018-10-24 10:36:45 +0200345}
346
347/* Replace the response reason in the HTX message <htx> by <reason>. It returns
348 * 1 on success, otherwise 0.
349*/
350int http_replace_res_reason(struct htx *htx, const struct ist reason)
351{
352 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200353 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100354 struct ist vsn, status;
Christopher Faulete010c802018-10-24 10:36:45 +0200355
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100356 if (!sl)
357 return 0;
358
Christopher Faulete010c802018-10-24 10:36:45 +0200359 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100360 chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
361 vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200362
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100363 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));
Christopher Faulete010c802018-10-24 10:36:45 +0200365
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100366 /* create the new start line */
367 return http_replace_stline(htx, vsn, status, reason);
Christopher Faulete010c802018-10-24 10:36:45 +0200368}
369
Christopher Faulet47596d32018-10-22 09:17:28 +0200370/* 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 */
374int 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 */
406int 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 */
432int 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);
Christopher Faulet41dc8432019-06-18 09:49:16 +0200478 htx_change_blk_value_len(htx, blk, v.len-len);
Christopher Faulet47596d32018-10-22 09:17:28 +0200479
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}
Christopher Faulet7ff1cea2018-10-24 10:39:35 +0200487
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 */
500unsigned 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 */
564unsigned 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}
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100616
Christopher Faulet7509aba2020-11-05 22:43:41 +0100617int http_str_to_htx(struct buffer *buf, struct ist raw, char **errmsg)
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100618{
619 struct htx *htx;
620 struct htx_sl *sl;
621 struct h1m h1m;
Christopher Faulete4ab11b2019-06-11 15:05:37 +0200622 struct http_hdr hdrs[global.tune.max_http_hdr];
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100623 union h1_sl h1sl;
624 unsigned int flags = HTX_SL_F_IS_RESP;
625 int ret = 0;
626
Christopher Faulet4c853bc2019-07-22 16:49:30 +0200627 b_reset(buf);
628 if (!raw.len) {
629 buf->size = 0;
630 buf->area = malloc(raw.len);
631 return 1;
632 }
633
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100634 buf->size = global.tune.bufsize;
635 buf->area = (char *)malloc(buf->size);
636 if (!buf->area)
637 goto error;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100638
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);
Christopher Faulet7509aba2020-11-05 22:43:41 +0100643 if (ret <= 0) {
644 memprintf(errmsg, "unabled to parse headers (error offset: %d)", h1m.err_pos);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100645 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100646 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100647
Christopher Faulet7509aba2020-11-05 22:43:41 +0100648 if (unlikely(h1sl.st.v.len != 8)) {
649 memprintf(errmsg, "invalid http version (%.*s)", (int)h1sl.st.v.len, h1sl.st.v.ptr);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100650 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100651 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100652 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
Christopher Faulet7509aba2020-11-05 22:43:41 +0100656 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);
Christopher Faulet9869f932019-06-26 14:23:54 +0200659 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100660 }
Christopher Faulet9869f932019-06-26 14:23:54 +0200661
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200662 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
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100671 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;
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200675 if (h1m.flags & H1_MF_XFER_LEN) {
676 flags |= HTX_SL_F_XFER_LEN;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100677 if (h1m.flags & H1_MF_CHNK) {
678 memprintf(errmsg, "chunk-encoded payload not supported");
679 goto error;
680 }
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200681 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
Christopher Faulet046a2e22019-10-16 09:09:04 +0200687 flags |= HTX_SL_F_BODYLESS;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100688 }
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200689
Christopher Faulet7509aba2020-11-05 22:43:41 +0100690 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)) {
Christopher Fauletd57402d2020-11-06 09:33:36 +0100695 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.",
Willy Tarreau37f44ac2020-11-06 14:24:02 +0100700 (unsigned long)(raw.len - ret), (unsigned long)h1m.body_len);
Christopher Fauletd57402d2020-11-06 09:33:36 +0100701
702 for (i = 0; hdrs[i].n.len; i++) {
703 if (isteqi(hdrs[i].n, ist("content-length")))
704 hdrs[i].v = clen;
705 }
Christopher Faulet7509aba2020-11-05 22:43:41 +0100706 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100707
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);
Christopher Faulet7509aba2020-11-05 22:43:41 +0100710 if (!sl || !htx_add_all_headers(htx, hdrs)) {
711 memprintf(errmsg, "unable to add headers into the HTX message");
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100712 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100713 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100714 sl->info.res.status = h1sl.st.status;
715
Willy Tarreau0a7ef022019-05-28 10:30:11 +0200716 while (raw.len > ret) {
717 int sent = htx_add_data(htx, ist2(raw.ptr + ret, raw.len - ret));
Christopher Faulet7509aba2020-11-05 22:43:41 +0100718 if (!sent) {
719 memprintf(errmsg, "unable to add payload into the HTX message");
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100720 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100721 }
Willy Tarreau0a7ef022019-05-28 10:30:11 +0200722 ret += sent;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100723 }
Christopher Faulet9869f932019-06-26 14:23:54 +0200724
Christopher Faulet7509aba2020-11-05 22:43:41 +0100725 if (!htx_add_endof(htx, HTX_BLK_EOM)) {
726 memprintf(errmsg, "unable to add EOM into the HTX message");
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100727 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100728 }
Christopher Faulet9869f932019-06-26 14:23:54 +0200729
Christopher Faulet4c853bc2019-07-22 16:49:30 +0200730 return 1;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100731
732error:
733 if (buf->size)
734 free(buf->area);
Christopher Faulet4c853bc2019-07-22 16:49:30 +0200735 return 0;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100736}
737
738static int http_htx_init(void)
739{
740 struct proxy *px;
741 struct buffer chk;
742 struct ist raw;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100743 char *errmsg = NULL;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100744 int rc;
745 int err_code = 0;
746
747 for (px = proxies_list; px; px = px->next) {
Christopher Faulet8264f132019-07-15 14:43:38 +0200748 if (!(px->options2 & PR_O2_USE_HTX))
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100749 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]));
Christopher Faulet7509aba2020-11-05 22:43:41 +0100756 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);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100759 err_code |= ERR_ALERT | ERR_FATAL;
760 }
Christopher Faulet7509aba2020-11-05 22:43:41 +0100761 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
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100769 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]) {
Christopher Faulet7509aba2020-11-05 22:43:41 +0100776 ha_alert("Internal error: no default message defined for HTTP return code %d", rc);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100777 err_code |= ERR_ALERT | ERR_FATAL;
778 continue;
779 }
780
781 raw = ist2(http_err_msgs[rc], strlen(http_err_msgs[rc]));
Christopher Faulet7509aba2020-11-05 22:43:41 +0100782 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);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100785 err_code |= ERR_ALERT | ERR_FATAL;
786 }
Christopher Faulet7509aba2020-11-05 22:43:41 +0100787 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
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100794 htx_err_chunks[rc] = chk;
795 }
796end:
797 return err_code;
798}
799
800REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init);