diff --git a/doc/configuration.txt b/doc/configuration.txt
index ae76ef3..2f4d066 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -3650,6 +3650,8 @@ http-request { allow | tarpit | auth [realm <realm>] | redirect <rule> |
               del-header <name> | set-nice <nice> | set-log-level <level> |
               replace-header <name> <match-regex> <replace-fmt> |
               replace-value <name> <match-regex> <replace-fmt> |
+              substitute-header <name> <match-regex> <replace-fmt> <options> |
+              substitute-value <name> <match-regex> <replace-fmt> <options> |
               set-method <fmt> | set-path <fmt> | set-query <fmt> |
               set-uri <fmt> | set-tos <tos> | set-mark <mark> |
               add-acl(<file name>) <key fmt> |
@@ -3769,6 +3771,29 @@ http-request { allow | tarpit | auth [realm <realm>] | redirect <rule> |
 
         X-Forwarded-For: 172.16.10.1, 172.16.13.24, 10.0.0.37
 
+    - "substitute-header" works similar to "replace-header" but it only
+      substitutes the matching parts within the original header. This makes
+      it possible to replace a varying number of matching parts within the
+      original header. <options> argument can only have value "g" meaning
+      substitute all matches. If <options> is empty "", only the first match
+      gets substituted.
+
+      Example:
+
+        http-request susbtitute-header Referer '-(?=.*\.example\.com)' '.' 'g'
+
+      applied to:
+
+        Referer: https://a-b-c.example.com/x-caliber
+
+      outputs:
+
+        Referer: https://a.b.c.example.com/x-caliber
+
+    - "substitute-value" works similar to "substitute-header" except that it 
+      matches the regex against every comma-delimited value of the header field
+      <name> instead of the entire header (as done in "replace-value"). 
+
     - "set-method" rewrites the request method with the result of the
       evaluation of format string <fmt>. There should be very few valid reasons
       for having to do so as this is more likely to break something than to fix
@@ -4122,6 +4147,8 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
                 set-header <name> <fmt> | del-header <name> |
                 replace-header <name> <regex-match> <replace-fmt> |
                 replace-value <name> <regex-match> <replace-fmt> |
+                substitute-header <name> <regex-match> <replace-fmt> <options> |
+                substitute-value <name> <regex-match> <replace-fmt> <options> |
                 set-status <status> |
                 set-log-level <level> | set-mark <mark> | set-tos <tos> |
                 add-acl(<file name>) <key fmt> |
@@ -4214,6 +4241,30 @@ http-response { allow | deny | add-header <name> <fmt> | set-nice <nice> |
 
         Cache-Control: max-age=3600, private
 
+    - "substitute-header" works similar to "replace-header" but it only
+      substitutes the matching parts within the original header. This makes
+      it possible to replace a varying number of matching parts within the 
+      original header. <options> argument can only have value "g" meaning
+      substitute all matches. If <options> is empty "", only the first match
+      gets substituted.
+
+      Example:
+
+        http-request susbtitute-header Access-Control-Allow-Origin \
+          '-(?=.*\.example\.com)' '.' 'g'
+
+      applied to:
+
+        Access-Control-Allow-Origin: https://a-b-c.example.com/x-caliber
+
+      outputs:
+
+        Access-Control-Allow-Origin: https://a.b.c.example.com/x-caliber
+
+    - "substitute-value" works similar to "substitute-header" except that it
+      matches the regex against every comma-delimited value of the header field
+      <name> instead of the entire header (as done in "replace-value").
+
     - "set-status" replaces the response status code with <status> which must
       be an integer between 100 and 999. Note that the reason is automatically
       adapted to the new code.
diff --git a/include/common/regex.h b/include/common/regex.h
index 8a1703f..6e1f508 100644
--- a/include/common/regex.h
+++ b/include/common/regex.h
@@ -62,6 +62,10 @@ struct my_regex {
 #define ACT_PASS	4	/* pass this header without allowing or denying the request */
 #define ACT_TARPIT	5	/* tarpit the connection matching this request */
 
+/* define regex substitution options */
+typedef char regex_subst_opts;
+#define RE_SUBST_GLOBAL 1       /* Global substitution */
+
 struct hdr_exp {
     struct hdr_exp *next;
     struct my_regex *preg;		/* expression to look for */
diff --git a/include/types/action.h b/include/types/action.h
index 5a70db0..f206b36 100644
--- a/include/types/action.h
+++ b/include/types/action.h
@@ -66,6 +66,8 @@ enum act_name {
 	ACT_HTTP_ADD_HDR,
 	ACT_HTTP_REPLACE_HDR,
 	ACT_HTTP_REPLACE_VAL,
+	ACT_HTTP_SUBSTITUTE_HDR,
+	ACT_HTTP_SUBSTITUTE_VAL,
 	ACT_HTTP_SET_HDR,
 	ACT_HTTP_DEL_HDR,
 	ACT_HTTP_REDIR,
@@ -112,7 +114,8 @@ struct act_rule {
 			char *name;            /* header name */
 			int name_len;          /* header name's length */
 			struct list fmt;       /* log-format compatible expression */
-			struct my_regex re;    /* used by replace-header and replace-value */
+			struct my_regex re;    /* used by replace/substitute-header and replace/substitute-value */
+			regex_subst_opts re_options; /* substitute-header/value regex flags */
 		} 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 ACT_HTTP_SET_NICE */
diff --git a/src/proto_http.c b/src/proto_http.c
index 5ac9bf3..8bc8082 100644
--- a/src/proto_http.c
+++ b/src/proto_http.c
@@ -3337,7 +3337,82 @@ void inet_set_tos(int fd, const struct sockaddr_storage *from, int tos)
 #endif
 }
 
-int http_transform_header_str(struct stream* s, struct http_msg *msg,
+int http_substitute_header_str(struct stream* s, struct http_msg *msg,
+                              const char* name, unsigned int name_len,
+                              const char *str, struct my_regex *re,
+                              int action, regex_subst_opts re_options)
+{
+	struct hdr_ctx ctx;
+	char *buf = msg->chn->buf->p;
+	struct hdr_idx *idx = &s->txn->hdr_idx;
+	int (*http_find_hdr_func)(const char *name, int len, char *sol,
+	                          struct hdr_idx *idx, struct hdr_ctx *ctx);
+	struct chunk *output = get_trash_chunk();
+
+	ctx.idx = 0;
+
+	/* Choose the header browsing function. */
+	switch (action) {
+	case ACT_HTTP_SUBSTITUTE_VAL:
+		http_find_hdr_func = http_find_header2;
+		break;
+	case ACT_HTTP_SUBSTITUTE_HDR:
+		http_find_hdr_func = http_find_full_header2;
+		break;
+	default: /* impossible */
+		return -1;
+	}
+
+	while (http_find_hdr_func(name, name_len, buf, idx, &ctx)) {
+		struct hdr_idx_elem *hdr = idx->v + ctx.idx;
+		int delta;
+		char *val = ctx.line + ctx.val;
+		char* val_end = val + ctx.vlen;
+                int end_match_offset = 0; // holds offset of end of all matches from start of string
+                int start_match_offset = 0; // holds offset of start of all matches from start of string
+                int res;
+                output->len = 0;
+
+                if (!regex_exec_match2(re, val, val_end-val, MAX_MATCH, pmatch, 0))
+                        continue;
+
+                start_match_offset = pmatch[0].rm_so;
+
+                do {
+                  /* if this is not first match and match does not start from beginning and enough space then copy directly */
+                  if (end_match_offset && pmatch[0].rm_so && ((output->size - output->len) >= pmatch[0].rm_so ))
+                    if (memcpy(output->str + output->len, val+end_match_offset, pmatch[0].rm_so))
+                      output->len += pmatch[0].rm_so;
+
+                  res = exp_replace(output->str + output->len, output->size - output->len, val + end_match_offset, str, pmatch);
+
+                  if (res == -1)
+                    return -1;
+                  output->len += res;
+
+                  end_match_offset += pmatch[0].rm_eo;
+
+                  /* checking if more matches are possibe: 
+                     Regex global matches flag is on and more bytes to read after last match */
+                  if ((re_options&RE_SUBST_GLOBAL) && (end_match_offset >= (val_end-val)))
+                    break;
+                }
+                while (regex_exec_match2(re, val + end_match_offset, (val_end-val) - end_match_offset , MAX_MATCH, pmatch, 0)  );
+
+                delta = buffer_replace2(msg->chn->buf, val+start_match_offset, val + end_match_offset, output->str, output->len);
+
+
+		hdr->len += delta;
+		http_msg_move_end(msg, delta);
+
+		/* Adjust the length of the current value of the index. */
+		ctx.vlen += delta;
+	}
+
+	return 0;
+}
+
+int http_replace_header_str(struct stream* s, struct http_msg *msg,
                               const char* name, unsigned int name_len,
                               const char *str, struct my_regex *re,
                               int action)
@@ -3391,7 +3466,7 @@ int http_transform_header_str(struct stream* s, struct http_msg *msg,
 static int http_transform_header(struct stream* s, struct http_msg *msg,
                                  const char* name, unsigned int name_len,
                                  struct list *fmt, struct my_regex *re,
-                                 int action)
+                                 int action, regex_subst_opts re_options)
 {
 	struct chunk *replace = get_trash_chunk();
 
@@ -3399,7 +3474,11 @@ static int http_transform_header(struct stream* s, struct http_msg *msg,
 	if (replace->len >= replace->size - 1)
 		return -1;
 
-	return http_transform_header_str(s, msg, name, name_len, replace->str, re, action);
+        if (action ==  ACT_HTTP_REPLACE_HDR || action ==  ACT_HTTP_REPLACE_VAL)
+		return http_replace_header_str(s, msg, name, name_len, replace->str, re, action);
+        else
+		/* action ==  ACT_HTTP_SUBSTITUTE_HDR || action ==  ACT_HTTP_SUBSTITUTE_VAL */
+		return http_substitute_header_str(s, msg, name, name_len, replace->str, re, action, re_options);
 }
 
 /* Executes the http-request rules <rules> for stream <s>, proxy <px> and
@@ -3515,10 +3594,13 @@ resume_execution:
 
 		case ACT_HTTP_REPLACE_HDR:
 		case ACT_HTTP_REPLACE_VAL:
+		case ACT_HTTP_SUBSTITUTE_HDR:
+		case ACT_HTTP_SUBSTITUTE_VAL:
 			if (http_transform_header(s, &txn->req, rule->arg.hdr_add.name,
 			                          rule->arg.hdr_add.name_len,
 			                          &rule->arg.hdr_add.fmt,
-			                          &rule->arg.hdr_add.re, rule->action))
+			                          &rule->arg.hdr_add.re, rule->action,
+			                          rule->arg.hdr_add.re_options))
 				return HTTP_RULE_RES_BADREQ;
 			break;
 
@@ -3784,10 +3866,13 @@ resume_execution:
 
 		case ACT_HTTP_REPLACE_HDR:
 		case ACT_HTTP_REPLACE_VAL:
+		case ACT_HTTP_SUBSTITUTE_HDR:
+		case ACT_HTTP_SUBSTITUTE_VAL:
 			if (http_transform_header(s, &txn->rsp, rule->arg.hdr_add.name,
 			                          rule->arg.hdr_add.name_len,
 			                          &rule->arg.hdr_add.fmt,
-			                          &rule->arg.hdr_add.re, rule->action))
+			                          &rule->arg.hdr_add.re, rule->action, 
+			                          rule->arg.hdr_add.re_options))
 				return HTTP_RULE_RES_STOP; /* note: we should report an error here */
 			break;
 
@@ -9195,6 +9280,55 @@ struct act_rule *parse_http_req_cond(const char **args, const char *file, int li
 		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
 		proxy->conf.lfs_line = proxy->conf.args.line;
 		cur_arg += 3;
+	} else if (strcmp(args[0], "substitute-header") == 0 || strcmp(args[0], "substitute-value") == 0 ) {
+		rule->action = args[0][11] == 'h' ? ACT_HTTP_SUBSTITUTE_HDR : ACT_HTTP_SUBSTITUTE_VAL;
+		cur_arg = 1;
+
+		/* Check for !*args[cur_arg+3] is ommitted on purpose because this can be empty. So
+		   in reality if no if/unless part is present, missing options arguments is interpreted as
+		   the empty options ''. */
+                if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2] ||
+			(*args[cur_arg+4] && strcmp(args[cur_arg+4], "if") != 0 && strcmp(args[cur_arg+4], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-request %s' expects exactly 4 arguments.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		rule->arg.hdr_add.name = strdup(args[cur_arg]);
+		rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name);
+		LIST_INIT(&rule->arg.hdr_add.fmt);
+
+		error = NULL;
+		if (!regex_comp(args[cur_arg + 1], &rule->arg.hdr_add.re, 1, 1, &error)) {
+			Alert("parsing [%s:%d] : '%s' : %s.\n", file, linenum,
+			      args[cur_arg + 1], error);
+			free(error);
+			goto out_err;
+		}
+
+		proxy->conf.args.ctx = ARGC_HRQ;
+		error = NULL;
+		if (!parse_logformat_string(args[cur_arg + 2], proxy, &rule->arg.hdr_add.fmt, LOG_OPT_HTTP,
+		                            (proxy->cap & PR_CAP_FE) ? SMP_VAL_FE_HRQ_HDR : SMP_VAL_BE_HRQ_HDR, &error)) {
+			Alert("parsing [%s:%d]: 'http-request %s': %s.\n",
+			      file, linenum, args[0], error);
+			free(error);
+			goto out_err;
+		}
+
+		rule->arg.hdr_add.re_options = 0;
+		if (strcmp(args[cur_arg+3], "g") == 0) {
+			rule->arg.hdr_add.re_options |= RE_SUBST_GLOBAL;
+		} else if (args[cur_arg+3][0] != '\0') {
+			Alert("parsing [%s:%d]: 'http-request %s': Invalid regex substitution option.\n", file, linenum,
+			args[0]);
+			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;
+		cur_arg += 4;
 	} else if (strcmp(args[0], "del-header") == 0) {
 		rule->action = ACT_HTTP_DEL_HDR;
 		cur_arg = 1;
@@ -9635,6 +9769,55 @@ struct act_rule *parse_http_res_cond(const char **args, const char *file, int li
 		proxy->conf.lfs_file = strdup(proxy->conf.args.file);
 		proxy->conf.lfs_line = proxy->conf.args.line;
 		cur_arg += 3;
+	} else if (strcmp(args[0], "substitute-header") == 0 || strcmp(args[0], "substitute-value") == 0) {
+		rule->action = args[0][11] == 'h' ? ACT_HTTP_SUBSTITUTE_HDR : ACT_HTTP_SUBSTITUTE_VAL;
+		cur_arg = 1;
+
+		/* Check for !*args[cur_arg+3] is ommitted on purpose because this can be empty. So
+		   in reality if no if/unless part is present, missing options arguments is interpreted as
+                   the empty options ''. */
+		if (!*args[cur_arg] || !*args[cur_arg+1] || !*args[cur_arg+2] ||
+		    (*args[cur_arg+4] && strcmp(args[cur_arg+4], "if") != 0 && strcmp(args[cur_arg+4], "unless") != 0)) {
+			Alert("parsing [%s:%d]: 'http-response %s' expects exactly 4 arguments.\n",
+			      file, linenum, args[0]);
+			goto out_err;
+		}
+
+		rule->arg.hdr_add.name = strdup(args[cur_arg]);
+		rule->arg.hdr_add.name_len = strlen(rule->arg.hdr_add.name);
+		LIST_INIT(&rule->arg.hdr_add.fmt);
+
+		error = NULL;
+		if (!regex_comp(args[cur_arg + 1], &rule->arg.hdr_add.re, 1, 1, &error)) {
+			Alert("parsing [%s:%d] : '%s' : %s.\n", file, linenum,
+			      args[cur_arg + 1], error);
+			free(error);
+			goto out_err;
+		}
+
+		proxy->conf.args.ctx = ARGC_HRQ;
+		error = NULL;
+		if (!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, &error)) {
+			Alert("parsing [%s:%d]: 'http-response %s': %s.\n",
+			      file, linenum, args[0], error);
+			free(error);
+			goto out_err;
+		}
+
+		rule->arg.hdr_add.re_options = 0;
+		if (strcmp(args[cur_arg+3], "g") == 0) {
+			rule->arg.hdr_add.re_options |= RE_SUBST_GLOBAL;
+		} else if (args[cur_arg+3][0] != '\0') {
+			Alert("parsing [%s:%d]: 'http-response %s': Invalid regex substitution option.\n", file, linenum,
+			args[0]);
+			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;
+		cur_arg += 4;
 	} else if (strcmp(args[0], "del-header") == 0) {
 		rule->action = ACT_HTTP_DEL_HDR;
 		cur_arg = 1;
@@ -9880,7 +10063,8 @@ struct act_rule *parse_http_res_cond(const char **args, const char *file, int li
 	} else {
 		action_build_list(&http_res_keywords.list, &trash);
 		Alert("parsing [%s:%d]: 'http-response' expects 'allow', 'deny', 'redirect', "
-		      "'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', 'set-nice', "
+		      "'add-header', 'del-header', 'set-header', 'replace-header', 'replace-value', "
+                      "'substitute-header', 'substitute-value', 'set-nice', "
 		      "'set-tos', 'set-mark', 'set-log-level', 'add-acl', 'del-acl', 'del-map', 'set-map', 'track-sc*'"
 		      "%s%s, but got '%s'%s.\n",
 		      file, linenum, *trash.str ? ", " : "", trash.str, args[0], *args[0] ? "" : " (missing argument)");
