[MINOR] redirect: add support for "set-cookie" and "clear-cookie"
It is now possible to set or clear a cookie during a redirection. This
is useful for logout pages, or for protecting against some DoSes. Check
the documentation for the options supported by the "redirect" keyword.
(cherry-picked from commit 4af993822e880d8c932f4ad6920db4c9242b0981)
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 2467007..ca6f293 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2336,34 +2336,68 @@
"transparent" option of the "bind" keyword.
-redirect location <to> [code <code>] {if | unless} <condition>
-redirect prefix <to> [drop-query] [code <code>] {if | unless} <condition>
+redirect location <to> [code <code>] <option> {if | unless} <condition>
+redirect prefix <to> [code <code>] <option> {if | unless} <condition>
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 <to> is placed into the HTTP
- "Location" header. With "prefix", the "Location" header is built from the
- concatenation of <to> and the URI. If the optional "drop-query" keyword is
- used in a prefix-based redirection, then the location will be set without any
- possible query-string, which is useful for directing users to a non-secure
- page for instance. The "prefix" mode is particularly suited for global site
- redirections.
+ response.
- The code is optional. It indicates in <code> which type of HTTP redirection
- is desired. Only codes 301, 302 and 303 are supported. 302 is used if no code
- is specified.
+ Arguments :
+ <to> With "redirect location", the exact value in <to> is placed into
+ the HTTP "Location" header. In case of "redirect prefix", the
+ "Location" header is built from the concatenation of <to> and the
+ complete URI, including the query string, unless the "drop-query"
+ option is specified (see below).
+
+ <code> The code is optional. It indicates which type of HTTP redirection
+ is desired. Only codes 301, 302 and 303 are supported, and 302 is
+ used if no code is specified. 301 means "Moved permanently", and
+ a browser may cache the Location. 302 means "Moved permanently"
+ and means that the browser should not cache the redirection. 303
+ is equivalent to 302 except that the browser will fetch the
+ location with a GET method.
+
+ <option> There are several options which can be specified to adjust the
+ expected behaviour of a redirection :
+
+ - "drop-query"
+ When this keyword is used in a prefix-based redirection, then the
+ location will be set without any possible query-string, which is useful
+ for directing users to a non-secure page for instance. It has no effect
+ with a location-type redirect.
+
+ - "set-cookie NAME[=value]"
+ A "Set-Cookie" header will be added with NAME (and optionally "=value")
+ to the response. This is sometimes used to indicate that a user has
+ been seen, for instance to protect against some types of DoS. No other
+ cookie option is added, so the cookie will be a session cookie. Note
+ that for a browser, a sole cookie name without an equal sign is
+ different from a cookie with an equal sign.
+
+ - "clear-cookie NAME[=]"
+ A "Set-Cookie" header will be added with NAME (and optionally "="), but
+ with the "Max-Age" attribute set to zero. This will tell the browser to
+ delete this cookie. It is useful for instance on logout pages. It is
+ important to note that clearing the cookie "NAME" will not remove a
+ cookie set with "NAME=value". You have to clear the cookie "NAME=" for
+ that, because the browser makes the difference.
Example: move the login URL only to HTTPS.
acl clear dst_port 80
acl secure dst_port 8080
acl login_page url_beg /login
+ acl logout url_beg /logout
acl uid_given url_reg /login?userid=[^&]+
+ acl cookie_set hdr_sub(cookie) SEEN=1
+
+ redirect prefix https://mysite.com set-cookie SEEN=1 if !cookie_set
redirect prefix https://mysite.com if login_page !secure
redirect prefix http://mysite.com drop-query if login_page !uid_given
redirect location http://mysite.com/ if !login_page secure
+ redirect location / clear-cookie USERID= if logout
See section 2.3 about ACL usage.
diff --git a/include/types/proxy.h b/include/types/proxy.h
index a7d2c40..90d5c4e 100644
--- a/include/types/proxy.h
+++ b/include/types/proxy.h
@@ -267,6 +267,8 @@
char *rdr_str;
int code;
unsigned int flags;
+ int cookie_len;
+ char *cookie_str;
};
extern struct proxy *proxy;
diff --git a/src/cfgparse.c b/src/cfgparse.c
index ede1b22..8668dfb 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1118,6 +1118,8 @@
int type = REDIRECT_TYPE_NONE;
int code = 302;
char *destination = NULL;
+ char *cookie = NULL;
+ int cookie_set = 0;
unsigned int flags = REDIRECT_FLAG_NONE;
cur_arg = 1;
@@ -1144,6 +1146,28 @@
cur_arg++;
destination = args[cur_arg];
}
+ else if (!strcmp(args[cur_arg], "set-cookie")) {
+ if (!*args[cur_arg + 1]) {
+ Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n",
+ file, linenum, args[0], args[cur_arg]);
+ return -1;
+ }
+
+ cur_arg++;
+ cookie = args[cur_arg];
+ cookie_set = 1;
+ }
+ else if (!strcmp(args[cur_arg], "clear-cookie")) {
+ if (!*args[cur_arg + 1]) {
+ Alert("parsing [%s:%d] : '%s': missing argument for '%s'.\n",
+ file, linenum, args[0], args[cur_arg]);
+ return -1;
+ }
+
+ cur_arg++;
+ cookie = args[cur_arg];
+ cookie_set = 0;
+ }
else if (!strcmp(args[cur_arg],"code")) {
if (!*args[cur_arg + 1]) {
Alert("parsing [%s:%d] : '%s': missing HTTP code.\n",
@@ -1202,6 +1226,20 @@
rule->cond = cond;
rule->rdr_str = strdup(destination);
rule->rdr_len = strlen(destination);
+ if (cookie) {
+ /* depending on cookie_set, either we want to set the cookie, or to clear it.
+ * a clear consists in appending "; Max-Age=0" at the end.
+ */
+ rule->cookie_len = strlen(cookie);
+ if (cookie_set)
+ rule->cookie_str = strdup(cookie);
+ else {
+ rule->cookie_str = malloc(rule->cookie_len + 12);
+ memcpy(rule->cookie_str, cookie, rule->cookie_len);
+ memcpy(rule->cookie_str + rule->cookie_len, "; Max-Age=0", 12);
+ rule->cookie_len += 11;
+ }
+ }
rule->type = type;
rule->code = code;
rule->flags = flags;
diff --git a/src/proto_http.c b/src/proto_http.c
index 6a9f705..2b72504 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -1932,6 +1932,15 @@
break;
}
+ if (rule->cookie_len) {
+ memcpy(rdr.str + rdr.len, "\r\nSet-Cookie: ", 14);
+ rdr.len += 14;
+ memcpy(rdr.str + rdr.len, rule->cookie_str, rule->cookie_len);
+ rdr.len += rule->cookie_len;
+ memcpy(rdr.str + rdr.len, "\r\n", 2);
+ rdr.len += 2;
+ }
+
/* add end of headers */
memcpy(rdr.str + rdr.len, "\r\n\r\n", 4);
rdr.len += 4;
diff --git a/tests/test-redirect.cfg b/tests/test-redirect.cfg
index a3bf2ab..780132b 100644
--- a/tests/test-redirect.cfg
+++ b/tests/test-redirect.cfg
@@ -18,10 +18,17 @@
acl url_test1 url_reg test1
acl url_test2 url_reg test2
acl url_test3 url_reg test3
+ acl url_test4 url_reg test4
+
+ acl seen hdr_sub(cookie) SEEN=1
+
redirect location /abs/test code 301 if url_test1
redirect prefix /pfx/test code 302 if url_test2
redirect prefix /pfx/test code 303 drop-query if url_test3
+ redirect location /test4 code 302 set-cookie SEEN=1 if url_test4 !seen
+ redirect location / code 302 clear-cookie SEEN= if url_test4 seen
+
### unconditional redirection
#redirect location https://example.com/ if TRUE