Przeglądaj źródła

Merge fixes from HTTP2 prototype

HTTP2 5 lat temu
rodzic
commit
9aa8192eba
4 zmienionych plików z 691 dodań i 362 usunięć
  1. 6 0
      Makefile
  2. 3 4
      src/civetweb.c
  3. 411 80
      src/mod_http2.inl
  4. 271 278
      src/response.inl

+ 6 - 0
Makefile

@@ -94,6 +94,7 @@ else
   LCC = $(CC)
 endif
 
+
 ifdef WITH_ALL
   WITH_WEBSOCKET = 1
   WITH_IPV6 = 1
@@ -101,6 +102,7 @@ ifdef WITH_ALL
   WITH_DUKTAPE = 1
   WITH_SERVER_STATS = 1
   WITH_ZLIB = 1
+  WITH_HTTP2 = 1
   WITH_EXPERIMENTAL = 1
   #WITH_CPP is not defined, ALL means only real features, not wrappers
 endif
@@ -151,6 +153,10 @@ ifdef WITH_ZLIB
   CFLAGS += -DUSE_ZLIB
 endif
 
+ifdef WITH_HTTP2
+  CFLAGS += -DUSE_HTTP2
+endif
+
 # Other features
 ifdef WITH_EXPERIMENTAL
   CFLAGS += -DMG_EXPERIMENTAL_INTERFACES

+ 3 - 4
src/civetweb.c

@@ -10200,9 +10200,7 @@ handle_static_file_request(struct mg_connection *conn,
 {
 	char lm[64], etag[64];
 	char range[128]; /* large enough, so there will be no overflow */
-	const char *msg = "OK";
 	const char *range_hdr;
-	time_t curtime = time(NULL);
 	int64_t cl, r1, r2;
 	struct vec mime_vec;
 	int n, truncated;
@@ -10335,7 +10333,6 @@ handle_static_file_request(struct mg_connection *conn,
 		            r1,
 		            r1 + cl - 1,
 		            filep->stat.size);
-		msg = "Partial Content";
 
 #if defined(USE_ZLIB)
 		/* Do not compress ranges. */
@@ -13251,7 +13248,6 @@ handle_websocket_request(struct mg_connection *conn,
 						; // ignore leading whitespaces
 					protocol = sep;
 
-
 					for (idx = 0; idx < subprotocols->nb_subprotocols; idx++) {
 						if ((strlen(subprotocols->subprotocols[idx]) == len)
 						    && (strncmp(curSubProtocol,
@@ -18522,6 +18518,9 @@ process_new_connection(struct mg_connection *conn)
 				}
 				log_access(conn);
 
+				/* Response complete. Free header buffer */
+				free_buffered_response_header_list(conn);
+
 			} else {
 				/* TODO: handle non-local request (PROXY) */
 				conn->must_close = 1;

+ 411 - 80
src/mod_http2.inl

@@ -1,3 +1,7 @@
+/* Prototype implementation for HTTP2. Do not use in production.
+ * There may be memory leaks, security vulnerabilities, ...
+ */
+
 
 /***********************************************************************/
 /***  HPACK                                                          ***/
@@ -72,11 +76,6 @@ static struct mg_header hpack_predefined[62] = {{NULL, NULL},
                                                 {"via", NULL},
                                                 {"www-authenticate", NULL}};
 
-/* buffers */
-// char hpack_slash[2] = "/";
-// char hpack_slash_index_html[12] = "/index.html";
-
-
 /* Huffman decoding: https://tools.ietf.org/html/rfc7541#appendix-B
 
 '0' ( 48)  |00000                                         0  [ 5]
@@ -602,15 +601,27 @@ struct {
     {(uint8_t)256, 30, 0x3fffffff} /* filling/termination */
 };
 
-uint32_t hpack_huff_end[] = {0x9,       0x2d,       0x7b,       0xfd,
-                             0,         0x3fc,      0x7fc,      0xffb,
-                             0x1ffd,    0x3ffd,     0x7ffe,     0,
-                             0,         0,          0x7fff2,    0xfffed,
-                             0x1fffe8,  0x3fffeb,   0x7ffff4,   0xfffff5,
-                             0x1ffffef, 0x3ffffee,  0x7fffff0,  0xffffffe,
-                             0,         0x3ffffffe, 0xFFFFFFFFu};
-
-
+/* highest value with 5, 6, 7, ... 28, 29, 30 and all (32) bits */
+uint32_t hpack_huff_end_code[] = {0x9,       0x2d,       0x7b,       0xfd,
+                                  0,         0x3fc,      0x7fc,      0xffb,
+                                  0x1ffd,    0x3ffd,     0x7ffe,     0,
+                                  0,         0,          0x7fff2,    0xfffed,
+                                  0x1fffe8,  0x3fffeb,   0x7ffff4,   0xfffff5,
+                                  0x1ffffef, 0x3ffffee,  0x7fffff0,  0xffffffe,
+                                  0,         0x3ffffffe, 0xFFFFFFFFu};
+
+/* lowest index with 5, 6, 7, ... 28, 29, 30 and all (32) bits */
+uint8_t hpack_huff_start_index[] = {0,   10,  36,  68,  0,   74,  79, 82,  84,
+                                    90,  92,  0,   0,   0,   95,  98, 106, 119,
+                                    145, 174, 186, 190, 205, 224, 0,  253, 0};
+
+
+/* Function to decode an integer from a HPACK encoded block */
+/* Integers have a variable size encoding, according to the RFC.
+ * The integer starts at index *i, idx_mask masks the available bits in
+ * the first byte. The index *i is advanced until the end of the
+ * encoded integer.
+ */
 static uint64_t
 hpack_getnum(const uint8_t *buf,
              int *i,
@@ -636,57 +647,79 @@ hpack_getnum(const uint8_t *buf,
 }
 
 
+/* Function to decode a string from a HPACK encoded block */
+/* Strings have a variable size and can be either encoded directly (8 bits
+ * per char), or using huffman encoding (variable bits per char).
+ * The string starts at index *i. This index is advanced until the end of
+ * the encoded string.
+ */
 static char *
 hpack_decode(const uint8_t *buf, int *i, struct mg_context *ctx)
 {
+	uint64_t byte_len64;
+	int byte_len;
+	int bit_len;
 	uint8_t is_huff = ((buf[*i] & 0x80) == 0x80);
-	uint8_t byte_len = (buf[*i] & 0x7F);
-	int bit_len = (int)(byte_len)*8;
-	int n;
-	int bit = 0;
-	uint32_t accu;
-	uint8_t bc;
-	uint8_t str[256];
-	int stridx = 0;
 
-	buf += (*i) + 1;
+	/* Get length of string in bytes */
+	byte_len64 = hpack_getnum(buf, i, 0x7f, ctx);
+	if (byte_len64 > 1024) {
+		/* TODO */
+		return NULL;
+	}
+	byte_len = (int)byte_len64;
+	bit_len = byte_len * 8;
 
+	/* Now read the string */
 	if (!is_huff) {
 		/* Not huffman encoded: Copy directly */
-		memcpy(str, buf, byte_len);
-		str[byte_len] = 0;
+		char *result = mg_malloc_ctx(byte_len + 1, ctx);
+		if (result) {
+			memcpy(result, buf + (*i), byte_len);
+			result[byte_len] = 0;
+		}
+		(*i) += byte_len;
+		return result;
+
 	} else {
+		/* Huffman encoded: need to decode bitwise */
+		const uint8_t *pData =
+		    buf + (*i);           /* begin pointer of bit input string */
+		int bitRead = 0;          /* number of encoded bits read */
+		uint32_t bytesStored = 0; /* number of decoded bytes stored */
+		uint8_t str[2048];        /* storage buffer for decoded string */
+
 		for (;;) {
-			/* Huffman encoded: need to decode bitwise */
-			accu = 0;
-			bc = 0;
+			uint32_t accu = 0; /* accu register: collect bits */
+			uint8_t bc = 0;    /* number of bits collected */
+			int n;
 
+			/* Collect bits in this loop, until we have a valid huff code in
+			 * accu */
 			do {
 				accu <<= 1;
-				accu |= (buf[bit / 8] >> (7 - (bit & 7))) & 1;
-				bit++;
+				accu |= (pData[bitRead / 8] >> (7 - (bitRead & 7))) & 1;
+				bitRead++;
 				bc++;
-				if (bit > bit_len) {
-					/* break from two loops */
-					goto huff_dec_done;
+				if (bitRead > bit_len) {
+					/* We used all bits. Return the decoded string. */
+					str[bytesStored] = 0; /* Terminate string */
+					(*i) += byte_len;     /* Advance parsing index */
+					return mg_strdup_ctx((char *)str,
+					                     ctx); /* Return a string copy */
 				}
-			} while ((bc < 5) || (accu > hpack_huff_end[bc - 5]));
+			} while ((bc < 5) || (accu > hpack_huff_end_code[bc - 5]));
 
-			for (n = 0 /* TODO startindex */; n < 256; n++) {
+			/* Find matching code in huffman encoding table */
+			for (n = hpack_huff_start_index[bc - 5]; n < 256; n++) {
 				if (accu == hpack_huff_dec[n].encoded) {
-					str[stridx] = hpack_huff_dec[n].decoded;
-					stridx++;
+					str[bytesStored] = hpack_huff_dec[n].decoded;
+					bytesStored++;
 					break;
 				}
 			}
 		}
-	huff_dec_done:
-		str[stridx] = 0;
 	}
-
-	(*i) += byte_len + 1;
-
-	return mg_strdup_ctx((char *)str, ctx);
 }
 
 
@@ -720,7 +753,7 @@ append_bits(uint8_t *target,
 
 
 static int
-hpack_encode(char *store, const char *load, int lower)
+hpack_encode(uint8_t *store, const char *load, int lower)
 {
 	uint32_t nohuff_len = strlen(load);
 
@@ -795,7 +828,7 @@ static unsigned char http2_pri_len = 24; /* = strlen(http2_pri) */
 static int
 is_valid_http2_primer(struct mg_connection *conn)
 {
-	int pri_len = http2_pri_len;
+	size_t pri_len = http2_pri_len;
 	char buf[32];
 
 	if (pri_len > sizeof(buf)) {
@@ -803,7 +836,8 @@ is_valid_http2_primer(struct mg_connection *conn)
 		return 0;
 	}
 	int read_pri_len = mg_read(conn, buf, pri_len);
-	if ((read_pri_len != pri_len) || (0 != memcmp(buf, http2_pri, pri_len))) {
+	if ((read_pri_len != (int)pri_len)
+	    || (0 != memcmp(buf, http2_pri, pri_len))) {
 		return 0;
 	}
 	return 1;
@@ -823,6 +857,8 @@ static void
 http2_settings_acknowledge(struct mg_connection *conn)
 {
 	unsigned char http2_set_ackn_frame[9] = {0, 0, 0, 4, 1, 0, 0, 0, 0};
+
+	DEBUG_TRACE("%s", "Sending settings frame");
 	mg_xwrite(conn, http2_set_ackn_frame, 9);
 }
 
@@ -900,6 +936,8 @@ http2_send_settings(struct mg_connection *conn,
 	data = htonl(set->settings_max_header_list_size);
 	mg_xwrite(conn, &id, 2);
 	mg_xwrite(conn, &data, 4);
+
+	DEBUG_TRACE("%s", "HTTP2 settings sent");
 }
 
 
@@ -1019,11 +1057,12 @@ http2_send_response_headers(struct mg_connection *conn)
 	http2_header_frame[7] = (conn->http2.stream_id & 0xFF00u) >> 8;
 	http2_header_frame[8] = (conn->http2.stream_id & 0xFFu);
 
-
 	/* Send header frame */
 	mg_xwrite(conn, http2_header_frame, 9);
 	mg_xwrite(conn, header_bin, header_len);
 
+	DEBUG_TRACE("HTTP2 response header sent: stream %u", conn->http2.stream_id);
+
 	return 42; /* TODO */
 }
 
@@ -1048,6 +1087,11 @@ http2_data_frame_head(struct mg_connection *conn,
 	http2_data_frame[7] = (stream_id & 0xFF00u) >> 8;
 	http2_data_frame[8] = (stream_id & 0xFFu);
 
+	DEBUG_TRACE("HTTP2 begin data frame: stream %u, frame_size %u (final: %i)",
+	            stream_id,
+	            frame_size,
+	            is_final);
+
 	mg_xwrite(conn, http2_data_frame, 9);
 }
 
@@ -1060,6 +1104,10 @@ http2_send_window(struct mg_connection *conn,
 	unsigned char http2_window_frame[9] = {0, 0, 4, 8, 0, 0, 0, 0, 0};
 	uint32_t data = htonl(window_size);
 
+	DEBUG_TRACE("HTTP2 send window_size: stream %u, error %u",
+	            stream_id,
+	            window_size);
+
 	http2_window_frame[5] = (stream_id & 0xFF000000u) >> 24;
 	http2_window_frame[6] = (stream_id & 0xFF0000u) >> 16;
 	http2_window_frame[7] = (stream_id & 0xFF00u) >> 8;
@@ -1077,6 +1125,8 @@ http2_reset_stream(struct mg_connection *conn,
 	unsigned char http2_reset_frame[9] = {0, 0, 4, 3, 0, 0, 0, 0, 0};
 	uint32_t val = htonl(error_id);
 
+	DEBUG_TRACE("HTTP2 send reset: stream %u, error %u", stream_id, error_id);
+
 	http2_reset_frame[5] = (stream_id & 0xFF000000u) >> 24;
 	http2_reset_frame[6] = (stream_id & 0xFF0000u) >> 16;
 	http2_reset_frame[7] = (stream_id & 0xFF00u) >> 8;
@@ -1089,10 +1139,104 @@ http2_reset_stream(struct mg_connection *conn,
 static void
 http2_must_use_http1(struct mg_connection *conn)
 {
+	DEBUG_TRACE("HTTP2 not available for this URL (%s)", conn->path_info);
 	http2_reset_stream(conn, conn->http2.stream_id, 0xd);
 }
 
 
+/* The HTTP2 implementation collects request headers as array of dynamically
+ * allocated string values. This array must be freed once the request is
+ * handled.
+ * This is different to the HTTP/1.x implementation: For HTTP/1.x, the header
+ * list is implemented as pointers into an existing buffer, so free must not
+ * be called for HTTP/1.x.
+ * Thus free_buffered_request_header_list is in mod_http2.inl.
+ */
+#if defined(DEBUG)
+static int mem_h_count = 0;
+static int mem_d_count = 0;
+#define CHECK_LEAK_HDR_ALLOC(ptr)                                              \
+	DEBUG_TRACE("H NEW %08x (%i): %s",                                         \
+	            (uint32_t)ptr,                                                 \
+	            ++mem_h_count,                                                 \
+	            (const char *)ptr)
+#define CHECK_LEAK_HDR_FREE(ptr)                                               \
+	DEBUG_TRACE("H DEL %08x (%i): %s",                                         \
+	            (uint32_t)ptr,                                                 \
+	            --mem_h_count,                                                 \
+	            (const char *)ptr)
+#define CHECK_LEAK_DYN_ALLOC(ptr)                                              \
+	DEBUG_TRACE("D NEW %08x (%i): %s",                                         \
+	            (uint32_t)ptr,                                                 \
+	            ++mem_d_count,                                                 \
+	            (const char *)ptr)
+#define CHECK_LEAK_DYN_FREE(ptr)                                               \
+	DEBUG_TRACE("D DEL %08x (%i): %s",                                         \
+	            (uint32_t)ptr,                                                 \
+	            --mem_d_count,                                                 \
+	            (const char *)ptr)
+#else
+#define CHECK_LEAK_HDR_ALLOC(ptr)
+#define CHECK_LEAK_HDR_FREE(ptr)
+#define CHECK_LEAK_DYN_ALLOC(ptr)
+#define CHECK_LEAK_DYN_FREE(ptr)
+#endif
+
+
+/* The dynamic header table may be resized on a HTTP2 client request.
+ * A tablesize=0 will free all memory.
+ */
+static void
+purge_dynamic_header_table(struct mg_connection *conn, uint32_t tableSize)
+{
+	DEBUG_TRACE("HTTP2 dynamic header table set to %u", tableSize);
+	while (conn->http2.dyn_table_size > tableSize) {
+		conn->http2.dyn_table_size--;
+
+		CHECK_LEAK_DYN_FREE(
+		    conn->http2.dyn_table[conn->http2.dyn_table_size].name);
+		CHECK_LEAK_DYN_FREE(
+		    conn->http2.dyn_table[conn->http2.dyn_table_size].value);
+
+		mg_free((void *)conn->http2.dyn_table[conn->http2.dyn_table_size].name);
+		conn->http2.dyn_table[conn->http2.dyn_table_size].name = 0;
+		mg_free(
+		    (void *)conn->http2.dyn_table[conn->http2.dyn_table_size].value);
+		conn->http2.dyn_table[conn->http2.dyn_table_size].value = 0;
+	}
+}
+
+
+/* Internal function to free request header list.
+ * Not to be confused with the response header list.
+ */
+static void
+free_buffered_request_header_list(struct mg_connection *conn)
+{
+	while (conn->request_info.num_headers > 0) {
+		conn->request_info.num_headers--;
+
+		CHECK_LEAK_HDR_FREE(
+		    conn->request_info.http_headers[conn->request_info.num_headers]
+		        .name);
+		CHECK_LEAK_HDR_FREE(
+		    conn->request_info.http_headers[conn->request_info.num_headers]
+		        .value);
+
+		mg_free((void *)conn->request_info
+		            .http_headers[conn->request_info.num_headers]
+		            .name);
+		conn->request_info.http_headers[conn->request_info.num_headers].name =
+		    0;
+		mg_free((void *)conn->request_info
+		            .http_headers[conn->request_info.num_headers]
+		            .value);
+		conn->request_info.http_headers[conn->request_info.num_headers].value =
+		    0;
+	}
+}
+
+
 /* HTTP2 requires a different handling loop */
 static void
 handle_http2(struct mg_connection *conn)
@@ -1127,6 +1271,7 @@ handle_http2(struct mg_connection *conn)
 	                               conn->phys_ctx);
 	if (!buf) {
 		/* Out of memory */
+		DEBUG_TRACE("%s", "Out of memory for HTTP2 frame");
 		return;
 	}
 
@@ -1161,27 +1306,38 @@ handle_http2(struct mg_connection *conn)
 
 		if (http2_frame_size > server_settings.settings_max_frame_size) {
 			/* TODO: Error Message */
+			DEBUG_TRACE("HTTP2 frame too large (%lu)",
+			            (unsigned long)http2_frame_size);
 			goto clean_http2;
 		}
 		bytes_read = mg_read(conn, buf, http2_frame_size);
-		if (bytes_read != http2_frame_size) {
+		if (bytes_read != (int)http2_frame_size) {
 			/* TODO: Error Message - or read again? */
+			DEBUG_TRACE("HTTP2 read error (%li != %li)",
+			            (signed long int)bytes_read,
+			            (signed long int)http2_frame_size);
 			goto clean_http2;
 		}
 
+		DEBUG_TRACE("HTTP2 frame type %u, size %u, stream %u, flags %02x",
+		            http2_frame_type,
+		            http2_frame_size,
+		            http2_frame_stream_id,
+		            http2_frame_flags);
+
 		/* Further processing according to frame type. See definition: */
 		/* https://tools.ietf.org/html/rfc7540#section-6 */
 		switch (http2_frame_type) {
 
 		case 0: /* DATA */
 		{
-			int i = 0;
+			int i = 0; /* TODO */
+			DEBUG_TRACE("%s", "HTTP2 DATA frame?");
 		} break;
 
 		case 1: /* HEADERS */
 		{
 			int i = 0;
-			int hbytes = 0;
 			uint8_t padding = 0;
 			uint32_t dependency = 0;
 			uint8_t weight = 0;
@@ -1190,6 +1346,7 @@ handle_http2(struct mg_connection *conn)
 			if (frame_is_padded) {
 				padding = buf[i];
 				i++;
+				DEBUG_TRACE("HTTP2 frame padded by %u bytes", padding);
 			}
 			if (frame_is_priority) {
 				uint32_t val = ((uint32_t)buf[0 + i] * 0x1000000u)
@@ -1200,6 +1357,11 @@ handle_http2(struct mg_connection *conn)
 				exclusive = ((val & 0x80000000u) != 0);
 				weight = buf[4 + i];
 				i += 5;
+				DEBUG_TRACE(
+				    "HTTP2 frame weight %u, dependency %u (exclusive: %i)",
+				    weight,
+				    dependency,
+				    exclusive);
 			}
 
 			conn->request_info.num_headers = 0;
@@ -1212,76 +1374,76 @@ handle_http2(struct mg_connection *conn)
 				uint8_t indexing = 0;
 				uint64_t idx = 0;
 
-				OutputDebugString("\n");
-
 				/* Classify next entry by checking the bit mask */
 				if ((buf[i] & 0x80u) == 0x80u) {
 					/* Indexed Header Field Representation:
 					 * https://tools.ietf.org/html/rfc7541#section-6.1 */
 					idx_mask = 0x7fu;
 					value_known = 1;
-					OutputDebugString("IHR ");
+
 				} else if ((buf[i] & 0xC0u) == 0x40u) {
 					/* Literal Header Field with Incremental Indexing:
 					 * https://tools.ietf.org/html/rfc7541#section-6.2.1 */
 					idx_mask = 0x3fu;
 					indexing = 1;
-					OutputDebugString("LHII ");
+
 				} else if ((buf[i] & 0xF0u) == 0x00u) {
 					/* Literal Header Field without Indexing:
 					 * https://tools.ietf.org/html/rfc7541#section-6.2.2 */
 					idx_mask = 0x0fu;
-					OutputDebugString("LHNI ");
+
 				} else if ((buf[i] & 0xF0u) == 0x10u) {
 					/* Literal Header Field Never Indexed:
 					 * https://tools.ietf.org/html/rfc7541#section-6.2.3 */
 					idx_mask = 0x0fu;
-					OutputDebugString("LHXI ");
+
 				} else if ((buf[i] & 0xE0u) == 0x20u) {
 					uint64_t tableSize;
 					/* Dynamic Table Size Update:
 					 * https://tools.ietf.org/html/rfc7541#section-6.3 */
 					idx_mask = 0x1fu;
 					tableSize = hpack_getnum(buf, &i, idx_mask, conn->phys_ctx);
-					OutputDebugString("DynTSU ");
+
+					/* TODO: check if tablesize > allowed table size */
 
 					/* Purge additional table entries */
-					while (conn->http2.dyn_table_size > tableSize) {
-						conn->http2.dyn_table_size--;
-						mg_free((void *)conn->http2
-						            .dyn_table[conn->http2.dyn_table_size]
-						            .name);
-						mg_free((void *)conn->http2
-						            .dyn_table[conn->http2.dyn_table_size]
-						            .value);
-					}
+					purge_dynamic_header_table(conn, (uint32_t)tableSize);
 
+					/* Process next frame */
 					continue;
 
 				} else {
-					OutputDebugString("unknown\n");
-					return;
+					DEBUG_TRACE("HTTP2 unknown start pattern %02x", buf[i]);
+					goto clean_http2;
 				}
 
 				/* Get the header name table index */
 				idx = hpack_getnum(buf, &i, idx_mask, conn->phys_ctx);
 
-				/* Index 0: Header name follows */
+				/* Get Header name "key" */
 				if (idx == 0) {
+					/* Index 0: Header name encoded in following bytes */
 					key = hpack_decode(buf, &i, conn->phys_ctx);
+					CHECK_LEAK_HDR_ALLOC(key);
 				} else if (/*(idx >= 15) &&*/ (idx <= 61)) {
+					/* Take key name from predefined header table */
 					key = mg_strdup_ctx(hpack_predefined[idx].name,
-					                    conn->phys_ctx);
+					                    conn->phys_ctx); /* leak? */
+					CHECK_LEAK_HDR_ALLOC(key);
 				} else if ((idx >= 62)
 				           && ((idx - 61) <= conn->http2.dyn_table_size)) {
+					/* Take from dynamic header table */
 					uint32_t local_table_idx = (uint32_t)idx - 62;
 					key = mg_strdup_ctx(
 					    conn->http2.dyn_table[local_table_idx].name,
 					    conn->phys_ctx);
+					CHECK_LEAK_HDR_ALLOC(key);
 				} else {
 					/* protocol violation */
-					return;
+					DEBUG_TRACE("HTTP2 invalid index %lu", (unsigned long)idx);
+					goto clean_http2;
 				}
+				/* key is allocated now and must be freed later */
 
 				/* Get header value */
 				if (value_known) {
@@ -1289,10 +1451,17 @@ handle_http2(struct mg_connection *conn)
 					if (idx <= 61) {
 						if (hpack_predefined[idx].value) {
 							val = mg_strdup_ctx(hpack_predefined[idx].value,
-							                    conn->phys_ctx);
+							                    conn->phys_ctx); /* leak? */
+							CHECK_LEAK_HDR_ALLOC(val);
 						} else {
 							/* protocol violation */
-							return;
+							DEBUG_TRACE("HTTP2 indexed header %lu has no value "
+							            "(key: %s)",
+							            (unsigned long)idx,
+							            key);
+							CHECK_LEAK_HDR_FREE(key);
+							mg_free((void *)key);
+							goto clean_http2;
 						}
 					} else if ((idx >= 62)
 					           && ((idx - 61) <= conn->http2.dyn_table_size)) {
@@ -1300,31 +1469,67 @@ handle_http2(struct mg_connection *conn)
 						val = mg_strdup_ctx(
 						    conn->http2.dyn_table[local_table_idx].value,
 						    conn->phys_ctx);
+						CHECK_LEAK_HDR_ALLOC(val);
 					} else {
 						/* protocol violation */
-						return;
+						DEBUG_TRACE(
+						    "HTTP2 indexed header %lu out of range (key: %s)",
+						    (unsigned long)idx,
+						    key);
+						CHECK_LEAK_HDR_FREE(key);
+						mg_free((void *)key);
+						goto clean_http2;
 					}
 
 				} else {
 					/* Read value from HTTP2 stream */
-					val = hpack_decode(buf, &i, conn->phys_ctx);
+					val = hpack_decode(buf, &i, conn->phys_ctx); /* leak? */
+					CHECK_LEAK_HDR_ALLOC(val);
 
 					if (indexing) {
 						/* Add to index */
 						if (conn->http2.dyn_table_size
 						    >= HTTP2_DYN_TABLE_SIZE) {
 							/* Too many elements */
-							return;
+							DEBUG_TRACE("HTTP2 index table is full (key: %s, "
+							            "value: %s)",
+							            key,
+							            val);
+
+							CHECK_LEAK_HDR_FREE(key);
+							CHECK_LEAK_HDR_FREE(val);
+
+							mg_free((void *)key);
+							mg_free((void *)val);
+							goto clean_http2;
 						}
 
 						/* Add to table of dynamic headers */
 						conn->http2.dyn_table[conn->http2.dyn_table_size].name =
-						    mg_strdup_ctx(key, conn->phys_ctx);
+						    mg_strdup_ctx(key, conn->phys_ctx); /* leak */
 						conn->http2.dyn_table[conn->http2.dyn_table_size]
-						    .value = mg_strdup_ctx(val, conn->phys_ctx);
+						    .value =
+						    mg_strdup_ctx(val, conn->phys_ctx); /* leak */
+
+						CHECK_LEAK_DYN_ALLOC(
+						    conn->http2.dyn_table[conn->http2.dyn_table_size]
+						        .name);
+						CHECK_LEAK_DYN_ALLOC(
+						    conn->http2.dyn_table[conn->http2.dyn_table_size]
+						        .value);
+
 						conn->http2.dyn_table_size++;
+
+						DEBUG_TRACE("HTTP2 new dynamic header table entry %i "
+						            "(key: %s, value: %s)",
+						            (int)conn->http2.dyn_table_size,
+						            key,
+						            val);
 					}
 				}
+				/* val and key are allocated now and must be freed later */
+				/* Store these pointers in conn->request_info[].http_headers,
+				 * free_buffered_header_list(conn) will clean up later. */
 
 				/* Add header for this request */
 				if ((key != NULL) && (val != NULL)
@@ -1348,11 +1553,19 @@ handle_http2(struct mg_connection *conn)
 						conn->status_code = atoi(val);
 					}
 
+					DEBUG_TRACE("HTTP2 request header (key: %s, value: %s)",
+					            key,
+					            val);
+
 				} else {
 					/* - either key or value are NULL (out of memory)
 					 * - or the max. number of headers is reached
 					 * in both cases free all memory
 					 */
+					DEBUG_TRACE("%s", "HTTP2 cannot add header");
+					CHECK_LEAK_HDR_FREE(key);
+					CHECK_LEAK_HDR_FREE(val);
+
 					mg_free((void *)key);
 					key = NULL;
 					mg_free((void *)val);
@@ -1364,10 +1577,16 @@ handle_http2(struct mg_connection *conn)
 			conn->http2.stream_id = http2_frame_stream_id;
 
 			/* header parsed */
+			DEBUG_TRACE("HTTP2 handle_request (stream %u)",
+			            http2_frame_stream_id);
 			handle_request(conn);
 
 			/* Send "final" frame */
+			DEBUG_TRACE("HTTP2 handle_request done (stream %u)",
+			            http2_frame_stream_id);
 			http2_data_frame_head(conn, 0, 1);
+			free_buffered_response_header_list(conn);
+			free_buffered_request_header_list(conn);
 		} break;
 
 		case 2: /* PRIORITY */
@@ -1376,6 +1595,9 @@ handle_http2(struct mg_connection *conn)
 			    ((uint32_t)buf[0] * 0x1000000u) + ((uint32_t)buf[1] * 0x10000u)
 			    + ((uint32_t)buf[2] * 0x100u) + ((uint32_t)buf[3]);
 			uint8_t weight = buf[4];
+			DEBUG_TRACE("HTTP2 priority %u dependent stream %u",
+			            weight,
+			            dependStream);
 		} break;
 
 		case 3: /* RST_STREAM */
@@ -1383,7 +1605,7 @@ handle_http2(struct mg_connection *conn)
 			uint32_t errorId =
 			    ((uint32_t)buf[0] * 0x1000000u) + ((uint32_t)buf[1] * 0x10000u)
 			    + ((uint32_t)buf[2] * 0x100u) + ((uint32_t)buf[3]);
-
+			DEBUG_TRACE("HTTP2 reset with error %u", errorId);
 		} break;
 
 		case 4: /* SETTINGS */
@@ -1392,9 +1614,11 @@ handle_http2(struct mg_connection *conn)
 				http2_reset_stream(conn,
 				                   http2_frame_stream_id,
 				                   HTTP2_ERR_PROTOCOL_ERROR);
+				DEBUG_TRACE("%s", "HTTP2 received invalid settings frame");
 			} else if (http2_frame_flags) {
 				/* ACK frame. Do not reply. */
 				my_settings_accepted++;
+				DEBUG_TRACE("%s", "CivetWeb settings confirmed by peer");
 			} else {
 				int i;
 				for (i = 0; i < (int)http2_frame_size; i += 6) {
@@ -1407,24 +1631,39 @@ handle_http2(struct mg_connection *conn)
 					switch (id) {
 					case 1:
 						client_settings.settings_header_table_size = val;
+						DEBUG_TRACE("Received settings header_table_size: %u",
+						            val);
 						break;
 					case 2:
 						client_settings.settings_enable_push = (val != 0);
+						DEBUG_TRACE("Received settings enable_push: %u", val);
 						break;
 					case 3:
 						client_settings.settings_max_concurrent_streams = val;
+						DEBUG_TRACE(
+						    "Received settings max_concurrent_streams: %u",
+						    val);
 						break;
 					case 4:
 						client_settings.settings_initial_window_size = val;
+						DEBUG_TRACE("Received settings initial_window_size: %u",
+						            val);
 						break;
 					case 5:
 						client_settings.settings_max_frame_size = val;
+						DEBUG_TRACE("Received settings max_frame_size: %u",
+						            val);
 						break;
 					case 6:
 						client_settings.settings_max_header_list_size = val;
+						DEBUG_TRACE(
+						    "Received settings max_header_list_size: %u", val);
 						break;
 					default:
-						/* ignore ??? */
+						/* Unknown setting. Ignore it. */
+						DEBUG_TRACE("Received unknown settings id=%u: %u",
+						            id,
+						            val);
 						break;
 					}
 				}
@@ -1435,11 +1674,13 @@ handle_http2(struct mg_connection *conn)
 			break;
 
 		case 5: /* PUSH_PROMISE */
+			DEBUG_TRACE("%s", "Push promise not supported");
 			break;
 
 		case 6: /* PING */
 			if (http2_frame_flags == 0) {
 				/* Set "reply" flag, and send same data back */
+				DEBUG_TRACE("%s", "Replying to ping");
 				http2_frame_head[4] = 1;
 				mg_xwrite(conn, http2_frame_head, sizeof(http2_frame_head));
 				mg_xwrite(conn, buf, http2_frame_size);
@@ -1457,6 +1698,13 @@ handle_http2(struct mg_connection *conn)
 			; /* followed by debug data */
 			uint32_t debugDataLen = http2_frame_size - 8;
 			char *debugData = (char *)buf + 8;
+
+			DEBUG_TRACE("HTTP2 goaway stream %u, error %u (%.*s)",
+			            lastStream,
+			            errorId,
+			            debugDataLen,
+			            debugData);
+
 		} break;
 
 		case 8: /* WINDOW_UPDATE */
@@ -1465,22 +1713,30 @@ handle_http2(struct mg_connection *conn)
 			               + ((uint32_t)buf[1] * 0x10000u)
 			               + ((uint32_t)buf[2] * 0x100u) + ((uint32_t)buf[3]);
 			http_window_length = (val & 0x7FFFFFFFu);
+
+			DEBUG_TRACE("HTTP2 window update stream %u, length %u",
+			            http2_frame_stream_id,
+			            http_window_length);
 		} break;
 
 		case 9: /* CONTINUATION */
+			DEBUG_TRACE("%s", "HTTP2 Continue");
 			break;
 
 		default:
 			/* TODO: Error Message */
+			DEBUG_TRACE("%s", "Unknown frame type");
 			goto clean_http2;
 		}
 	}
 
 clean_http2:
+	DEBUG_TRACE("%s", "HTTP2 free buffer, connection handler finished");
 	mg_free(buf);
 }
 
 
+#if 0
 static void
 HPACK_TEST()
 {
@@ -1505,6 +1761,72 @@ HPACK_TEST()
 	}
 }
 
+static void
+HPACK_TABLE_TEST()
+{
+	int i;
+
+	uint32_t hpack_huff_end_code_expected[32] = { 0 };
+	uint8_t hpack_huff_start_index_expected[32] = { 0 };
+	int reverse_map[256] = { 0 };
+
+	for (i = 0; i < 256; i++) {
+		reverse_map[i] = -1;
+	}
+
+	for (i = 0; i < 256; i++) {
+		uint8_t bits = hpack_huff_dec[i].bitcount;
+		uint8_t dec = hpack_huff_dec[i].decoded;
+		if (bits > hpack_huff_dec[i + 1].bitcount) {
+			ck_abort_msg("hpack_huff_dec disorder at index %i", i);
+		}
+		if (hpack_huff_dec[i].encoded & (0xFFFFFFFFul << bits)) {
+			ck_abort_msg("hpack_huff_dec bits inconsistent at index %i", i);
+		}
+		if ((bits < 5) || (bits > 30)) {
+			ck_abort_msg("hpack_huff_dec bits out of range at index %i", i);
+		}
+		if (reverse_map[dec] != -1) {
+			ck_abort_msg("hpack_huff_dec duplicate: %i", hpack_huff_dec[i].decoded);
+		}
+		reverse_map[dec] = i;
+
+		hpack_huff_end_code_expected[bits - 5] = hpack_huff_dec[i].encoded;
+	}
+
+	for (i = 255; i >= 0; i--) {
+		uint8_t bits = hpack_huff_dec[i].bitcount;
+		hpack_huff_start_index_expected[bits - 5] = i;
+	}
+
+	for (i = 0; i < 256; i++) {
+		if (reverse_map[i] == -1) {
+			ck_abort_msg("reverse map at %i mising", i);
+		}
+	}
+
+	i = sizeof(hpack_huff_start_index) / sizeof(hpack_huff_start_index[0]);
+	if (i != 27) {
+		ck_abort_msg("hpack_huff_start_index size error: ", i);
+	}
+
+	i = sizeof(hpack_huff_end_code) / sizeof(hpack_huff_end_code[0]);
+	if (i != 27) {
+		ck_abort_msg("hpack_huff_end_code size error: ", i);
+	}
+
+	for (i = 0; i < 27; i++) {
+		if (hpack_huff_start_index_expected[i] != hpack_huff_start_index[i]) {
+			ck_abort_msg("hpack_huff_start_index error at %i", i);
+		}
+		if (hpack_huff_end_code_expected[i] != hpack_huff_end_code[i]) {
+			ck_abort_msg("hpack_huff_end_code error at %i", i);
+		}
+	}
+
+}
+#endif
+
 
 static void
 process_new_http2_connection(struct mg_connection *conn)
@@ -1512,9 +1834,18 @@ process_new_http2_connection(struct mg_connection *conn)
 	if (!is_valid_http2_primer(conn)) {
 		/* Primer does not match expectation from RFC.
 		 * See https://tools.ietf.org/html/rfc7540#section-3.5 */
+		DEBUG_TRACE("%s", "No valid HTTP2 primer");
 		mg_send_http_error(conn, 400, "%s", "Invalid HTTP/2 primer");
+
 	} else {
 		/* Valid HTTP/2 primer received */
+		DEBUG_TRACE("%s", "Start handling HTTP2");
 		handle_http2(conn);
+
+		/* Free memory allocated for headers, if not done yet */
+		DEBUG_TRACE("%s", "Free remaining HTTP2 header memory");
+		free_buffered_response_header_list(conn);
+		free_buffered_request_header_list(conn);
+		purge_dynamic_header_table(conn, 0);
 	}
 }

+ 271 - 278
src/response.inl

@@ -1,278 +1,271 @@
-/* response.inl
- *
- * Bufferring for HTTP headers for HTTP response.
- * This function are only intended to be used at the server side.
- * Optional for HTTP/1.0 and HTTP/1.1, mandatory for HTTP/2.
- *
- * This file is part of the CivetWeb project.
- */
-
-
-/* Internal function to free header list */
-static void
-free_buffered_header_list(struct mg_connection *conn)
-{
-	while (conn->response_info.num_headers > 0) {
-		conn->response_info.num_headers--;
-		mg_free((void *)conn->response_info
-		            .http_headers[conn->response_info.num_headers]
-		            .name);
-		conn->response_info.http_headers[conn->response_info.num_headers].name =
-		    0;
-		mg_free((void *)conn->response_info
-		            .http_headers[conn->response_info.num_headers]
-		            .value);
-		conn->response_info.http_headers[conn->response_info.num_headers]
-		    .value = 0;
-	}
-}
-
-
-/* Initialize a new HTTP response
- * Parameters:
- *   conn: Current connection handle.
- *   status: HTTP status code (e.g., 200 for "OK").
- * Return:
- *   0:    ok
- *  -1:    parameter error
- *  -2:    invalid connection type
- *  -3:    invalid connection status
- */
-int
-mg_response_start(struct mg_connection *conn, int status)
-{
-	if ((conn == NULL) || (status < 100) || (status > 999)) {
-		/* Parameter error */
-		return -1;
-	}
-	if ((conn->connection_type != CONNECTION_TYPE_REQUEST)
-	    || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) {
-		/* Only allowed in server context */
-		return -2;
-	}
-	if (conn->request_state != 0) {
-		/* only allowed if nothing was sent up to now */
-		return -3;
-	}
-	conn->status_code = status;
-	conn->request_state = 1;
-	free_buffered_header_list(conn);
-
-	return 0;
-}
-
-
-/* Add a new HTTP response header line
- * Parameters:
- *   conn: Current connection handle.
- *   header: Header name.
- *   value: Header value.
- *   value_len: Length of header value, excluding the terminating zero. Use -1
- * for "strlen(value)". Return: 0:    ok -1:    parameter error -2:    invalid
- * connection type -3:    invalid connection status -4:    too many headers -5:
- * out of memory
- */
-int
-mg_response_add_header(struct mg_connection *conn,
-                       const char *header,
-                       const char *value,
-                       int value_len)
-{
-	if ((conn == NULL) || (header == NULL) || (value == NULL)) {
-		/* Parameter error */
-		return -1;
-	}
-	if ((conn->connection_type != CONNECTION_TYPE_REQUEST)
-	    || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) {
-		/* Only allowed in server context */
-		return -2;
-	}
-	if (conn->request_state != 1) {
-		/* only allowed if mg_response_start was called */
-		return -3;
-	}
-
-	if (conn->response_info.num_headers >= MG_MAX_HEADERS) {
-		/* Too many headers */
-		return -4;
-	}
-
-	/* Alloc new element */
-	conn->response_info.http_headers[conn->response_info.num_headers].name =
-	    mg_strdup_ctx(header, conn->phys_ctx);
-	if (value_len >= 0) {
-		char *hbuf = mg_malloc_ctx(value_len + 1, conn->phys_ctx);
-		if (hbuf) {
-			memcpy(hbuf, value, value_len);
-			hbuf[value_len] = 0;
-		}
-		conn->response_info.http_headers[conn->response_info.num_headers]
-		    .value = hbuf;
-	} else {
-		conn->response_info.http_headers[conn->response_info.num_headers]
-		    .value = mg_strdup_ctx(value, conn->phys_ctx);
-	}
-
-	if ((conn->response_info.http_headers[conn->response_info.num_headers].name
-	     == 0)
-	    || (conn->response_info.http_headers[conn->response_info.num_headers]
-	            .value
-	        == 0)) {
-		/* Out of memory */
-		mg_free((void *)conn->response_info
-		            .http_headers[conn->response_info.num_headers]
-		            .name);
-		conn->response_info.http_headers[conn->response_info.num_headers].name =
-		    0;
-		mg_free((void *)conn->response_info
-		            .http_headers[conn->response_info.num_headers]
-		            .value);
-		conn->response_info.http_headers[conn->response_info.num_headers]
-		    .value = 0;
-		return -5;
-	}
-
-	/* OK, header stored */
-	conn->response_info.num_headers++;
-	return 0;
-}
-
-
-/* forward */
-static int parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS]);
-
-
-/* Add a complete header string (key + value).
- * Parameters:
- *   conn: Current connection handle.
- *   http1_headers: Header line(s) in the form "name: value".
- * Return:
- *  >=0:   no error, number of header lines added
- *  -1:    parameter error
- *  -2:    invalid connection type
- *  -3:    invalid connection status
- *  -4:    too many headers
- *  -5:    out of memory
- */
-int
-mg_response_add_headerlines(struct mg_connection *conn,
-                            const char *http1_headers)
-{
-	struct mg_header add_hdr[MG_MAX_HEADERS];
-	int num_hdr, i, ret;
-	char *workbuffer, *parse;
-
-	/* We need to work on a copy of the work buffer, sice parse_http_headers
-	 * will modify */
-	workbuffer = mg_strdup_ctx(http1_headers, conn->phys_ctx);
-	if (!workbuffer) {
-		/* Out of memory */
-		return -5;
-	}
-
-	/* Call existing method to split header buffer */
-	parse = workbuffer;
-	num_hdr = parse_http_headers(&parse, add_hdr);
-	ret = num_hdr;
-
-	for (i = 0; i < num_hdr; i++) {
-		int lret =
-		    mg_response_add_header(conn, add_hdr[i].name, add_hdr[i].value, -1);
-		if ((ret > 0) && (lret < 0)) {
-			/* Store error return value */
-			ret = lret;
-		}
-	}
-
-	/* mg_response_add_header created a copy, so we can free the original */
-	mg_free(workbuffer);
-	return ret;
-}
-
-
-#if defined USE_HTTP2
-static int http2_send_response_headers(struct mg_connection *conn);
-#endif
-
-
-/* Send http response
- * Parameters:
- *   conn: Current connection handle.
- * Return:
- *   0:    ok
- *  -1:    parameter error
- *  -2:    invalid connection type
- *  -3:    invalid connection status
- */
-int
-mg_response_send_headers(struct mg_connection *conn)
-{
-	const char *txt;
-	int i;
-	int has_date = 0;
-	int has_connection = 0;
-
-	if (conn == NULL) {
-		/* Parameter error */
-		return -1;
-	}
-	if ((conn->connection_type != CONNECTION_TYPE_REQUEST)
-	    || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) {
-		/* Only allowed in server context */
-		return -2;
-	}
-	if (conn->request_state != 1) {
-		/* only allowed if mg_response_start was called */
-		return -3;
-	}
-
-	/* State: 2 */
-	conn->request_state = 2;
-	if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) {
-#if defined USE_HTTP2
-		int ret = http2_send_response_headers(conn);
-		return ret ? 0 : 0; /* todo */
-#else
-		return -2;
-#endif
-	}
-
-	/* Send */
-	txt = mg_get_response_code_text(conn, conn->status_code);
-	mg_printf(conn,
-	          "HTTP/%s %i %s\r\n",
-	          conn->request_info.http_version,
-	          conn->status_code,
-	          txt);
-	for (i = 0; i < conn->response_info.num_headers; i++) {
-		mg_printf(conn,
-		          "%s: %s\r\n",
-		          conn->response_info.http_headers[i].name,
-		          conn->response_info.http_headers[i].value);
-
-		/* Check for some special headers */
-		if (!mg_strcasecmp("Date", conn->response_info.http_headers[i].name)) {
-			has_date = 1;
-		}
-		if (!mg_strcasecmp("Connection",
-		                   conn->response_info.http_headers[i].name)) {
-			has_connection = 1;
-		}
-	}
-
-	if (!has_date) {
-		time_t curtime = time(NULL);
-		char date[64];
-		gmt_time_string(date, sizeof(date), &curtime);
-		mg_printf(conn, "Date: %s\r\n", date);
-	}
-	if (!has_connection) {
-		mg_printf(conn, "Connection: %s\r\n", suggest_connection_header(conn));
-	}
-
-	mg_write(conn, "\r\n", 2);
-	conn->request_state = 3;
-
-	/* ok */
-	return 0;
-}
+/* response.inl
+ *
+ * Bufferring for HTTP headers for HTTP response.
+ * This function are only intended to be used at the server side.
+ * Optional for HTTP/1.0 and HTTP/1.1, mandatory for HTTP/2.
+ *
+ * This file is part of the CivetWeb project.
+ */
+
+
+/* Internal function to free header list */
+static void
+free_buffered_response_header_list(struct mg_connection *conn)
+{
+	while (conn->response_info.num_headers > 0) {
+		conn->response_info.num_headers--;
+		mg_free((void *)conn->response_info
+		            .http_headers[conn->response_info.num_headers]
+		            .name);
+		conn->response_info.http_headers[conn->response_info.num_headers].name =
+		    0;
+		mg_free((void *)conn->response_info
+		            .http_headers[conn->response_info.num_headers]
+		            .value);
+		conn->response_info.http_headers[conn->response_info.num_headers]
+		    .value = 0;
+	}
+}
+
+
+/* Initialize a new HTTP response
+ * Parameters:
+ *   conn: Current connection handle.
+ *   status: HTTP status code (e.g., 200 for "OK").
+ * Return:
+ *   0:    ok
+ *  -1:    parameter error
+ *  -2:    invalid connection type
+ *  -3:    invalid connection status
+ */
+int
+mg_response_start(struct mg_connection *conn, int status)
+{
+	if ((conn == NULL) || (status < 100) || (status > 999)) {
+		/* Parameter error */
+		return -1;
+	}
+	if ((conn->connection_type != CONNECTION_TYPE_REQUEST)
+	    || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) {
+		/* Only allowed in server context */
+		return -2;
+	}
+	if (conn->request_state != 0) {
+		/* only allowed if nothing was sent up to now */
+		return -3;
+	}
+	conn->status_code = status;
+	conn->request_state = 1;
+	free_buffered_response_header_list(conn);
+
+	return 0;
+}
+
+
+/* Add a new HTTP response header line
+ * Parameters:
+ *   conn: Current connection handle.
+ *   header: Header name.
+ *   value: Header value.
+ *   value_len: Length of header value, excluding the terminating zero. Use -1
+ * for "strlen(value)". Return: 0:    ok -1:    parameter error -2:    invalid
+ * connection type -3:    invalid connection status -4:    too many headers -5:
+ * out of memory
+ */
+int
+mg_response_add_header(struct mg_connection *conn,
+                       const char *header,
+                       const char *value,
+                       int value_len)
+{
+	int hidx;
+
+	if ((conn == NULL) || (header == NULL) || (value == NULL)) {
+		/* Parameter error */
+		return -1;
+	}
+	if ((conn->connection_type != CONNECTION_TYPE_REQUEST)
+	    || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) {
+		/* Only allowed in server context */
+		return -2;
+	}
+	if (conn->request_state != 1) {
+		/* only allowed if mg_response_start was called */
+		return -3;
+	}
+
+	hidx = conn->response_info.num_headers;
+	if (hidx >= MG_MAX_HEADERS) {
+		/* Too many headers */
+		return -4;
+	}
+
+	/* Alloc new element */
+	conn->response_info.http_headers[hidx].name =
+	    mg_strdup_ctx(header, conn->phys_ctx);
+	if (value_len >= 0) {
+		char *hbuf = mg_malloc_ctx(value_len + 1, conn->phys_ctx);
+		if (hbuf) {
+			memcpy(hbuf, value, value_len);
+			hbuf[value_len] = 0;
+		}
+		conn->response_info.http_headers[hidx].value = hbuf;
+	} else {
+		conn->response_info.http_headers[hidx].value =
+		    mg_strdup_ctx(value, conn->phys_ctx);
+	}
+
+	if ((conn->response_info.http_headers[hidx].name == 0)
+	    || (conn->response_info.http_headers[hidx].value == 0)) {
+		/* Out of memory */
+		mg_free((void *)conn->response_info.http_headers[hidx].name);
+		conn->response_info.http_headers[hidx].name = 0;
+		mg_free((void *)conn->response_info.http_headers[hidx].value);
+		conn->response_info.http_headers[hidx].value = 0;
+		return -5;
+	}
+
+	/* OK, header stored */
+	conn->response_info.num_headers++;
+	return 0;
+}
+
+
+/* forward */
+static int parse_http_headers(char **buf, struct mg_header hdr[MG_MAX_HEADERS]);
+
+
+/* Add a complete header string (key + value).
+ * Parameters:
+ *   conn: Current connection handle.
+ *   http1_headers: Header line(s) in the form "name: value".
+ * Return:
+ *  >=0:   no error, number of header lines added
+ *  -1:    parameter error
+ *  -2:    invalid connection type
+ *  -3:    invalid connection status
+ *  -4:    too many headers
+ *  -5:    out of memory
+ */
+int
+mg_response_add_headerlines(struct mg_connection *conn,
+                            const char *http1_headers)
+{
+	struct mg_header add_hdr[MG_MAX_HEADERS];
+	int num_hdr, i, ret;
+	char *workbuffer, *parse;
+
+	/* We need to work on a copy of the work buffer, sice parse_http_headers
+	 * will modify */
+	workbuffer = mg_strdup_ctx(http1_headers, conn->phys_ctx);
+	if (!workbuffer) {
+		/* Out of memory */
+		return -5;
+	}
+
+	/* Call existing method to split header buffer */
+	parse = workbuffer;
+	num_hdr = parse_http_headers(&parse, add_hdr);
+	ret = num_hdr;
+
+	for (i = 0; i < num_hdr; i++) {
+		int lret =
+		    mg_response_add_header(conn, add_hdr[i].name, add_hdr[i].value, -1);
+		if ((ret > 0) && (lret < 0)) {
+			/* Store error return value */
+			ret = lret;
+		}
+	}
+
+	/* mg_response_add_header created a copy, so we can free the original */
+	mg_free(workbuffer);
+	return ret;
+}
+
+
+#if defined USE_HTTP2
+static int http2_send_response_headers(struct mg_connection *conn);
+#endif
+
+
+/* Send http response
+ * Parameters:
+ *   conn: Current connection handle.
+ * Return:
+ *   0:    ok
+ *  -1:    parameter error
+ *  -2:    invalid connection type
+ *  -3:    invalid connection status
+ */
+int
+mg_response_send_headers(struct mg_connection *conn)
+{
+	const char *txt;
+	int i;
+	int has_date = 0;
+	int has_connection = 0;
+
+	if (conn == NULL) {
+		/* Parameter error */
+		return -1;
+	}
+	if ((conn->connection_type != CONNECTION_TYPE_REQUEST)
+	    || (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)) {
+		/* Only allowed in server context */
+		return -2;
+	}
+	if (conn->request_state != 1) {
+		/* only allowed if mg_response_start was called */
+		return -3;
+	}
+
+	/* State: 2 */
+	conn->request_state = 2;
+	if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) {
+#if defined USE_HTTP2
+		int ret = http2_send_response_headers(conn);
+		return ret ? 0 : 0; /* todo */
+#else
+		return -2;
+#endif
+	}
+
+	/* Send */
+	txt = mg_get_response_code_text(conn, conn->status_code);
+	mg_printf(conn,
+	          "HTTP/%s %i %s\r\n",
+	          conn->request_info.http_version,
+	          conn->status_code,
+	          txt);
+	for (i = 0; i < conn->response_info.num_headers; i++) {
+		mg_printf(conn,
+		          "%s: %s\r\n",
+		          conn->response_info.http_headers[i].name,
+		          conn->response_info.http_headers[i].value);
+
+		/* Check for some special headers */
+		if (!mg_strcasecmp("Date", conn->response_info.http_headers[i].name)) {
+			has_date = 1;
+		}
+		if (!mg_strcasecmp("Connection",
+		                   conn->response_info.http_headers[i].name)) {
+			has_connection = 1;
+		}
+	}
+
+	if (!has_date) {
+		time_t curtime = time(NULL);
+		char date[64];
+		gmt_time_string(date, sizeof(date), &curtime);
+		mg_printf(conn, "Date: %s\r\n", date);
+	}
+	if (!has_connection) {
+		mg_printf(conn, "Connection: %s\r\n", suggest_connection_header(conn));
+	}
+
+	mg_write(conn, "\r\n", 2);
+	conn->request_state = 3;
+
+	/* ok */
+	return 0;
+}