[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