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