blob: 54fa0e9f0b16a52befabc01c61dff2e700524fd3 [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 Tarreauc761f842020-06-04 11:40:28 +020028#include <haproxy/http_rules.h>
Willy Tarreau36979d92020-06-05 17:27:29 +020029#include <haproxy/log.h>
Willy Tarreaud0ef4392020-06-02 09:38:52 +020030#include <haproxy/pool.h>
Willy Tarreaub2551052020-06-09 09:07:15 +020031#include <haproxy/sample.h>
Willy Tarreau48fbcae2020-06-03 18:09:46 +020032#include <haproxy/tools.h>
Willy Tarreaud6788052020-05-27 15:59:00 +020033#include <haproxy/version.h>
Willy Tarreau61c112a2018-10-02 16:43:32 +020034
Willy Tarreau61c112a2018-10-02 16:43:32 +020035
36/* List head of all known action keywords for "http-request" */
37struct action_kw_list http_req_keywords = {
38 .list = LIST_HEAD_INIT(http_req_keywords.list)
39};
40
41/* List head of all known action keywords for "http-response" */
42struct action_kw_list http_res_keywords = {
43 .list = LIST_HEAD_INIT(http_res_keywords.list)
44};
45
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010046/* List head of all known action keywords for "http-after-response" */
47struct action_kw_list http_after_res_keywords = {
48 .list = LIST_HEAD_INIT(http_after_res_keywords.list)
49};
50
Willy Tarreau61c112a2018-10-02 16:43:32 +020051/*
52 * Return the struct http_req_action_kw associated to a keyword.
53 */
Thierry Fournier7a71a6d2020-11-28 17:40:24 +010054struct action_kw *action_http_req_custom(const char *kw)
Willy Tarreau61c112a2018-10-02 16:43:32 +020055{
56 return action_lookup(&http_req_keywords.list, kw);
57}
58
59/*
60 * Return the struct http_res_action_kw associated to a keyword.
61 */
Thierry Fournier7a71a6d2020-11-28 17:40:24 +010062struct action_kw *action_http_res_custom(const char *kw)
Willy Tarreau61c112a2018-10-02 16:43:32 +020063{
64 return action_lookup(&http_res_keywords.list, kw);
65}
66
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010067/*
68 * Return the struct http_after_res_action_kw associated to a keyword.
69 */
Thierry Fournier7a71a6d2020-11-28 17:40:24 +010070struct action_kw *action_http_after_res_custom(const char *kw)
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010071{
72 return action_lookup(&http_after_res_keywords.list, kw);
73}
74
Willy Tarreau61c112a2018-10-02 16:43:32 +020075/* parse an "http-request" rule */
76struct act_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
77{
78 struct act_rule *rule;
Willy Tarreau49bf7be2021-03-12 12:01:34 +010079 const struct action_kw *custom = NULL;
Willy Tarreau61c112a2018-10-02 16:43:32 +020080 int cur_arg;
Willy Tarreau61c112a2018-10-02 16:43:32 +020081
82 rule = calloc(1, sizeof(*rule));
83 if (!rule) {
84 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
85 goto out_err;
86 }
Christopher Faulet81e20172019-12-12 16:40:30 +010087 rule->from = ACT_F_HTTP_REQ;
Willy Tarreau61c112a2018-10-02 16:43:32 +020088
Christopher Faulet81e20172019-12-12 16:40:30 +010089 if (((custom = action_http_req_custom(args[0])) != NULL)) {
Willy Tarreau61c112a2018-10-02 16:43:32 +020090 char *errmsg = NULL;
91
Willy Tarreau61c112a2018-10-02 16:43:32 +020092 cur_arg = 1;
93 /* try in the module list */
Willy Tarreau61c112a2018-10-02 16:43:32 +020094 rule->kw = custom;
Amaury Denoyelle03517732021-05-07 14:25:01 +020095
96 if (custom->flags & KWF_EXPERIMENTAL) {
97 if (!experimental_directives_allowed) {
98 ha_alert("parsing [%s:%d] : '%s' action is experimental, must be allowed via a global 'expose-experimental-directives'\n",
99 file, linenum, custom->kw);
100 goto out_err;
101 }
102 mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
103 }
104
Willy Tarreau61c112a2018-10-02 16:43:32 +0200105 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
106 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule : %s.\n",
107 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
108 free(errmsg);
109 goto out_err;
110 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100111 else if (errmsg) {
112 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
113 free(errmsg);
114 }
115 }
116 else {
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100117 const char *best = action_suggest(args[0], &http_req_keywords.list, NULL);
118
Willy Tarreau61c112a2018-10-02 16:43:32 +0200119 action_build_list(&http_req_keywords.list, &trash);
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100120 ha_alert("parsing [%s:%d]: 'http-request' expects %s, but got '%s'%s.%s%s%s\n",
121 file, linenum, trash.area,
122 args[0], *args[0] ? "" : " (missing argument)",
123 best ? " Did you mean '" : "",
124 best ? best : "",
125 best ? "' maybe ?" : "");
Willy Tarreau61c112a2018-10-02 16:43:32 +0200126 goto out_err;
127 }
128
129 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
130 struct acl_cond *cond;
131 char *errmsg = NULL;
132
133 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
134 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-request %s' condition : %s.\n",
135 file, linenum, args[0], errmsg);
136 free(errmsg);
137 goto out_err;
138 }
139 rule->cond = cond;
140 }
141 else if (*args[cur_arg]) {
Christopher Faulet81e20172019-12-12 16:40:30 +0100142 ha_alert("parsing [%s:%d]: 'http-request %s' expects"
Willy Tarreau61c112a2018-10-02 16:43:32 +0200143 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
144 file, linenum, args[0], args[cur_arg]);
145 goto out_err;
146 }
147
148 return rule;
149 out_err:
150 free(rule);
151 return NULL;
152}
153
154/* parse an "http-respose" rule */
155struct act_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
156{
157 struct act_rule *rule;
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100158 const struct action_kw *custom = NULL;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200159 int cur_arg;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200160
161 rule = calloc(1, sizeof(*rule));
162 if (!rule) {
163 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
164 goto out_err;
165 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100166 rule->from = ACT_F_HTTP_RES;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200167
Christopher Faulet81e20172019-12-12 16:40:30 +0100168 if (((custom = action_http_res_custom(args[0])) != NULL)) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200169 char *errmsg = NULL;
170
Willy Tarreau61c112a2018-10-02 16:43:32 +0200171 cur_arg = 1;
172 /* try in the module list */
Willy Tarreau61c112a2018-10-02 16:43:32 +0200173 rule->kw = custom;
Amaury Denoyelle03517732021-05-07 14:25:01 +0200174
175 if (custom->flags & KWF_EXPERIMENTAL) {
176 if (!experimental_directives_allowed) {
177 ha_alert("parsing [%s:%d] : '%s' action is experimental, must be allowed via a global 'expose-experimental-directives'\n",
178 file, linenum, custom->kw);
179 goto out_err;
180 }
181 mark_tainted(TAINTED_CONFIG_EXP_KW_DECLARED);
182 }
183
Willy Tarreau61c112a2018-10-02 16:43:32 +0200184 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
185 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : %s.\n",
186 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
187 free(errmsg);
188 goto out_err;
189 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100190 else if (errmsg) {
191 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
192 free(errmsg);
193 }
194 }
195 else {
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100196 const char *best = action_suggest(args[0], &http_res_keywords.list, NULL);
197
Willy Tarreau61c112a2018-10-02 16:43:32 +0200198 action_build_list(&http_res_keywords.list, &trash);
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100199 ha_alert("parsing [%s:%d]: 'http-response' expects %s, but got '%s'%s.%s%s%s\n",
200 file, linenum, trash.area,
201 args[0], *args[0] ? "" : " (missing argument)",
202 best ? " Did you mean '" : "",
203 best ? best : "",
204 best ? "' maybe ?" : "");
Willy Tarreau61c112a2018-10-02 16:43:32 +0200205 goto out_err;
206 }
207
208 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
209 struct acl_cond *cond;
210 char *errmsg = NULL;
211
212 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
213 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-response %s' condition : %s.\n",
214 file, linenum, args[0], errmsg);
215 free(errmsg);
216 goto out_err;
217 }
218 rule->cond = cond;
219 }
220 else if (*args[cur_arg]) {
221 ha_alert("parsing [%s:%d]: 'http-response %s' expects"
222 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
223 file, linenum, args[0], args[cur_arg]);
224 goto out_err;
225 }
226
227 return rule;
228 out_err:
229 free(rule);
230 return NULL;
231}
232
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100233
234/* parse an "http-after-response" rule */
235struct act_rule *parse_http_after_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
236{
237 struct act_rule *rule;
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100238 const struct action_kw *custom = NULL;
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100239 int cur_arg;
240
241 rule = calloc(1, sizeof(*rule));
242 if (!rule) {
243 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
244 goto out_err;
245 }
246 rule->from = ACT_F_HTTP_RES;
247
248 if (((custom = action_http_after_res_custom(args[0])) != NULL)) {
249 char *errmsg = NULL;
250
251 cur_arg = 1;
252 /* try in the module list */
253 rule->kw = custom;
254 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
255 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-after-response %s' rule : %s.\n",
256 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
257 free(errmsg);
258 goto out_err;
259 }
260 else if (errmsg) {
261 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
262 free(errmsg);
263 }
264 }
265 else {
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100266 const char *best = action_suggest(args[0], &http_after_res_keywords.list, NULL);
267
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100268 action_build_list(&http_after_res_keywords.list, &trash);
Willy Tarreau49bf7be2021-03-12 12:01:34 +0100269 ha_alert("parsing [%s:%d]: 'http-after-response' expects %s, but got '%s'%s.%s%s%s\n",
270 file, linenum, trash.area,
271 args[0], *args[0] ? "" : " (missing argument)",
272 best ? " Did you mean '" : "",
273 best ? best : "",
274 best ? "' maybe ?" : "");
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100275 goto out_err;
276 }
277
278 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
279 struct acl_cond *cond;
280 char *errmsg = NULL;
281
282 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
283 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-after-response %s' condition : %s.\n",
284 file, linenum, args[0], errmsg);
285 free(errmsg);
286 goto out_err;
287 }
288 rule->cond = cond;
289 }
290 else if (*args[cur_arg]) {
291 ha_alert("parsing [%s:%d]: 'http-after-response %s' expects"
292 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
293 file, linenum, args[0], args[cur_arg]);
294 goto out_err;
295 }
296
297 return rule;
298 out_err:
299 free(rule);
300 return NULL;
301}
302
Willy Tarreau61c112a2018-10-02 16:43:32 +0200303/* Parses a redirect rule. Returns the redirect rule on success or NULL on error,
304 * with <err> filled with the error message. If <use_fmt> is not null, builds a
305 * dynamic log-format rule instead of a static string. Parameter <dir> indicates
306 * the direction of the rule, and equals 0 for request, non-zero for responses.
307 */
308struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, struct proxy *curproxy,
309 const char **args, char **errmsg, int use_fmt, int dir)
310{
311 struct redirect_rule *rule;
312 int cur_arg;
313 int type = REDIRECT_TYPE_NONE;
314 int code = 302;
315 const char *destination = NULL;
316 const char *cookie = NULL;
317 int cookie_set = 0;
Christopher Fauletc87e4682020-01-28 09:13:41 +0100318 unsigned int flags = (!dir ? REDIRECT_FLAG_FROM_REQ : REDIRECT_FLAG_NONE);
Willy Tarreau61c112a2018-10-02 16:43:32 +0200319 struct acl_cond *cond = NULL;
320
321 cur_arg = 0;
322 while (*(args[cur_arg])) {
323 if (strcmp(args[cur_arg], "location") == 0) {
324 if (!*args[cur_arg + 1])
325 goto missing_arg;
326
327 type = REDIRECT_TYPE_LOCATION;
328 cur_arg++;
329 destination = args[cur_arg];
330 }
331 else if (strcmp(args[cur_arg], "prefix") == 0) {
332 if (!*args[cur_arg + 1])
333 goto missing_arg;
334 type = REDIRECT_TYPE_PREFIX;
335 cur_arg++;
336 destination = args[cur_arg];
337 }
338 else if (strcmp(args[cur_arg], "scheme") == 0) {
339 if (!*args[cur_arg + 1])
340 goto missing_arg;
341
342 type = REDIRECT_TYPE_SCHEME;
343 cur_arg++;
344 destination = args[cur_arg];
345 }
346 else if (strcmp(args[cur_arg], "set-cookie") == 0) {
347 if (!*args[cur_arg + 1])
348 goto missing_arg;
349
350 cur_arg++;
351 cookie = args[cur_arg];
352 cookie_set = 1;
353 }
354 else if (strcmp(args[cur_arg], "clear-cookie") == 0) {
355 if (!*args[cur_arg + 1])
356 goto missing_arg;
357
358 cur_arg++;
359 cookie = args[cur_arg];
360 cookie_set = 0;
361 }
362 else if (strcmp(args[cur_arg], "code") == 0) {
363 if (!*args[cur_arg + 1])
364 goto missing_arg;
365
366 cur_arg++;
367 code = atol(args[cur_arg]);
368 if (code < 301 || code > 308 || (code > 303 && code < 307)) {
369 memprintf(errmsg,
370 "'%s': unsupported HTTP code '%s' (must be one of 301, 302, 303, 307 or 308)",
371 args[cur_arg - 1], args[cur_arg]);
372 return NULL;
373 }
374 }
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100375 else if (strcmp(args[cur_arg], "drop-query") == 0) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200376 flags |= REDIRECT_FLAG_DROP_QS;
377 }
Tim Duesterhuse5ff1412021-01-02 22:31:53 +0100378 else if (strcmp(args[cur_arg], "append-slash") == 0) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200379 flags |= REDIRECT_FLAG_APPEND_SLASH;
380 }
381 else if (strcmp(args[cur_arg], "if") == 0 ||
382 strcmp(args[cur_arg], "unless") == 0) {
383 cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + cur_arg, errmsg);
384 if (!cond) {
385 memprintf(errmsg, "error in condition: %s", *errmsg);
386 return NULL;
387 }
388 break;
389 }
390 else {
391 memprintf(errmsg,
392 "expects 'code', 'prefix', 'location', 'scheme', 'set-cookie', 'clear-cookie', 'drop-query' or 'append-slash' (was '%s')",
393 args[cur_arg]);
394 return NULL;
395 }
396 cur_arg++;
397 }
398
399 if (type == REDIRECT_TYPE_NONE) {
400 memprintf(errmsg, "redirection type expected ('prefix', 'location', or 'scheme')");
401 return NULL;
402 }
403
404 if (dir && type != REDIRECT_TYPE_LOCATION) {
405 memprintf(errmsg, "response only supports redirect type 'location'");
406 return NULL;
407 }
408
409 rule = calloc(1, sizeof(*rule));
410 rule->cond = cond;
411 LIST_INIT(&rule->rdr_fmt);
412
413 if (!use_fmt) {
414 /* old-style static redirect rule */
415 rule->rdr_str = strdup(destination);
416 rule->rdr_len = strlen(destination);
417 }
418 else {
419 /* log-format based redirect rule */
420
421 /* Parse destination. Note that in the REDIRECT_TYPE_PREFIX case,
422 * if prefix == "/", we don't want to add anything, otherwise it
423 * makes it hard for the user to configure a self-redirection.
424 */
425 curproxy->conf.args.ctx = ARGC_RDR;
426 if (!(type == REDIRECT_TYPE_PREFIX && destination[0] == '/' && destination[1] == '\0')) {
427 if (!parse_logformat_string(destination, curproxy, &rule->rdr_fmt, LOG_OPT_HTTP,
428 dir ? (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRS_HDR : SMP_VAL_BE_HRS_HDR
429 : (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
430 errmsg)) {
431 return NULL;
432 }
433 free(curproxy->conf.lfs_file);
434 curproxy->conf.lfs_file = strdup(curproxy->conf.args.file);
435 curproxy->conf.lfs_line = curproxy->conf.args.line;
436 }
437 }
438
439 if (cookie) {
440 /* depending on cookie_set, either we want to set the cookie, or to clear it.
441 * a clear consists in appending "; path=/; Max-Age=0;" at the end.
442 */
443 rule->cookie_len = strlen(cookie);
444 if (cookie_set) {
445 rule->cookie_str = malloc(rule->cookie_len + 10);
446 memcpy(rule->cookie_str, cookie, rule->cookie_len);
447 memcpy(rule->cookie_str + rule->cookie_len, "; path=/;", 10);
448 rule->cookie_len += 9;
449 } else {
450 rule->cookie_str = malloc(rule->cookie_len + 21);
451 memcpy(rule->cookie_str, cookie, rule->cookie_len);
452 memcpy(rule->cookie_str + rule->cookie_len, "; path=/; Max-Age=0;", 21);
453 rule->cookie_len += 20;
454 }
455 }
456 rule->type = type;
457 rule->code = code;
458 rule->flags = flags;
459 LIST_INIT(&rule->list);
460 return rule;
461
462 missing_arg:
463 memprintf(errmsg, "missing argument for '%s'", args[cur_arg]);
464 return NULL;
465}
466
Willy Tarreau61c112a2018-10-02 16:43:32 +0200467__attribute__((constructor))
468static void __http_rules_init(void)
469{
470}
471
472/*
473 * Local variables:
474 * c-indent-level: 8
475 * c-basic-offset: 8
476 * End:
477 */