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