MEDIUM: tune.http.maxhdr makes it possible to configure the maximum number of HTTP headers

For a long time, the max number of headers was taken as a part of the buffer
size. Since the header size can be configured at runtime, it does not make
much sense anymore.

Nothing was making it necessary to have a static value, so let's turn this into
a tunable with a default value of 101 which equals what was previously used.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 5cce3b4..afe1bfc 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -458,6 +458,7 @@
    - spread-checks
    - tune.bufsize
    - tune.chksize
+   - tune.http.maxhdr
    - tune.maxaccept
    - tune.maxpollevents
    - tune.maxrewrite
@@ -725,6 +726,17 @@
   build time. It is not recommended to change this value, but to use better
   checks whenever possible.
 
+tune.http.maxhdr <number>
+  Sets the maximum number of headers in a request. When a request comes with a
+  number of headers greater than this value (including the first line), it is
+  rejected with a "400 Bad Request" status code. Similarly, too large responses
+  are blocked with "502 Bad Gateway". The default value is 101, which is enough
+  for all usages, considering that the widely deployed Apache server uses the
+  same limit. It can be useful to push this limit further to temporarily allow
+  a buggy application to work by the time it gets fixed. Keep in mind that each
+  new header consumes 32bits of memory for each session, so don't push this
+  limit too high.
+
 tune.maxaccept <number>
   Sets the maximum number of consecutive accepts that a process may perform on
   a single wake up. High values give higher priority to high connection rates,
diff --git a/include/common/defaults.h b/include/common/defaults.h
index 0a4f420..8647131 100644
--- a/include/common/defaults.h
+++ b/include/common/defaults.h
@@ -58,9 +58,9 @@
 #define	MAX_MATCH       10
 
 // max # of headers in one HTTP request or response
-// By default, about 100 headers per 8 kB.
+// By default, about 100 headers (+1 for the first line)
 #ifndef MAX_HTTP_HDR
-#define MAX_HTTP_HDR    ((BUFSIZE+79)/80)
+#define MAX_HTTP_HDR    101
 #endif
 
 // max # of headers in history when looking for header #-X
diff --git a/include/types/global.h b/include/types/global.h
index 078a1d5..56110e8 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -97,6 +97,7 @@
 		int server_rcvbuf; /* set server rcvbuf to this value if not null */
 		int chksize;       /* check buffer size in bytes, defaults to BUFSIZE */
 		int pipesize;      /* pipe size in bytes, system defaults if zero */
+		int max_http_hdr;  /* max number of HTTP headers, use MAX_HTTP_HDR if zero */
 	} tune;
 	struct {
 		char *prefix;           /* path prefix of unix bind socket */
diff --git a/include/types/proto_http.h b/include/types/proto_http.h
index d9bf830..f1b3eef 100644
--- a/include/types/proto_http.h
+++ b/include/types/proto_http.h
@@ -322,7 +322,7 @@
  */
 struct http_txn {
 	struct http_msg req;            /* HTTP request message */
-	struct hdr_idx hdr_idx;         /* array of header indexes (max: MAX_HTTP_HDR) */
+	struct hdr_idx hdr_idx;         /* array of header indexes (max: global.tune.max_http_hdr) */
 	unsigned int flags;             /* transaction flags */
 	http_meth_t meth;               /* HTTP method */
 
diff --git a/src/cfgparse.c b/src/cfgparse.c
index dac6bca..5ddfbe2 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -596,6 +596,14 @@
 		}
 		global.tune.pipesize = atol(args[1]);
 	}
+	else if (!strcmp(args[0], "tune.http.maxhdr")) {
+		if (*(args[1]) == 0) {
+			Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+		global.tune.max_http_hdr = atol(args[1]);
+	}
 	else if (!strcmp(args[0], "uid")) {
 		if (global.uid != 0) {
 			Alert("parsing [%s:%d] : user/uid already specified. Continuing.\n", file, linenum);
@@ -6595,8 +6603,11 @@
 		}
 	}
 
+	if (!global.tune.max_http_hdr)
+		global.tune.max_http_hdr = MAX_HTTP_HDR;
+
 	pool2_hdr_idx = create_pool("hdr_idx",
-				    MAX_HTTP_HDR * sizeof(struct hdr_idx_elem),
+				    global.tune.max_http_hdr * sizeof(struct hdr_idx_elem),
 				    MEM_F_SHARED);
 
 	if (cfgerr > 0)
diff --git a/src/frontend.c b/src/frontend.c
index 195a424..19980c0 100644
--- a/src/frontend.c
+++ b/src/frontend.c
@@ -134,7 +134,7 @@
 		 * that we may make use of them. This of course includes
 		 * (mode == PR_MODE_HTTP).
 		 */
-		s->txn.hdr_idx.size = MAX_HTTP_HDR;
+		s->txn.hdr_idx.size = global.tune.max_http_hdr;
 
 		if (unlikely((s->txn.hdr_idx.v = pool_alloc2(pool2_hdr_idx)) == NULL))
 			goto out_free_rspcap; /* no memory */
diff --git a/src/proxy.c b/src/proxy.c
index d274dfe..caec45d 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -819,7 +819,7 @@
 		/* and now initialize the HTTP transaction state */
 		http_init_txn(s);
 
-		s->txn.hdr_idx.size = MAX_HTTP_HDR;
+		s->txn.hdr_idx.size = global.tune.max_http_hdr;
 		hdr_idx_init(&s->txn.hdr_idx);
 	}