blob: 4182958a6377402f821b8351524417e9a05a8672 [file] [log] [blame]
Willy Tarreau61c112a2018-10-02 16:43:32 +02001/*
2 * HTTP rules parsing and registration
3 *
4 * Copyright 2000-2018 Willy Tarreau <w@1wt.eu>
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 <sys/types.h>
14
15#include <ctype.h>
16#include <string.h>
17#include <time.h>
18
Willy Tarreaudcc048a2020-06-04 19:11:43 +020019#include <haproxy/acl.h>
Willy Tarreau122eba92020-06-04 10:15:32 +020020#include <haproxy/action.h>
Willy Tarreau4c7e4b72020-05-27 12:58:42 +020021#include <haproxy/api.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020022#include <haproxy/arg.h>
23#include <haproxy/capture-t.h>
Willy Tarreau6be78492020-06-05 00:00:29 +020024#include <haproxy/cfgparse.h>
Willy Tarreauc13ed532020-06-02 10:22:45 +020025#include <haproxy/chunk.h>
Willy Tarreaudfd3de82020-06-04 23:46:14 +020026#include <haproxy/global.h>
Willy Tarreaucd72d8c2020-06-02 19:11:26 +020027#include <haproxy/http.h>
Willy Tarreau0ce6dc02021-10-06 16:38:53 +020028#include <haproxy/http_ana-t.h>
Willy Tarreauc761f842020-06-04 11:40:28 +020029#include <haproxy/http_rules.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020030#include <haproxy/log.h>
Willy Tarreaud0ef4392020-06-02 09:38:52 +020031#include <haproxy/pool.h>
Willy Tarreaud1dd2502021-05-08 20:30:37 +020032#include <haproxy/proxy.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020033#include <haproxy/sample.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020034#include <haproxy/tools.h>
Willy Tarreaud6788052020-05-27 15:59:00 +020035#include <haproxy/version.h>
Willy Tarreau61c112a2018-10-02 16:43:32 +020036
Willy Tarreau61c112a2018-10-02 16:43:32 +020037
38/* List head of all known action keywords for "http-request" */
39struct action_kw_list http_req_keywords = {
40 .list = LIST_HEAD_INIT(http_req_keywords.list)
41};
42
43/* List head of all known action keywords for "http-response" */
44struct action_kw_list http_res_keywords = {
45 .list = LIST_HEAD_INIT(http_res_keywords.list)
46};
47
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010048/* List head of all known action keywords for "http-after-response" */
49struct action_kw_list http_after_res_keywords = {
50 .list = LIST_HEAD_INIT(http_after_res_keywords.list)
51};
52
Willy Tarreau61c112a2018-10-02 16:43:32 +020053/*
54 * Return the struct http_req_action_kw associated to a keyword.
55 */
Thierry Fournier7a71a6d2020-11-28 17:40:24 +010056struct action_kw *action_http_req_custom(const char *kw)
Willy Tarreau61c112a2018-10-02 16:43:32 +020057{
58 return action_lookup(&http_req_keywords.list, kw);
59}
60
61/*
62 * Return the struct http_res_action_kw associated to a keyword.
63 */
Thierry Fournier7a71a6d2020-11-28 17:40:24 +010064struct action_kw *action_http_res_custom(const char *kw)
Willy Tarreau61c112a2018-10-02 16:43:32 +020065{
66 return action_lookup(&http_res_keywords.list, kw);
67}
68
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010069/*
70 * Return the struct http_after_res_action_kw associated to a keyword.
71 */
Thierry Fournier7a71a6d2020-11-28 17:40:24 +010072struct action_kw *action_http_after_res_custom(const char *kw)
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010073{
74 return action_lookup(&http_after_res_keywords.list, kw);
75}
76
Willy Tarreau61c112a2018-10-02 16:43:32 +020077/* parse an "http-request" rule */
78struct act_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
79{
80 struct act_rule *rule;
Willy Tarreau49bf7be2021-03-12 12:01:34 +010081 const struct action_kw *custom = NULL;
Willy Tarreau61c112a2018-10-02 16:43:32 +020082 int cur_arg;
Willy Tarreau61c112a2018-10-02 16:43:32 +020083
84 rule = calloc(1, sizeof(*rule));
85 if (!rule) {
86 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
87 goto out_err;
88 }
Christopher Faulet81e20172019-12-12 16:40:30 +010089 rule->from = ACT_F_HTTP_REQ;
Willy Tarreau61c112a2018-10-02 16:43:32 +020090
Christopher Faulet81e20172019-12-12 16:40:30 +010091 if (((custom = action_http_req_custom(args[0])) != NULL)) {
Willy Tarreau61c112a2018-10-02 16:43:32 +020092 char *errmsg = NULL;
93
Willy Tarreau61c112a2018-10-02 16:43:32 +020094 cur_arg = 1;
95 /* try in the module list */
Willy Tarreau61c112a2018-10-02 16:43:32 +020096 rule->kw = custom;
Amaury Denoyelle03517732021-05-07 14:25:01 +020097
98 if (custom->flags & KWF_EXPERIMENTAL) {
99 if (!experimental_directives_allowed) {
100 ha_alert("parsing [%s:%d] : '%s' action is experimental, must be allowed via a global 'expose-experimental-directives'\n",
101 file, linenum, custom->kw);
102 goto out_err;
103 }
104 mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
105 }
106
Willy Tarreau61c112a2018-10-02 16:43:32 +0200107 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
108 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule : %s.\n",
109 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
110 free(errmsg);
111 goto out_err;
112 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100113 else if (errmsg) {
114 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
115 free(errmsg);
116 }
117 }
118 else {
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100119 const char *best = action_suggest(args[0], &http_req_keywords.list, NULL);
120
Willy Tarreau61c112a2018-10-02 16:43:32 +0200121 action_build_list(&http_req_keywords.list, &trash);
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100122 ha_alert("parsing [%s:%d]: 'http-request' expects %s, but got '%s'%s.%s%s%s\n",
123 file, linenum, trash.area,
124 args[0], *args[0] ? "" : " (missing argument)",
125 best ? " Did you mean '" : "",
126 best ? best : "",
127 best ? "' maybe ?" : "");
Willy Tarreau61c112a2018-10-02 16:43:32 +0200128 goto out_err;
129 }
130
131 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
132 struct acl_cond *cond;
133 char *errmsg = NULL;
134
135 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
136 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-request %s' condition : %s.\n",
137 file, linenum, args[0], errmsg);
138 free(errmsg);
139 goto out_err;
140 }
141 rule->cond = cond;
142 }
143 else if (*args[cur_arg]) {
Christopher Faulet81e20172019-12-12 16:40:30 +0100144 ha_alert("parsing [%s:%d]: 'http-request %s' expects"
Willy Tarreau61c112a2018-10-02 16:43:32 +0200145 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
146 file, linenum, args[0], args[cur_arg]);
147 goto out_err;
148 }
149
150 return rule;
151 out_err:
152 free(rule);
153 return NULL;
154}
155
156/* parse an "http-respose" rule */
157struct act_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
158{
159 struct act_rule *rule;
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100160 const struct action_kw *custom = NULL;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200161 int cur_arg;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200162
163 rule = calloc(1, sizeof(*rule));
164 if (!rule) {
165 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
166 goto out_err;
167 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100168 rule->from = ACT_F_HTTP_RES;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200169
Christopher Faulet81e20172019-12-12 16:40:30 +0100170 if (((custom = action_http_res_custom(args[0])) != NULL)) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200171 char *errmsg = NULL;
172
Willy Tarreau61c112a2018-10-02 16:43:32 +0200173 cur_arg = 1;
174 /* try in the module list */
Willy Tarreau61c112a2018-10-02 16:43:32 +0200175 rule->kw = custom;
Amaury Denoyelle03517732021-05-07 14:25:01 +0200176
177 if (custom->flags & KWF_EXPERIMENTAL) {
178 if (!experimental_directives_allowed) {
179 ha_alert("parsing [%s:%d] : '%s' action is experimental, must be allowed via a global 'expose-experimental-directives'\n",
180 file, linenum, custom->kw);
181 goto out_err;
182 }
183 mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
184 }
185
Willy Tarreau61c112a2018-10-02 16:43:32 +0200186 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
187 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : %s.\n",
188 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
189 free(errmsg);
190 goto out_err;
191 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100192 else if (errmsg) {
193 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
194 free(errmsg);
195 }
196 }
197 else {
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100198 const char *best = action_suggest(args[0], &http_res_keywords.list, NULL);
199
Willy Tarreau61c112a2018-10-02 16:43:32 +0200200 action_build_list(&http_res_keywords.list, &trash);
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100201 ha_alert("parsing [%s:%d]: 'http-response' expects %s, but got '%s'%s.%s%s%s\n",
202 file, linenum, trash.area,
203 args[0], *args[0] ? "" : " (missing argument)",
204 best ? " Did you mean '" : "",
205 best ? best : "",
206 best ? "' maybe ?" : "");
Willy Tarreau61c112a2018-10-02 16:43:32 +0200207 goto out_err;
208 }
209
210 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
211 struct acl_cond *cond;
212 char *errmsg = NULL;
213
214 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
215 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-response %s' condition : %s.\n",
216 file, linenum, args[0], errmsg);
217 free(errmsg);
218 goto out_err;
219 }
220 rule->cond = cond;
221 }
222 else if (*args[cur_arg]) {
223 ha_alert("parsing [%s:%d]: 'http-response %s' expects"
224 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
225 file, linenum, args[0], args[cur_arg]);
226 goto out_err;
227 }
228
229 return rule;
230 out_err:
231 free(rule);
232 return NULL;
233}
234
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100235
236/* parse an "http-after-response" rule */
237struct act_rule *parse_http_after_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
238{
239 struct act_rule *rule;
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100240 const struct action_kw *custom = NULL;
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100241 int cur_arg;
242
243 rule = calloc(1, sizeof(*rule));
244 if (!rule) {
245 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
246 goto out_err;
247 }
248 rule->from = ACT_F_HTTP_RES;
249
250 if (((custom = action_http_after_res_custom(args[0])) != NULL)) {
251 char *errmsg = NULL;
252
253 cur_arg = 1;
254 /* try in the module list */
255 rule->kw = custom;
256 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
257 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-after-response %s' rule : %s.\n",
258 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
259 free(errmsg);
260 goto out_err;
261 }
262 else if (errmsg) {
263 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
264 free(errmsg);
265 }
266 }
267 else {
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100268 const char *best = action_suggest(args[0], &http_after_res_keywords.list, NULL);
269
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100270 action_build_list(&http_after_res_keywords.list, &trash);
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100271 ha_alert("parsing [%s:%d]: 'http-after-response' expects %s, but got '%s'%s.%s%s%s\n",
272 file, linenum, trash.area,
273 args[0], *args[0] ? "" : " (missing argument)",
274 best ? " Did you mean '" : "",
275 best ? best : "",
276 best ? "' maybe ?" : "");
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100277 goto out_err;
278 }
279
280 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
281 struct acl_cond *cond;
282 char *errmsg = NULL;
283
284 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
285 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-after-response %s' condition : %s.\n",
286 file, linenum, args[0], errmsg);
287 free(errmsg);
288 goto out_err;
289 }
290 rule->cond = cond;
291 }
292 else if (*args[cur_arg]) {
293 ha_alert("parsing [%s:%d]: 'http-after-response %s' expects"
294 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
295 file, linenum, args[0], args[cur_arg]);
296 goto out_err;
297 }
298
299 return rule;
300 out_err:
301 free(rule);
302 return NULL;
303}
304
Willy Tarreau61c112a2018-10-02 16:43:32 +0200305/* Parses a redirect rule. Returns the redirect rule on success or NULL on error,
306 * with <err> filled with the error message. If <use_fmt> is not null, builds a
307 * dynamic log-format rule instead of a static string. Parameter <dir> indicates
308 * the direction of the rule, and equals 0 for request, non-zero for responses.
309 */
310struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, struct proxy *curproxy,
311 const char **args, char **errmsg, int use_fmt, int dir)
312{
313 struct redirect_rule *rule;
314 int cur_arg;
315 int type = REDIRECT_TYPE_NONE;
316 int code = 302;
317 const char *destination = NULL;
318 const char *cookie = NULL;
319 int cookie_set = 0;
Christopher Fauletc87e4682020-01-28 09:13:41 +0100320 unsigned int flags = (!dir ? REDIRECT_FLAG_FROM_REQ : REDIRECT_FLAG_NONE);
Willy Tarreau61c112a2018-10-02 16:43:32 +0200321 struct acl_cond *cond = NULL;
322
323 cur_arg = 0;
324 while (*(args[cur_arg])) {
325 if (strcmp(args[cur_arg], "location") == 0) {
326 if (!*args[cur_arg + 1])
327 goto missing_arg;
328
329 type = REDIRECT_TYPE_LOCATION;
330 cur_arg++;
331 destination = args[cur_arg];
332 }
333 else if (strcmp(args[cur_arg], "prefix") == 0) {
334 if (!*args[cur_arg + 1])
335 goto missing_arg;
336 type = REDIRECT_TYPE_PREFIX;
337 cur_arg++;
338 destination = args[cur_arg];
339 }
340 else if (strcmp(args[cur_arg], "scheme") == 0) {
341 if (!*args[cur_arg + 1])
342 goto missing_arg;
343
344 type = REDIRECT_TYPE_SCHEME;
345 cur_arg++;
346 destination = args[cur_arg];
347 }
348 else if (strcmp(args[cur_arg], "set-cookie") == 0) {
349 if (!*args[cur_arg + 1])
350 goto missing_arg;
351
352 cur_arg++;
353 cookie = args[cur_arg];
354 cookie_set = 1;
355 }
356 else if (strcmp(args[cur_arg], "clear-cookie") == 0) {
357 if (!*args[cur_arg + 1])
358 goto missing_arg;
359
360 cur_arg++;
361 cookie = args[cur_arg];
362 cookie_set = 0;
363 }
364 else if (strcmp(args[cur_arg], "code") == 0) {
365 if (!*args[cur_arg + 1])
366 goto missing_arg;
367
368 cur_arg++;
369 code = atol(args[cur_arg]);
370 if (code < 301 || code > 308 || (code > 303 && code < 307)) {
371 memprintf(errmsg,
372 "'%s': unsupported HTTP code '%s' (must be one of 301, 302, 303, 307 or 308)",
373 args[cur_arg - 1], args[cur_arg]);
374 return NULL;
375 }
376 }
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100377 else if (strcmp(args[cur_arg], "drop-query") == 0) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200378 flags |= REDIRECT_FLAG_DROP_QS;
379 }
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100380 else if (strcmp(args[cur_arg], "append-slash") == 0) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200381 flags |= REDIRECT_FLAG_APPEND_SLASH;
382 }
Willy Tarreaubc1223b2021-09-02 16:54:33 +0200383 else if (strcmp(args[cur_arg], "ignore-empty") == 0) {
384 flags |= REDIRECT_FLAG_IGNORE_EMPTY;
385 }
Willy Tarreau61c112a2018-10-02 16:43:32 +0200386 else if (strcmp(args[cur_arg], "if") == 0 ||
387 strcmp(args[cur_arg], "unless") == 0) {
388 cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + cur_arg, errmsg);
389 if (!cond) {
390 memprintf(errmsg, "error in condition: %s", *errmsg);
391 return NULL;
392 }
393 break;
394 }
395 else {
396 memprintf(errmsg,
Willy Tarreaubc1223b2021-09-02 16:54:33 +0200397 "expects 'code', 'prefix', 'location', 'scheme', 'set-cookie', 'clear-cookie', 'drop-query', 'ignore-empty' or 'append-slash' (was '%s')",
Willy Tarreau61c112a2018-10-02 16:43:32 +0200398 args[cur_arg]);
399 return NULL;
400 }
401 cur_arg++;
402 }
403
404 if (type == REDIRECT_TYPE_NONE) {
405 memprintf(errmsg, "redirection type expected ('prefix', 'location', or 'scheme')");
406 return NULL;
407 }
408
409 if (dir && type != REDIRECT_TYPE_LOCATION) {
410 memprintf(errmsg, "response only supports redirect type 'location'");
411 return NULL;
412 }
413
414 rule = calloc(1, sizeof(*rule));
Remi Tricot-Le Bretonb6864a52021-05-19 11:32:04 +0200415 if (!rule) {
416 memprintf(errmsg, "parsing [%s:%d]: out of memory.", file, linenum);
417 return NULL;
418 }
Willy Tarreau61c112a2018-10-02 16:43:32 +0200419 rule->cond = cond;
420 LIST_INIT(&rule->rdr_fmt);
421
422 if (!use_fmt) {
423 /* old-style static redirect rule */
424 rule->rdr_str = strdup(destination);
425 rule->rdr_len = strlen(destination);
426 }
427 else {
428 /* log-format based redirect rule */
429
430 /* Parse destination. Note that in the REDIRECT_TYPE_PREFIX case,
431 * if prefix == "/", we don't want to add anything, otherwise it
432 * makes it hard for the user to configure a self-redirection.
433 */
434 curproxy->conf.args.ctx = ARGC_RDR;
435 if (!(type == REDIRECT_TYPE_PREFIX && destination[0] == '/' && destination[1] == '\0')) {
436 if (!parse_logformat_string(destination, curproxy, &rule->rdr_fmt, LOG_OPT_HTTP,
437 dir ? (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRS_HDR : SMP_VAL_BE_HRS_HDR
438 : (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
439 errmsg)) {
440 return NULL;
441 }
442 free(curproxy->conf.lfs_file);
443 curproxy->conf.lfs_file = strdup(curproxy->conf.args.file);
444 curproxy->conf.lfs_line = curproxy->conf.args.line;
445 }
446 }
447
448 if (cookie) {
449 /* depending on cookie_set, either we want to set the cookie, or to clear it.
450 * a clear consists in appending "; path=/; Max-Age=0;" at the end.
451 */
452 rule->cookie_len = strlen(cookie);
453 if (cookie_set) {
454 rule->cookie_str = malloc(rule->cookie_len + 10);
455 memcpy(rule->cookie_str, cookie, rule->cookie_len);
456 memcpy(rule->cookie_str + rule->cookie_len, "; path=/;", 10);
457 rule->cookie_len += 9;
458 } else {
459 rule->cookie_str = malloc(rule->cookie_len + 21);
460 memcpy(rule->cookie_str, cookie, rule->cookie_len);
461 memcpy(rule->cookie_str + rule->cookie_len, "; path=/; Max-Age=0;", 21);
462 rule->cookie_len += 20;
463 }
464 }
465 rule->type = type;
466 rule->code = code;
467 rule->flags = flags;
468 LIST_INIT(&rule->list);
469 return rule;
470
471 missing_arg:
472 memprintf(errmsg, "missing argument for '%s'", args[cur_arg]);
473 return NULL;
474}
475
Willy Tarreau61c112a2018-10-02 16:43:32 +0200476__attribute__((constructor))
477static void __http_rules_init(void)
478{
479}
480
481/*
482 * Local variables:
483 * c-indent-level: 8
484 * c-basic-offset: 8
485 * End:
486 */