summary refs log tree commit diff
path: root/hittpd.c
diff options
context:
space:
mode:
authorLeah Neukirchen <leah@vuxu.org>2022-01-13 18:42:06 +0100
committerLeah Neukirchen <leah@vuxu.org>2022-01-13 18:42:06 +0100
commit8bf60bbd1de2a329340e88d3fb31ea30e83eed8f (patch)
treefbe8224dd989a90aac58e1e76d5efeb6a4b3c55a /hittpd.c
parent39b303bffbcb52ea36b1d5e30d8531e752233505 (diff)
downloadhittpd-8bf60bbd1de2a329340e88d3fb31ea30e83eed8f.tar.gz
hittpd-8bf60bbd1de2a329340e88d3fb31ea30e83eed8f.tar.xz
hittpd-8bf60bbd1de2a329340e88d3fb31ea30e83eed8f.zip
detect and refuse pipelining
Previously, two HTTP requests within the same read() would both
be parsed by http_parser_execute (and corrupted state).
We don't support this, since we distinguish between reading and
writing parts of the server action, and there's no way to let
poll perform the writing part in this case.

Detect pipelining by pausing the parser in on_message_complete and
checking if we parsed fewer bytes than we passed.  Then handle the
first request and drop the connection; a compliant HTTP client must
retry without pipelining.

Found by @duncaen.

Signed-off-by: Leah Neukirchen <leah@vuxu.org>
Diffstat (limited to 'hittpd.c')
-rw-r--r--hittpd.c19
1 files changed, 17 insertions, 2 deletions
diff --git a/hittpd.c b/hittpd.c
index e5114d6..9ddd2c4 100644
--- a/hittpd.c
+++ b/hittpd.c
@@ -1,6 +1,6 @@
 /* hittpd - efficient, no-frills HTTP 1.1 server */
 
-/* Copyright 2020, 2021 Leah Neukirchen <leah@vuxu.org>
+/* Copyright 2020, 2021, 2022 Leah Neukirchen <leah@vuxu.org>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to
@@ -516,6 +516,7 @@ on_message_complete(http_parser *p) {
 	struct conn_data *data = p->data;
 
 	data->state = SENDING;
+	http_parser_pause(p, 1);  // can't do more than one request at a time
 
 	if (p->http_major == 0 && p->http_minor == 9)
 		return send_error(p, 400, "Bad Request");
@@ -919,7 +920,21 @@ read_client(int i)
 	} else if (n == 0) {
 		close_connection(i);
 	} else {
-		http_parser_execute(&parsers[i], &settings, buf, n);
+		size_t r = http_parser_execute(&parsers[i], &settings, buf, n);
+
+		if ((size_t)n == r &&
+		    HTTP_PARSER_ERRNO(&parsers[i]) == HPE_PAUSED) {
+			// we handled a complete request, we can reuse
+			// the parser
+			http_parser_pause(&parsers[i], 0);
+		} else {
+			// the read data was longer than a single request
+			// drop the rest and make sure we close the connection,
+			// so the client tries without pipelining
+			// (this is conformant)
+			http_parser_pause(&parsers[i], 0);
+			parsers[i].flags |= F_CONNECTION_CLOSE;
+		}
 
 		if (parsers[i].http_errno) {
 			printf("err=%s\n",