From a55511ea75003f5ca79a25f6e64eadf91b6dfe9f Mon Sep 17 00:00:00 2001
From: Lars-Dominik Braun <lars@6xq.net>
Date: Fri, 30 Nov 2012 15:10:36 +0100
Subject: waitress: Fix chunked decoder

Closes #322.
---
 src/libwaitress/waitress.c | 95 ++++++++++++++++++++++++----------------------
 src/libwaitress/waitress.h |  1 +
 2 files changed, 51 insertions(+), 45 deletions(-)

diff --git a/src/libwaitress/waitress.c b/src/libwaitress/waitress.c
index ce2a254..8a8315f 100644
--- a/src/libwaitress/waitress.c
+++ b/src/libwaitress/waitress.c
@@ -628,64 +628,69 @@ static WaitressHandlerReturn_t WaitressHandleIdentity (void *data, char *buf,
 	}
 }
 
-/*	chunked encoding handler. buf must be \0-terminated, size does not include
- *	trailing \0.
+/*	chunked encoding handler
  */
 static WaitressHandlerReturn_t WaitressHandleChunked (void *data, char *buf,
 		const size_t size) {
 	assert (data != NULL);
 	assert (buf != NULL);
 
-	WaitressHandle_t *waith = data;
-	char *content = buf, *nextContent;
-
-	assert (waith != NULL);
-	assert (buf != NULL);
-
-	while (1) {
-		if (waith->request.chunkSize > 0) {
-			const size_t remaining = size-(content-buf);
-
-			if (remaining >= waith->request.chunkSize) {
-				if (WaitressHandleIdentity (waith, content,
-						waith->request.chunkSize) == WAITRESS_HANDLER_ABORTED) {
-					return WAITRESS_HANDLER_ABORTED;
-				}
-
-				content += waith->request.chunkSize;
-				if (content[0] == '\r' && content[1] == '\n') {
-					content += 2;
+	WaitressHandle_t * const waith = data;
+	size_t pos = 0;
+
+	while (pos < size) {
+		switch (waith->request.chunkedState) {
+			case CHUNKSIZE:
+				/* Poor man’s hex to integer. This avoids another buffer that
+				 * fills until the terminating \r\n is received. */
+				if (buf[pos] >= '0' && buf[pos] <= '9') {
+					waith->request.chunkSize <<= 4;
+					waith->request.chunkSize |= buf[pos] & 0xf;
+				} else if (buf[pos] >= 'a' && buf[pos] <= 'f') {
+					waith->request.chunkSize <<= 4;
+					waith->request.chunkSize |= (buf[pos]+9) & 0xf;
+				} else if (buf[pos] == '\r') {
+					/* ignore */
+				} else if (buf[pos] == '\n') {
+					waith->request.chunkedState = DATA;
+					/* last chunk has size 0 */
+					if (waith->request.chunkSize == 0) {
+						return WAITRESS_HANDLER_DONE;
+					}
 				} else {
+					/* everything else is a protocol violation */
 					return WAITRESS_HANDLER_ERR;
 				}
-				waith->request.chunkSize = 0;
-			} else {
-				if (WaitressHandleIdentity (waith, content, remaining) ==
-						WAITRESS_HANDLER_ABORTED) {
-					return WAITRESS_HANDLER_ABORTED;
-				}
-				waith->request.chunkSize -= remaining;
-				return WAITRESS_HANDLER_CONTINUE;
-			}
-		}
+				++pos;
+				break;
 
-		if ((nextContent = WaitressGetline (content)) != NULL) {
-			const long int chunkSize = strtol (content, NULL, 16);
-			if (chunkSize == 0) {
-				return WAITRESS_HANDLER_DONE;
-			} else if (chunkSize < 0) {
-				return WAITRESS_HANDLER_ERR;
-			} else {
-				waith->request.chunkSize = chunkSize;
-				content = nextContent;
-			}
-		} else {
-			return WAITRESS_HANDLER_ERR;
+			case DATA:
+				if (waith->request.chunkSize > 0) {
+					assert (size >= pos);
+					size_t payloadSize = size - pos;
+
+					if (payloadSize > waith->request.chunkSize) {
+						payloadSize = waith->request.chunkSize;
+					}
+					if (WaitressHandleIdentity (waith, &buf[pos],
+							payloadSize) == WAITRESS_HANDLER_ABORTED) {
+						return WAITRESS_HANDLER_ABORTED;
+					}
+					pos += payloadSize;
+					assert (waith->request.chunkSize >= payloadSize);
+					waith->request.chunkSize -= payloadSize;
+				} else {
+					/* next chunk size starts in the next line */
+					if (buf[pos] == '\n') {
+						waith->request.chunkedState = CHUNKSIZE;
+					}
+					++pos;
+				}
+				break;
 		}
 	}
 
-	assert (0);
-	return WAITRESS_HANDLER_ERR;
+	return WAITRESS_HANDLER_CONTINUE;
 }
 
 /*	handle http header
diff --git a/src/libwaitress/waitress.h b/src/libwaitress/waitress.h
index 0d69f0e..da516bb 100644
--- a/src/libwaitress/waitress.h
+++ b/src/libwaitress/waitress.h
@@ -107,6 +107,7 @@ typedef struct {
 		WaitressReturn_t readWriteRet;
 
 		size_t contentLength, contentReceived, chunkSize;
+		enum {CHUNKSIZE = 0, DATA = 1} chunkedState;
 
 		char *buf;
 		/* first argument is WaitressHandle_t, but that's not defined yet */
-- 
cgit v1.2.3