blob: 2b368a4ec8bf561e3049858983ef64eb6b492cbf [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 */
Christopher Faulet5031ef52020-01-15 11:22:07 +010012#include <sys/types.h>
13#include <sys/stat.h>
14#include <fcntl.h>
15#include <unistd.h>
16
17#include <types/global.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020018
19#include <common/config.h>
Christopher Faulet29f17582019-05-23 11:03:26 +020020#include <common/debug.h>
Christopher Fauleta7b677c2018-11-29 16:48:49 +010021#include <common/cfgparse.h>
Willy Tarreauafba57a2018-12-11 13:44:24 +010022#include <common/h1.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020023#include <common/http.h>
Willy Tarreaub96b77e2018-12-11 10:22:41 +010024#include <common/htx.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020025
Christopher Faulet29f72842019-12-11 15:52:32 +010026#include <proto/arg.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020027#include <proto/http_htx.h>
Christopher Faulet29f72842019-12-11 15:52:32 +010028#include <proto/http_fetch.h>
29#include <proto/sample.h>
Christopher Faulet47596d32018-10-22 09:17:28 +020030
Christopher Fauletf7346382019-07-17 22:02:08 +020031struct buffer http_err_chunks[HTTP_ERR_SIZE];
Christopher Fauleta7b677c2018-11-29 16:48:49 +010032
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +020033static int http_update_authority(struct htx *htx, struct htx_sl *sl, const struct ist host);
34static int http_update_host(struct htx *htx, struct htx_sl *sl, const struct ist uri);
35
Christopher Faulet297fbb42019-05-13 14:41:27 +020036/* Returns the next unporocessed start line in the HTX message. It returns NULL
Christopher Faulet29f17582019-05-23 11:03:26 +020037 * if the start-line is undefined (first == -1). Otherwise, it returns the
Christopher Faulet297fbb42019-05-13 14:41:27 +020038 * pointer on the htx_sl structure.
Christopher Faulet47596d32018-10-22 09:17:28 +020039 */
Christopher Faulet297fbb42019-05-13 14:41:27 +020040struct htx_sl *http_get_stline(struct htx *htx)
Christopher Faulet47596d32018-10-22 09:17:28 +020041{
Christopher Faulet297fbb42019-05-13 14:41:27 +020042 struct htx_blk *blk;
Christopher Faulet573fe732018-11-28 16:55:12 +010043
Christopher Faulet29f17582019-05-23 11:03:26 +020044 BUG_ON(htx->first == -1);
45 blk = htx_get_first_blk(htx);
Christopher Faulet297fbb42019-05-13 14:41:27 +020046 if (!blk)
47 return NULL;
Christopher Faulet29f17582019-05-23 11:03:26 +020048 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 +020049 return htx_get_blk_ptr(htx, blk);
Christopher Faulet47596d32018-10-22 09:17:28 +020050}
51
52/* Finds the first or next occurrence of header <name> in the HTX message <htx>
53 * using the context <ctx>. This structure holds everything necessary to use the
54 * header and find next occurrence. If its <blk> member is NULL, the header is
55 * searched from the beginning. Otherwise, the next occurrence is returned. The
56 * function returns 1 when it finds a value, and 0 when there is no more. It is
57 * designed to work with headers defined as comma-separated lists. If <full> is
58 * set, it works on full-line headers in whose comma is not a delimiter but is
59 * part of the syntax. A special case, if ctx->value is NULL when searching for
60 * a new values of a header, the current header is rescanned. This allows
61 * rescanning after a header deletion.
62 */
63int http_find_header(const struct htx *htx, const struct ist name,
64 struct http_hdr_ctx *ctx, int full)
65{
66 struct htx_blk *blk = ctx->blk;
67 struct ist n, v;
68 enum htx_blk_type type;
Christopher Faulet47596d32018-10-22 09:17:28 +020069
70 if (blk) {
71 char *p;
72
Christopher Faulet47596d32018-10-22 09:17:28 +020073 if (!ctx->value.ptr)
74 goto rescan_hdr;
75 if (full)
76 goto next_blk;
77 v = htx_get_blk_value(htx, blk);
78 p = ctx->value.ptr + ctx->value.len + ctx->lws_after;
79 v.len -= (p - v.ptr);
80 v.ptr = p;
81 if (!v.len)
82 goto next_blk;
83 /* Skip comma */
84 if (*(v.ptr) == ',') {
85 v.ptr++;
86 v.len--;
87 }
88
89 goto return_hdr;
90 }
91
Christopher Faulet192c6a22019-06-11 16:32:24 +020092 if (htx_is_empty(htx))
Christopher Faulet47596d32018-10-22 09:17:28 +020093 return 0;
94
Christopher Fauleta3f15502019-05-13 15:27:23 +020095 for (blk = htx_get_first_blk(htx); blk; blk = htx_get_next_blk(htx, blk)) {
Christopher Faulet47596d32018-10-22 09:17:28 +020096 rescan_hdr:
Christopher Faulet47596d32018-10-22 09:17:28 +020097 type = htx_get_blk_type(blk);
Christopher Faulet573fe732018-11-28 16:55:12 +010098 if (type == HTX_BLK_EOH || type == HTX_BLK_EOM)
99 break;
Christopher Faulet47596d32018-10-22 09:17:28 +0200100 if (type != HTX_BLK_HDR)
Christopher Faulet28f29c72019-04-30 17:55:45 +0200101 continue;
Christopher Faulet47596d32018-10-22 09:17:28 +0200102 if (name.len) {
103 /* If no name was passed, we want any header. So skip the comparison */
104 n = htx_get_blk_name(htx, blk);
105 if (!isteqi(n, name))
106 goto next_blk;
107 }
108 v = htx_get_blk_value(htx, blk);
109
110 return_hdr:
111 ctx->lws_before = 0;
112 ctx->lws_after = 0;
113 while (v.len && HTTP_IS_LWS(*v.ptr)) {
114 v.ptr++;
115 v.len--;
116 ctx->lws_before++;
117 }
118 if (!full)
119 v.len = http_find_hdr_value_end(v.ptr, v.ptr + v.len) - v.ptr;
120 while (v.len && HTTP_IS_LWS(*(v.ptr + v.len - 1))) {
121 v.len--;
122 ctx->lws_after++;
123 }
Christopher Faulet47596d32018-10-22 09:17:28 +0200124 ctx->blk = blk;
125 ctx->value = v;
126 return 1;
127
128 next_blk:
Christopher Faulet28f29c72019-04-30 17:55:45 +0200129 ;
Christopher Faulet47596d32018-10-22 09:17:28 +0200130 }
131
132 ctx->blk = NULL;
133 ctx->value = ist("");
134 ctx->lws_before = ctx->lws_after = 0;
135 return 0;
136}
137
138/* Adds a header block int the HTX message <htx>, just before the EOH block. It
139 * returns 1 on success, otherwise it returns 0.
140 */
141int http_add_header(struct htx *htx, const struct ist n, const struct ist v)
142{
143 struct htx_blk *blk;
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200144 struct htx_sl *sl;
Christopher Faulet47596d32018-10-22 09:17:28 +0200145 enum htx_blk_type type = htx_get_tail_type(htx);
146 int32_t prev;
147
148 blk = htx_add_header(htx, n, v);
149 if (!blk)
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200150 goto fail;
Christopher Faulet47596d32018-10-22 09:17:28 +0200151
152 if (unlikely(type < HTX_BLK_EOH))
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200153 goto end;
Christopher Faulet47596d32018-10-22 09:17:28 +0200154
155 /* <blk> is the head, swap it iteratively with its predecessor to place
156 * it just before the end-of-header block. So blocks remains ordered. */
Christopher Faulet29f17582019-05-23 11:03:26 +0200157 for (prev = htx_get_prev(htx, htx->tail); prev != htx->first; prev = htx_get_prev(htx, prev)) {
Christopher Faulet47596d32018-10-22 09:17:28 +0200158 struct htx_blk *pblk = htx_get_blk(htx, prev);
159 enum htx_blk_type type = htx_get_blk_type(pblk);
160
161 /* Swap .addr and .info fields */
162 blk->addr ^= pblk->addr; pblk->addr ^= blk->addr; blk->addr ^= pblk->addr;
163 blk->info ^= pblk->info; pblk->info ^= blk->info; blk->info ^= pblk->info;
164
165 if (blk->addr == pblk->addr)
166 blk->addr += htx_get_blksz(pblk);
Christopher Faulet47596d32018-10-22 09:17:28 +0200167
168 /* Stop when end-of-header is reached */
169 if (type == HTX_BLK_EOH)
170 break;
171
172 blk = pblk;
173 }
Christopher Faulet05aab642019-04-11 13:43:57 +0200174
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200175 end:
176 sl = http_get_stline(htx);
177 if (sl && (sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteq(n, ist("host"))) {
178 if (!http_update_authority(htx, sl, v))
179 goto fail;
180 }
Christopher Faulet47596d32018-10-22 09:17:28 +0200181 return 1;
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200182
183 fail:
184 return 0;
Christopher Faulet47596d32018-10-22 09:17:28 +0200185}
186
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100187/* Replaces parts of the start-line of the HTX message <htx>. It returns 1 on
Christopher Faulet29f17582019-05-23 11:03:26 +0200188 * success, otherwise it returns 0.
Christopher Faulet47596d32018-10-22 09:17:28 +0200189 */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100190int 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 +0200191{
Christopher Faulet7b7d5072019-05-13 15:22:59 +0200192 struct htx_blk *blk;
Christopher Faulet47596d32018-10-22 09:17:28 +0200193
Christopher Faulet29f17582019-05-23 11:03:26 +0200194 blk = htx_get_first_blk(htx);
195 if (!blk || !htx_replace_stline(htx, blk, p1, p2, p3))
Christopher Faulet7b7d5072019-05-13 15:22:59 +0200196 return 0;
197 return 1;
Christopher Faulet47596d32018-10-22 09:17:28 +0200198}
199
Christopher Faulete010c802018-10-24 10:36:45 +0200200/* Replace the request method in the HTX message <htx> by <meth>. It returns 1
201 * on success, otherwise 0.
202 */
203int http_replace_req_meth(struct htx *htx, const struct ist meth)
204{
205 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200206 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100207 struct ist uri, vsn;
Christopher Faulete010c802018-10-24 10:36:45 +0200208
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100209 if (!sl)
210 return 0;
211
Christopher Faulete010c802018-10-24 10:36:45 +0200212 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100213 chunk_memcat(temp, HTX_SL_REQ_UPTR(sl), HTX_SL_REQ_ULEN(sl)); /* uri */
214 uri = ist2(temp->area, HTX_SL_REQ_ULEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200215
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100216 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
217 vsn = ist2(temp->area + uri.len, HTX_SL_REQ_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200218
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100219 /* create the new start line */
220 sl->info.req.meth = find_http_meth(meth.ptr, meth.len);
221 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200222}
223
224/* Replace the request uri in the HTX message <htx> by <uri>. It returns 1 on
225 * success, otherwise 0.
226 */
227int http_replace_req_uri(struct htx *htx, const struct ist uri)
228{
229 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200230 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100231 struct ist meth, vsn;
Christopher Faulete010c802018-10-24 10:36:45 +0200232
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100233 if (!sl)
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200234 goto fail;
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100235
Christopher Faulete010c802018-10-24 10:36:45 +0200236 /* Start by copying old method and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100237 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
238 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200239
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100240 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
241 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200242
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100243 /* create the new start line */
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200244 if (!http_replace_stline(htx, meth, uri, vsn))
245 goto fail;
246
247 sl = http_get_stline(htx);
248 if (!http_update_host(htx, sl, uri))
249 goto fail;
250
251 return 1;
252 fail:
253 return 0;
Christopher Faulete010c802018-10-24 10:36:45 +0200254}
255
256/* Replace the request path in the HTX message <htx> by <path>. The host part
257 * and the query string are preserved. It returns 1 on success, otherwise 0.
258 */
259int http_replace_req_path(struct htx *htx, const struct ist path)
260{
261 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200262 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100263 struct ist meth, uri, vsn, p;
Christopher Faulete010c802018-10-24 10:36:45 +0200264 size_t plen = 0;
265
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100266 if (!sl)
267 return 0;
268
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100269 uri = htx_sl_req_uri(sl);
270 p = http_get_path(uri);
Christopher Faulete010c802018-10-24 10:36:45 +0200271 if (!p.ptr)
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100272 p = uri;
Christopher Faulete010c802018-10-24 10:36:45 +0200273 while (plen < p.len && *(p.ptr + plen) != '?')
274 plen++;
275
276 /* Start by copying old method and version and create the new uri */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100277 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
278 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200279
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100280 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
281 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
282
283 chunk_memcat(temp, uri.ptr, p.ptr - uri.ptr); /* uri: host part */
Christopher Faulete010c802018-10-24 10:36:45 +0200284 chunk_memcat(temp, path.ptr, path.len); /* uri: new path */
285 chunk_memcat(temp, p.ptr + plen, p.len - plen); /* uri: QS part */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100286 uri = ist2(temp->area + meth.len + vsn.len, uri.len - plen + path.len);
Christopher Faulete010c802018-10-24 10:36:45 +0200287
288 /* create the new start line */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100289 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200290}
291
292/* Replace the request query-string in the HTX message <htx> by <query>. The
293 * host part and the path are preserved. It returns 1 on success, otherwise
294 * 0.
295 */
296int http_replace_req_query(struct htx *htx, const struct ist query)
297{
298 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200299 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100300 struct ist meth, uri, vsn, q;
Christopher Faulete010c802018-10-24 10:36:45 +0200301 int offset = 1;
302
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100303 if (!sl)
304 return 0;
305
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100306 uri = htx_sl_req_uri(sl);
307 q = uri;
Christopher Faulete010c802018-10-24 10:36:45 +0200308 while (q.len > 0 && *(q.ptr) != '?') {
309 q.ptr++;
310 q.len--;
311 }
312
313 /* skip the question mark or indicate that we must insert it
314 * (but only if the format string is not empty then).
315 */
316 if (q.len) {
317 q.ptr++;
318 q.len--;
319 }
320 else if (query.len > 1)
321 offset = 0;
322
323 /* Start by copying old method and version and create the new uri */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100324 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
325 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200326
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100327 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
328 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200329
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100330 chunk_memcat(temp, uri.ptr, q.ptr - uri.ptr); /* uri: host + path part */
331 chunk_memcat(temp, query.ptr + offset, query.len - offset); /* uri: new QS */
332 uri = ist2(temp->area + meth.len + vsn.len, uri.len - q.len + query.len - offset);
Christopher Faulete010c802018-10-24 10:36:45 +0200333
334 /* create the new start line */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100335 return http_replace_stline(htx, meth, uri, vsn);
Christopher Faulete010c802018-10-24 10:36:45 +0200336}
337
338/* Replace the response status in the HTX message <htx> by <status>. It returns
339 * 1 on success, otherwise 0.
340*/
341int http_replace_res_status(struct htx *htx, const struct ist status)
342{
343 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200344 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100345 struct ist vsn, reason;
Christopher Faulete010c802018-10-24 10:36:45 +0200346
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100347 if (!sl)
348 return 0;
349
Christopher Faulete010c802018-10-24 10:36:45 +0200350 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100351 chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
352 vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200353
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100354 chunk_memcat(temp, HTX_SL_RES_RPTR(sl), HTX_SL_RES_RLEN(sl)); /* reason */
355 reason = ist2(temp->area + vsn.len, HTX_SL_RES_RLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200356
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100357 /* create the new start line */
358 sl->info.res.status = strl2ui(status.ptr, status.len);
359 return http_replace_stline(htx, vsn, status, reason);
Christopher Faulete010c802018-10-24 10:36:45 +0200360}
361
362/* Replace the response reason in the HTX message <htx> by <reason>. It returns
363 * 1 on success, otherwise 0.
364*/
365int http_replace_res_reason(struct htx *htx, const struct ist reason)
366{
367 struct buffer *temp = get_trash_chunk();
Christopher Faulet297fbb42019-05-13 14:41:27 +0200368 struct htx_sl *sl = http_get_stline(htx);
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100369 struct ist vsn, status;
Christopher Faulete010c802018-10-24 10:36:45 +0200370
Willy Tarreaucdce54c2019-02-12 12:02:27 +0100371 if (!sl)
372 return 0;
373
Christopher Faulete010c802018-10-24 10:36:45 +0200374 /* Start by copying old uri and version */
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100375 chunk_memcat(temp, HTX_SL_RES_VPTR(sl), HTX_SL_RES_VLEN(sl)); /* vsn */
376 vsn = ist2(temp->area, HTX_SL_RES_VLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200377
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100378 chunk_memcat(temp, HTX_SL_RES_CPTR(sl), HTX_SL_RES_CLEN(sl)); /* code */
379 status = ist2(temp->area + vsn.len, HTX_SL_RES_CLEN(sl));
Christopher Faulete010c802018-10-24 10:36:45 +0200380
Christopher Fauletf1ba18d2018-11-26 21:37:08 +0100381 /* create the new start line */
382 return http_replace_stline(htx, vsn, status, reason);
Christopher Faulete010c802018-10-24 10:36:45 +0200383}
384
Christopher Faulet47596d32018-10-22 09:17:28 +0200385/* Replaces a part of a header value referenced in the context <ctx> by
386 * <data>. It returns 1 on success, otherwise it returns 0. The context is
387 * updated if necessary.
388 */
389int http_replace_header_value(struct htx *htx, struct http_hdr_ctx *ctx, const struct ist data)
390{
391 struct htx_blk *blk = ctx->blk;
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200392 struct htx_sl *sl;
Christopher Faulet47596d32018-10-22 09:17:28 +0200393 char *start;
394 struct ist v;
395 uint32_t len, off;
396
397 if (!blk)
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200398 goto fail;
Christopher Faulet47596d32018-10-22 09:17:28 +0200399
400 v = htx_get_blk_value(htx, blk);
401 start = ctx->value.ptr - ctx->lws_before;
402 len = ctx->lws_before + ctx->value.len + ctx->lws_after;
403 off = start - v.ptr;
404
405 blk = htx_replace_blk_value(htx, blk, ist2(start, len), data);
406 if (!blk)
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200407 goto fail;
Christopher Faulet47596d32018-10-22 09:17:28 +0200408
409 v = htx_get_blk_value(htx, blk);
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200410
411 sl = http_get_stline(htx);
412 if (sl && (sl->flags & HTX_SL_F_HAS_AUTHORITY)) {
413 struct ist n = htx_get_blk_name(htx, blk);
414
415 if (isteq(n, ist("host"))) {
416 if (!http_update_authority(htx, sl, v))
417 goto fail;
418 ctx->blk = NULL;
419 http_find_header(htx, ist("host"), ctx, 1);
420 blk = ctx->blk;
421 v = htx_get_blk_value(htx, blk);
422 }
423 }
424
Christopher Faulet47596d32018-10-22 09:17:28 +0200425 ctx->blk = blk;
426 ctx->value.ptr = v.ptr + off;
427 ctx->value.len = data.len;
428 ctx->lws_before = ctx->lws_after = 0;
429
430 return 1;
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200431 fail:
432 return 0;
Christopher Faulet47596d32018-10-22 09:17:28 +0200433}
434
435/* Fully replaces a header referenced in the context <ctx> by the name <name>
436 * with the value <value>. It returns 1 on success, otherwise it returns 0. The
437 * context is updated if necessary.
438 */
439int http_replace_header(struct htx *htx, struct http_hdr_ctx *ctx,
440 const struct ist name, const struct ist value)
441{
442 struct htx_blk *blk = ctx->blk;
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200443 struct htx_sl *sl;
Christopher Faulet47596d32018-10-22 09:17:28 +0200444
445 if (!blk)
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200446 goto fail;
Christopher Faulet47596d32018-10-22 09:17:28 +0200447
448 blk = htx_replace_header(htx, blk, name, value);
449 if (!blk)
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200450 goto fail;
451
452 sl = http_get_stline(htx);
453 if (sl && (sl->flags & HTX_SL_F_HAS_AUTHORITY) && isteq(name, ist("host"))) {
454 if (!http_update_authority(htx, sl, value))
455 goto fail;
456 ctx->blk = NULL;
457 http_find_header(htx, ist("host"), ctx, 1);
458 blk = ctx->blk;
459 }
Christopher Faulet47596d32018-10-22 09:17:28 +0200460
461 ctx->blk = blk;
462 ctx->value = ist(NULL);
463 ctx->lws_before = ctx->lws_after = 0;
464
465 return 1;
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200466 fail:
467 return 0;
Christopher Faulet47596d32018-10-22 09:17:28 +0200468}
469
470/* Remove one value of a header. This only works on a <ctx> returned by
471 * http_find_header function. The value is removed, as well as surrounding commas
472 * if any. If the removed value was alone, the whole header is removed. The
473 * <ctx> is always updated accordingly, as well as the HTX message <htx>. It
474 * returns 1 on success. Otherwise, it returns 0. The <ctx> is always left in a
475 * form that can be handled by http_find_header() to find next occurrence.
476 */
477int http_remove_header(struct htx *htx, struct http_hdr_ctx *ctx)
478{
479 struct htx_blk *blk = ctx->blk;
480 char *start;
481 struct ist v;
482 uint32_t len;
483
484 if (!blk)
485 return 0;
486
487 start = ctx->value.ptr - ctx->lws_before;
488 len = ctx->lws_before + ctx->value.len + ctx->lws_after;
489
490 v = htx_get_blk_value(htx, blk);
491 if (len == v.len) {
492 blk = htx_remove_blk(htx, blk);
Christopher Faulet192c6a22019-06-11 16:32:24 +0200493 if (blk || htx_is_empty(htx)) {
Christopher Faulet47596d32018-10-22 09:17:28 +0200494 ctx->blk = blk;
495 ctx->value = ist2(NULL, 0);
496 ctx->lws_before = ctx->lws_after = 0;
497 }
498 else {
499 ctx->blk = htx_get_blk(htx, htx->tail);
500 ctx->value = htx_get_blk_value(htx, ctx->blk);
501 ctx->lws_before = ctx->lws_after = 0;
502 }
503 return 1;
504 }
505
506 /* This was not the only value of this header. We have to remove the
507 * part pointed by ctx->value. If it is the last entry of the list, we
508 * remove the last separator.
509 */
510 if (start == v.ptr) {
511 /* It's the first header part but not the only one. So remove
512 * the comma after it. */
513 len++;
514 }
515 else {
516 /* There is at least one header part before the removed one. So
517 * remove the comma between them. */
518 start--;
519 len++;
520 }
521 /* Update the block content and its len */
522 memmove(start, start+len, v.len-len);
Christopher Faulet3e2638e2019-06-18 09:49:16 +0200523 htx_change_blk_value_len(htx, blk, v.len-len);
Christopher Faulet47596d32018-10-22 09:17:28 +0200524
525 /* Finally update the ctx */
526 ctx->value.ptr = start;
527 ctx->value.len = 0;
528 ctx->lws_before = ctx->lws_after = 0;
529
530 return 1;
531}
Christopher Faulet7ff1cea2018-10-24 10:39:35 +0200532
Christopher Fauletd7b7a1c2019-10-08 15:24:52 +0200533/* Updates the authority part of the uri with the value <host>. It happens when
534 * the header host is modified. It returns 0 on failure and 1 on success. It is
535 * the caller responsibility to provide the start-line and to be sure the uri
536 * contains an authority. Thus, if no authority is found in the uri, an error is
537 * returned.
538 */
539static int http_update_authority(struct htx *htx, struct htx_sl *sl, const struct ist host)
540{
541 struct buffer *temp = get_trash_chunk();
542 struct ist meth, vsn, uri, authority;
543
544 uri = htx_sl_req_uri(sl);
545 authority = http_get_authority(uri, 1);
546 if (!authority.len || isteq(host, authority))
547 return 0;
548
549 /* Start by copying old method and version */
550 chunk_memcat(temp, HTX_SL_REQ_MPTR(sl), HTX_SL_REQ_MLEN(sl)); /* meth */
551 meth = ist2(temp->area, HTX_SL_REQ_MLEN(sl));
552
553 chunk_memcat(temp, HTX_SL_REQ_VPTR(sl), HTX_SL_REQ_VLEN(sl)); /* vsn */
554 vsn = ist2(temp->area + meth.len, HTX_SL_REQ_VLEN(sl));
555
556 chunk_memcat(temp, uri.ptr, authority.ptr - uri.ptr);
557 chunk_memcat(temp, host.ptr, host.len);
558 chunk_memcat(temp, authority.ptr + authority.len, uri.ptr + uri.len - (authority.ptr + authority.len));
559 uri = ist2(temp->area + meth.len + vsn.len, host.len + uri.len - authority.len); /* uri */
560
561 return http_replace_stline(htx, meth, uri, vsn);
562
563}
564
565/* Update the header host by extracting the authority of the uri <uri>. flags of
566 * the start-line are also updated accordingly. For orgin-form and asterisk-form
567 * uri, the header host is not changed and the flag HTX_SL_F_HAS_AUTHORITY is
568 * removed from the flags of the start-line. Otherwise, this flag is set and the
569 * authority is used to set the value of the header host. This function returns
570 * 0 on failure and 1 on success.
571*/
572static int http_update_host(struct htx *htx, struct htx_sl *sl, const struct ist uri)
573{
574 struct ist authority;
575 struct http_hdr_ctx ctx;
576
577 if (!uri.len || uri.ptr[0] == '/' || uri.ptr[0] == '*') {
578 // origin-form or a asterisk-form (RFC7320 #5.3.1 and #5.3.4)
579 sl->flags &= ~HTX_SL_F_HAS_AUTHORITY;
580 }
581 else {
582 sl->flags |= HTX_SL_F_HAS_AUTHORITY;
583 if (sl->info.req.meth != HTTP_METH_CONNECT) {
584 // absolute-form (RFC7320 #5.3.2)
585 sl->flags |= HTX_SL_F_HAS_SCHM;
586 if (uri.len > 4 && (uri.ptr[0] | 0x20) == 'h')
587 sl->flags |= ((uri.ptr[4] == ':') ? HTX_SL_F_SCHM_HTTP : HTX_SL_F_SCHM_HTTPS);
588
589 authority = http_get_authority(uri, 1);
590 if (!authority.len)
591 goto fail;
592 }
593 else {
594 // authority-form (RFC7320 #5.3.3)
595 authority = uri;
596 }
597
598 /* Replace header host value */
599 ctx.blk = NULL;
600 while (http_find_header(htx, ist("host"), &ctx, 1)) {
601 if (!http_replace_header_value(htx, &ctx, authority))
602 goto fail;
603 }
604
605 }
606 return 1;
607 fail:
608 return 0;
609}
Christopher Faulet7ff1cea2018-10-24 10:39:35 +0200610
611/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
612 * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
613 * performed over the whole headers. Otherwise it must contain a valid header
614 * context, initialised with ctx->blk=NULL for the first lookup in a series. If
615 * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
616 * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
617 * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
618 * -1. The value fetch stops at commas, so this function is suited for use with
619 * list headers.
620 * The return value is 0 if nothing was found, or non-zero otherwise.
621 */
622unsigned int http_get_htx_hdr(const struct htx *htx, const struct ist hdr,
623 int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
624{
625 struct http_hdr_ctx local_ctx;
626 struct ist val_hist[MAX_HDR_HISTORY];
627 unsigned int hist_idx;
628 int found;
629
630 if (!ctx) {
631 local_ctx.blk = NULL;
632 ctx = &local_ctx;
633 }
634
635 if (occ >= 0) {
636 /* search from the beginning */
637 while (http_find_header(htx, hdr, ctx, 0)) {
638 occ--;
639 if (occ <= 0) {
640 *vptr = ctx->value.ptr;
641 *vlen = ctx->value.len;
642 return 1;
643 }
644 }
645 return 0;
646 }
647
648 /* negative occurrence, we scan all the list then walk back */
649 if (-occ > MAX_HDR_HISTORY)
650 return 0;
651
652 found = hist_idx = 0;
653 while (http_find_header(htx, hdr, ctx, 0)) {
654 val_hist[hist_idx] = ctx->value;
655 if (++hist_idx >= MAX_HDR_HISTORY)
656 hist_idx = 0;
657 found++;
658 }
659 if (-occ > found)
660 return 0;
661
662 /* OK now we have the last occurrence in [hist_idx-1], and we need to
663 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
664 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
665 * to remain in the 0..9 range.
666 */
667 hist_idx += occ + MAX_HDR_HISTORY;
668 if (hist_idx >= MAX_HDR_HISTORY)
669 hist_idx -= MAX_HDR_HISTORY;
670 *vptr = val_hist[hist_idx].ptr;
671 *vlen = val_hist[hist_idx].len;
672 return 1;
673}
674
675/* Return in <vptr> and <vlen> the pointer and length of occurrence <occ> of
676 * header whose name is <hname> of length <hlen>. If <ctx> is null, lookup is
677 * performed over the whole headers. Otherwise it must contain a valid header
678 * context, initialised with ctx->blk=NULL for the first lookup in a series. If
679 * <occ> is positive or null, occurrence #occ from the beginning (or last ctx)
680 * is returned. Occ #0 and #1 are equivalent. If <occ> is negative (and no less
681 * than -MAX_HDR_HISTORY), the occurrence is counted from the last one which is
682 * -1. This function differs from http_get_hdr() in that it only returns full
683 * line header values and does not stop at commas.
684 * The return value is 0 if nothing was found, or non-zero otherwise.
685 */
686unsigned int http_get_htx_fhdr(const struct htx *htx, const struct ist hdr,
687 int occ, struct http_hdr_ctx *ctx, char **vptr, size_t *vlen)
688{
689 struct http_hdr_ctx local_ctx;
690 struct ist val_hist[MAX_HDR_HISTORY];
691 unsigned int hist_idx;
692 int found;
693
694 if (!ctx) {
695 local_ctx.blk = NULL;
696 ctx = &local_ctx;
697 }
698
699 if (occ >= 0) {
700 /* search from the beginning */
701 while (http_find_header(htx, hdr, ctx, 1)) {
702 occ--;
703 if (occ <= 0) {
704 *vptr = ctx->value.ptr;
705 *vlen = ctx->value.len;
706 return 1;
707 }
708 }
709 return 0;
710 }
711
712 /* negative occurrence, we scan all the list then walk back */
713 if (-occ > MAX_HDR_HISTORY)
714 return 0;
715
716 found = hist_idx = 0;
717 while (http_find_header(htx, hdr, ctx, 1)) {
718 val_hist[hist_idx] = ctx->value;
719 if (++hist_idx >= MAX_HDR_HISTORY)
720 hist_idx = 0;
721 found++;
722 }
723 if (-occ > found)
724 return 0;
725
726 /* OK now we have the last occurrence in [hist_idx-1], and we need to
727 * find occurrence -occ. 0 <= hist_idx < MAX_HDR_HISTORY, and we have
728 * -10 <= occ <= -1. So we have to check [hist_idx%MAX_HDR_HISTORY+occ]
729 * to remain in the 0..9 range.
730 */
731 hist_idx += occ + MAX_HDR_HISTORY;
732 if (hist_idx >= MAX_HDR_HISTORY)
733 hist_idx -= MAX_HDR_HISTORY;
734 *vptr = val_hist[hist_idx].ptr;
735 *vlen = val_hist[hist_idx].len;
736 return 1;
737}
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100738
Christopher Faulet90cc4812019-07-22 16:49:30 +0200739int http_str_to_htx(struct buffer *buf, struct ist raw)
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100740{
741 struct htx *htx;
742 struct htx_sl *sl;
743 struct h1m h1m;
Christopher Faulete4ab11b2019-06-11 15:05:37 +0200744 struct http_hdr hdrs[global.tune.max_http_hdr];
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100745 union h1_sl h1sl;
746 unsigned int flags = HTX_SL_F_IS_RESP;
747 int ret = 0;
748
Christopher Faulet90cc4812019-07-22 16:49:30 +0200749 b_reset(buf);
750 if (!raw.len) {
751 buf->size = 0;
752 buf->area = malloc(raw.len);
753 return 1;
754 }
755
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100756 buf->size = global.tune.bufsize;
757 buf->area = (char *)malloc(buf->size);
758 if (!buf->area)
759 goto error;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100760
761 h1m_init_res(&h1m);
762 h1m.flags |= H1_MF_NO_PHDR;
763 ret = h1_headers_to_hdr_list(raw.ptr, raw.ptr + raw.len,
764 hdrs, sizeof(hdrs)/sizeof(hdrs[0]), &h1m, &h1sl);
765 if (ret <= 0)
766 goto error;
767
768 if (unlikely(h1sl.st.v.len != 8))
769 goto error;
770 if ((*(h1sl.st.v.ptr + 5) > '1') ||
771 ((*(h1sl.st.v.ptr + 5) == '1') && (*(h1sl.st.v.ptr + 7) >= '1')))
772 h1m.flags |= H1_MF_VER_11;
773
Christopher Faulet1d5ec092019-06-26 14:23:54 +0200774 if (h1sl.st.status < 200 && (h1sl.st.status == 100 || h1sl.st.status >= 102))
775 goto error;
776
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100777 if (h1m.flags & H1_MF_VER_11)
778 flags |= HTX_SL_F_VER_11;
779 if (h1m.flags & H1_MF_XFER_ENC)
780 flags |= HTX_SL_F_XFER_ENC;
Christopher Faulet0d4ce932019-10-16 09:09:04 +0200781 if (h1m.flags & H1_MF_CLEN) {
782 flags |= (HTX_SL_F_XFER_LEN|HTX_SL_F_CLEN);
783 if (h1m.body_len == 0)
784 flags |= HTX_SL_F_BODYLESS;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100785 }
Christopher Faulet0d4ce932019-10-16 09:09:04 +0200786 if (h1m.flags & H1_MF_CHNK)
787 goto error; /* Unsupported because there is no body parsing */
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100788
789 htx = htx_from_buf(buf);
790 sl = htx_add_stline(htx, HTX_BLK_RES_SL, flags, h1sl.st.v, h1sl.st.c, h1sl.st.r);
791 if (!sl || !htx_add_all_headers(htx, hdrs))
792 goto error;
793 sl->info.res.status = h1sl.st.status;
794
Willy Tarreau0a7ef022019-05-28 10:30:11 +0200795 while (raw.len > ret) {
796 int sent = htx_add_data(htx, ist2(raw.ptr + ret, raw.len - ret));
797 if (!sent)
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100798 goto error;
Willy Tarreau0a7ef022019-05-28 10:30:11 +0200799 ret += sent;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100800 }
Christopher Faulet1d5ec092019-06-26 14:23:54 +0200801
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100802 if (!htx_add_endof(htx, HTX_BLK_EOM))
803 goto error;
Christopher Faulet1d5ec092019-06-26 14:23:54 +0200804
Christopher Faulet90cc4812019-07-22 16:49:30 +0200805 return 1;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100806
807error:
808 if (buf->size)
809 free(buf->area);
Christopher Faulet90cc4812019-07-22 16:49:30 +0200810 return 0;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100811}
812
813static int http_htx_init(void)
814{
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100815 struct buffer chk;
816 struct ist raw;
817 int rc;
818 int err_code = 0;
819
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100820 for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
821 if (!http_err_msgs[rc]) {
822 ha_alert("Internal error: no message defined for HTTP return code %d", rc);
823 err_code |= ERR_ALERT | ERR_FATAL;
824 continue;
825 }
826
827 raw = ist2(http_err_msgs[rc], strlen(http_err_msgs[rc]));
828 if (!http_str_to_htx(&chk, raw)) {
829 ha_alert("Internal error: Unable to convert message in HTX for HTTP return code %d.\n",
830 http_err_codes[rc]);
831 err_code |= ERR_ALERT | ERR_FATAL;
832 }
Christopher Fauletf7346382019-07-17 22:02:08 +0200833 http_err_chunks[rc] = chk;
Christopher Fauleta7b677c2018-11-29 16:48:49 +0100834 }
835end:
836 return err_code;
837}
838
839REGISTER_CONFIG_POSTPARSER("http_htx", http_htx_init);
Christopher Faulet29f72842019-12-11 15:52:32 +0100840
Christopher Faulet5031ef52020-01-15 11:22:07 +0100841/* Reads content of an error file and convert it in an HTX message. On success,
842 * the result is stored in <buf> and 1 is returned. On error, 0 is returned and
843 * an error message is written into the <errmsg> buffer. It is this function
844 * responsibility to allocate <buf> and to release it if an error occurred.
845 */
846int http_load_errorfile(const char *file, struct buffer *buf, char **errmsg)
847{
848 struct stat stat;
849 char *err = NULL;
850 int errnum, errlen;
851 int fd = -1;
852 int ret = 0;
853
854 fd = open(file, O_RDONLY);
855 if ((fd < 0) || (fstat(fd, &stat) < 0)) {
856 memprintf(errmsg, "error opening file '%s'.", file);
857 goto out;
858 }
859
860 if (stat.st_size <= global.tune.bufsize)
861 errlen = stat.st_size;
862 else {
863 ha_warning("custom error message file '%s' larger than %d bytes. Truncating.\n",
864 file, global.tune.bufsize);
865 errlen = global.tune.bufsize;
866 }
867
868 err = malloc(errlen);
869 if (!err) {
870 memprintf(errmsg, "out of memory.");
871 goto out;
872 }
873
874 errnum = read(fd, err, errlen);
875 if (errnum != errlen) {
876 memprintf(errmsg, "error reading file '%s'.", file);
877 goto out;
878 }
879
880 if (!http_str_to_htx(buf, ist2(err, errlen))) {
881 memprintf(errmsg, "unable to convert custom error message file '%s' in HTX.", file);
882 goto out;
883 }
884
885 ret = 1;
886 out:
887 if (fd >= 0)
888 close(fd);
889 free(err);
890 return ret;
891}
892
Christopher Fauletbdf65262020-01-16 15:51:59 +0100893/* Convert the raw http message <msg> into an HTX message. On success, the
894 * result is stored in <buf> and 1 is returned. On error, 0 is returned and an
895 * error message is written into the <errmsg> buffer. It is this function
896 * responsibility to allocate <buf> and to release it if an error occurred.
897 */
898int http_load_errormsg(const struct ist msg, struct buffer *buf, char **errmsg)
899{
900 int ret = 0;
901
902 /* Convert the error file into an HTX message */
903 if (!http_str_to_htx(buf, msg)) {
904 memprintf(errmsg, "unable to convert message in HTX.");
905 goto out;
906 }
907 ret = 1;
908
909 out:
910 return ret;
911}
912
Christopher Faulet5031ef52020-01-15 11:22:07 +0100913
914/* This function parses the raw HTTP error file <file> for the status code
915 * <status>. On success, it returns the HTTP_ERR_* value corresponding to the
916 * specified status code and it allocated and fills the buffer <buf> with the
917 * HTX message. On error, it returns -1 and nothing is allocated.
918 */
919int http_parse_errorfile(int status, const char *file, struct buffer *buf, char **errmsg)
920{
921 int rc, ret = -1;
922
923 for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
924 if (http_err_codes[rc] == status) {
925 if (http_load_errorfile(file, buf, errmsg))
926 ret = rc;
927 break;
928 }
929 }
930
931 if (rc >= HTTP_ERR_SIZE)
932 memprintf(errmsg, "status code '%d' not handled.", status);
Christopher Fauletbdf65262020-01-16 15:51:59 +0100933 return ret;
934}
935
936/* This function creates HTX error message corresponding to a redirect message
937 * for the status code <status>. <url> is used as location url for the
938 * redirect. <errloc> is used to know if it is a 302 or a 303 redirect. On
939 * success, it returns the HTTP_ERR_* value corresponding to the specified
940 * status code and it allocated and fills the buffer <buf> with the HTX
941 * message. On error, it returns -1 and nothing is allocated.
942 */
943int http_parse_errorloc(int errloc, int status, const char *url, struct buffer *buf, char **errmsg)
944{
945 const char *msg;
946 char *err = NULL;
947 int rc, errlen;
948 int ret = -1;
949
950 for (rc = 0; rc < HTTP_ERR_SIZE; rc++) {
951 if (http_err_codes[rc] == status) {
952 /* Create the error message */
953 msg = (errloc == 302 ? HTTP_302 : HTTP_303);
954 errlen = strlen(msg) + strlen(url) + 5;
955 err = malloc(errlen);
956 if (!err) {
957 memprintf(errmsg, "out of memory.");
958 goto out;
959 }
960 errlen = snprintf(err, errlen, "%s%s\r\n\r\n", msg, url);
961
962 /* Load it */
963 if (http_load_errormsg(ist2(err, errlen), buf, errmsg))
964 ret = rc;
965 break;
966 }
967 }
968
969 if (rc >= HTTP_ERR_SIZE)
970 memprintf(errmsg, "status code '%d' not handled.", status);
971out:
972 free(err);
Christopher Faulet5031ef52020-01-15 11:22:07 +0100973 return ret;
974}
975
Christopher Faulet29f72842019-12-11 15:52:32 +0100976/************************************************************************/
977/* HTX sample fetches */
978/************************************************************************/
979
980/* Returns 1 if a stream is an HTX stream. Otherwise, it returns 0. */
981static int
982smp_fetch_is_htx(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
983{
984 if (!smp->strm)
985 return 0;
986
987 smp->data.u.sint = !!IS_HTX_STRM(smp->strm);
988 smp->data.type = SMP_T_BOOL;
989 return 1;
990}
991
992/* Returns the number of blocks in an HTX message. The channel is chosen
993 * depending on the sample direction. */
994static int
995smp_fetch_htx_nbblks(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
996{
997 struct channel *chn;
998 struct htx *htx;
999
1000 if (!smp->strm)
1001 return 0;
1002
1003 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1004 htx = smp_prefetch_htx(smp, chn, 0);
1005 if (!htx)
1006 return 0;
1007
1008 smp->data.u.sint = htx_nbblks(htx);
1009 smp->data.type = SMP_T_SINT;
1010 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1011 return 1;
1012}
1013
1014/* Returns the size of an HTX message. The channel is chosen depending on the
1015 * sample direction. */
1016static int
1017smp_fetch_htx_size(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1018{
1019 struct channel *chn;
1020 struct htx *htx;
1021
1022 if (!smp->strm)
1023 return 0;
1024
1025 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1026 htx = smp_prefetch_htx(smp, chn, 0);
1027 if (!htx)
1028 return 0;
1029
1030 smp->data.u.sint = htx->size;
1031 smp->data.type = SMP_T_SINT;
1032 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1033 return 1;
1034}
1035
1036/* Returns the data size of an HTX message. The channel is chosen depending on the
1037 * sample direction. */
1038static int
1039smp_fetch_htx_data(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1040{
1041 struct channel *chn;
1042 struct htx *htx;
1043
1044 if (!smp->strm)
1045 return 0;
1046
1047 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1048 htx = smp_prefetch_htx(smp, chn, 0);
1049 if (!htx)
1050 return 0;
1051
1052 smp->data.u.sint = htx->data;
1053 smp->data.type = SMP_T_SINT;
1054 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1055 return 1;
1056}
1057
1058/* Returns the used space (data+meta) of an HTX message. The channel is chosen
1059 * depending on the sample direction. */
1060static int
1061smp_fetch_htx_used(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1062{
1063 struct channel *chn;
1064 struct htx *htx;
1065
1066 if (!smp->strm)
1067 return 0;
1068
1069 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1070 htx = smp_prefetch_htx(smp, chn, 0);
1071 if (!htx)
1072 return 0;
1073
1074 smp->data.u.sint = htx_used_space(htx);
1075 smp->data.type = SMP_T_SINT;
1076 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1077 return 1;
1078}
1079
1080/* Returns the free space (size-used) of an HTX message. The channel is chosen
1081 * depending on the sample direction. */
1082static int
1083smp_fetch_htx_free(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1084{
1085 struct channel *chn;
1086 struct htx *htx;
1087
1088 if (!smp->strm)
1089 return 0;
1090
1091 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1092 htx = smp_prefetch_htx(smp, chn, 0);
1093 if (!htx)
1094 return 0;
1095
1096 smp->data.u.sint = htx_free_space(htx);
1097 smp->data.type = SMP_T_SINT;
1098 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1099 return 1;
1100}
1101
1102/* Returns the free space for data (free-sizeof(blk)) of an HTX message. The
1103 * channel is chosen depending on the sample direction. */
1104static int
1105smp_fetch_htx_free_data(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1106{
1107 struct channel *chn;
1108 struct htx *htx;
1109
1110 if (!smp->strm)
1111 return 0;
1112
1113 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1114 htx = smp_prefetch_htx(smp, chn, 0);
1115 if (!htx)
1116 return 0;
1117
1118 smp->data.u.sint = htx_free_data_space(htx);
1119 smp->data.type = SMP_T_SINT;
1120 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1121 return 1;
1122}
1123
1124/* Returns 1 if the HTX message contains an EOM block. Otherwise it returns
1125 * 0. Concretely, it only checks the tail. The channel is chosen depending on
1126 * the sample direction. */
1127static int
1128smp_fetch_htx_has_eom(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1129{
1130 struct channel *chn;
1131 struct htx *htx;
1132
1133 if (!smp->strm)
1134 return 0;
1135
1136 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1137 htx = smp_prefetch_htx(smp, chn, 0);
1138 if (!htx)
1139 return 0;
1140
1141 smp->data.u.sint = (htx_get_tail_type(htx) == HTX_BLK_EOM);
1142 smp->data.type = SMP_T_BOOL;
1143 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1144 return 1;
1145}
1146
1147/* Returns the type of a specific HTX block, if found in the message. Otherwise
1148 * HTX_BLK_UNUSED is returned. Any positive integer (>= 0) is supported or
1149 * "head", "tail" or "first". The channel is chosen depending on the sample
1150 * direction. */
1151static int
1152smp_fetch_htx_blk_type(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1153{
1154 struct channel *chn;
1155 struct htx *htx;
1156 enum htx_blk_type type;
1157 int32_t pos;
1158
1159 if (!smp->strm || !arg_p)
1160 return 0;
1161
1162 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1163 htx = smp_prefetch_htx(smp, chn, 0);
1164 if (!htx)
1165 return 0;
1166
1167 pos = arg_p[0].data.sint;
1168 if (pos == -1)
1169 type = htx_get_head_type(htx);
1170 else if (pos == -2)
1171 type = htx_get_tail_type(htx);
1172 else if (pos == -3)
1173 type = htx_get_first_type(htx);
1174 else
1175 type = ((pos >= htx->head && pos <= htx->tail)
1176 ? htx_get_blk_type(htx_get_blk(htx, pos))
1177 : HTX_BLK_UNUSED);
1178
1179 chunk_initstr(&smp->data.u.str, htx_blk_type_str(type));
1180 smp->data.type = SMP_T_STR;
1181 smp->flags = SMP_F_CONST | SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1182 return 1;
1183}
1184
1185/* Returns the size of a specific HTX block, if found in the message. Otherwise
1186 * 0 is returned. Any positive integer (>= 0) is supported or "head", "tail" or
1187 * "first". The channel is chosen depending on the sample direction. */
1188static int
1189smp_fetch_htx_blk_size(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1190{
1191 struct channel *chn;
1192 struct htx *htx;
1193 struct htx_blk *blk;
1194 int32_t pos;
1195
1196 if (!smp->strm || !arg_p)
1197 return 0;
1198
1199 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1200 htx = smp_prefetch_htx(smp, chn, 0);
1201 if (!htx)
1202 return 0;
1203
1204 pos = arg_p[0].data.sint;
1205 if (pos == -1)
1206 blk = htx_get_head_blk(htx);
1207 else if (pos == -2)
1208 blk = htx_get_tail_blk(htx);
1209 else if (pos == -3)
1210 blk = htx_get_first_blk(htx);
1211 else
1212 blk = ((pos >= htx->head && pos <= htx->tail) ? htx_get_blk(htx, pos) : NULL);
1213
1214 smp->data.u.sint = (blk ? htx_get_blksz(blk) : 0);
1215 smp->data.type = SMP_T_SINT;
1216 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1217 return 1;
1218}
1219
1220/* Returns the start-line if the selected HTX block exists and is a
1221 * start-line. Otherwise 0 an empty string. Any positive integer (>= 0) is
1222 * supported or "head", "tail" or "first". The channel is chosen depending on
1223 * the sample direction. */
1224static int
1225smp_fetch_htx_blk_stline(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1226{
1227 struct buffer *temp;
1228 struct channel *chn;
1229 struct htx *htx;
1230 struct htx_blk *blk;
1231 struct htx_sl *sl;
1232 int32_t pos;
1233
1234 if (!smp->strm || !arg_p)
1235 return 0;
1236
1237 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1238 htx = smp_prefetch_htx(smp, chn, 0);
1239 if (!htx)
1240 return 0;
1241
1242 pos = arg_p[0].data.sint;
1243 if (pos == -1)
1244 blk = htx_get_head_blk(htx);
1245 else if (pos == -2)
1246 blk = htx_get_tail_blk(htx);
1247 else if (pos == -3)
1248 blk = htx_get_first_blk(htx);
1249 else
1250 blk = ((pos >= htx->head && pos <= htx->tail) ? htx_get_blk(htx, pos) : NULL);
1251
1252 if (!blk || (htx_get_blk_type(blk) != HTX_BLK_REQ_SL && htx_get_blk_type(blk) != HTX_BLK_RES_SL)) {
1253 smp->data.u.str.size = 0;
1254 smp->data.u.str.area = "";
1255 smp->data.u.str.data = 0;
1256 }
1257 else {
1258 sl = htx_get_blk_ptr(htx, blk);
1259
1260 temp = get_trash_chunk();
1261 chunk_istcat(temp, htx_sl_p1(sl));
1262 temp->area[temp->data++] = ' ';
1263 chunk_istcat(temp, htx_sl_p2(sl));
1264 temp->area[temp->data++] = ' ';
1265 chunk_istcat(temp, htx_sl_p3(sl));
1266
1267 smp->data.u.str = *temp;
1268 }
1269
1270 smp->data.type = SMP_T_STR;
1271 smp->flags = SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1272 return 1;
1273}
1274
1275/* Returns the header name if the selected HTX block exists and is a header or a
1276 * trailer. Otherwise 0 an empty string. Any positive integer (>= 0) is
1277 * supported or "head", "tail" or "first". The channel is chosen depending on
1278 * the sample direction. */
1279static int
1280smp_fetch_htx_blk_hdrname(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1281{
1282 struct channel *chn;
1283 struct htx *htx;
1284 struct htx_blk *blk;
1285 int32_t pos;
1286
1287 if (!smp->strm || !arg_p)
1288 return 0;
1289
1290 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1291 htx = smp_prefetch_htx(smp, chn, 0);
1292 if (!htx)
1293 return 0;
1294
1295 pos = arg_p[0].data.sint;
1296 if (pos == -1)
1297 blk = htx_get_head_blk(htx);
1298 else if (pos == -2)
1299 blk = htx_get_tail_blk(htx);
1300 else if (pos == -3)
1301 blk = htx_get_first_blk(htx);
1302 else
1303 blk = ((pos >= htx->head && pos <= htx->tail) ? htx_get_blk(htx, pos) : NULL);
1304
1305 if (!blk || (htx_get_blk_type(blk) != HTX_BLK_HDR && htx_get_blk_type(blk) != HTX_BLK_TLR)) {
1306 smp->data.u.str.size = 0;
1307 smp->data.u.str.area = "";
1308 smp->data.u.str.data = 0;
1309 }
1310 else {
1311 struct ist name = htx_get_blk_name(htx, blk);
1312
1313 chunk_initlen(&smp->data.u.str, name.ptr, name.len, name.len);
1314 }
1315 smp->data.type = SMP_T_STR;
1316 smp->flags = SMP_F_CONST | SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1317 return 1;
1318}
1319
1320/* Returns the header value if the selected HTX block exists and is a header or
1321 * a trailer. Otherwise 0 an empty string. Any positive integer (>= 0) is
1322 * supported or "head", "tail" or "first". The channel is chosen depending on
1323 * the sample direction. */
1324static int
1325smp_fetch_htx_blk_hdrval(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
1326{
1327 struct channel *chn;
1328 struct htx *htx;
1329 struct htx_blk *blk;
1330 int32_t pos;
1331
1332 if (!smp->strm || !arg_p)
1333 return 0;
1334
1335 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1336 htx = smp_prefetch_htx(smp, chn, 0);
1337 if (!htx)
1338 return 0;
1339
1340 pos = arg_p[0].data.sint;
1341 if (pos == -1)
1342 blk = htx_get_head_blk(htx);
1343 else if (pos == -2)
1344 blk = htx_get_tail_blk(htx);
1345 else if (pos == -3)
1346 blk = htx_get_first_blk(htx);
1347 else
1348 blk = ((pos >= htx->head && pos <= htx->tail) ? htx_get_blk(htx, pos) : NULL);
1349
1350 if (!blk || (htx_get_blk_type(blk) != HTX_BLK_HDR && htx_get_blk_type(blk) != HTX_BLK_TLR)) {
1351 smp->data.u.str.size = 0;
1352 smp->data.u.str.area = "";
1353 smp->data.u.str.data = 0;
1354 }
1355 else {
1356 struct ist val = htx_get_blk_value(htx, blk);
1357
1358 chunk_initlen(&smp->data.u.str, val.ptr, val.len, val.len);
1359 }
1360 smp->data.type = SMP_T_STR;
1361 smp->flags = SMP_F_CONST | SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1362 return 1;
1363}
1364
1365/* Returns the value if the selected HTX block exists and is a data
1366 * block. Otherwise 0 an empty string. Any positive integer (>= 0) is supported
1367 * or "head", "tail" or "first". The channel is chosen depending on the sample
1368 * direction. */
1369static int
Christopher Fauletc5db14c2020-01-08 14:51:03 +01001370smp_fetch_htx_blk_data(const struct arg *arg_p, struct sample *smp, const char *kw, void *private)
Christopher Faulet29f72842019-12-11 15:52:32 +01001371{
1372 struct channel *chn;
1373 struct htx *htx;
1374 struct htx_blk *blk;
1375 int32_t pos;
1376
1377 if (!smp->strm || !arg_p)
1378 return 0;
1379
1380 chn = ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_RES) ? &smp->strm->res : &smp->strm->req;
1381 htx = smp_prefetch_htx(smp, chn, 0);
1382 if (!htx)
1383 return 0;
1384
1385 pos = arg_p[0].data.sint;
1386 if (pos == -1)
1387 blk = htx_get_head_blk(htx);
1388 else if (pos == -2)
1389 blk = htx_get_tail_blk(htx);
1390 else if (pos == -3)
1391 blk = htx_get_first_blk(htx);
1392 else
1393 blk = ((pos >= htx->head && pos <= htx->tail) ? htx_get_blk(htx, pos) : NULL);
1394
1395 if (!blk || htx_get_blk_type(blk) != HTX_BLK_DATA) {
1396 smp->data.u.str.size = 0;
1397 smp->data.u.str.area = "";
1398 smp->data.u.str.data = 0;
1399 }
1400 else {
1401 struct ist val = htx_get_blk_value(htx, blk);
1402
1403 chunk_initlen(&smp->data.u.str, val.ptr, val.len, val.len);
1404 }
Christopher Faulet8178e402020-01-08 14:38:58 +01001405 smp->data.type = SMP_T_BIN;
Christopher Faulet29f72842019-12-11 15:52:32 +01001406 smp->flags = SMP_F_CONST | SMP_F_VOLATILE | SMP_F_MAY_CHANGE;
1407 return 1;
1408}
1409
1410/* This function is used to validate the arguments passed to any "htx_blk" fetch
1411 * keywords. An argument is expected by these keywords. It must be a positive
1412 * integer or on of the following strings: "head", "tail" or "first". It returns
1413 * 0 on error, and a non-zero value if OK.
1414 */
1415int val_blk_arg(struct arg *arg, char **err_msg)
1416{
1417 if (arg[0].type != ARGT_STR || !arg[0].data.str.data) {
1418 memprintf(err_msg, "a block position is expected (> 0) or a special block name (head, tail, first)");
1419 return 0;
1420 }
1421 if (arg[0].data.str.data == 4 && !strncmp(arg[0].data.str.area, "head", 4)) {
1422 free(arg[0].data.str.area);
1423 arg[0].type = ARGT_SINT;
1424 arg[0].data.sint = -1;
1425 }
1426 else if (arg[0].data.str.data == 4 && !strncmp(arg[0].data.str.area, "tail", 4)) {
1427 free(arg[0].data.str.area);
1428 arg[0].type = ARGT_SINT;
1429 arg[0].data.sint = -2;
1430 }
1431 else if (arg[0].data.str.data == 5 && !strncmp(arg[0].data.str.area, "first", 5)) {
1432 free(arg[0].data.str.area);
1433 arg[0].type = ARGT_SINT;
1434 arg[0].data.sint = -3;
1435 }
1436 else {
1437 int pos;
1438
1439 for (pos = 0; pos < arg[0].data.str.data; pos++) {
1440 if (!isdigit(arg[0].data.str.area[pos])) {
1441 memprintf(err_msg, "invalid block position");
1442 return 0;
1443 }
1444 }
1445
1446 pos = strl2uic(arg[0].data.str.area, arg[0].data.str.data);
1447 if (pos < 0) {
1448 memprintf(err_msg, "block position must not be negative");
1449 return 0;
1450 }
1451 free(arg[0].data.str.area);
1452 arg[0].type = ARGT_SINT;
1453 arg[0].data.sint = pos;
1454 }
1455
1456 return 1;
1457}
1458
1459
1460/* Note: must not be declared <const> as its list will be overwritten.
1461 * Note: htx sample fetches should only used for developpement purpose.
1462 */
1463static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, {
Christopher Faulet01f44452020-01-08 14:23:40 +01001464 { "internal.strm.is_htx", smp_fetch_is_htx, 0, NULL, SMP_T_BOOL, SMP_USE_L6REQ },
Christopher Faulet29f72842019-12-11 15:52:32 +01001465
Christopher Faulet01f44452020-01-08 14:23:40 +01001466 { "internal.htx.nbblks", smp_fetch_htx_nbblks, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1467 { "internal.htx.size", smp_fetch_htx_size, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1468 { "internal.htx.data", smp_fetch_htx_data, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1469 { "internal.htx.used", smp_fetch_htx_used, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1470 { "internal.htx.free", smp_fetch_htx_free, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1471 { "internal.htx.free_data", smp_fetch_htx_free_data, 0, NULL, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1472 { "internal.htx.has_eom", smp_fetch_htx_has_eom, 0, NULL, SMP_T_BOOL, SMP_USE_HRQHV|SMP_USE_HRSHV},
Christopher Faulet29f72842019-12-11 15:52:32 +01001473
Christopher Faulet01f44452020-01-08 14:23:40 +01001474 { "internal.htx_blk.type", smp_fetch_htx_blk_type, ARG1(1,STR), val_blk_arg, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV},
1475 { "internal.htx_blk.size", smp_fetch_htx_blk_size, ARG1(1,STR), val_blk_arg, SMP_T_SINT, SMP_USE_HRQHV|SMP_USE_HRSHV},
1476 { "internal.htx_blk.start_line", smp_fetch_htx_blk_stline, ARG1(1,STR), val_blk_arg, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV},
1477 { "internal.htx_blk.hdrname", smp_fetch_htx_blk_hdrname, ARG1(1,STR), val_blk_arg, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV},
1478 { "internal.htx_blk.hdrval", smp_fetch_htx_blk_hdrval, ARG1(1,STR), val_blk_arg, SMP_T_STR, SMP_USE_HRQHV|SMP_USE_HRSHV},
Christopher Fauletc5db14c2020-01-08 14:51:03 +01001479 { "internal.htx_blk.data", smp_fetch_htx_blk_data, ARG1(1,STR), val_blk_arg, SMP_T_BIN, SMP_USE_HRQHV|SMP_USE_HRSHV},
Christopher Faulet29f72842019-12-11 15:52:32 +01001480
1481 { /* END */ },
1482}};
1483
1484INITCALL1(STG_REGISTER, sample_register_fetches, &sample_fetch_keywords);