blob: e445fef3d451a4c644c5ede979c06f0546bc08e4 [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 BUG_ON(htx->first == -1);
33 blk = htx_get_first_blk(htx);
Christopher Faulet297fbb42019-05-13 14:41:27 +020034 if (!blk)
35 return NULL;
Christopher Faulet29f17582019-05-23 11:03:26 +020036 BUG_ON(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 +020037 return htx_get_blk_ptr(htx, blk);
Christopher Faulet47596d32018-10-22 09:17:28 +020038}
39
Christopher Faulet733d3852020-02-07 16:39:41 +010040/* Returns the headers size in the HTX message */
41size_t http_get_hdrs_size(struct htx *htx)
42{
43 struct htx_blk *blk;
44 size_t sz = 0;
45
46 blk = htx_get_first_blk(htx);
47 if (!blk || htx_get_blk_type(blk) > HTX_BLK_EOH)
48 return sz;
49
50 for (; blk; blk = htx_get_next_blk(htx, blk)) {
51 sz += htx_get_blksz(blk);
52 if (htx_get_blk_type(blk) == HTX_BLK_EOH)
53 break;
54 }
55 return sz;
56}
57
Christopher Faulet47596d32018-10-22 09:17:28 +020058/* Finds the first or next occurrence of header <name> in the HTX message <htx>
59 * using the context <ctx>. This structure holds everything necessary to use the
60 * header and find next occurrence. If its <blk> member is NULL, the header is
61 * searched from the beginning. Otherwise, the next occurrence is returned. The
62 * function returns 1 when it finds a value, and 0 when there is no more. It is
63 * designed to work with headers defined as comma-separated lists. If <full> is
64 * set, it works on full-line headers in whose comma is not a delimiter but is
65 * part of the syntax. A special case, if ctx->value is NULL when searching for
66 * a new values of a header, the current header is rescanned. This allows
67 * rescanning after a header deletion.
68 */
69int http_find_header(const struct htx *htx, const struct ist name,
70 struct http_hdr_ctx *ctx, int full)
71{
72 struct htx_blk *blk = ctx->blk;
73 struct ist n, v;
74 enum htx_blk_type type;
Christopher Faulet47596d32018-10-22 09:17:28 +020075
76 if (blk) {
77 char *p;
78
Christopher Faulet47596d32018-10-22 09:17:28 +020079 if (!ctx->value.ptr)
80 goto rescan_hdr;
81 if (full)
82 goto next_blk;
83 v = htx_get_blk_value(htx, blk);
84 p = ctx->value.ptr + ctx->value.len + ctx->lws_after;
85 v.len -= (p - v.ptr);
86 v.ptr = p;
87 if (!v.len)
88 goto next_blk;
89 /* Skip comma */
90 if (*(v.ptr) == ',') {
91 v.ptr++;
92 v.len--;
93 }
94
95 goto return_hdr;
96 }
97
98 if (!htx->used)
99 return 0;
100
Christopher Fauleta3f15502019-05-13 15:27:23 +0200101 for (blk = htx_get_first_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
Christopher Faulet47596d32018-10-22 09:17:28 +0200102 rescan_hdr:
Christopher Faulet47596d32018-10-22 09:17:28 +0200103 type = htx_get_blk_type(blk);
Christopher Faulet573fe732018-11-28 16:55:12 +0100104 if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
105 break;
Christopher Faulet47596d32018-10-22 09:17:28 +0200106 if (type != HTX_BLK_HDR)
Christopher Faulet28f29c72019-04-30 17:55:45 +0200107 continue;
Christopher Faulet47596d32018-10-22 09:17:28 +0200108 if (name.len) {
109 /* If no name was passed, we want any header. So skip the comparison */
110 n = htx_get_blk_name(htx, blk);
111 if (!isteqi(n, name))
112 goto next_blk;
113 }
114 v = htx_get_blk_value(htx, blk);
115
116 return_hdr:
117 ctx->lws_before = 0;
118 ctx->lws_after = 0;
119 while (v.len && HTTP_IS_LWS(*v.ptr)) {
120 v.ptr++;
121 v.len--;
122 ctx->lws_before++;
123 }
124 if (!full)
125 v.len = http_find_hdr_value_end(v.ptr, v.ptr + v.len) - v.ptr;
126 while (v.len && HTTP_IS_LWS(*(v.ptr + v.len - 1))) {
127 v.len--;
128 ctx->lws_after++;
129 }
Christopher Faulet47596d32018-10-22 09:17:28 +0200130 ctx->blk = blk;
131 ctx->value = v;
132 return 1;
133
134 next_blk:
Christopher Faulet28f29c72019-04-30 17:55:45 +0200135 ;
Christopher Faulet47596d32018-10-22 09:17:28 +0200136 }
137
138 ctx->blk = NULL;
139 ctx->value = ist("");
140 ctx->lws_before = ctx->lws_after = 0;
141 return 0;
142}
143
144/* Adds a header block int the HTX message <htx>, just before the EOH block. It
145 * returns 1 on success, otherwise it returns 0.
146 */
147int http_add_header(struct htx *htx, const struct ist n, const struct ist v)
148{
149 struct htx_blk *blk;
150 enum htx_blk_type type = htx_get_tail_type(htx);
151 int32_t prev;
152
153 blk = htx_add_header(htx, n, v);
154 if (!blk)
155 return 0;
156
157 if (unlikely(type < HTX_BLK_EOH))
158 return 1;
159
160 /* <blk> is the head, swap it iteratively with its predecessor to place
161 * it just before the end-of-header block. So blocks remains ordered. */
Christopher Faulet29f17582019-05-23 11:03:26 +0200162 for (prev = htx_get_prev(htx, htx->tail); prev != htx->first; prev = htx_get_prev(htx, prev)) {
Christopher Faulet47596d32018-10-22 09:17:28 +0200163 struct htx_blk *pblk = htx_get_blk(htx, prev);
164 enum htx_blk_type type = htx_get_blk_type(pblk);
165
166 /* Swap .addr and .info fields */
167 blk->addr ^= pblk->addr; pblk->addr ^= blk->addr; blk->addr ^= pblk->addr;
168 blk->info ^= pblk->info; pblk->info ^= blk->info; blk->info ^= pblk->info;
169
170 if (blk->addr == pblk->addr)
171 blk->addr += htx_get_blksz(pblk);
Christopher Faulet47596d32018-10-22 09:17:28 +0200172
173 /* Stop when end-of-header is reached */
174 if (type == HTX_BLK_EOH)
175 break;
176
177 blk = pblk;
178 }
Christopher Faulet05aab642019-04-11 13:43:57 +0200179
Christopher Faulet47596d32018-10-22 09:17:28 +0200180 return 1;
181}
182
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100183/* Replaces parts of the start-line of the HTX message <htx>. It returns 1 on
Christopher Faulet29f17582019-05-23 11:03:26 +0200184 * success, otherwise it returns 0.
Christopher Faulet47596d32018-10-22 09:17:28 +0200185 */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100186int 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 +0200187{
Christopher Faulet7b7d5072019-05-13 15:22:59 +0200188 struct htx_blk *blk;
Christopher Faulet47596d32018-10-22 09:17:28 +0200189
Christopher Faulet29f17582019-05-23 11:03:26 +0200190 blk = htx_get_first_blk(htx);
191 if (!blk || !htx_replace_stline(htx, blk, p1, p2, p3))
Christopher Faulet7b7d5072019-05-13 15:22:59 +0200192 return 0;
193 return 1;
Christopher Faulet47596d32018-10-22 09:17:28 +0200194}
195
Christopher Faulete010c802018-10-24 10:36:45 +0200196/* Replace the request method in the HTX message <htx> by <meth>. It returns 1
197 * on success, otherwise 0.
198 */
199int http_replace_req_meth(struct htx *htx, const struct ist meth)
200{
201 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200202 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100203 struct ist uri, vsn;
Christopher Faulete010c802018-10-24 10:36:45 +0200204
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100205 if (!sl)
206 return 0;
207
Christopher Faulete010c802018-10-24 10:36:45 +0200208 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100209 chunk_memcat(temp, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); /* uri */
210 uri = ist2(temp->area, HTX_SL_REQ_ULEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200211
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100212 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
213 vsn = ist2(temp->area + uri.len, HTX_SL_REQ_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200214
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100215 /* create the new start line */
216 sl->info.req.meth = find_http_meth(meth.ptr, meth.len);
217 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200218}
219
220/* Replace the request uri in the HTX message <htx> by <uri>. It returns 1 on
221 * success, otherwise 0.
222 */
223int http_replace_req_uri(struct htx *htx, const struct ist uri)
224{
225 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200226 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100227 struct ist meth, vsn;
Christopher Faulete010c802018-10-24 10:36:45 +0200228
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100229 if (!sl)
230 return 0;
231
Christopher Faulete010c802018-10-24 10:36:45 +0200232 /* Start by copying old method and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100233 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
234 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200235
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100236 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
237 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200238
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100239 /* create the new start line */
240 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200241}
242
243/* Replace the request path in the HTX message <htx> by <path>. The host part
244 * and the query string are preserved. It returns 1 on success, otherwise 0.
245 */
246int http_replace_req_path(struct htx *htx, const struct ist path)
247{
248 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200249 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100250 struct ist meth, uri, vsn, p;
Christopher Faulete010c802018-10-24 10:36:45 +0200251 size_t plen = 0;
252
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100253 if (!sl)
254 return 0;
255
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100256 uri = htx_sl_req_uri(sl);
257 p = http_get_path(uri);
Christopher Faulete010c802018-10-24 10:36:45 +0200258 if (!p.ptr)
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100259 p = uri;
Christopher Faulete010c802018-10-24 10:36:45 +0200260 while (plen < p.len && *(p.ptr + plen) != '?')
261 plen++;
262
263 /* Start by copying old method and version and create the new uri */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100264 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
265 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200266
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100267 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
268 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
269
270 chunk_memcat(temp, uri.ptr, p.ptr - uri.ptr); /* uri: host part */
Christopher Faulete010c802018-10-24 10:36:45 +0200271 chunk_memcat(temp, path.ptr, path.len); /* uri: new path */
272 chunk_memcat(temp, p.ptr + plen, p.len - plen); /* uri: QS part */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100273 uri = ist2(temp->area + meth.len + vsn.len, uri.len - plen + path.len);
Christopher Faulete010c802018-10-24 10:36:45 +0200274
275 /* create the new start line */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100276 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200277}
278
279/* Replace the request query-string in the HTX message <htx> by <query>. The
280 * host part and the path are preserved. It returns 1 on success, otherwise
281 * 0.
282 */
283int http_replace_req_query(struct htx *htx, const struct ist query)
284{
285 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200286 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100287 struct ist meth, uri, vsn, q;
Christopher Faulete010c802018-10-24 10:36:45 +0200288 int offset = 1;
289
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100290 if (!sl)
291 return 0;
292
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100293 uri = htx_sl_req_uri(sl);
294 q = uri;
Christopher Faulete010c802018-10-24 10:36:45 +0200295 while (q.len > 0 && *(q.ptr) != '?') {
296 q.ptr++;
297 q.len--;
298 }
299
300 /* skip the question mark or indicate that we must insert it
301 * (but only if the format string is not empty then).
302 */
303 if (q.len) {
304 q.ptr++;
305 q.len--;
306 }
307 else if (query.len > 1)
308 offset = 0;
309
310 /* Start by copying old method and version and create the new uri */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100311 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
312 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200313
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100314 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
315 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200316
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100317 chunk_memcat(temp, uri.ptr, q.ptr - uri.ptr); /* uri: host + path part */
318 chunk_memcat(temp, query.ptr + offset, query.len - offset); /* uri: new QS */
319 uri = ist2(temp->area + meth.len + vsn.len, uri.len - q.len + query.len - offset);
Christopher Faulete010c802018-10-24 10:36:45 +0200320
321 /* create the new start line */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100322 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200323}
324
325/* Replace the response status in the HTX message <htx> by <status>. It returns
326 * 1 on success, otherwise 0.
327*/
328int http_replace_res_status(struct htx *htx, const struct ist status)
329{
330 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200331 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100332 struct ist vsn, reason;
Christopher Faulete010c802018-10-24 10:36:45 +0200333
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100334 if (!sl)
335 return 0;
336
Christopher Faulete010c802018-10-24 10:36:45 +0200337 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100338 chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
339 vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200340
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100341 chunk_memcat(temp, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); /* reason */
342 reason = ist2(temp->area + vsn.len, HTX_SL_RES_RLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200343
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100344 /* create the new start line */
345 sl->info.res.status = strl2ui(status.ptr, status.len);
346 return http_replace_stline(htx, vsn, status, reason);
Christopher Faulete010c802018-10-24 10:36:45 +0200347}
348
349/* Replace the response reason in the HTX message <htx> by <reason>. It returns
350 * 1 on success, otherwise 0.
351*/
352int http_replace_res_reason(struct htx *htx, const struct ist reason)
353{
354 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200355 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100356 struct ist vsn, status;
Christopher Faulete010c802018-10-24 10:36:45 +0200357
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100358 if (!sl)
359 return 0;
360
Christopher Faulete010c802018-10-24 10:36:45 +0200361 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100362 chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
363 vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200364
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100365 chunk_memcat(temp, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); /* code */
366 status = ist2(temp->area + vsn.len, HTX_SL_RES_CLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200367
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100368 /* create the new start line */
369 return http_replace_stline(htx, vsn, status, reason);
Christopher Faulete010c802018-10-24 10:36:45 +0200370}
371
Christopher Faulet47596d32018-10-22 09:17:28 +0200372/* Replaces a part of a header value referenced in the context <ctx> by
373 * <data>. It returns 1 on success, otherwise it returns 0. The context is
374 * updated if necessary.
375 */
376int http_replace_header_value(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist data)
377{
378 struct htx_blk *blk = ctx->blk;
379 char *start;
380 struct ist v;
381 uint32_t len, off;
382
383 if (!blk)
384 return 0;
385
386 v = htx_get_blk_value(htx, blk);
387 start = ctx->value.ptr - ctx->lws_before;
388 len = ctx->lws_before + ctx->value.len + ctx->lws_after;
389 off = start - v.ptr;
390
391 blk = htx_replace_blk_value(htx, blk, ist2(start, len), data);
392 if (!blk)
393 return 0;
394
395 v = htx_get_blk_value(htx, blk);
396 ctx->blk = blk;
397 ctx->value.ptr = v.ptr + off;
398 ctx->value.len = data.len;
399 ctx->lws_before = ctx->lws_after = 0;
400
401 return 1;
402}
403
404/* Fully replaces a header referenced in the context <ctx> by the name <name>
405 * with the value <value>. It returns 1 on success, otherwise it returns 0. The
406 * context is updated if necessary.
407 */
408int http_replace_header(struct htx *htx, struct http_hdr_ctx *ctx,
409 const struct ist name, const struct ist value)
410{
411 struct htx_blk *blk = ctx->blk;
412
413 if (!blk)
414 return 0;
415
416 blk = htx_replace_header(htx, blk, name, value);
417 if (!blk)
418 return 0;
419
420 ctx->blk = blk;
421 ctx->value = ist(NULL);
422 ctx->lws_before = ctx->lws_after = 0;
423
424 return 1;
425}
426
427/* Remove one value of a header. This only works on a <ctx> returned by
428 * http_find_header function. The value is removed, as well as surrounding commas
429 * if any. If the removed value was alone, the whole header is removed. The
430 * <ctx> is always updated accordingly, as well as the HTX message <htx>. It
431 * returns 1 on success. Otherwise, it returns 0. The <ctx> is always left in a
432 * form that can be handled by http_find_header() to find next occurrence.
433 */
434int http_remove_header(struct htx *htx, struct http_hdr_ctx *ctx)
435{
436 struct htx_blk *blk = ctx->blk;
437 char *start;
438 struct ist v;
439 uint32_t len;
440
441 if (!blk)
442 return 0;
443
444 start = ctx->value.ptr - ctx->lws_before;
445 len = ctx->lws_before + ctx->value.len + ctx->lws_after;
446
447 v = htx_get_blk_value(htx, blk);
448 if (len == v.len) {
449 blk = htx_remove_blk(htx, blk);
450 if (blk || !htx->used) {
451 ctx->blk = blk;
452 ctx->value = ist2(NULL, 0);
453 ctx->lws_before = ctx->lws_after = 0;
454 }
455 else {
456 ctx->blk = htx_get_blk(htx, htx->tail);
457 ctx->value = htx_get_blk_value(htx, ctx->blk);
458 ctx->lws_before = ctx->lws_after = 0;
459 }
460 return 1;
461 }
462
463 /* This was not the only value of this header. We have to remove the
464 * part pointed by ctx->value. If it is the last entry of the list, we
465 * remove the last separator.
466 */
467 if (start == v.ptr) {
468 /* It's the first header part but not the only one. So remove
469 * the comma after it. */
470 len++;
471 }
472 else {
473 /* There is at least one header part before the removed one. So
474 * remove the comma between them. */
475 start--;
476 len++;
477 }
478 /* Update the block content and its len */
479 memmove(start, start+len, v.len-len);
Christopher Faulet41dc8432019-06-18 09:49:16 +0200480 htx_change_blk_value_len(htx, blk, v.len-len);
Christopher Faulet47596d32018-10-22 09:17:28 +0200481
482 /* Finally update the ctx */
483 ctx->value.ptr = start;
484 ctx->value.len = 0;
485 ctx->lws_before = ctx->lws_after = 0;
486
487 return 1;
488}
Christopher Faulet7ff1cea2018-10-24 10:39:35 +0200489
490
491/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
492 * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
493 * performed over the whole headers. Otherwise it must contain a valid header
494 * context, initialised with ctx->blk=NULL for the first lookup in a series. If
495 * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
496 * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
497 * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
498 * -1. The value fetch stops at commas, so this function is suited for use with
499 * list headers.
500 * The return value is 0 if nothing was found, or non-zero otherwise.
501 */
502unsigned int http_get_htx_hdr(const struct htx *htx, const struct ist hdr,
503 int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
504{
505 struct http_hdr_ctx local_ctx;
506 struct ist val_hist[MAX_HDR_HISTORY];
507 unsigned int hist_idx;
508 int found;
509
510 if (!ctx) {
511 local_ctx.blk = NULL;
512 ctx = &local_ctx;
513 }
514
515 if (occ >= 0) {
516 /* search from the beginning */
517 while (http_find_header(htx, hdr, ctx, 0)) {
518 occ--;
519 if (occ <= 0) {
520 *vptr = ctx->value.ptr;
521 *vlen = ctx->value.len;
522 return 1;
523 }
524 }
525 return 0;
526 }
527
528 /* negative occurrence, we scan all the list then walk back */
529 if (-occ > MAX_HDR_HISTORY)
530 return 0;
531
532 found = hist_idx = 0;
533 while (http_find_header(htx, hdr, ctx, 0)) {
534 val_hist[hist_idx] = ctx->value;
535 if (++hist_idx >= MAX_HDR_HISTORY)
536 hist_idx = 0;
537 found++;
538 }
539 if (-occ > found)
540 return 0;
541
542 /* OK now we have the last occurrence in [hist_idx-1], and we need to
543 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
544 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
545 * to remain in the 0..9 range.
546 */
547 hist_idx += occ + MAX_HDR_HISTORY;
548 if (hist_idx >= MAX_HDR_HISTORY)
549 hist_idx -= MAX_HDR_HISTORY;
550 *vptr = val_hist[hist_idx].ptr;
551 *vlen = val_hist[hist_idx].len;
552 return 1;
553}
554
555/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
556 * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
557 * performed over the whole headers. Otherwise it must contain a valid header
558 * context, initialised with ctx->blk=NULL for the first lookup in a series. If
559 * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
560 * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
561 * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
562 * -1. This function differs from http_get_hdr() in that it only returns full
563 * line header values and does not stop at commas.
564 * The return value is 0 if nothing was found, or non-zero otherwise.
565 */
566unsigned int http_get_htx_fhdr(const struct htx *htx, const struct ist hdr,
567 int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
568{
569 struct http_hdr_ctx local_ctx;
570 struct ist val_hist[MAX_HDR_HISTORY];
571 unsigned int hist_idx;
572 int found;
573
574 if (!ctx) {
575 local_ctx.blk = NULL;
576 ctx = &local_ctx;
577 }
578
579 if (occ >= 0) {
580 /* search from the beginning */
581 while (http_find_header(htx, hdr, ctx, 1)) {
582 occ--;
583 if (occ <= 0) {
584 *vptr = ctx->value.ptr;
585 *vlen = ctx->value.len;
586 return 1;
587 }
588 }
589 return 0;
590 }
591
592 /* negative occurrence, we scan all the list then walk back */
593 if (-occ > MAX_HDR_HISTORY)
594 return 0;
595
596 found = hist_idx = 0;
597 while (http_find_header(htx, hdr, ctx, 1)) {
598 val_hist[hist_idx] = ctx->value;
599 if (++hist_idx >= MAX_HDR_HISTORY)
600 hist_idx = 0;
601 found++;
602 }
603 if (-occ > found)
604 return 0;
605
606 /* OK now we have the last occurrence in [hist_idx-1], and we need to
607 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
608 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
609 * to remain in the 0..9 range.
610 */
611 hist_idx += occ + MAX_HDR_HISTORY;
612 if (hist_idx >= MAX_HDR_HISTORY)
613 hist_idx -= MAX_HDR_HISTORY;
614 *vptr = val_hist[hist_idx].ptr;
615 *vlen = val_hist[hist_idx].len;
616 return 1;
617}
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100618
Christopher Faulet7509aba2020-11-05 22:43:41 +0100619int http_str_to_htx(struct buffer *buf, struct ist raw, char **errmsg)
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100620{
621 struct htx *htx;
622 struct htx_sl *sl;
623 struct h1m h1m;
Christopher Faulete4ab11b2019-06-11 15:05:37 +0200624 struct http_hdr hdrs[global.tune.max_http_hdr];
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100625 union h1_sl h1sl;
626 unsigned int flags = HTX_SL_F_IS_RESP;
627 int ret = 0;
628
Christopher Faulet4c853bc2019-07-22 16:49:30 +0200629 b_reset(buf);
630 if (!raw.len) {
631 buf->size = 0;
632 buf->area = malloc(raw.len);
633 return 1;
634 }
635
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100636 buf->size = global.tune.bufsize;
637 buf->area = (char *)malloc(buf->size);
638 if (!buf->area)
639 goto error;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100640
641 h1m_init_res(&h1m);
642 h1m.flags |= H1_MF_NO_PHDR;
643 ret = h1_headers_to_hdr_list(raw.ptr, raw.ptr + raw.len,
644 hdrs, sizeof(hdrs)/sizeof(hdrs[0]), &h1m, &h1sl);
Christopher Faulet7509aba2020-11-05 22:43:41 +0100645 if (ret <= 0) {
646 memprintf(errmsg, "unabled to parse headers (error offset: %d)", h1m.err_pos);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100647 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100648 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100649
Christopher Faulet7509aba2020-11-05 22:43:41 +0100650 if (unlikely(h1sl.st.v.len != 8)) {
651 memprintf(errmsg, "invalid http version (%.*s)", (int)h1sl.st.v.len, h1sl.st.v.ptr);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100652 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100653 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100654 if ((*(h1sl.st.v.ptr + 5) > '1') ||
655 ((*(h1sl.st.v.ptr + 5) == '1') && (*(h1sl.st.v.ptr + 7) >= '1')))
656 h1m.flags |= H1_MF_VER_11;
657
Christopher Faulet7509aba2020-11-05 22:43:41 +0100658 if (h1sl.st.status < 200 && (h1sl.st.status == 100 || h1sl.st.status >= 102)) {
659 memprintf(errmsg, "invalid http status code for an error message (%u)",
660 h1sl.st.status);
Christopher Faulet9869f932019-06-26 14:23:54 +0200661 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100662 }
Christopher Faulet9869f932019-06-26 14:23:54 +0200663
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200664 if (h1sl.st.status == 204 || h1sl.st.status == 304) {
665 /* Responses known to have no body. */
666 h1m.flags &= ~(H1_MF_CLEN|H1_MF_CHNK);
667 h1m.flags |= H1_MF_XFER_LEN;
668 h1m.curr_len = h1m.body_len = 0;
669 }
670 else if (h1m.flags & (H1_MF_CLEN|H1_MF_CHNK))
671 h1m.flags |= H1_MF_XFER_LEN;
672
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100673 if (h1m.flags & H1_MF_VER_11)
674 flags |= HTX_SL_F_VER_11;
675 if (h1m.flags & H1_MF_XFER_ENC)
676 flags |= HTX_SL_F_XFER_ENC;
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200677 if (h1m.flags & H1_MF_XFER_LEN) {
678 flags |= HTX_SL_F_XFER_LEN;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100679 if (h1m.flags & H1_MF_CHNK) {
680 memprintf(errmsg, "chunk-encoded payload not supported");
681 goto error;
682 }
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200683 else if (h1m.flags & H1_MF_CLEN) {
684 flags |= HTX_SL_F_CLEN;
685 if (h1m.body_len == 0)
686 flags |= HTX_SL_F_BODYLESS;
687 }
688 else
Christopher Faulet046a2e22019-10-16 09:09:04 +0200689 flags |= HTX_SL_F_BODYLESS;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100690 }
Christopher Fauleta42e6c32020-10-09 08:50:26 +0200691
Christopher Faulet7509aba2020-11-05 22:43:41 +0100692 if ((flags & HTX_SL_F_BODYLESS) && raw.len > ret) {
693 memprintf(errmsg, "message payload not expected");
694 goto error;
695 }
696 if ((flags & HTX_SL_F_CLEN) && h1m.body_len != (raw.len - ret)) {
Christopher Fauletd57402d2020-11-06 09:33:36 +0100697 struct ist clen = ist(ultoa(raw.len - ret));
698 int i;
699
700 memprintf(errmsg, "payload size does not match the announced content-length (%lu != %lu)."
701 " 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 +0100702 (unsigned long)(raw.len - ret), (unsigned long)h1m.body_len);
Christopher Fauletd57402d2020-11-06 09:33:36 +0100703
704 for (i = 0; hdrs[i].n.len; i++) {
705 if (isteqi(hdrs[i].n, ist("content-length")))
706 hdrs[i].v = clen;
707 }
Christopher Faulet7509aba2020-11-05 22:43:41 +0100708 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100709
710 htx = htx_from_buf(buf);
711 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 +0100712 if (!sl || !htx_add_all_headers(htx, hdrs)) {
713 memprintf(errmsg, "unable to add headers into the HTX message");
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100714 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100715 }
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100716 sl->info.res.status = h1sl.st.status;
717
Willy Tarreau0a7ef022019-05-28 10:30:11 +0200718 while (raw.len > ret) {
719 int sent = htx_add_data(htx, ist2(raw.ptr + ret, raw.len - ret));
Christopher Faulet7509aba2020-11-05 22:43:41 +0100720 if (!sent) {
721 memprintf(errmsg, "unable to add payload into the HTX message");
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100722 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100723 }
Willy Tarreau0a7ef022019-05-28 10:30:11 +0200724 ret += sent;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100725 }
Christopher Faulet9869f932019-06-26 14:23:54 +0200726
Christopher Faulet7509aba2020-11-05 22:43:41 +0100727 if (!htx_add_endof(htx, HTX_BLK_EOM)) {
728 memprintf(errmsg, "unable to add EOM into the HTX message");
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100729 goto error;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100730 }
Christopher Faulet9869f932019-06-26 14:23:54 +0200731
Christopher Faulet4c853bc2019-07-22 16:49:30 +0200732 return 1;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100733
734error:
735 if (buf->size)
736 free(buf->area);
Christopher Faulet4c853bc2019-07-22 16:49:30 +0200737 return 0;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100738}
739
740static int http_htx_init(void)
741{
742 struct proxy *px;
743 struct buffer chk;
744 struct ist raw;
Christopher Faulet7509aba2020-11-05 22:43:41 +0100745 char *errmsg = NULL;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100746 int rc;
747 int err_code = 0;
748
749 for (px = proxies_list; px; px = px->next) {
Christopher Faulet8264f132019-07-15 14:43:38 +0200750 if (!(px->options2 & PR_O2_USE_HTX))
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100751 continue;
752
753 for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
754 if (!b_data(&px->errmsg[rc]))
755 continue;
756
757 raw = ist2(b_head(&px->errmsg[rc]), b_data(&px->errmsg[rc]));
Christopher Faulet7509aba2020-11-05 22:43:41 +0100758 if (!http_str_to_htx(&chk, raw, &errmsg)) {
759 ha_alert("config: %s '%s': invalid message for HTTP return code %d: %s.\n",
760 proxy_type_str(px), px->id, http_err_codes[rc], errmsg);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100761 err_code |= ERR_ALERT | ERR_FATAL;
762 }
Christopher Faulet7509aba2020-11-05 22:43:41 +0100763 else if (errmsg)
764 ha_warning("config: %s '%s': invalid default message for HTTP return code %d: %s.\n",
765 proxy_type_str(px), px->id, http_err_codes[rc], errmsg);
766
767 /* Reset errmsg */
768 free(errmsg);
769 errmsg = NULL;
770
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100771 chunk_destroy(&px->errmsg[rc]);
772 px->errmsg[rc] = chk;
773 }
774 }
775
776 for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
777 if (!http_err_msgs[rc]) {
Christopher Faulet7509aba2020-11-05 22:43:41 +0100778 ha_alert("Internal error: no default message defined for HTTP return code %d", rc);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100779 err_code |= ERR_ALERT | ERR_FATAL;
780 continue;
781 }
782
783 raw = ist2(http_err_msgs[rc], strlen(http_err_msgs[rc]));
Christopher Faulet7509aba2020-11-05 22:43:41 +0100784 if (!http_str_to_htx(&chk, raw, &errmsg)) {
785 ha_alert("Internal error: invalid default message for HTTP return code %d: %s.\n",
786 http_err_codes[rc], errmsg);
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100787 err_code |= ERR_ALERT | ERR_FATAL;
788 }
Christopher Faulet7509aba2020-11-05 22:43:41 +0100789 else if (errmsg)
790 ha_warning("invalid default message for HTTP return code %d: %s.\n", http_err_codes[rc], errmsg);
791
792 /* Reset errmsg */
793 free(errmsg);
794 errmsg = NULL;
795
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100796 htx_err_chunks[rc] = chk;
797 }
798end:
799 return err_code;
800}
801
802REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init);