]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] http: add support for conditional response header rewriting
authorWilly Tarreau <w@1wt.eu>
Sun, 31 Jan 2010 14:43:27 +0000 (15:43 +0100)
committerWilly Tarreau <w@1wt.eu>
Sun, 31 Jan 2010 14:43:27 +0000 (15:43 +0100)
Just as for the req* rules, we can now condition rsp* rules with ACLs.
ACLs match on response, so volatile request information cannot be used.
A warning is emitted if a configuration contains such an anomaly.

doc/configuration.txt
include/proto/proto_http.h
src/cfgparse.c
src/proto_http.c

index e6cffa97730809ac0d810289d274913ec1a8dd66..3c27e8eb942631002ae92034554e9712bc851f89 100644 (file)
@@ -3857,7 +3857,7 @@ retries <value>
   See also : "option redispatch"
 
 
-rspadd <string>
+rspadd <string> [{if | unless} <cond>]
   Add a header at the end of the HTTP response
   May be used in sections :   defaults | frontend | listen | backend
                                  no    |    yes   |   yes  |   yes
@@ -3866,6 +3866,9 @@ rspadd <string>
               must be escaped using a backslash ('\'). Please refer to section
               6 about HTTP header manipulation for more information.
 
+    <cond>    is an optional matching condition built from ACLs. It makes it
+              possible to ignore this rule when other conditions are not met.
+
   A new line consisting in <string> followed by a line feed will be added after
   the last header of an HTTP response.
 
@@ -3873,11 +3876,12 @@ rspadd <string>
   and not to traffic generated by HAProxy, such as health-checks or error
   responses.
 
-  See also: "reqadd" and section 6 about HTTP header manipulation
+  See also: "reqadd", section 6 about HTTP header manipulation, and section 7
+            about ACLs.
 
 
-rspdel  <search>
-rspidel <search>  (ignore case)
+rspdel  <search> [{if | unless} <cond>]
+rspidel <search> [{if | unless} <cond>]  (ignore case)
   Delete all headers matching a regular expression in an HTTP response
   May be used in sections :   defaults | frontend | listen | backend
                                  no    |    yes   |   yes  |   yes
@@ -3890,6 +3894,9 @@ rspidel <search>  (ignore case)
               The "rspdel" keyword strictly matches case while "rspidel"
               ignores case.
 
+    <cond>    is an optional matching condition built from ACLs. It makes it
+              possible to ignore this rule when other conditions are not met.
+
   Any header line matching extended regular expression <search> in the response
   will be completely deleted. Most common use of this is to remove unwanted
   and/or sensible headers or cookies from a response before passing it to the
@@ -3903,12 +3910,12 @@ rspidel <search>  (ignore case)
      # remove the Server header from responses
      reqidel ^Server:.*
 
-  See also: "rspadd", "rsprep", "reqdel" and section 6 about HTTP header
-            manipulation
+  See also: "rspadd", "rsprep", "reqdel", section 6 about HTTP header
+            manipulation, and section 7 about ACLs.
 
 
-rspdeny  <search>
-rspideny <search>  (ignore case)
+rspdeny  <search> [{if | unless} <cond>]
+rspideny <search> [{if | unless} <cond>]  (ignore case)
   Block an HTTP response if a line matches a regular expression
   May be used in sections :   defaults | frontend | listen | backend
                                  no    |    yes   |   yes  |   yes
@@ -3921,6 +3928,9 @@ rspideny <search>  (ignore case)
               The "rspdeny" keyword strictly matches case while "rspideny"
               ignores case.
 
+    <cond>    is an optional matching condition built from ACLs. It makes it
+              possible to ignore this rule when other conditions are not met.
+
   A response containing any line which matches extended regular expression
   <search> will mark the request as denied. The test applies both to the
   response line and to response headers. Keep in mind that header names are not
@@ -3938,12 +3948,12 @@ rspideny <search>  (ignore case)
      # Ensure that no content type matching ms-word will leak
      rspideny  ^Content-type:\.*/ms-word
 
-  See also: "reqdeny", "acl", "block" and section 6 about HTTP header
-            manipulation
+  See also: "reqdeny", "acl", "block", section 6 about HTTP header manipulation
+            and section 7 about ACLs.
 
 
-rsprep  <search> <string>
-rspirep <search> <string>  (ignore case)
+rsprep  <search> <string> [{if | unless} <cond>]
+rspirep <search> <string> [{if | unless} <cond>]  (ignore case)
   Replace a regular expression with a string in an HTTP response line
   May be used in sections :   defaults | frontend | listen | backend
                                  no    |    yes   |   yes  |   yes
@@ -3962,6 +3972,9 @@ rspirep <search> <string>  (ignore case)
               being a single digit between 0 and 9. Please refer to section
               6 about HTTP header manipulation for more information.
 
+    <cond>    is an optional matching condition built from ACLs. It makes it
+              possible to ignore this rule when other conditions are not met.
+
   Any line matching extended regular expression <search> in the response (both
   the response line and header lines) will be completely replaced with
   <string>. Most common use of this is to rewrite Location headers.
@@ -3976,8 +3989,8 @@ rspirep <search> <string>  (ignore case)
      # replace "Location: 127.0.0.1:8080" with "Location: www.mydomain.com"
      rspirep ^Location:\ 127.0.0.1:8080    Location:\ www.mydomain.com
 
-  See also: "rspadd", "rspdel", "reqrep" and section 6 about HTTP header
-            manipulation
+  See also: "rspadd", "rspdel", "reqrep", section 6 about HTTP header
+            manipulation, and section 7 about ACLs.
 
 
 server <name> <address>[:port] [param*]
index 6a424c0f06beb2673c6bdfa3fb301327da8473a7..68a771ede6f33c59a127fb700aad9669efea6d36 100644 (file)
@@ -2,7 +2,7 @@
  * include/proto/proto_http.h
  * This file contains HTTP protocol definitions.
  *
- * Copyright (C) 2000-2009 Willy Tarreau - w@1wt.eu
+ * Copyright (C) 2000-2010 Willy Tarreau - w@1wt.eu
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -76,7 +76,7 @@ void get_srv_from_appsession(struct session *t, const char *begin, int len);
 int apply_filter_to_req_headers(struct session *t, struct buffer *req, struct hdr_exp *exp);
 int apply_filter_to_req_line(struct session *t, struct buffer *req, struct hdr_exp *exp);
 int apply_filters_to_request(struct session *s, struct buffer *req, struct proxy *px);
-int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp);
+int apply_filters_to_response(struct session *t, struct buffer *rtr, struct proxy *px);
 void manage_client_side_appsession(struct session *t, const char *buf, int len);
 void manage_client_side_cookies(struct session *t, struct buffer *req);
 void manage_server_side_cookies(struct session *t, struct buffer *rtr);
index c7f549d3ebcdd4b48b67c31525f8979ab5ff8fe4..77e61d864d2032292b1edfea7529537e8c6655f2 100644 (file)
@@ -405,6 +405,23 @@ static int warnif_cond_requires_resp(const struct acl_cond *cond, const char *fi
        return ERR_WARN;
 }
 
+/* Report it if a request ACL condition uses some request-only volatile parameters.
+ * It returns either 0 or ERR_WARN so that its result can be or'ed with err_code.
+ * Note that <cond> may be NULL and then will be ignored.
+ */
+static int warnif_cond_requires_req(const struct acl_cond *cond, const char *file, int line)
+{
+       struct acl *acl;
+
+       if (!cond || !(cond->requires & ACL_USE_REQ_VOLATILE))
+               return 0;
+
+       acl = cond_find_require(cond, ACL_USE_REQ_VOLATILE);
+       Warning("parsing [%s:%d] : acl '%s' involves some volatile request-only criteria which will be ignored.\n",
+               file, line, acl ? acl->name : "(unknown)");
+       return ERR_WARN;
+}
+
 
 /*
  * parse a line in a <global> section. Returns the error code, 0 if OK, or
@@ -947,6 +964,8 @@ static int create_cond_regex_rule(const char *file, int line,
 
        if (dir == ACL_DIR_REQ)
                err_code |= warnif_cond_requires_resp(cond, file, line);
+       else
+               err_code |= warnif_cond_requires_req(cond, file, line);
 
        preg = calloc(1, sizeof(regex_t));
        if (!preg) {
@@ -3770,21 +3789,21 @@ stats_error_parsing:
 
                err_code |= create_cond_regex_rule(file, linenum, curproxy,
                                                   ACL_DIR_RTR, ACT_REPLACE, 0,
-                                                  args[0], args[1], args[2], NULL);
+                                                  args[0], args[1], args[2], (const char **)args+3);
                if (err_code & ERR_FATAL)
                        goto out;
        }
        else if (!strcmp(args[0], "rspdel")) {  /* delete response header from a regex */
                err_code |= create_cond_regex_rule(file, linenum, curproxy,
                                                   ACL_DIR_RTR, ACT_REMOVE, 0,
-                                                  args[0], args[1], NULL, NULL);
+                                                  args[0], args[1], NULL, (const char **)args+2);
                if (err_code & ERR_FATAL)
                        goto out;
        }
        else if (!strcmp(args[0], "rspdeny")) {  /* block response header from a regex */
                err_code |= create_cond_regex_rule(file, linenum, curproxy,
                                                   ACL_DIR_RTR, ACT_DENY, 0,
-                                                  args[0], args[1], NULL, NULL);
+                                                  args[0], args[1], NULL, (const char **)args+2);
                if (err_code & ERR_FATAL)
                        goto out;
        }
@@ -3798,21 +3817,21 @@ stats_error_parsing:
 
                err_code |= create_cond_regex_rule(file, linenum, curproxy,
                                                   ACL_DIR_RTR, ACT_REPLACE, REG_ICASE,
-                                                  args[0], args[1], args[2], NULL);
+                                                  args[0], args[1], args[2], (const char **)args+3);
                if (err_code & ERR_FATAL)
                        goto out;
        }
        else if (!strcmp(args[0], "rspidel")) {  /* delete response header from a regex ignoring case */
                err_code |= create_cond_regex_rule(file, linenum, curproxy,
                                                   ACL_DIR_RTR, ACT_REMOVE, REG_ICASE,
-                                                  args[0], args[1], NULL, NULL);
+                                                  args[0], args[1], NULL, (const char **)args+2);
                if (err_code & ERR_FATAL)
                        goto out;
        }
        else if (!strcmp(args[0], "rspideny")) {  /* block response header from a regex ignoring case */
                err_code |= create_cond_regex_rule(file, linenum, curproxy,
                                                   ACL_DIR_RTR, ACT_DENY, REG_ICASE,
-                                                  args[0], args[1], NULL, NULL);
+                                                  args[0], args[1], NULL, (const char **)args+2);
                if (err_code & ERR_FATAL)
                        goto out;
        }
@@ -3833,7 +3852,24 @@ stats_error_parsing:
                        goto out;
                }
        
+               if ((strcmp(args[2], "if") == 0 || strcmp(args[2], "unless") == 0)) {
+                       if ((cond = build_acl_cond(file, linenum, curproxy, (const char **)args+2)) == NULL) {
+                               Alert("parsing [%s:%d] : error detected while parsing a '%s' condition.\n",
+                                     file, linenum, args[0]);
+                               err_code |= ERR_ALERT | ERR_FATAL;
+                               goto out;
+                       }
+                       err_code |= warnif_cond_requires_req(cond, file, linenum);
+               }
+               else if (*args[2]) {
+                       Alert("parsing [%s:%d] : '%s' : Expecting nothing, 'if', or 'unless', got '%s'.\n",
+                             file, linenum, args[0], args[2]);
+                       err_code |= ERR_ALERT | ERR_FATAL;
+                       goto out;
+               }
+
                wl = calloc(1, sizeof(*wl));
+               wl->cond = cond;
                wl->s = strdup(args[1]);
                LIST_ADDQ(&curproxy->rsp_add, &wl->list);
        }
index 1ee230712cb0755d4302690b01d8e57b16e3ceb1..b2dd232ceef7415a343538bc17ac16a8297e94ab 100644 (file)
@@ -4582,7 +4582,7 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
 
                        /* try headers filters */
                        if (rule_set->rsp_exp != NULL) {
-                               if (apply_filters_to_response(t, rep, rule_set->rsp_exp) < 0) {
+                               if (apply_filters_to_response(t, rep, rule_set) < 0) {
                                return_bad_resp:
                                        if (t->srv) {
                                                t->srv->counters.failed_resp++;
@@ -4618,6 +4618,14 @@ int http_process_res_common(struct session *t, struct buffer *rep, int an_bit, s
                        list_for_each_entry(wl, &rule_set->rsp_add, list) {
                                if (txn->status < 200)
                                        break;
+                               if (wl->cond) {
+                                       int ret = acl_exec_cond(wl->cond, px, t, txn, ACL_DIR_RTR);
+                                       ret = acl_pass(ret);
+                                       if (((struct acl_cond *)wl->cond)->pol == ACL_COND_UNLESS)
+                                               ret = !ret;
+                                       if (!ret)
+                                               continue;
+                               }
                                if (unlikely(http_header_add_tail(rep, &txn->rsp, &txn->hdr_idx, wl->s) < 0))
                                        goto return_bad_resp;
                        }
@@ -5806,15 +5814,16 @@ int apply_filter_to_sts_line(struct session *t, struct buffer *rtr, struct hdr_e
 
 
 /*
- * Apply all the resp filters <exp> to all headers in buffer <rtr> of session <t>.
+ * Apply all the resp filters of proxy <px> to all headers in buffer <rtr> of session <s>.
  * Returns 0 if everything is alright, or -1 in case a replacement lead to an
  * unparsable response.
  */
-int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_exp *exp)
+int apply_filters_to_response(struct session *s, struct buffer *rtr, struct proxy *px)
 {
-       struct http_txn *txn = &t->txn;
-       /* iterate through the filters in the outer loop */
-       while (exp && !(txn->flags & TX_SVDENY)) {
+       struct http_txn *txn = &s->txn;
+       struct hdr_exp *exp;
+
+       for (exp = px->rsp_exp; exp; exp = exp->next) {
                int ret;
 
                /*
@@ -5823,6 +5832,9 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
                 * the evaluation.
                 */
 
+               if (txn->flags & TX_SVDENY)
+                       break;
+
                if ((txn->flags & TX_SVALLOW) &&
                    (exp->action == ACT_ALLOW || exp->action == ACT_DENY ||
                     exp->action == ACT_PASS)) {
@@ -5830,8 +5842,20 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
                        continue;
                }
 
+               /* if this filter had a condition, evaluate it now and skip to
+                * next filter if the condition does not match.
+                */
+               if (exp->cond) {
+                       ret = acl_exec_cond(exp->cond, px, s, txn, ACL_DIR_RTR);
+                       ret = acl_pass(ret);
+                       if (((struct acl_cond *)exp->cond)->pol == ACL_COND_UNLESS)
+                               ret = !ret;
+                       if (!ret)
+                               continue;
+               }
+
                /* Apply the filter to the status line. */
-               ret = apply_filter_to_sts_line(t, rtr, exp);
+               ret = apply_filter_to_sts_line(s, rtr, exp);
                if (unlikely(ret < 0))
                        return -1;
 
@@ -5839,9 +5863,8 @@ int apply_filters_to_response(struct session *t, struct buffer *rtr, struct hdr_
                        /* The filter did not match the response, it can be
                         * iterated through all headers.
                         */
-                       apply_filter_to_resp_headers(t, rtr, exp);
+                       apply_filter_to_resp_headers(s, rtr, exp);
                }
-               exp = exp->next;
        }
        return 0;
 }