diff --git a/doc/configuration.txt b/doc/configuration.txt index 725d9fa..1452d50 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -568,6 +568,7 @@ option tcpka X X X X option tcplog X X X X [no] option tcpsplice X X X X [no] option transparent X X X - +redirect - X X X redisp X - X X (deprecated) redispatch X - X X (deprecated) reqadd - X X X @@ -2293,6 +2294,32 @@ no option transparent "transparent" option of the "bind" keyword. +redirect {location | prefix} [code ] {if | unless} + Return an HTTP redirection if/unless a condition is matched + May be used in sections : defaults | frontend | listen | backend + no | yes | yes | yes + + If/unless the condition is matched, the HTTP request will lead to a redirect + response. There are currently two types of redirections : "location" and + "prefix". With "location", the exact value in is placed into the HTTP + "Location" header. With "prefix", the "Location" header is built from the + concatenation of and the URI. It is particularly suited for global site + redirections. + + The code is optional. It indicates in which type of HTTP redirection + is desired. Only codes 301, 302 and 303 are supported. 302 is used if no code + is specified. + + Example: move the login URL only to HTTPS. + acl clear dst_port 80 + acl secure dst_port 8080 + acl login_page url_beg /login + redirect prefix https://mysite.com if login_page !secure + redirect location http://mysite.com/ if !login_page secure + + See section 2.3 about ACL usage. + + redisp (deprecated) redispatch (deprecated) Enable or disable session redistribution in case of connection failure diff --git a/include/types/proto_http.h b/include/types/proto_http.h index 885fbc6..5b0914f 100644 --- a/include/types/proto_http.h +++ b/include/types/proto_http.h @@ -162,6 +162,15 @@ enum { DATA_ST_PX_FIN, }; + + +/* Redirect types (location, prefix, extended ) */ +enum { + REDIRECT_TYPE_NONE = 0, /* no redirection */ + REDIRECT_TYPE_LOCATION, /* location redirect */ + REDIRECT_TYPE_PREFIX, /* prefix redirect */ +}; + /* Known HTTP methods */ typedef enum { HTTP_METH_NONE = 0, diff --git a/include/types/proxy.h b/include/types/proxy.h index 091be57..329b4c7 100644 --- a/include/types/proxy.h +++ b/include/types/proxy.h @@ -129,6 +129,7 @@ struct proxy { } defbe; struct list acl; /* ACL declared on this proxy */ struct list block_cond; /* early blocking conditions (chained) */ + struct list redirect_rules; /* content redirecting rules (chained) */ struct list switching_rules; /* content switching rules (chained) */ struct server *srv; /* known servers */ int srv_act, srv_bck; /* # of servers eligible for LB (UP|!checked) AND (enabled+weight!=0) */ @@ -249,6 +250,15 @@ struct switching_rule { } be; }; +struct redirect_rule { + struct list list; /* list linked to from the proxy */ + struct acl_cond *cond; /* acl condition to meet */ + int type; + int rdr_len; + char *rdr_str; + int code; +}; + extern struct proxy *proxy; extern int next_pxid; diff --git a/src/cfgparse.c b/src/cfgparse.c index d34bfbe..9302bb5 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -587,6 +587,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv) LIST_INIT(&curproxy->pendconns); LIST_INIT(&curproxy->acl); LIST_INIT(&curproxy->block_cond); + LIST_INIT(&curproxy->redirect_rules); LIST_INIT(&curproxy->mon_fail_cond); LIST_INIT(&curproxy->switching_rules); @@ -1094,6 +1095,98 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int inv) } LIST_ADDQ(&curproxy->block_cond, &cond->list); } + else if (!strcmp(args[0], "redirect")) { + int pol = ACL_COND_NONE; + struct acl_cond *cond; + struct redirect_rule *rule; + int cur_arg; + int type = REDIRECT_TYPE_NONE; + int code = 302; + char *destination = NULL; + + cur_arg = 1; + while (*(args[cur_arg])) { + if (!strcmp(args[cur_arg], "location")) { + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n", + file, linenum, args[0], args[cur_arg]); + return -1; + } + + type = REDIRECT_TYPE_LOCATION; + cur_arg++; + destination = args[cur_arg]; + } + else if (!strcmp(args[cur_arg], "prefix")) { + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n", + file, linenum, args[0], args[cur_arg]); + return -1; + } + + type = REDIRECT_TYPE_PREFIX; + cur_arg++; + destination = args[cur_arg]; + } + else if (!strcmp(args[cur_arg],"code")) { + if (!*args[cur_arg + 1]) { + Alert("parsing [%s:%d] : '%s': missing HTTP code.\n", + file, linenum, args[0]); + return -1; + } + cur_arg++; + code = atol(args[cur_arg]); + if (code < 301 || code > 303) { + Alert("parsing [%s:%d] : '%s': unsupported HTTP code '%d'.\n", + file, linenum, args[0], code); + return -1; + } + } + else if (!strcmp(args[cur_arg], "if")) { + pol = ACL_COND_IF; + cur_arg++; + break; + } + else if (!strcmp(args[cur_arg], "unless")) { + pol = ACL_COND_UNLESS; + cur_arg++; + break; + } + else { + Alert("parsing [%s:%d] : '%s' expects 'code', 'prefix' or 'location' (was '%s').\n", + file, linenum, args[0], args[cur_arg]); + return -1; + } + cur_arg++; + } + + if (type == REDIRECT_TYPE_NONE) { + Alert("parsing [%s:%d] : '%s' expects a redirection type ('prefix' or 'location').\n", + file, linenum, args[0]); + return -1; + } + + if (pol == ACL_COND_NONE) { + Alert("parsing [%s:%d] : '%s' requires either 'if' or 'unless' followed by a condition.\n", + file, linenum, args[0]); + return -1; + } + + if ((cond = parse_acl_cond((const char **)args + cur_arg, &curproxy->acl, pol)) == NULL) { + Alert("parsing [%s:%d] : '%s': error detected while parsing condition.\n", + file, linenum, args[0]); + return -1; + } + + rule = (struct redirect_rule *)calloc(1, sizeof(*rule)); + rule->cond = cond; + rule->rdr_str = strdup(destination); + rule->rdr_len = strlen(destination); + rule->type = type; + rule->code = code; + LIST_INIT(&rule->list); + LIST_ADDQ(&curproxy->redirect_rules, &rule->list); + } else if (!strcmp(args[0], "use_backend")) { /* early blocking based on ACLs */ int pol = ACL_COND_NONE; struct acl_cond *cond; diff --git a/src/haproxy.c b/src/haproxy.c index 50b013b..3ab2f4d 100644 --- a/src/haproxy.c +++ b/src/haproxy.c @@ -639,6 +639,7 @@ void deinit(void) struct listener *l,*l_next; struct acl_cond *cond, *condb; struct hdr_exp *exp, *expb; + struct redirect_rule *rdr, *rdrb; int i; while (p) { @@ -712,6 +713,14 @@ void deinit(void) * - uri_auth (but it's shared) */ + list_for_each_entry_safe(rdr, rdrb, &p->redirect_rules, list) { + LIST_DEL(&rdr->list); + prune_acl_cond(rdr->cond); + free(rdr->cond); + free(rdr->rdr_str); + free(rdr); + } + if (p->appsession_name) free(p->appsession_name); diff --git a/src/proto_http.c b/src/proto_http.c index 7500798..d63dae9 100644 --- a/src/proto_http.c +++ b/src/proto_http.c @@ -88,6 +88,12 @@ const struct chunk http_200_chunk = { .len = sizeof(HTTP_200)-1 }; +const char *HTTP_301 = + "HTTP/1.0 301 Moved Permantenly\r\n" + "Cache-Control: no-cache\r\n" + "Connection: close\r\n" + "Location: "; /* not terminated since it will be concatenated with the URL */ + const char *HTTP_302 = "HTTP/1.0 302 Found\r\n" "Cache-Control: no-cache\r\n" @@ -1805,9 +1811,90 @@ int process_cli(struct session *t) do { struct acl_cond *cond; + struct redirect_rule *rule; struct proxy *rule_set = t->be; cur_proxy = t->be; + /* first check whether we have some ACLs set to redirect this request */ + list_for_each_entry(rule, &cur_proxy->redirect_rules, list) { + int ret = acl_exec_cond(rule->cond, cur_proxy, t, txn, ACL_DIR_REQ); + if (rule->cond->pol == ACL_COND_UNLESS) + ret = !ret; + + if (ret) { + struct chunk rdr = { trash, 0 }; + const char *msg_fmt; + + /* build redirect message */ + switch(rule->code) { + case 303: + rdr.len = strlen(HTTP_303); + msg_fmt = HTTP_303; + break; + case 301: + rdr.len = strlen(HTTP_301); + msg_fmt = HTTP_301; + break; + case 302: + default: + rdr.len = strlen(HTTP_302); + msg_fmt = HTTP_302; + break; + } + + if (unlikely(rdr.len > sizeof(trash))) + goto return_bad_req; + memcpy(rdr.str, msg_fmt, rdr.len); + + switch(rule->type) { + case REDIRECT_TYPE_PREFIX: { + const char *path; + int pathlen; + + path = http_get_path(txn); + /* build message using path */ + if (path) { + pathlen = txn->req.sl.rq.u_l + (txn->req.sol+txn->req.sl.rq.u) - path; + } else { + path = "/"; + pathlen = 1; + } + + if (rdr.len + rule->rdr_len + pathlen > sizeof(trash) - 4) + goto return_bad_req; + + /* add prefix */ + memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len); + rdr.len += rule->rdr_len; + + /* add path */ + memcpy(rdr.str + rdr.len, path, pathlen); + rdr.len += pathlen; + break; + } + case REDIRECT_TYPE_LOCATION: + default: + if (rdr.len + rule->rdr_len > sizeof(trash) - 4) + goto return_bad_req; + + /* add location */ + memcpy(rdr.str + rdr.len, rule->rdr_str, rule->rdr_len); + rdr.len += rule->rdr_len; + break; + } + + /* add end of headers */ + memcpy(rdr.str + rdr.len, "\r\n\r\n", 4); + rdr.len += 4; + + txn->status = rule->code; + /* let's log the request time */ + t->logs.tv_request = now; + client_retnclose(t, &rdr); + goto return_prx_cond; + } + } + /* first check whether we have some ACLs set to block this request */ list_for_each_entry(cond, &cur_proxy->block_cond, list) { int ret = acl_exec_cond(cond, cur_proxy, t, txn, ACL_DIR_REQ);