From 0456f0f59b71626974ab3cf9bc2d25c3c33bae82 Mon Sep 17 00:00:00 2001
From: Alexander Alashkin <alexander.alashkin@cesanta.com>
Date: Wed, 16 Mar 2016 15:38:02 +0100
Subject: [PATCH] Fix boundary problem

PUBLISHED_FROM=b78c97a337208007e1f622fde072cd59664ef76f
---
 mongoose.c | 304 ++++++++++++++++++++++++++++++++++++-----------------
 1 file changed, 207 insertions(+), 97 deletions(-)

diff --git a/mongoose.c b/mongoose.c
index 2cd26063d..caaf8de3d 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -4210,10 +4210,24 @@ struct mg_http_endpoint {
   mg_event_handler_t handler;
 };
 
+enum mg_http_multipart_stream_state {
+  MPS_BEGIN,
+  MPS_WAITING_FOR_BOUNDARY,
+  MPS_WAITING_FOR_CHUNK,
+  MPS_GOT_CHUNK,
+  MPS_GOT_BOUNDARY,
+  MPS_FINALIZE,
+  MPS_FINISHED
+};
+
 struct mg_http_multipart_stream {
   const char *boundary;
+  int boundary_len;
   const char *var_name;
   const char *file_name;
+  int prev_io_len;
+  enum mg_http_multipart_stream_state state;
+  int processing_part;
 };
 
 struct mg_http_proto_data {
@@ -5027,12 +5041,10 @@ static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
 }
 
 #ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
-static void mg_http_multipart_continue(struct mg_connection *nc,
-                                       struct mbuf *io, int ev, void *ev_data);
+static void mg_http_multipart_continue(struct mg_connection *nc);
 
 static void mg_http_multipart_begin(struct mg_connection *nc,
-                                    struct http_message *hm, struct mbuf *io,
-                                    int req_len);
+                                    struct http_message *hm, int req_len);
 
 #endif
 
@@ -5106,7 +5118,7 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
 
 #ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
     if (pd->mp_stream.boundary != NULL) {
-      mg_http_multipart_continue(nc, io, ev, ev_data);
+      mg_http_multipart_continue(nc);
       return;
     }
 #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
@@ -5210,18 +5222,25 @@ void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
       mbuf_remove(io, hm->message.len);
 #ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
     } else {
-      mg_http_multipart_begin(nc, hm, io, req_len);
+      mg_http_multipart_begin(nc, hm, req_len);
 #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
     }
   }
 }
 
+static size_t mg_get_line_len(const char *buf, size_t buf_len) {
+  size_t len = 0;
+  while (len < buf_len && buf[len] != '\n') len++;
+  return len == buf_len ? 0 : len + 1;
+}
+
 #ifdef MG_ENABLE_HTTP_STREAMING_MULTIPART
 static void mg_http_multipart_begin(struct mg_connection *nc,
-                                    struct http_message *hm, struct mbuf *io,
-                                    int req_len) {
+                                    struct http_message *hm, int req_len) {
   struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
   struct mg_str *ct;
+  struct mbuf *io = &nc->recv_mbuf;
+
   const char multipart[] = "multipart";
   char boundary[100];
   int boundary_len;
@@ -5266,6 +5285,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
     nc->flags |= MG_F_CLOSE_IMMEDIATELY;
   } else {
     pd->mp_stream.boundary = strdup(boundary);
+    pd->mp_stream.boundary_len = strlen(boundary);
     pd->mp_stream.var_name = pd->mp_stream.file_name = NULL;
 
     pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
@@ -5281,104 +5301,200 @@ exit_mp:
   ;
 }
 
-static void mg_http_multipart_continue(struct mg_connection *nc,
-                                       struct mbuf *io, int ev, void *ev_data) {
-  /* Continue to stream multipart */
-  struct mg_http_multipart_part mp;
-  const char *boundary;
-  int req_len;
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+#define CONTENT_DISPOSITION "Content-Disposition: "
 
+static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
+                                           const char *data, size_t data_len) {
+  struct mg_http_multipart_part mp;
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
   memset(&mp, 0, sizeof(mp));
+
   mp.var_name = pd->mp_stream.var_name;
   mp.file_name = pd->mp_stream.file_name;
+  mp.data.p = data;
+  mp.data.len = data_len;
+  mg_call(c, pd->endpoint_handler, ev, &mp);
+}
+
+static int mg_http_multipart_got_chunk(struct mg_connection *c) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  struct mbuf *io = &c->recv_mbuf;
+
+  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf,
+                                 pd->mp_stream.prev_io_len);
+  mbuf_remove(io, pd->mp_stream.prev_io_len);
+  pd->mp_stream.prev_io_len = 0;
+  pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
+
+  return 0;
+}
+
+static int mg_http_multipart_finalize(struct mg_connection *c) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+
+  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+  mg_http_free_proto_data_mp_stream(&pd->mp_stream);
+  pd->mp_stream.state = MPS_FINISHED;
+
+  return 1;
+}
+
+static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
+  const char *boundary;
+  struct mbuf *io = &c->recv_mbuf;
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+
+  if ((int) io->len < pd->mp_stream.boundary_len + 2) {
+    return 0;
+  }
+
   boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
-  if (boundary == NULL) {
-    mp.data.p = io->buf;
-    mp.data.len = io->len;
-    mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_DATA, &mp);
-    mbuf_remove(io, io->len);
+  if (boundary != NULL) {
+    if (io->len - (boundary - io->buf) < 4) {
+      return 0;
+    }
+    if (memcmp(boundary + pd->mp_stream.boundary_len, "--", 2) == 0) {
+      pd->mp_stream.state = MPS_FINALIZE;
+    } else {
+      pd->mp_stream.state = MPS_GOT_BOUNDARY;
+    }
   } else {
-    int has_prefix = 0, has_suffix = 0,
-        boundary_len = strlen(pd->mp_stream.boundary);
-    if (boundary - 2 >= io->buf) {
-      has_prefix = (strncmp(boundary - 2, "--", 2) == 0);
-    }
-    if (boundary + boundary_len <= io->buf + io->len) {
-      has_suffix = (strncmp(boundary + boundary_len, "--", 2) == 0);
-    }
-    if (has_prefix && !has_suffix) {
-      /* No suffix - not last boundary */
-      char varname[100] = {0}, filename[100] = {0};
-      const char *data = NULL;
-      size_t data_len = 0;
-      int num_left = (boundary - io->buf) - 2;
-      /* Send remainder of the previous part (if any) to callback */
-      if (num_left > 2) { /* \r\n */
-        mp.data.p = io->buf;
-        mp.data.len = num_left - 2;
-        mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_DATA, &mp);
-        mp.data.len = 0;
-        mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_END, &mp);
-        mbuf_remove(io, num_left);
-      }
+    return 0;
+  }
 
-      mg_parse_multipart(io->buf, io->len, varname, sizeof(varname), filename,
-                         sizeof(filename), &data, &data_len);
-      mp.var_name = varname;
-      mp.file_name = filename;
-      if ((req_len = mg_http_get_request_len(io->buf, io->len)) > 0) {
-        const char *tmp;
-        mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_BEGIN, &mp);
-        free((void *) pd->mp_stream.var_name);
-        pd->mp_stream.var_name = strdup(mp.var_name);
-        free((void *) pd->mp_stream.file_name);
-        pd->mp_stream.file_name = strdup(mp.file_name);
-
-        mbuf_remove(io, req_len);
-        mp.data.p = io->buf;
-
-        tmp = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
-        if (tmp == NULL) {
-          mp.data.len = io->len;
-        } else {
-          mp.data.len = tmp - io->buf - 2;
-        }
+  return 1;
+}
 
-        if (mp.data.len != 0) {
-          size_t data_len = mp.data.len;
-          mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_DATA, &mp);
-          if (data_len != io->len) {
-            mp.data.len = 0;
-            mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_END, &mp);
-          }
-          mbuf_remove(io, data_len);
-        }
+static int mg_http_multipart_process_boundary(struct mg_connection *c) {
+  int data_size;
+  const char *boundary, *block_begin;
+  struct mbuf *io = &c->recv_mbuf;
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  char file_name[100], var_name[100];
+  int line_len;
+  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+  block_begin = boundary + pd->mp_stream.boundary_len + 2;
+  data_size = io->len - (block_begin - io->buf);
+
+  while (data_size > 0 &&
+         (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
+    if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
+        mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
+                    sizeof(CONTENT_DISPOSITION) - 1) == 0) {
+      struct mg_str header;
 
-        if (io->len != 0) {
-          mg_http_handler(nc, ev, ev_data);
-        }
-      } /* else wait for data */
-    } else if (has_prefix && has_suffix) {
-      /* Last boundary */
-      mp.data.p = io->buf;
-      mp.data.len = boundary - io->buf - 4;
-      if (mp.data.len != 0) {
-        mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_DATA, &mp);
+      header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
+      header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
+      mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
+      mg_http_parse_header(&header, "filename", file_name,
+                           sizeof(file_name) - 2);
+      block_begin += line_len;
+      data_size -= line_len;
+      continue;
+    }
+
+    if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
+      mbuf_remove(io, block_begin - io->buf + 2);
+
+      if (pd->mp_stream.processing_part != 0) {
+        mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
       }
 
-      mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_PART_END, &mp);
+      free((void *) pd->mp_stream.file_name);
+      pd->mp_stream.file_name = strdup(file_name);
+      free((void *) pd->mp_stream.var_name);
+      pd->mp_stream.var_name = strdup(var_name);
 
-      /* Skip epilogue (if any) */
-      mbuf_remove(io, io->len);
-      mg_http_free_proto_data_mp_stream(&pd->mp_stream);
-    } else {
-      /* Malformed request */
-      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-      DBG(("invalid request"));
+      mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
+      pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
+      pd->mp_stream.processing_part++;
+      return 1;
     }
+
+    block_begin += line_len;
   }
+
+  pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+
+  return 0;
 }
+
+static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  struct mbuf *io = &c->recv_mbuf;
+
+  const char *boundary;
+  if ((int) io->len < pd->mp_stream.boundary_len + 6 /* \r\n, --, -- */) {
+    return 0;
+  }
+
+  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+  if (boundary == NULL && pd->mp_stream.prev_io_len == 0) {
+    pd->mp_stream.prev_io_len = io->len;
+    return 0;
+  } else if (boundary == NULL &&
+             (int) io->len >
+                 pd->mp_stream.prev_io_len + pd->mp_stream.boundary_len + 4) {
+    pd->mp_stream.state = MPS_GOT_CHUNK;
+    return 1;
+  } else if (boundary != NULL) {
+    int data_size = (boundary - io->buf - 4);
+    mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_size);
+    mbuf_remove(io, (boundary - io->buf));
+    pd->mp_stream.prev_io_len = 0;
+    pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+static void mg_http_multipart_continue(struct mg_connection *c) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  while (1) {
+    switch (pd->mp_stream.state) {
+      case MPS_BEGIN: {
+        pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
+        break;
+      }
+      case MPS_WAITING_FOR_BOUNDARY: {
+        if (mg_http_multipart_wait_for_boundary(c) == 0) {
+          return;
+        }
+        break;
+      }
+      case MPS_GOT_BOUNDARY: {
+        if (mg_http_multipart_process_boundary(c) == 0) {
+          return;
+        }
+        break;
+      }
+      case MPS_WAITING_FOR_CHUNK: {
+        if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {
+          return;
+        }
+        break;
+      }
+      case MPS_GOT_CHUNK: {
+        if (mg_http_multipart_got_chunk(c) == 0) {
+          return;
+        }
+        break;
+      }
+      case MPS_FINALIZE: {
+        if (mg_http_multipart_finalize(c) == 0) {
+          return;
+        }
+        break;
+      }
+      case MPS_FINISHED: {
+        mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);
+        return;
+      }
+    }
+  }
+}
+
 #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
 
 void mg_set_protocol_http_websocket(struct mg_connection *nc) {
@@ -7526,12 +7642,6 @@ struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
   return nc;
 }
 
-static size_t mg_get_line_len(const char *buf, size_t buf_len) {
-  size_t len = 0;
-  while (len < buf_len && buf[len] != '\n') len++;
-  return buf[len] == '\n' ? len + 1 : 0;
-}
-
 size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
                           size_t var_name_len, char *file_name,
                           size_t file_name_len, const char **data,
-- 
GitLab