blob: 2e58ec2e0b67cbf0d96ee1c4177d452ed6c66989 [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
19#include <common/cfgparse.h>
20#include <common/chunk.h>
21#include <common/compat.h>
22#include <common/config.h>
23#include <common/debug.h>
24#include <common/http.h>
25#include <common/memory.h>
26#include <common/standard.h>
27#include <common/version.h>
28
29#include <types/capture.h>
30#include <types/global.h>
31
32#include <proto/acl.h>
33#include <proto/action.h>
34#include <proto/arg.h>
35#include <proto/http_rules.h>
Christopher Fauletfc9cfe42019-07-16 14:54:53 +020036#include <proto/http_ana.h>
Willy Tarreau61c112a2018-10-02 16:43:32 +020037#include <proto/sample.h>
38
39
40/* List head of all known action keywords for "http-request" */
41struct action_kw_list http_req_keywords = {
42 .list = LIST_HEAD_INIT(http_req_keywords.list)
43};
44
45/* List head of all known action keywords for "http-response" */
46struct action_kw_list http_res_keywords = {
47 .list = LIST_HEAD_INIT(http_res_keywords.list)
48};
49
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010050/* List head of all known action keywords for "http-after-response" */
51struct action_kw_list http_after_res_keywords = {
52 .list = LIST_HEAD_INIT(http_after_res_keywords.list)
53};
54
Willy Tarreau61c112a2018-10-02 16:43:32 +020055/*
56 * Return the struct http_req_action_kw associated to a keyword.
57 */
58static struct action_kw *action_http_req_custom(const char *kw)
59{
60 return action_lookup(&http_req_keywords.list, kw);
61}
62
63/*
64 * Return the struct http_res_action_kw associated to a keyword.
65 */
66static struct action_kw *action_http_res_custom(const char *kw)
67{
68 return action_lookup(&http_res_keywords.list, kw);
69}
70
Christopher Faulet6d0c3df2020-01-22 09:26:35 +010071/*
72 * Return the struct http_after_res_action_kw associated to a keyword.
73 */
74static struct action_kw *action_http_after_res_custom(const char *kw)
75{
76 return action_lookup(&http_after_res_keywords.list, kw);
77}
78
Willy Tarreau61c112a2018-10-02 16:43:32 +020079/* parse an "http-request" rule */
80struct act_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
81{
82 struct act_rule *rule;
83 struct action_kw *custom = NULL;
84 int cur_arg;
Willy Tarreau61c112a2018-10-02 16:43:32 +020085
86 rule = calloc(1, sizeof(*rule));
87 if (!rule) {
88 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
89 goto out_err;
90 }
Christopher Faulet81e20172019-12-12 16:40:30 +010091 rule->from = ACT_F_HTTP_REQ;
Willy Tarreau61c112a2018-10-02 16:43:32 +020092
Christopher Faulet81e20172019-12-12 16:40:30 +010093 if (((custom = action_http_req_custom(args[0])) != NULL)) {
Willy Tarreau61c112a2018-10-02 16:43:32 +020094 char *errmsg = NULL;
95
Willy Tarreau61c112a2018-10-02 16:43:32 +020096 cur_arg = 1;
97 /* try in the module list */
Willy Tarreau61c112a2018-10-02 16:43:32 +020098 rule->kw = custom;
99 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
100 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-request %s' rule : %s.\n",
101 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
102 free(errmsg);
103 goto out_err;
104 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100105 else if (errmsg) {
106 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
107 free(errmsg);
108 }
109 }
110 else {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200111 action_build_list(&http_req_keywords.list, &trash);
Christopher Faulet81e20172019-12-12 16:40:30 +0100112 ha_alert("parsing [%s:%d]: 'http-request' expects %s%s, but got '%s'%s.\n",
Willy Tarreau61c112a2018-10-02 16:43:32 +0200113 file, linenum, *trash.area ? ", " : "", trash.area,
114 args[0], *args[0] ? "" : " (missing argument)");
115 goto out_err;
116 }
117
118 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
119 struct acl_cond *cond;
120 char *errmsg = NULL;
121
122 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
123 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-request %s' condition : %s.\n",
124 file, linenum, args[0], errmsg);
125 free(errmsg);
126 goto out_err;
127 }
128 rule->cond = cond;
129 }
130 else if (*args[cur_arg]) {
Christopher Faulet81e20172019-12-12 16:40:30 +0100131 ha_alert("parsing [%s:%d]: 'http-request %s' expects"
Willy Tarreau61c112a2018-10-02 16:43:32 +0200132 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
133 file, linenum, args[0], args[cur_arg]);
134 goto out_err;
135 }
136
137 return rule;
138 out_err:
139 free(rule);
140 return NULL;
141}
142
143/* parse an "http-respose" rule */
144struct act_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
145{
146 struct act_rule *rule;
147 struct action_kw *custom = NULL;
148 int cur_arg;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200149
150 rule = calloc(1, sizeof(*rule));
151 if (!rule) {
152 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
153 goto out_err;
154 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100155 rule->from = ACT_F_HTTP_RES;
Willy Tarreau61c112a2018-10-02 16:43:32 +0200156
Christopher Faulet81e20172019-12-12 16:40:30 +0100157 if (((custom = action_http_res_custom(args[0])) != NULL)) {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200158 char *errmsg = NULL;
159
Willy Tarreau61c112a2018-10-02 16:43:32 +0200160 cur_arg = 1;
161 /* try in the module list */
Willy Tarreau61c112a2018-10-02 16:43:32 +0200162 rule->kw = custom;
163 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
164 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-response %s' rule : %s.\n",
165 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
166 free(errmsg);
167 goto out_err;
168 }
Christopher Faulet81e20172019-12-12 16:40:30 +0100169 else if (errmsg) {
170 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
171 free(errmsg);
172 }
173 }
174 else {
Willy Tarreau61c112a2018-10-02 16:43:32 +0200175 action_build_list(&http_res_keywords.list, &trash);
Christopher Faulet81e20172019-12-12 16:40:30 +0100176 ha_alert("parsing [%s:%d]: 'http-response' expects %s%s, but got '%s'%s.\n",
Willy Tarreau61c112a2018-10-02 16:43:32 +0200177 file, linenum, *trash.area ? ", " : "", trash.area,
178 args[0], *args[0] ? "" : " (missing argument)");
179 goto out_err;
180 }
181
182 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
183 struct acl_cond *cond;
184 char *errmsg = NULL;
185
186 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
187 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-response %s' condition : %s.\n",
188 file, linenum, args[0], errmsg);
189 free(errmsg);
190 goto out_err;
191 }
192 rule->cond = cond;
193 }
194 else if (*args[cur_arg]) {
195 ha_alert("parsing [%s:%d]: 'http-response %s' expects"
196 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
197 file, linenum, args[0], args[cur_arg]);
198 goto out_err;
199 }
200
201 return rule;
202 out_err:
203 free(rule);
204 return NULL;
205}
206
Christopher Faulet6d0c3df2020-01-22 09:26:35 +0100207
208/* parse an "http-after-response" rule */
209struct act_rule *parse_http_after_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy)
210{
211 struct act_rule *rule;
212 struct action_kw *custom = NULL;
213 int cur_arg;
214
215 rule = calloc(1, sizeof(*rule));
216 if (!rule) {
217 ha_alert("parsing [%s:%d]: out of memory.\n", file, linenum);
218 goto out_err;
219 }
220 rule->from = ACT_F_HTTP_RES;
221
222 if (((custom = action_http_after_res_custom(args[0])) != NULL)) {
223 char *errmsg = NULL;
224
225 cur_arg = 1;
226 /* try in the module list */
227 rule->kw = custom;
228 if (custom->parse(args, &cur_arg, proxy, rule, &errmsg) == ACT_RET_PRS_ERR) {
229 ha_alert("parsing [%s:%d] : error detected in %s '%s' while parsing 'http-after-response %s' rule : %s.\n",
230 file, linenum, proxy_type_str(proxy), proxy->id, args[0], errmsg);
231 free(errmsg);
232 goto out_err;
233 }
234 else if (errmsg) {
235 ha_warning("parsing [%s:%d] : %s.\n", file, linenum, errmsg);
236 free(errmsg);
237 }
238 }
239 else {
240 action_build_list(&http_after_res_keywords.list, &trash);
241 ha_alert("parsing [%s:%d]: 'http-after-response' expects %s%s, but got '%s'%s.\n",
242 file, linenum, *trash.area ? ", " : "", trash.area,
243 args[0], *args[0] ? "" : " (missing argument)");
244 goto out_err;
245 }
246
247 if (strcmp(args[cur_arg], "if") == 0 || strcmp(args[cur_arg], "unless") == 0) {
248 struct acl_cond *cond;
249 char *errmsg = NULL;
250
251 if ((cond = build_acl_cond(file, linenum, &proxy->acl, proxy, args+cur_arg, &errmsg)) == NULL) {
252 ha_alert("parsing [%s:%d] : error detected while parsing an 'http-after-response %s' condition : %s.\n",
253 file, linenum, args[0], errmsg);
254 free(errmsg);
255 goto out_err;
256 }
257 rule->cond = cond;
258 }
259 else if (*args[cur_arg]) {
260 ha_alert("parsing [%s:%d]: 'http-after-response %s' expects"
261 " either 'if' or 'unless' followed by a condition but found '%s'.\n",
262 file, linenum, args[0], args[cur_arg]);
263 goto out_err;
264 }
265
266 return rule;
267 out_err:
268 free(rule);
269 return NULL;
270}
271
Willy Tarreau61c112a2018-10-02 16:43:32 +0200272/* Parses a redirect rule. Returns the redirect rule on success or NULL on error,
273 * with <err> filled with the error message. If <use_fmt> is not null, builds a
274 * dynamic log-format rule instead of a static string. Parameter <dir> indicates
275 * the direction of the rule, and equals 0 for request, non-zero for responses.
276 */
277struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, struct proxy *curproxy,
278 const char **args, char **errmsg, int use_fmt, int dir)
279{
280 struct redirect_rule *rule;
281 int cur_arg;
282 int type = REDIRECT_TYPE_NONE;
283 int code = 302;
284 const char *destination = NULL;
285 const char *cookie = NULL;
286 int cookie_set = 0;
Christopher Fauletc87e4682020-01-28 09:13:41 +0100287 unsigned int flags = (!dir ? REDIRECT_FLAG_FROM_REQ : REDIRECT_FLAG_NONE);
Willy Tarreau61c112a2018-10-02 16:43:32 +0200288 struct acl_cond *cond = NULL;
289
290 cur_arg = 0;
291 while (*(args[cur_arg])) {
292 if (strcmp(args[cur_arg], "location") == 0) {
293 if (!*args[cur_arg + 1])
294 goto missing_arg;
295
296 type = REDIRECT_TYPE_LOCATION;
297 cur_arg++;
298 destination = args[cur_arg];
299 }
300 else if (strcmp(args[cur_arg], "prefix") == 0) {
301 if (!*args[cur_arg + 1])
302 goto missing_arg;
303 type = REDIRECT_TYPE_PREFIX;
304 cur_arg++;
305 destination = args[cur_arg];
306 }
307 else if (strcmp(args[cur_arg], "scheme") == 0) {
308 if (!*args[cur_arg + 1])
309 goto missing_arg;
310
311 type = REDIRECT_TYPE_SCHEME;
312 cur_arg++;
313 destination = args[cur_arg];
314 }
315 else if (strcmp(args[cur_arg], "set-cookie") == 0) {
316 if (!*args[cur_arg + 1])
317 goto missing_arg;
318
319 cur_arg++;
320 cookie = args[cur_arg];
321 cookie_set = 1;
322 }
323 else if (strcmp(args[cur_arg], "clear-cookie") == 0) {
324 if (!*args[cur_arg + 1])
325 goto missing_arg;
326
327 cur_arg++;
328 cookie = args[cur_arg];
329 cookie_set = 0;
330 }
331 else if (strcmp(args[cur_arg], "code") == 0) {
332 if (!*args[cur_arg + 1])
333 goto missing_arg;
334
335 cur_arg++;
336 code = atol(args[cur_arg]);
337 if (code < 301 || code > 308 || (code > 303 && code < 307)) {
338 memprintf(errmsg,
339 "'%s': unsupported HTTP code '%s' (must be one of 301, 302, 303, 307 or 308)",
340 args[cur_arg - 1], args[cur_arg]);
341 return NULL;
342 }
343 }
344 else if (!strcmp(args[cur_arg],"drop-query")) {
345 flags |= REDIRECT_FLAG_DROP_QS;
346 }
347 else if (!strcmp(args[cur_arg],"append-slash")) {
348 flags |= REDIRECT_FLAG_APPEND_SLASH;
349 }
350 else if (strcmp(args[cur_arg], "if") == 0 ||
351 strcmp(args[cur_arg], "unless") == 0) {
352 cond = build_acl_cond(file, linenum, &curproxy->acl, curproxy, (const char **)args + cur_arg, errmsg);
353 if (!cond) {
354 memprintf(errmsg, "error in condition: %s", *errmsg);
355 return NULL;
356 }
357 break;
358 }
359 else {
360 memprintf(errmsg,
361 "expects 'code', 'prefix', 'location', 'scheme', 'set-cookie', 'clear-cookie', 'drop-query' or 'append-slash' (was '%s')",
362 args[cur_arg]);
363 return NULL;
364 }
365 cur_arg++;
366 }
367
368 if (type == REDIRECT_TYPE_NONE) {
369 memprintf(errmsg, "redirection type expected ('prefix', 'location', or 'scheme')");
370 return NULL;
371 }
372
373 if (dir && type != REDIRECT_TYPE_LOCATION) {
374 memprintf(errmsg, "response only supports redirect type 'location'");
375 return NULL;
376 }
377
378 rule = calloc(1, sizeof(*rule));
379 rule->cond = cond;
380 LIST_INIT(&rule->rdr_fmt);
381
382 if (!use_fmt) {
383 /* old-style static redirect rule */
384 rule->rdr_str = strdup(destination);
385 rule->rdr_len = strlen(destination);
386 }
387 else {
388 /* log-format based redirect rule */
389
390 /* Parse destination. Note that in the REDIRECT_TYPE_PREFIX case,
391 * if prefix == "/", we don't want to add anything, otherwise it
392 * makes it hard for the user to configure a self-redirection.
393 */
394 curproxy->conf.args.ctx = ARGC_RDR;
395 if (!(type == REDIRECT_TYPE_PREFIX && destination[0] == '/' && destination[1] == '\0')) {
396 if (!parse_logformat_string(destination, curproxy, &rule->rdr_fmt, LOG_OPT_HTTP,
397 dir ? (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRS_HDR : SMP_VAL_BE_HRS_HDR
398 : (curproxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
399 errmsg)) {
400 return NULL;
401 }
402 free(curproxy->conf.lfs_file);
403 curproxy->conf.lfs_file = strdup(curproxy->conf.args.file);
404 curproxy->conf.lfs_line = curproxy->conf.args.line;
405 }
406 }
407
408 if (cookie) {
409 /* depending on cookie_set, either we want to set the cookie, or to clear it.
410 * a clear consists in appending "; path=/; Max-Age=0;" at the end.
411 */
412 rule->cookie_len = strlen(cookie);
413 if (cookie_set) {
414 rule->cookie_str = malloc(rule->cookie_len + 10);
415 memcpy(rule->cookie_str, cookie, rule->cookie_len);
416 memcpy(rule->cookie_str + rule->cookie_len, "; path=/;", 10);
417 rule->cookie_len += 9;
418 } else {
419 rule->cookie_str = malloc(rule->cookie_len + 21);
420 memcpy(rule->cookie_str, cookie, rule->cookie_len);
421 memcpy(rule->cookie_str + rule->cookie_len, "; path=/; Max-Age=0;", 21);
422 rule->cookie_len += 20;
423 }
424 }
425 rule->type = type;
426 rule->code = code;
427 rule->flags = flags;
428 LIST_INIT(&rule->list);
429 return rule;
430
431 missing_arg:
432 memprintf(errmsg, "missing argument for '%s'", args[cur_arg]);
433 return NULL;
434}
435
Willy Tarreau61c112a2018-10-02 16:43:32 +0200436__attribute__((constructor))
437static void __http_rules_init(void)
438{
439}
440
441/*
442 * Local variables:
443 * c-indent-level: 8
444 * c-basic-offset: 8
445 * End:
446 */