Per our discussion with Willy earlier, attached is the patch for the
feature. I noticed that while we had free_http_req_rules() there was
no free_http_res_rules() even though the memory for each response rule
entry was being allocated with calloc() just like with the request
rule and should have been getting free during deinit() but was not. No
big deal really, only affect memory leak detection test runs, but I
felt bad about allocating memory for a regex and not freeing it, so I
added free_http_res_rules().

-- 
Sasha Pachev

Fast Running Blog.
http://fastrunningblog.com
Run. Blog. Improve. Repeat.
diff --git a/include/proto/proto_http.h b/include/proto/proto_http.h
index 6370e2d..e898ca8 100644
--- a/include/proto/proto_http.h
+++ b/include/proto/proto_http.h
@@ -116,6 +116,7 @@ void http_reset_txn(struct session *s);
 struct http_req_rule *parse_http_req_cond(const char **args, const char *file, int linenum, struct proxy *proxy);
 struct http_res_rule *parse_http_res_cond(const char **args, const char *file, int linenum, struct proxy *proxy);
 void free_http_req_rules(struct list *r);
+void free_http_res_rules(struct list *r);
 struct chunk *http_error_message(struct session *s, int msgnum);
 struct redirect_rule *http_parse_redirect_rule(const char *file, int linenum, struct proxy *curproxy,
                                                const char **args, char **errmsg, int use_fmt);
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index e1d04d6..d7d4430 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -247,6 +247,8 @@ enum {
 	HTTP_REQ_ACT_ADD_HDR,
 	HTTP_REQ_ACT_SET_HDR,
 	HTTP_REQ_ACT_DEL_HDR,
+	HTTP_REQ_ACT_REPLACE_HDR,
+	HTTP_REQ_ACT_MODIFY_HDR,
 	HTTP_REQ_ACT_REDIR,
 	HTTP_REQ_ACT_SET_NICE,
 	HTTP_REQ_ACT_SET_LOGL,
@@ -267,6 +269,8 @@ enum {
 	HTTP_RES_ACT_ALLOW,
 	HTTP_RES_ACT_DENY,
 	HTTP_RES_ACT_ADD_HDR,
+	HTTP_RES_ACT_MODIFY_HDR,
+	HTTP_RES_ACT_REPLACE_HDR,
 	HTTP_RES_ACT_SET_HDR,
 	HTTP_RES_ACT_DEL_HDR,
 	HTTP_RES_ACT_SET_NICE,
@@ -425,6 +429,7 @@ struct http_req_rule {
 			char *name;            /* header name */
 			int name_len;          /* header name's length */
 			struct list fmt;       /* log-format compatible expression */
+			regex_t* re;           /* used by replace-header and modify-header */
 		} hdr_add;                     /* args used by "add-header" and "set-header" */
 		struct redirect_rule *redir;   /* redirect rule or "http-request redirect" */
 		int nice;                      /* nice value for HTTP_REQ_ACT_SET_NICE */
@@ -450,6 +455,7 @@ struct http_res_rule {
 			char *name;            /* header name */
 			int name_len;          /* header name's length */
 			struct list fmt;       /* log-format compatible expression */
+			regex_t* re;           /* used by replace-header and modify-header */
 		} hdr_add;                     /* args used by "add-header" and "set-header" */
 		int nice;                      /* nice value for HTTP_RES_ACT_SET_NICE */
 		int loglevel;                  /* log-level value for HTTP_RES_ACT_SET_LOGL */
diff --git a/src/haproxy.c b/src/haproxy.c
index 77871b0..84e22e3 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -1202,6 +1202,7 @@ void deinit(void)
 		free(p->fwdfor_hdr_name);
 
 		free_http_req_rules(&p->http_req_rules);
+		free_http_res_rules(&p->http_res_rules);
 		free(p->task);
 
 		pool_destroy2(p->req_cap_pool);
diff --git a/src/proto_http.c b/src/proto_http.c
index 30bd359..232d386 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -252,6 +252,9 @@ fd_set http_encode_map[(sizeof(fd_set) > (256/8)) ? 1 : ((256/8) / sizeof(fd_set
 #error "Check if your OS uses bitfields for fd_sets"
 #endif
 
+#define ACTION_IS_REGFIX(rule,req_or_res) ((rule)->action == HTTP_##req_or_res##_ACT_MODIFY_HDR || \
+ 	(rule)->action == HTTP_##req_or_res##_ACT_REPLACE_HDR)
+
 static int http_apply_redirect_rule(struct redirect_rule *rule, struct session *s, struct http_txn *txn);
 
 void init_proto_http()
@@ -3176,6 +3179,125 @@ static inline void inet_set_tos(int fd, struct sockaddr_storage from, int tos)
 #endif
 }
 
+static int http_regfix_replace(regex_t* re, char* dst, uint dst_size, char* val,
+	const char*  rep_str)
+{
+
+	if (regexec(re, val, MAX_MATCH, pmatch, 0)) 
+		return 0;
+
+	return exp_replace(dst, dst_size, val, rep_str, pmatch);
+}
+
+static int http_regfix_modify(regex_t* re, char* dst, uint dst_size, char* val, char delim,
+	const char*  rep_str)
+{
+	char* p = val;
+	char* dst_end = dst + dst_size;
+	char* dst_p = dst;
+
+	for (;;) {
+		char *p_delim;
+		const char* tok_end;
+
+		if ((p_delim = (char*)strchr(p, delim))) {
+			*p_delim = 0;
+			tok_end = p_delim;
+		} else {
+			tok_end = p + strlen(p);
+		}
+
+		if (regexec(re, p, MAX_MATCH, pmatch, 0) == 0) {
+			int replace_n = exp_replace(dst_p, dst_end - dst_p, p, rep_str, pmatch);
+
+			if (replace_n < 0)
+				return -1;
+
+			dst_p += replace_n;
+		}	else {
+			uint len = tok_end - p;
+
+			if (dst_p + len >= dst_end)
+				return -1;
+
+			memcpy(dst_p, p, len);
+			dst_p += len;
+		}
+
+		if (dst_p >= dst_end)
+			return -1;
+
+		if (p_delim) {
+			*p_delim = delim;
+			*dst_p++ = delim;
+			p = p_delim + 1;
+		} else {
+			*dst_p = 0;
+			break;
+		}
+	}
+
+	return dst_p - dst;
+}
+
+static int http_regfix_header(struct session* s, struct http_msg *msg, const char* name, uint name_len, 
+		char* buf, struct hdr_idx* idx, struct list *fmt, regex_t* re,
+		struct hdr_ctx* ctx, int action)
+{
+	ctx->idx = 0;
+
+	while (http_find_full_header2(name, name_len, buf, idx, ctx))
+  {
+  	struct hdr_idx_elem *hdr = idx->v + ctx->idx;
+  	int delta;
+  	char* val = (char*)ctx->line + name_len + 2;
+  	char* val_end = (char*)ctx->line + hdr->len;
+  	char save_val_end = *val_end;
+  	char* reg_dst_buf;
+  	uint reg_dst_buf_size;
+  	int n_replaced;
+
+  	*val_end = 0;
+		trash.len = build_logline(s, trash.str, trash.size, fmt);
+
+		if (trash.len >= trash.size - 1)
+			return -1;
+
+ 		reg_dst_buf = trash.str + trash.len + 1;
+ 		reg_dst_buf_size = trash.size - trash.len - 1;
+
+ 		switch (action)
+ 		{
+ 			case HTTP_REQ_ACT_MODIFY_HDR:
+ 			case HTTP_RES_ACT_MODIFY_HDR:
+ 				n_replaced = http_regfix_modify(re, reg_dst_buf, reg_dst_buf_size, val, ';', trash.str);
+ 				break;
+ 			case HTTP_REQ_ACT_REPLACE_HDR:
+ 			case HTTP_RES_ACT_REPLACE_HDR:
+ 				n_replaced = http_regfix_replace(re, reg_dst_buf, reg_dst_buf_size, val, trash.str);
+ 				break;
+ 			default: /* impossible */
+ 				return -1;
+ 		}
+
+		*val_end = save_val_end;
+ 		
+ 		if (n_replaced == 0)
+ 			continue;
+
+ 		if (n_replaced < 0) 
+ 			return -1;
+
+  	delta = buffer_replace2(msg->chn->buf, val, val_end,
+  	   reg_dst_buf, n_replaced);
+
+  	hdr->len += delta;
+  	http_msg_move_end(msg, delta);
+  }
+
+  return 0;
+}
+
 /* Executes the http-request rules <rules> for session <s>, proxy <px> and
  * transaction <txn>. Returns the verdict of the first rule that prevents
  * further processing of the request (auth, deny, ...), and defaults to
@@ -3265,6 +3387,14 @@ http_req_get_intercept_rule(struct proxy *px, struct list *rules, struct session
 			s->logs.level = rule->arg.loglevel;
 			break;
 
+		case HTTP_REQ_ACT_REPLACE_HDR:
+		case HTTP_REQ_ACT_MODIFY_HDR:
+			if (http_regfix_header(s, &txn->req, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len,
+						 txn->req.chn->buf->p, &txn->hdr_idx, &rule->arg.hdr_add.fmt, 
+						 rule->arg.hdr_add.re, &ctx, rule->action))
+				return HTTP_RULE_RES_ABRT;
+			break;
+
 		case HTTP_REQ_ACT_DEL_HDR:
 		case HTTP_REQ_ACT_SET_HDR:
 			ctx.idx = 0;
@@ -3446,6 +3576,14 @@ http_res_get_intercept_rule(struct proxy *px, struct list *rules, struct session
 			s->logs.level = rule->arg.loglevel;
 			break;
 
+		case HTTP_RES_ACT_MODIFY_HDR:
+		case HTTP_RES_ACT_REPLACE_HDR:
+			if (http_regfix_header(s, &txn->rsp, rule->arg.hdr_add.name, rule->arg.hdr_add.name_len,
+						 txn->rsp.chn->buf->p, &txn->hdr_idx, &rule->arg.hdr_add.fmt,
+						 rule->arg.hdr_add.re, &ctx, rule->action))
+				return NULL;
+			break;
+		
 		case HTTP_RES_ACT_DEL_HDR:
 		case HTTP_RES_ACT_SET_HDR:
 			ctx.idx = 0;
@@ -8757,6 +8895,24 @@ void http_reset_txn(struct session *s)
 	s->rep->analyse_exp = TICK_ETERNITY;
 }
 
+static inline void free_regex(regex_t* re)
+{
+	if (re) {
+		regfree(re);
+		free(re);
+	}
+}
+
+void free_http_res_rules(struct list *r) {
+	struct http_res_rule *tr, *pr;
+
+	list_for_each_entry_safe(pr, tr, r, list) {
+		LIST_DEL(&pr->list);
+		free_regex(pr->arg.hdr_add.re); 
+		free(pr);
+	}
+}
+
 void free_http_req_rules(struct list *r) {
 	struct http_req_rule *tr, *pr;
 
@@ -8765,6 +8921,7 @@ void free_http_req_rules(struct list *r) {
 		if (pr->action == HTTP_REQ_ACT_AUTH)
 			free(pr->arg.auth.realm);
 
+		free_regex(pr->arg.hdr_add.re); 
 		free(pr);
 	}
 }
@@ -8775,6 +8932,7 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
 	struct http_req_rule *rule;
 	struct http_req_action_kw *custom = NULL;
 	int cur_arg;
+	int regfix_rule = 0;
 
 	rule = (struct http_req_rule*)calloc(1, sizeof(struct http_req_rule));
 	if (!rule) {
@@ -8884,12 +9042,33 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
 		else if ((rule->arg.loglevel = get_log_level(args[cur_arg]) + 1) == 0)
 			goto bad_log_level;
 		cur_arg++;
-	} else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0) {
-		rule->action = *args[0] == 'a' ? HTTP_REQ_ACT_ADD_HDR : HTTP_REQ_ACT_SET_HDR;
+	} else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0 ||
+			strcmp(args[0], "replace-header") == 0 || strcmp(args[0], "modify-header") == 0) {
+		const char* fmt_arg;
+
+		switch (*args[0])
+		{
+			case 'a':
+				rule->action = HTTP_REQ_ACT_ADD_HDR;
+				break;
+			case 's':
+				rule->action = HTTP_REQ_ACT_SET_HDR;
+				break;
+			case 'r':
+				rule->action = HTTP_REQ_ACT_REPLACE_HDR;
+				regfix_rule = 1;
+				break;
+			case 'm':
+				rule->action = HTTP_REQ_ACT_MODIFY_HDR;
+				regfix_rule = 1;
+				break;
+		}
+
 		cur_arg = 1;
 
 		if (!*args[cur_arg] || !*args[cur_arg+1] ||
-		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
+		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 
+		    	&& strcmp(args[cur_arg+2], "unless") != 0 && !ACTION_IS_REGFIX(rule,REQ))) {
 			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 2 arguments.\n",
 			      file, linenum, args[0]);
 			goto out_err;
@@ -8900,9 +9079,31 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
 		LIST_INIT(&rule->arg.hdr_add.fmt);
 
 		proxy->conf.args.ctx = ARGC_HRQ;
-		parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
+		fmt_arg = (regfix_rule) ? args[cur_arg + 2] : args[cur_arg + 1];
+
+		if (regfix_rule && !*fmt_arg) {
+				Alert("parsing [%s:%d]: 'http-response %s' expects exactly 3 arguments.\n",
+			      file, linenum, args[0]);
+				goto out_err;
+		}
+
+		parse_logformat_string(fmt_arg, proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
 				       (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR,
 				       file, linenum);
+
+		if (regfix_rule) {
+			if (!(rule->arg.hdr_add.re = (regex_t*)calloc(1, sizeof(regex_t)))) {
+				Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+				goto out_err;
+			}
+
+			if (regcomp(rule->arg.hdr_add.re, args[cur_arg + 1], REG_EXTENDED)) {
+				Alert("parsing [%s:%d] : '%s' : bad regular expression.\n", file, linenum, 
+					args[cur_arg + 1]);
+				goto out_err;
+			}
+		}
+
 		free(proxy->conf.lfs_file);
 		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
 		proxy->conf.lfs_line = proxy->conf.args.line;
@@ -8912,7 +9113,8 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
 		cur_arg = 1;
 
 		if (!*args[cur_arg] ||
-		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && strcmp(args[cur_arg+1], "unless") != 0)) {
+		    (*args[cur_arg+1] && strcmp(args[cur_arg+1], "if") != 0 && 
+		    	strcmp(args[cur_arg+1], "unless") != 0 && !regfix_rule)) {
 			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 1 argument.\n",
 			      file, linenum, args[0]);
 			goto out_err;
@@ -9090,7 +9292,7 @@ struct http_req_rule *parse_http_req_cond(const char **args, const char *file, i
 		}
 		rule->cond = cond;
 	}
-	else if (*args[cur_arg]) {
+	else if (*args[cur_arg] && !regfix_rule) {
 		Alert("parsing [%s:%d]: 'http-request %s' expects 'realm' for 'auth' or"
 		      " either 'if' or 'unless' followed by a condition but found '%s'.\n",
 		      file, linenum, args[0], args[cur_arg]);
@@ -9109,6 +9311,7 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i
 	struct http_res_rule *rule;
 	struct http_res_action_kw *custom = NULL;
 	int cur_arg;
+	int regfix_rule;
 
 	rule = calloc(1, sizeof(*rule));
 	if (!rule) {
@@ -9203,12 +9406,34 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i
 		else if ((rule->arg.loglevel = get_log_level(args[cur_arg] + 1)) == 0)
 			goto bad_log_level;
 		cur_arg++;
-	} else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0) {
-		rule->action = *args[0] == 'a' ? HTTP_RES_ACT_ADD_HDR : HTTP_RES_ACT_SET_HDR;
+	} else if (strcmp(args[0], "add-header") == 0 || strcmp(args[0], "set-header") == 0 || 
+		       strcmp(args[0], "replace-header") == 0 || 
+		       strcmp(args[0], "modify-header") == 0) {
+		const char* fmt_arg;
+
+		switch ((args[0])[0])
+		{
+			case 'a':
+				rule->action = HTTP_RES_ACT_ADD_HDR;
+				break;
+			case 's':
+				rule->action = HTTP_RES_ACT_SET_HDR;
+				break;
+			case 'r':
+				rule->action = HTTP_RES_ACT_REPLACE_HDR;
+				regfix_rule = 1;
+				break;
+			case 'm':
+				rule->action = HTTP_RES_ACT_MODIFY_HDR;
+				regfix_rule = 1;
+				break;
+		}
+
 		cur_arg = 1;
 
 		if (!*args[cur_arg] || !*args[cur_arg+1] ||
-		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && strcmp(args[cur_arg+2], "unless") != 0)) {
+		    (*args[cur_arg+2] && strcmp(args[cur_arg+2], "if") != 0 && 
+		    	strcmp(args[cur_arg+2], "unless") != 0 && !regfix_rule)) {
 			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 2 arguments.\n",
 			      file, linenum, args[0]);
 			goto out_err;
@@ -9219,9 +9444,34 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i
 		LIST_INIT(&rule->arg.hdr_add.fmt);
 
 		proxy->conf.args.ctx = ARGC_HRS;
-		parse_logformat_string(args[cur_arg + 1], proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
+		fmt_arg = (regfix_rule) ? args[cur_arg + 2] : args[cur_arg + 1];
+
+		if (regfix_rule && !*fmt_arg) {
+				Alert("parsing [%s:%d]: 'http-response %s' expects exactly 3 arguments.\n",
+			      file, linenum, args[0]);
+				goto out_err;
+		}
+		parse_logformat_string(fmt_arg, proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
+					       (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
+					       file, linenum);
+
+		if (regfix_rule) {
+			if (!(rule->arg.hdr_add.re = (regex_t*)calloc(1, sizeof(regex_t)))) {
+				Alert("parsing [%s:%d]: out of memory.\n", file, linenum);
+				goto out_err;
+			}
+
+			if (regcomp(rule->arg.hdr_add.re, args[cur_arg + 1], REG_EXTENDED)) {
+				Alert("parsing [%s:%d] : '%s' : bad regular expression.\n", file, linenum, 
+					args[cur_arg + 1]);
+				goto out_err;
+			}
+
+			parse_logformat_string(args[cur_arg + 2], proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
 				       (proxy->cap & PR_CAP_BE) ? SMP_VAL_BE_HRS_HDR : SMP_VAL_FE_HRS_HDR,
-				       file, linenum);
+				       file, linenum); 
+		}
+
 		free(proxy->conf.lfs_file);
 		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
 		proxy->conf.lfs_line = proxy->conf.args.line;
@@ -9393,7 +9643,7 @@ struct http_res_rule *parse_http_res_cond(const char **args, const char *file, i
 		}
 		rule->cond = cond;
 	}
-	else if (*args[cur_arg]) {
+	else if (*args[cur_arg] && !regfix_rule) {
 		Alert("parsing [%s:%d]: 'http-response %s' expects"
 		      " either 'if' or 'unless' followed by a condition but found '%s'.\n",
 		      file, linenum, args[0], args[cur_arg]);

Reply via email to