From 5153eebc9b618f739ae0f05ce01676615ebe72a3 Mon Sep 17 00:00:00 2001
From: Sergey Lyubka <valenok@gmail.com>
Date: Tue, 1 Oct 2013 17:47:32 +0100
Subject: [PATCH] mg_read() does not block on content_len=0. mg_read() reads
 until socket is closed if content-length is not provided

---
 build/src/mongoose.c | 16 ++++++--------
 mongoose.c           | 16 ++++++--------
 test/test.pl         |  3 ++-
 test/unit_test.c     | 50 +++++++++++++++++++++++++-------------------
 4 files changed, 43 insertions(+), 42 deletions(-)

diff --git a/build/src/mongoose.c b/build/src/mongoose.c
index 00ca8db14..fcb293617 100644
--- a/build/src/mongoose.c
+++ b/build/src/mongoose.c
@@ -734,10 +734,8 @@ int mg_read(struct mg_connection *conn, void *buf, int len) {
   int n, buffered_len, nread = 0;
   int64_t left;
 
-  // If Content-Length is not set, read until socket is closed
   if (conn->content_len <= 0) {
-    conn->content_len = INT64_MAX;
-    conn->must_close = 1;
+    return 0;
   }
 
   // conn->buf           body
@@ -1790,7 +1788,7 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
   expect = mg_get_header(conn, "Expect");
   assert(fp != NULL);
 
-  if (conn->content_len == -1) {
+  if (conn->content_len == INT64_MAX) {
     send_http_error(conn, 411, "Length Required", "%s", "");
   } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
     send_http_error(conn, 417, "Expectation Failed", "%s", "");
@@ -3644,14 +3642,12 @@ static int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len) {
                                 &conn->request_info) <= 0) {
     snprintf(ebuf, ebuf_len, "Bad request: [%.*s]", conn->data_len, conn->buf);
   } else {
-    // Request is valid
+    // Request is valid. Set content_len attribute by parsing Content-Length
+    // If Content-Length is absent, instruct mg_read() to read from the socket
+    // until socket is closed.
+    conn->content_len = INT64_MAX;
     if ((cl = get_header(&conn->request_info, "Content-Length")) != NULL) {
       conn->content_len = strtoll(cl, NULL, 10);
-    } else if (!mg_strcasecmp(conn->request_info.request_method, "POST") ||
-               !mg_strcasecmp(conn->request_info.request_method, "PUT")) {
-      conn->content_len = -1;
-    } else {
-      conn->content_len = 0;
     }
     conn->birth_time = time(NULL);
   }
diff --git a/mongoose.c b/mongoose.c
index 8a424ecf0..35ce41a96 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -2045,10 +2045,8 @@ int mg_read(struct mg_connection *conn, void *buf, int len) {
   int n, buffered_len, nread = 0;
   int64_t left;
 
-  // If Content-Length is not set, read until socket is closed
   if (conn->content_len <= 0) {
-    conn->content_len = INT64_MAX;
-    conn->must_close = 1;
+    return 0;
   }
 
   // conn->buf           body
@@ -3101,7 +3099,7 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
   expect = mg_get_header(conn, "Expect");
   assert(fp != NULL);
 
-  if (conn->content_len == -1) {
+  if (conn->content_len == INT64_MAX) {
     send_http_error(conn, 411, "Length Required", "%s", "");
   } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
     send_http_error(conn, 417, "Expectation Failed", "%s", "");
@@ -4955,14 +4953,12 @@ static int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len) {
                                 &conn->request_info) <= 0) {
     snprintf(ebuf, ebuf_len, "Bad request: [%.*s]", conn->data_len, conn->buf);
   } else {
-    // Request is valid
+    // Request is valid. Set content_len attribute by parsing Content-Length
+    // By default, in the absence of Content-Length, instruct mg_read()
+    // to read from the socket until the socket is closed.
+    conn->content_len = INT64_MAX;
     if ((cl = get_header(&conn->request_info, "Content-Length")) != NULL) {
       conn->content_len = strtoll(cl, NULL, 10);
-    } else if (!mg_strcasecmp(conn->request_info.request_method, "POST") ||
-               !mg_strcasecmp(conn->request_info.request_method, "PUT")) {
-      conn->content_len = -1;
-    } else {
-      conn->content_len = 0;
     }
     conn->birth_time = time(NULL);
   }
diff --git a/test/test.pl b/test/test.pl
index cdc8bd86c..3f7b5b650 100644
--- a/test/test.pl
+++ b/test/test.pl
@@ -180,7 +180,8 @@ o("GET /dir%20with%20spaces/桌面/ HTTP/1.0\r\n\r\n", 'куку!',
   'Non-ascii chars in path');
 o("GET /hello.txt HTTP/1.1\nConnection: close\nRange: bytes=3-50\r\n\r\n",
   'Content-Length: 15\s', 'Range past the file end');
-o("GET /hello.txt HTTP/1.1\n\n   GET /hello.txt HTTP/1.0\n\n",
+o("GET /hello.txt HTTP/1.1\nContent-Length: 0\n\n   ".
+  "GET /hello.txt HTTP/1.0\nContent-Length: 0\n\n",
   'HTTP/1.1 200.+keep-alive.+HTTP/1.1 200.+close',
   'Request pipelining', 2);
 
diff --git a/test/unit_test.c b/test/unit_test.c
index b12b244c6..632c25ce6 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -220,6 +220,15 @@ static int event_handler(struct mg_event *event) {
       return 1;
     }
 
+    if (!strcmp(ri->uri, "/zerolen")) {
+      char buf[100];
+      mg_printf(event->conn, "%s",
+                "HTTP/1.0 200 OK\r\nContent-Length: 2\r\n\r\nok");
+      printf("[%d]\n", mg_read(event->conn, buf, sizeof(buf)));
+      ASSERT(mg_read(event->conn, buf, sizeof(buf)) == 0);
+      return 1;
+    }
+
     if (!strcmp(ri->uri, "/upload")) {
       test_upload(event->conn, "lua_5.2.1.h", "./f1.txt");
       test_upload(event->conn, "lsqlite3.c", "./f2.txt");
@@ -281,6 +290,16 @@ static void test_mg_download(void) {
                              "GET / HTTP/1.0\r\n\r\n")) != NULL);
   mg_close_connection(conn);
 
+  // POST with "Content-Length: 0", must not block
+  ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
+                             ebuf, sizeof(ebuf), "%s",
+                             "POST /zerolen HTTP/1.1\r\n"
+                             "Content-Lengh: 0\r\n\r\n    ")) != NULL);
+  ASSERT((p1 = read_conn(conn, &len1)) != NULL);
+  ASSERT(len1 = 2);
+  ASSERT(memcmp(p1, "ok", 2) == 0);
+  mg_close_connection(conn);
+
   // Fetch main.c, should succeed
   ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
                              "GET /main.c HTTP/1.0\r\n\r\n")) != NULL);
@@ -333,27 +352,16 @@ static void test_mg_upload(void) {
   ASSERT((file2_data = read_file("lsqlite3.c", &file2_len)) != NULL);
   post_data = NULL;
   post_data_len = alloc_printf(&post_data, 0,
-                               // First file
-                               "--%s\r\n"
-                               "Content-Disposition: form-data; "
-                               "name=\"file\"; "
-                               "filename=\"%s\"\r\n\r\n"
-                               "%.*s\r\n"
-
-                               // Second file
-                               "--%s\r\n"
-                               "Content-Disposition: form-data; "
-                               "name=\"file\"; "
-                               "filename=\"%s\"\r\n\r\n"
-                               "%.*s\r\n"
-
-                               // Final boundary
-                               "--%s--\r\n",
-                               boundary, "f1.txt",
-                               file_len, file_data,
-                               boundary, "f2.txt",
-                               file2_len, file2_data,
-                               boundary);
+      // First file
+      "--%s\r\n" "Content-Disposition: form-data; " "name=\"file\"; "
+      "filename=\"%s\"\r\n\r\n" "%.*s\r\n"
+      // Second file
+      "--%s\r\n" "Content-Disposition: form-data; " "name=\"file\"; "
+      "filename=\"%s\"\r\n\r\n" "%.*s\r\n"
+      // Final boundary
+      "--%s--\r\n",
+      boundary, "f1.txt", file_len, file_data, boundary, "f2.txt",
+      file2_len, file2_data, boundary);
   ASSERT(post_data_len > 0);
   ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
                              ebuf, sizeof(ebuf),
-- 
GitLab