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