MEDIUM: logs: Add HTTP request-line log format directives

This commit adds 4 new log format variables that parse the
HTTP Request-Line for more specific logging than "%r" provides.

For example, we can parse the following HTTP Request-Line with
these new variables:

  "GET /foo?bar=baz HTTP/1.1"

- %HM: HTTP Method ("GET")
- %HV: HTTP Version ("HTTP/1.1")
- %HU: HTTP Request-URI ("/foo?bar=baz")
- %HP: HTTP Request-URI without query string ("/foo")
diff --git a/doc/configuration.txt b/doc/configuration.txt
index a37f54c..b6b3f08 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -13159,6 +13159,10 @@
   | H | %CC  | captured_request_cookie                       | string      |
   | H | %CS  | captured_response_cookie                      | string      |
   |   | %H   | hostname                                      | string      |
+  | H | %HM  | HTTP method (ex: POST)                        | string      |
+  | H | %HP  | HTTP request URI without query string (path)  | string      |
+  | H | %HU  | HTTP request URI (ex: /foo?bar=baz)           | string      |
+  | H | %HV  | HTTP version (ex: HTTP/1.0)                   | string      |
   |   | %ID  | unique-id                                     | string      |
   |   | %ST  | status_code                                   | numeric     |
   |   | %T   | gmt_date_time                                 | date        |
diff --git a/include/types/log.h b/include/types/log.h
index 345a09f..bbfe020 100644
--- a/include/types/log.h
+++ b/include/types/log.h
@@ -93,6 +93,10 @@
 	LOG_FMT_HDRREQUESTLIST,
 	LOG_FMT_HDRRESPONSLIST,
 	LOG_FMT_REQ,
+	LOG_FMT_HTTP_METHOD,
+	LOG_FMT_HTTP_URI,
+	LOG_FMT_HTTP_PATH,
+	LOG_FMT_HTTP_VERSION,
 	LOG_FMT_HOSTNAME,
 	LOG_FMT_UNIQUEID,
 	LOG_FMT_SSL_CIPHER,
diff --git a/src/log.c b/src/log.c
index 866b110..2affac3 100644
--- a/src/log.c
+++ b/src/log.c
@@ -32,6 +32,7 @@
 #include <types/log.h>
 
 #include <proto/frontend.h>
+#include <proto/proto_http.h>
 #include <proto/log.h>
 #include <proto/sample.h>
 #include <proto/stream.h>
@@ -108,6 +109,10 @@
 	{ "hrl", LOG_FMT_HDRREQUESTLIST, PR_MODE_TCP, LW_REQHDR, NULL }, /* header request list */
 	{ "hs", LOG_FMT_HDRRESPONS, PR_MODE_TCP, LW_RSPHDR, NULL },  /* header response */
 	{ "hsl", LOG_FMT_HDRRESPONSLIST, PR_MODE_TCP, LW_RSPHDR, NULL },  /* header response list */
+	{ "HM", LOG_FMT_HTTP_METHOD, PR_MODE_HTTP, LW_REQ, NULL },  /* HTTP method */
+	{ "HP", LOG_FMT_HTTP_PATH, PR_MODE_HTTP, LW_REQ, NULL },  /* HTTP path */
+	{ "HU", LOG_FMT_HTTP_URI, PR_MODE_HTTP, LW_REQ, NULL },  /* HTTP full URI */
+	{ "HV", LOG_FMT_HTTP_VERSION, PR_MODE_HTTP, LW_REQ, NULL },  /* HTTP version */
 	{ "lc", LOG_FMT_LOGCNT, PR_MODE_TCP, LW_INIT, NULL }, /* log counter */
 	{ "ms", LOG_FMT_MS, PR_MODE_TCP, LW_INIT, NULL },       /* accept date millisecond */
 	{ "pid", LOG_FMT_PID, PR_MODE_TCP, LW_INIT, NULL }, /* log pid */
@@ -923,11 +928,15 @@
 	struct proxy *fe = sess->fe;
 	struct proxy *be = s->be;
 	struct http_txn *txn = s->txn;
+	struct chunk chunk;
 	char *uri;
+	char *spc;
+	char *end;
 	struct tm tm;
 	int t_request;
 	int hdr;
 	int last_isspace = 1;
+	int nspaces = 0;
 	char *tmplog;
 	char *ret;
 	int iret;
@@ -1523,6 +1532,161 @@
 				last_isspace = 0;
 				break;
 
+			case LOG_FMT_HTTP_PATH: // %HP
+				uri = txn->uri ? txn->uri : "<BADREQ>";
+
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				end = uri + strlen(uri);
+				// look for the first whitespace character
+				while (uri < end && !HTTP_IS_SPHT(*uri))
+					uri++;
+
+				// keep advancing past multiple spaces
+				while (uri < end && HTTP_IS_SPHT(*uri)) {
+					uri++; nspaces++;
+				}
+
+				// look for first space or question mark after url
+				spc = uri;
+				while (spc < end && *spc != '?' && !HTTP_IS_SPHT(*spc))
+					spc++;
+
+				if (!txn->uri || nspaces == 0) {
+					chunk.str = "<BADREQ>";
+					chunk.len = strlen("<BADREQ>");
+				} else {
+					chunk.str = uri;
+					chunk.len = spc - uri;
+				}
+
+				ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk);
+				if (ret == NULL || *ret != '\0')
+					goto out;
+
+				tmplog = ret;
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				last_isspace = 0;
+				break;
+
+			case LOG_FMT_HTTP_URI: // %HU
+				uri = txn->uri ? txn->uri : "<BADREQ>";
+
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				end = uri + strlen(uri);
+				// look for the first whitespace character
+				while (uri < end && !HTTP_IS_SPHT(*uri))
+					uri++;
+
+				// keep advancing past multiple spaces
+				while (uri < end && HTTP_IS_SPHT(*uri)) {
+					uri++; nspaces++;
+				}
+
+				// look for first space after url
+				spc = uri;
+				while (spc < end && !HTTP_IS_SPHT(*spc))
+					spc++;
+
+				if (!txn->uri || nspaces == 0) {
+					chunk.str = "<BADREQ>";
+					chunk.len = strlen("<BADREQ>");
+				} else {
+					chunk.str = uri;
+					chunk.len = spc - uri;
+				}
+
+				ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk);
+				if (ret == NULL || *ret != '\0')
+					goto out;
+
+				tmplog = ret;
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				last_isspace = 0;
+				break;
+
+			case LOG_FMT_HTTP_METHOD: // %HM
+				uri = txn->uri ? txn->uri : "<BADREQ>";
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				end = uri + strlen(uri);
+				// look for the first whitespace character
+				spc = uri;
+				while (spc < end && !HTTP_IS_SPHT(*spc))
+					spc++;
+
+				if (spc == end) { // odd case, we have txn->uri, but we only got a verb
+					chunk.str = "<BADREQ>";
+					chunk.len = strlen("<BADREQ>");
+				} else {
+					chunk.str = uri;
+					chunk.len = spc - uri;
+				}
+
+				ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk);
+				if (ret == NULL || *ret != '\0')
+					goto out;
+
+				tmplog = ret;
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				last_isspace = 0;
+				break;
+
+			case LOG_FMT_HTTP_VERSION: // %HV
+				uri = txn->uri ? txn->uri : "<BADREQ>";
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				end = uri + strlen(uri);
+				// look for the first whitespace character
+				while (uri < end && !HTTP_IS_SPHT(*uri))
+					uri++;
+
+				// keep advancing past multiple spaces
+				while (uri < end && HTTP_IS_SPHT(*uri)) {
+					uri++; nspaces++;
+				}
+
+				// look for the next whitespace character
+				while (uri < end && !HTTP_IS_SPHT(*uri))
+					uri++;
+
+				// keep advancing past multiple spaces
+				while (uri < end && HTTP_IS_SPHT(*uri))
+					uri++;
+
+				if (!txn->uri || nspaces == 0) {
+					chunk.str = "<BADREQ>";
+					chunk.len = strlen("<BADREQ>");
+				} else if (uri == end) {
+					chunk.str = "HTTP/0.9";
+					chunk.len = strlen("HTTP/0.9");
+				} else {
+					chunk.str = uri;
+					chunk.len = end - uri;
+				}
+
+				ret = encode_chunk(tmplog, dst + maxsize, '#', url_encode_map, &chunk);
+				if (ret == NULL || *ret != '\0')
+					goto out;
+
+				tmplog = ret;
+				if (tmp->options && LOG_OPT_QUOTE)
+					LOGCHAR('"');
+
+				last_isspace = 0;
+				break;
+
 			case LOG_FMT_COUNTER: // %rt
 				if (tmp->options & LOG_OPT_HEXA) {
 					iret = snprintf(tmplog, dst + maxsize - tmplog, "%04X", s->uniq_id);