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