Merge tag 'v2.4.17'

HAProxy 2.4.17
diff --git a/include/haproxy/backend-t.h b/include/haproxy/backend-t.h
index 8bee110..b5d448f 100644
--- a/include/haproxy/backend-t.h
+++ b/include/haproxy/backend-t.h
@@ -157,6 +157,7 @@
 	int   arg_opt1;			/* extra option 1 for the LB algo (algo-specific) */
 	int   arg_opt2;			/* extra option 2 for the LB algo (algo-specific) */
 	int   arg_opt3;			/* extra option 3 for the LB algo (algo-specific) */
+	int   arg_opt4;                 /* extra option 4 for the LB algo (algo-specific) */
 	__decl_thread(HA_RWLOCK_T lock);
 	struct server *fbck;		/* first backup server when !PR_O_USE_ALL_BK, or NULL */
 
diff --git a/include/haproxy/backend.h b/include/haproxy/backend.h
index 8a763bf..0e2d75f 100644
--- a/include/haproxy/backend.h
+++ b/include/haproxy/backend.h
@@ -48,6 +48,7 @@
 void recount_servers(struct proxy *px);
 void update_backend_weight(struct proxy *px);
 int be_lastsession(const struct proxy *be);
+struct server *get_server_uh(struct proxy *px, char *uri, int uri_len, const struct server *avoid);
 
 /* Returns number of usable servers in backend */
 static inline int be_usable_srv(struct proxy *be)
diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h
index d4b91a3..78beaf7 100644
--- a/include/haproxy/global-t.h
+++ b/include/haproxy/global-t.h
@@ -89,6 +89,9 @@
 
 /* FIXME : this will have to be redefined correctly */
 struct global {
+	int email_alert;
+	char email_to[3][50];
+	char email_from[50];
 	int uid;
 	int gid;
 	int external_check;
diff --git a/include/haproxy/mailers.h b/include/haproxy/mailers.h
index 43db167..8e03f59 100644
--- a/include/haproxy/mailers.h
+++ b/include/haproxy/mailers.h
@@ -36,6 +36,6 @@
 int init_email_alert(struct mailers *mailers, struct proxy *p, char **err);
 void send_email_alert(struct server *s, int priority, const char *format, ...)
 	__attribute__ ((format(printf, 3, 4)));
-
+void Emaila(const char *fmt, ...);
 
 #endif /* _HAPROXY_MAILERS_H */
diff --git a/include/haproxy/proxy.h b/include/haproxy/proxy.h
index 8a2a5c8..5f742e3 100644
--- a/include/haproxy/proxy.h
+++ b/include/haproxy/proxy.h
@@ -70,6 +70,8 @@
                               const struct proxy *defproxy);
 int get_backend_server(const char *bk_name, const char *sv_name,
 		       struct proxy **bk, struct server **sv);
+int test_backend_server(const char *bk_name, char *uri,
+		struct proxy **bk, struct server **sv);
 void proxy_capture_error(struct proxy *proxy, int is_back,
 			 struct proxy *other_end, enum obj_type *target,
 			 const struct session *sess,
diff --git a/src/backend.c b/src/backend.c
index b222e66..a864fc1 100644
--- a/src/backend.c
+++ b/src/backend.c
@@ -202,12 +202,18 @@
  * algorithm out of a tens because it gave him the best results.
  *
  */
-static struct server *get_server_uh(struct proxy *px, char *uri, int uri_len, const struct server *avoid)
+struct server *get_server_uh(struct proxy *px, char *uri, int uri_len, const struct server *avoid)
 {
 	unsigned int hash = 0;
 	int c;
 	int slashes = 0;
 	const char *start, *end;
+	int depth = 1;
+	int orig_uri_len = uri_len;
+	const char *orig_start;
+	const char *p, *params;
+	int buflen = 0;
+	char *buf;
 
 	if (px->lbprm.tot_weight == 0)
 		return NULL;
@@ -219,20 +225,120 @@
 	if (px->lbprm.arg_opt2) // "len"
 		uri_len = MIN(uri_len, px->lbprm.arg_opt2);
 
-	start = end = uri;
-	while (uri_len--) {
-		c = *end;
-		if (c == '/') {
-			slashes++;
-			if (slashes == px->lbprm.arg_opt3) /* depth+1 */
+        orig_start = start = end = uri;
+        if (px->lbprm.arg_opt4 == 1) {
+                p = memchr(uri, '?', uri_len);
+                params = p;
+                if (p) {
+                        p++;
+                        uri_len = p - uri;
+                        params = p;
+                        if (p[7] == '=') {
+                                if (memcmp(p, "service", 7) == 0) {
+                                        p += 8;
+                                        depth = 2;
+                                }
+                        }
+                }
+
+                end = uri + uri_len - 1;
+                while (uri_len--) {
+                        c = *end;
+                        if (c == '/') {
+                                if (p == NULL)
+                                        p = end + 1;
+                                slashes++;
+                                if (slashes == depth)
+                                        break;
+                        }
+                        end--;
+                }
+                if (p != NULL && (!strncmp(p, "git-receive-pack", 16) || !strncmp(p, "git-upload-pack", 15))) {
+                        if (!strncmp(start, "/a/", 3) || !strncmp(start, "/p/", 3))
+                                start += 2;
+			// we have git request here
+                        // get rid of redundant '/'
+                        while ((end - start) > 1 && *(start+1) == '/')
+                                start++;
+                        while ((end - start) > 1 && *(end-1) == '/')
+                                end--;
+                        // get rid of trailing ".git"
+                        if ((end - start) > 4 && strncmp(end-4, ".git", 4) == 0)
+                                end -= 4;
+                        // get rid of redundant '/'
+			while ((end - start) > 1 && *(end-1) == '/')
+                                end--;
+                        hash = gen_hash(px, start, (end - start));
+                } else {
+                        if (px->lbprm.arg_opt3 > 0){
+                                // reset state
+				uri_len = orig_uri_len;
+                                if (params != NULL)
+                                        uri_len = params - uri;
+                                depth = px->lbprm.arg_opt3;
+                                slashes = 0;
+                                buf = (char *)malloc(uri_len);
+                                orig_start = start = end = uri;
+                                while (uri_len--) {
+                                        c = *end;
+                                        if (c == '/') {
+                                                if (slashes == 1 && end - start == 2) {
+                                                        depth += 1;
+                                                        orig_start = start = end;
+                                                }
+                                                slashes++;
+                                                if (slashes == depth - 1) {
+                                                        if (memcmp(start, "/changes", end - start) != 0 && memcmp(start, "/projects", end - start) != 0)
+                                                                break;
+                                                        start = end;
+                                                        p = start;
+                                                }
+                                                else if (slashes == depth) { /* depth+1 */
+                                                        memcpy(buf + buflen, p, end - p);
+                                                        buflen += (end - p);
+                                                        break;
+                                                }
+                                        }
+                                        else if (c == '%') {
+                                                if (orig_start != start && uri_len > 2 && (memcmp(end, "%2F", 3) == 0 || memcmp(end, "%2f", 3) == 0)) {
+                                                        memcpy(buf + buflen, p, end - p);
+                                                        buflen += (end - p) + 1;
+                                                        buf[buflen - 1] = '/';
+                                                        p = end + 3;
+                                                }
+                                        }
+                                        else if (c == '~') {
+                                                if (orig_start != start) {
+                                                  memcpy(buf + buflen, p, end - p);
+                                                  buflen += (end - p);
+                                                  break;
+                                                }
+                                        }
+                                        end++;
+                                }
+                        }
+                        if (buflen > 0)
+                                hash = gen_hash(px, buf, buflen);
+                        if (buf != NULL)
+                                free(buf);
+                        if (buflen <= 0) /*let's fallback to round robin */
+                                return NULL;
+                }
+        } else {
+		while (uri_len--) {
+			c = *end;
+			if (c == '/') {
+				slashes++;
+				if (slashes == px->lbprm.arg_opt3) /* depth+1 */
+					break;
+			}
+			else if (c == '?' && !px->lbprm.arg_opt1) // "whole"
 				break;
+			end++;
 		}
-		else if (c == '?' && !(px->lbprm.arg_opt1 & 1)) // "whole"
-			break;
-		end++;
-	}
 
-	hash = gen_hash(px, start, (end - start));
+		hash = gen_hash(px, start, (end - start));
+	}
 
 	if ((px->lbprm.algo & BE_LB_HASH_MOD) == BE_LB_HMOD_AVAL)
 		hash = full_hash(hash);
@@ -2597,6 +2703,7 @@
 		curproxy->lbprm.arg_opt1 = 0; // "whole", "path-only"
 		curproxy->lbprm.arg_opt2 = 0; // "len"
 		curproxy->lbprm.arg_opt3 = 0; // "depth"
+		curproxy->lbprm.arg_opt4 = 0; // "gerrit"
 
 		while (*args[arg]) {
 			if (strcmp(args[arg], "len") == 0) {
@@ -2626,6 +2733,16 @@
 				curproxy->lbprm.arg_opt1 |= 2;
 				arg += 1;
 			}
+			else if (!strcmp(args[arg], "gerrit")) {
+                                if (!*args[arg+1] || (atoi(args[arg+1]) < 0)) {
+                                        memprintf(err, "%s : '%s' expects a non negative integer  (got '%s').", args[0], args[arg], args[arg+1]);
+                                        return -1;
+                                }
+                                curproxy->lbprm.arg_opt4 = 1;
+                                // 0 means whole uri
+                                curproxy->lbprm.arg_opt3 = atoi(args[arg+1]);
+                                arg += 2;
+			}
 			else {
 				memprintf(err, "%s only accepts parameters 'len', 'depth', 'path-only', and 'whole' (got '%s').", args[0], args[arg]);
 				return -1;
diff --git a/src/cfgparse-global.c b/src/cfgparse-global.c
index c2a44d9..d88d026 100644
--- a/src/cfgparse-global.c
+++ b/src/cfgparse-global.c
@@ -62,6 +62,7 @@
  */
 int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
 {
+	int i = 0;
 	int err_code = 0;
 	char *errmsg = NULL;
 
@@ -1306,6 +1307,20 @@
 	else if (strcmp(args[0], "numa-cpu-mapping") == 0) {
 		global.numa_cpu_mapping = (kwm == KWM_NO) ? 0 : 1;
 	}
+	else if (!strcmp(args[0], "email_alert")) {
+		global.email_alert = 1;
+		if (*(args[1]) == 0) {
+			ha_alert("parsing [%s:%d] : email_alert Expects email address as argument.\n", file, linenum);
+			global.email_alert = 0;
+			err_code |= ERR_ALERT;
+			goto out;
+		}
+		strncpy(global.email_from,args[1],50);
+		for (i=0; i<3; i++) {
+			if (*(args[i+2]) != 0)
+				strncpy(global.email_to[i],args[i+2],50);
+		}
+	}	
 	else {
 		struct cfg_kw_list *kwl;
 		const char *best;
diff --git a/src/mailers.c b/src/mailers.c
index 4138bae..5e680f2 100644
--- a/src/mailers.c
+++ b/src/mailers.c
@@ -294,6 +294,69 @@
 	return;
 }
 
+void make_literal(char const *input, char *output) {
+	// the following two arrays must be maintained in matching order:
+	static char inputs[] = "\a\b\f\n\r\t\v\\\"\'";
+	static char outputs[] = "abfnrtv\\\"\'";
+
+	char *pos;
+
+	for (;*input;input++) {
+		if (NULL!= (pos=strchr(inputs, *input))) {
+			*output++ = '\\';
+			*output++ = outputs[pos-inputs];
+		}
+		else
+			*output++ = *input;
+	}
+	*output = '\0';
+}
+
+void Emaila(const char *fmt, ...)
+{
+	va_list argp;
+	char tmp[256];
+	char buf[500];
+	char alertcmd[1024];
+	int sysreturn;
+	size_t len = 0, len1 = 0, len2 = 0, len3 = 0;
+
+	if (global.email_alert) {
+		va_start(argp, fmt);
+		vsnprintf(tmp,128,fmt, argp);
+		make_literal(tmp, buf);
+
+		len1 = strlen(global.email_to[0]);
+		len2 = strlen(global.email_to[1]);
+		len3 = strlen(global.email_to[2]);
+		memcpy(tmp, global.email_to[0], len1);
+		tmp[len1] = ' ';
+		len = len1 + 1;
+		if (len2 > 0) {
+			memcpy(tmp + len, global.email_to[1], len2); // includes terminating null
+			len += len2;
+			tmp[len] = ' ';
+			len += 1;
+			if (len3 > 0) {
+				memcpy(tmp + len, global.email_to[2], len3); // includes terminating null
+				len += len3;
+				tmp[len] = ' ';
+				len += 1;
+			}
+		}
+		tmp[len] = '\0';
+		snprintf(alertcmd, 1024, "echo \"%s\" | mail -s \"$(echo -e \"[HAProxy alert %s] %.60s...\nFrom:HAProxy <%s>\n\")\" %s&", buf, global.email_from, buf, global.email_from, tmp);
+
+		sysreturn = system(alertcmd);
+		if (sysreturn == -1) {
+			ha_warning("There was an error sending the email alert");
+		}
+		vfprintf(stderr, fmt, argp);
+		fflush(stderr);
+		va_end(argp);
+	}
+}
+
 /*
  * Send email alert if configured.
  */
diff --git a/src/proxy.c b/src/proxy.c
index 07898b0..5d0f1f8 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -422,6 +422,26 @@
 	return best_ptr;
 }
 
+int test_backend_server(const char *bk_name, char *uri,
+		struct proxy **bk, struct server **sv)
+{
+	struct proxy *p;
+	struct server *s;
+	int uri_len = strlen(uri);
+	*sv = NULL;
+	p = proxy_be_by_name(bk_name);
+	if (bk)
+		*bk = p;
+	if (!p || (p->lbprm.algo & BE_LB_PARM) != BE_LB_HASH_URI)
+		return 0;
+	fprintf(stderr, "%s\n", uri);
+	s = get_server_uh(p, uri, uri_len, NULL);
+	*sv = s;
+	if (!s)
+		return 0;
+	return 1;
+}
+
 /*
  * This function scans the list of backends and servers to retrieve the first
  * backend and the first server with the given names, and sets them in both
@@ -1606,6 +1626,7 @@
 		curproxy->lbprm.arg_opt1 = defproxy->lbprm.arg_opt1;
 		curproxy->lbprm.arg_opt2 = defproxy->lbprm.arg_opt2;
 		curproxy->lbprm.arg_opt3 = defproxy->lbprm.arg_opt3;
+		curproxy->lbprm.arg_opt4 = defproxy->lbprm.arg_opt4;
 
 		if (defproxy->conn_src.iface_name)
 			curproxy->conn_src.iface_name = strdup(defproxy->conn_src.iface_name);
diff --git a/src/server.c b/src/server.c
index 9d0a35b..22d6202 100644
--- a/src/server.c
+++ b/src/server.c
@@ -4288,6 +4288,50 @@
 	return 1;
 }
 
+static int cli_parse_get_server(char **args, char *payload, struct appctx *appctx, void *private)
+{
+	struct stream_interface *si = appctx->owner;
+	struct proxy *px;
+	struct server *sv;
+	char *line;
+	char pn[INET6_ADDRSTRLEN];
+	uint16_t port;
+	struct sockaddr_storage* addr;
+
+	/* split "backend/server" and make <line> point to server */
+	for (line = args[2]; *line; line++)
+		if (*line == '/') {
+			*line++ = '\0';
+			break;
+		}
+
+	if (!*line) {
+		return cli_err(appctx, "Require 'backend/server'.\n");
+	}
+
+	if (!test_backend_server(args[2], line, &px, &sv)) {
+		return cli_err(appctx, px ? "No such server.\n" : "No such backend.\n");
+	}
+	addr = &(sv->addr);
+	switch (addr_to_str(addr, pn, sizeof(pn))) {
+	case AF_INET:
+		port = ((struct sockaddr_in *)addr)->sin_port;
+		break;
+	case AF_INET6:
+		port = ((struct sockaddr_in6 *)addr)->sin6_port;
+		break;
+	default:
+		port = 0;
+		break;
+	}
+	snprintf(trash.area, trash.size, "%s,%s,%u\n", sv->id, pn, ntohs(port));
+        if (ci_putstr(si_ic(si), trash.area) == -1) {
+                si_rx_room_blk(si);
+                return 0;
+        }
+        return 1;
+}
+
 static int cli_parse_get_weight(char **args, char *payload, struct appctx *appctx, void *private)
 {
 	struct stream_interface *si = appctx->owner;
@@ -4878,6 +4922,7 @@
 	{ { "enable", "server",  NULL },         "enable server  (DEPRECATED)             : enable a disabled server (use 'set server' instead)",         cli_parse_enable_server, NULL },
 	{ { "set", "maxconn", "server",  NULL }, "set maxconn server <bk>/<srv>           : change a server's maxconn setting",                           cli_parse_set_maxconn_server, NULL },
 	{ { "set", "server", NULL },             "set server <bk>/<srv> [opts]            : change a server's state, weight, address or ssl",             cli_parse_set_server },
+        { { "get", "server", NULL },             "get server <bk>/<req>                   : get target backend server by request",                        cli_parse_get_server },
 	{ { "get", "weight", NULL },             "get weight <bk>/<srv>                   : report a server's current weight",                            cli_parse_get_weight },
 	{ { "set", "weight", NULL },             "set weight <bk>/<srv>  (DEPRECATED)     : change a server's weight (use 'set server' instead)",         cli_parse_set_weight },
 	{ { "add", "server", NULL },             "add server <bk>/<srv>                   : create a new server (EXPERIMENTAL)",                          cli_parse_add_server, NULL, NULL, NULL, ACCESS_EXPERIMENTAL },
@@ -4937,6 +4982,7 @@
 
 				srv_append_status(tmptrash, s, NULL, xferred, 0);
 				ha_warning("%s.\n", tmptrash->area);
+				Emaila("%s \n", tmptrash->area);
 
 				/* we don't send an alert if the server was previously paused */
 				log_level = srv_was_stopping ? LOG_NOTICE : LOG_ALERT;