From 03e737829ab8c57b965d82d16db663e681e67f3c Mon Sep 17 00:00:00 2001
From: Sergey Lyubka <valenok@gmail.com>
Date: Thu, 5 Dec 2013 00:22:21 +0000
Subject: [PATCH] Made URI handler work

---
 build/src/core.c | 424 ++++++++++++++++++++++-------------------------
 build/src/core.h |  16 +-
 2 files changed, 207 insertions(+), 233 deletions(-)

diff --git a/build/src/core.c b/build/src/core.c
index ebb45c649..5f102a883 100644
--- a/build/src/core.c
+++ b/build/src/core.c
@@ -66,6 +66,7 @@ typedef struct _stati64 file_stat_t;
 #define S_ISDIR(x) ((x) & _S_IFDIR)
 #define sleep(x) Sleep((x) * 1000)
 #define stat(x, y) _stati64((x), (y))
+#define to64(x) _atoi64(x)
 #ifndef va_copy
 #define va_copy(x,y) x = y
 #endif // MINGW #defines va_copy
@@ -89,6 +90,7 @@ typedef struct stat file_stat_t;
 #define mutex_unlock(x) pthread_mutex_unlock(x)
 #define INVALID_SOCKET ((sock_t) -1)
 #define INT64_FMT PRId64
+#define to64(x) strtoll(x, NULL, 10)
 #endif
 
 //#include "mongoose.h"
@@ -120,7 +122,7 @@ struct linked_list_link { struct linked_list_link *prev, *next; };
 #endif
 
 #ifdef ENABLE_DBG
-#define DBG(x) do { printf("%s::%s() ", __FILE__, __func__); \
+#define DBG(x) do { printf("%s::%s ", __FILE__, __func__); \
   printf x; putchar('\n'); fflush(stdout); } while(0)
 #else
 #define DBG(x)
@@ -195,8 +197,9 @@ struct iobuf {
 };
 
 union endpoint {
-  int fd;       // Opened regular local file
-  void *ssl;    // SSL descriptor
+  int fd;                   // Opened regular local file
+  void *ssl;                // SSL descriptor
+  struct uri_handler *uh;   // URI handler user function
 };
 
 enum endpoint_type { EP_NONE, EP_FILE, EP_USER };
@@ -207,17 +210,16 @@ struct connection {
   struct linked_list_link link;   // Linkage to server->active_connections
   struct mg_server *server;
   sock_t client_sock;             // Connected client
-  union socket_address csa;       // Client's socket address
   struct iobuf local_iobuf;
   struct iobuf remote_iobuf;
   union endpoint endpoint;
   enum endpoint_type endpoint_type;
   time_t expire_time;
   char *path_info;
-  int request_len;
-  int flags;
-  int status_code;
-  mutex_t mutex;   // Guards concurrent mg_write() calls
+  char *request;
+  int request_len;  // Request length, including last \r\n after last header
+  int flags;        // CONN_* flags: CONN_CLOSE, CONN_SPOOL_DONE, etc
+  mutex_t mutex;    // Guards concurrent mg_write() calls
 };
 
 static void close_local_endpoint(struct connection *conn);
@@ -326,20 +328,6 @@ int mg_start_thread(void *(*f)(void *), void *p) {
 #endif
 }
 
-#if 0
-static int call_user(int event_type, struct mg_connection *conn, void *p) {
-  if (conn != NULL && conn->server != NULL) {
-    conn->event.user_data = conn->server->user_data;
-    conn->event.type = event_type;
-    conn->event.event_param = p;
-    conn->event.request_info = &conn->request_info;
-    conn->event.conn = conn;
-  }
-  return !conn || !conn->server || !conn->server->event_handler ? 0 :
-    conn->server->event_handler(&conn->event);
-}
-#endif
-
 static void set_close_on_exec(int fd) {
 #ifdef _WIN32
   (void) SetHandleInformation((HANDLE) fd, HANDLE_FLAG_INHERIT, 0);
@@ -565,29 +553,19 @@ static struct connection *accept_new_connection(struct mg_server *server) {
   sock_t sock = INVALID_SOCKET;
   struct connection *conn = NULL;
 
+  // NOTE(lsm): on Windows, sock is always > FD_SETSIZE
   if ((sock = accept(server->listening_sock, &sa.sa, &len)) == INVALID_SOCKET) {
-#if 0
-  } else if (sock >= FD_SETSIZE) {
-    DBG((">fd_setsize"));
-    closesocket(sock);
-#endif
   } else if (!check_acl(server->config_options[ACCESS_CONTROL_LIST],
                         ntohl(* (uint32_t *) &sa.sin.sin_addr))) {
+    // NOTE(lsm): check_acl doesn't work for IPv6
     closesocket(sock);
-  } else if ((conn = (struct connection *)
-              calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
+  } else if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
     closesocket(sock);
   } else {
-    // Put so socket structure into the queue
     set_close_on_exec(sock);
     set_non_blocking_mode(sock);
     conn->server = server;
     conn->client_sock = sock;
-    conn->csa = sa;
-    conn->local_iobuf.buf = (char *) conn + sizeof(*conn);
-    conn->local_iobuf.size = MAX_REQUEST_SIZE;
-    conn->remote_iobuf.buf = (char *) calloc(1, IOBUF_SIZE);
-    conn->remote_iobuf.size = IOBUF_SIZE;
     mutex_init(&conn->mutex);
     LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link);
     DBG(("added conn %p", conn));
@@ -600,7 +578,9 @@ static void close_conn(struct connection *conn) {
   DBG(("closing %p", conn));
   LINKED_LIST_REMOVE(&conn->link);
   closesocket(conn->client_sock);
-  free(conn->remote_iobuf.buf);
+  free(conn->request);            // It's OK to free(NULL)
+  free(conn->remote_iobuf.buf);   // It's OK to free(NULL)
+  free(conn->local_iobuf.buf);    // It's OK to free(NULL)
   mutex_destroy(&conn->mutex);
   free(conn);
 }
@@ -648,6 +628,50 @@ static char *skip(char **buf, const char *delimiters) {
   return begin_word;
 }
 
+// Protect against directory disclosure attack by removing '..',
+// excessive '/' and '\' characters
+static void remove_double_dots_and_double_slashes(char *s) {
+  char *p = s;
+
+  while (*s != '\0') {
+    *p++ = *s++;
+    if (s[-1] == '/' || s[-1] == '\\') {
+      // Skip all following slashes, backslashes and double-dots
+      while (s[0] != '\0') {
+        if (s[0] == '/' || s[0] == '\\') { s++; }
+        else if (s[0] == '.' && s[1] == '.') { s += 2; }
+        else { break; }
+      }
+    }
+  }
+  *p = '\0';
+}
+
+int mg_url_decode(const char *src, int src_len, char *dst,
+                  int dst_len, int is_form_url_encoded) {
+  int i, j, a, b;
+#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
+
+  for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
+    if (src[i] == '%' && i < src_len - 2 &&
+        isxdigit(* (const unsigned char *) (src + i + 1)) &&
+        isxdigit(* (const unsigned char *) (src + i + 2))) {
+      a = tolower(* (const unsigned char *) (src + i + 1));
+      b = tolower(* (const unsigned char *) (src + i + 2));
+      dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
+      i += 2;
+    } else if (is_form_url_encoded && src[i] == '+') {
+      dst[j] = ' ';
+    } else {
+      dst[j] = src[i];
+    }
+  }
+
+  dst[j] = '\0'; // Null-terminate the destination
+
+  return i >= src_len ? j : -1;
+}
+
 // Parse HTTP headers from the given buffer, advance buffer to the point
 // where parsing stopped.
 static void parse_http_headers(char **buf, struct mg_connection *ri) {
@@ -674,35 +698,42 @@ static int is_valid_http_method(const char *method) {
 // This function modifies the buffer by NUL-terminating
 // HTTP request components, header names and header values.
 static int parse_http_message(char *buf, int len, struct mg_connection *ri) {
-  int is_request, request_len = get_request_len((unsigned char *) buf, len);
-  if (request_len > 0) {
-    // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
-    ri->request_method = ri->uri = ri->http_version = NULL;
-    ri->num_headers = 0;
-    buf[request_len - 1] = '\0';
-
-    // RFC says that all initial whitespaces should be ingored
-    while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
-      buf++;
+  int is_request, n;
+
+  // Reset attributes. DO NOT TOUCH remote_ip, remote_port
+  ri->request_method = ri->uri = ri->http_version = NULL;
+  ri->num_headers = 0;
+  buf[len - 1] = '\0';
+
+  // RFC says that all initial whitespaces should be ingored
+  while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
+    buf++;
+  }
+  ri->request_method = skip(&buf, " ");
+  ri->uri = skip(&buf, " ");
+  ri->http_version = skip(&buf, "\r\n");
+
+  // HTTP message could be either HTTP request or HTTP response, e.g.
+  // "GET / HTTP/1.0 ...." or  "HTTP/1.0 200 OK ..."
+  is_request = is_valid_http_method(ri->request_method);
+  if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
+      (!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
+    len = -1;
+  } else {
+    if (is_request) {
+      ri->http_version += 5;
     }
-    ri->request_method = skip(&buf, " ");
-    ri->uri = skip(&buf, " ");
-    ri->http_version = skip(&buf, "\r\n");
-
-    // HTTP message could be either HTTP request or HTTP response, e.g.
-    // "GET / HTTP/1.0 ...." or  "HTTP/1.0 200 OK ..."
-    is_request = is_valid_http_method(ri->request_method);
-    if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
-        (!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
-      request_len = -1;
-    } else {
-      if (is_request) {
-        ri->http_version += 5;
-      }
-      parse_http_headers(&buf, ri);
+    parse_http_headers(&buf, ri);
+
+    if ((ri->query_string = strchr(ri->uri, '?')) != NULL) {
+      *(char *) ri->query_string++ = '\0';
     }
+    n = (int) strlen(ri->uri);
+    mg_url_decode(ri->uri, n, (char *) ri->uri, n + 1, 0);
+    remove_double_dots_and_double_slashes((char *) ri->uri);
   }
-  return request_len;
+
+  return len;
 }
 
 static int lowercase(const char *s) {
@@ -770,50 +801,6 @@ static int match_prefix(const char *pattern, int pattern_len, const char *str) {
   return j;
 }
 
-// Protect against directory disclosure attack by removing '..',
-// excessive '/' and '\' characters
-static void remove_double_dots_and_double_slashes(char *s) {
-  char *p = s;
-
-  while (*s != '\0') {
-    *p++ = *s++;
-    if (s[-1] == '/' || s[-1] == '\\') {
-      // Skip all following slashes, backslashes and double-dots
-      while (s[0] != '\0') {
-        if (s[0] == '/' || s[0] == '\\') { s++; }
-        else if (s[0] == '.' && s[1] == '.') { s += 2; }
-        else { break; }
-      }
-    }
-  }
-  *p = '\0';
-}
-
-int mg_url_decode(const char *src, int src_len, char *dst,
-                  int dst_len, int is_form_url_encoded) {
-  int i, j, a, b;
-#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
-
-  for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
-    if (src[i] == '%' && i < src_len - 2 &&
-        isxdigit(* (const unsigned char *) (src + i + 1)) &&
-        isxdigit(* (const unsigned char *) (src + i + 2))) {
-      a = tolower(* (const unsigned char *) (src + i + 1));
-      b = tolower(* (const unsigned char *) (src + i + 2));
-      dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
-      i += 2;
-    } else if (is_form_url_encoded && src[i] == '+') {
-      dst[j] = ' ';
-    } else {
-      dst[j] = src[i];
-    }
-  }
-
-  dst[j] = '\0'; // Null-terminate the destination
-
-  return i >= src_len ? j : -1;
-}
-
 // Return HTTP header value, or NULL if not found.
 const char *mg_get_header(const struct mg_connection *ri, const char *s) {
   int i;
@@ -867,67 +854,40 @@ static int convert_uri_to_file_name(struct connection *conn, char *buf,
 }
 
 static int spool(struct iobuf *io, const void *buf, int len) {
+  static const float mult = 1.2;
   char *p = NULL;
-  int new_len = io->len + len;
+  int new_len = 0;
 
-  DBG(("%d %d %d", len, io->len, io->size));
+  assert(io->len >= 0);
+  assert(io->len <= io->size);
+
+  //DBG(("1. %d %d %d", len, io->len, io->size));
   if (len <= 0) {
-  } else if (new_len < io->size) {
+  } else if ((new_len = io->len + len) < io->size) {
     memcpy(io->buf + io->len, buf, len);
-    io->len += len;
-  } else if ((p = (char *) realloc(io->buf, new_len * 2)) != NULL) {
+    io->len = new_len;
+  } else if ((p = (char *) realloc(io->buf, new_len * mult)) != NULL) {
     io->buf = p;
     memcpy(io->buf + io->len, buf, len);
     io->len = new_len;
-    io->size = new_len * 2;
-  } else {
-    len = 0;
-  }
-  return len;
-}
-
-static int vspool(struct iobuf *io, const char *fmt, va_list ap) {
-  char *ptr = io->buf;
-  va_list ap_copy;
-  int len;
-
-  va_copy(ap_copy, ap);
-  len = vsnprintf(NULL, 0, fmt, ap_copy);
-
-  if (len <= 0) {
-  } else if (len < io->size - io->len ||
-             (ptr = (char *) realloc(io->buf, io->len + len + 1 - io->size))
-             != NULL) {
-    io->buf = ptr;
-    vsnprintf(io->buf + io->len, len + 1, fmt, ap);
-    io->len += len;
+    io->size = new_len * mult;
   } else {
     len = 0;
   }
+  DBG(("%d %d %d", len, io->len, io->size));
 
   return len;
 }
 
-int mg_printf(struct mg_connection *c, const char *fmt, ...) {
-  struct connection *conn = (struct connection *) c;
-  va_list ap;
-  int ret;
-  va_start(ap, fmt);
-  mutex_lock(&conn->mutex);
-  ret = vspool(&conn->remote_iobuf, fmt, ap);
-  mutex_unlock(&conn->mutex);
-  va_end(ap);
-  send(conn->server->ctl[1], ".", 1, 0);  // Wake up select call
-  return ret;
-}
-
 int mg_write(struct mg_connection *c, const void *buf, int len) {
   struct connection *conn = (struct connection *) c;
   int ret;
+
   mutex_lock(&conn->mutex);
   ret = spool(&conn->remote_iobuf, buf, len);
   mutex_unlock(&conn->mutex);
   send(conn->server->ctl[1], ".", 1, 0);  // Wake up select call
+
   return ret;
 }
 
@@ -1010,16 +970,15 @@ static void construct_etag(char *buf, size_t buf_len, const file_stat_t *st) {
 
 static void open_file_endpoint(struct connection *conn, const char *path,
                                file_stat_t *st) {
-  char date[64], lm[64], etag[64], range[64];
+  char date[64], lm[64], etag[64], range[64], headers[500];
   const char *msg = "OK", *hdr;
   time_t curtime = time(NULL);
   int64_t cl, r1, r2;
   struct vec mime_vec;
-  int n;
+  int n, status_code = 200;
 
   conn->endpoint_type = EP_FILE;
   set_close_on_exec(conn->endpoint.fd);
-  conn->status_code = 200;
 
   get_mime_type(conn->server, path, &mime_vec);
   cl = st->st_size;
@@ -1030,7 +989,7 @@ static void open_file_endpoint(struct connection *conn, const char *path,
   hdr = mg_get_header(&conn->mg_conn, "Range");
   if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
       r1 >= 0 && r2 >= 0) {
-    conn->status_code = 206;
+    status_code = 206;
     cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1;
     snprintf(range, sizeof(range), "Content-Range: bytes "
              "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n",
@@ -1045,18 +1004,19 @@ static void open_file_endpoint(struct connection *conn, const char *path,
   gmt_time_string(lm, sizeof(lm), &st->st_mtime);
   construct_etag(etag, sizeof(etag), st);
 
-  mg_printf(&conn->mg_conn,
-            "HTTP/1.1 %d %s\r\n"
-            "Date: %s\r\n"
-            "Last-Modified: %s\r\n"
-            "Etag: %s\r\n"
-            "Content-Type: %.*s\r\n"
-            "Content-Length: %" INT64_FMT "\r\n"
-            "Connection: %s\r\n"
-            "Accept-Ranges: bytes\r\n"
-            "%s%s\r\n",
-            conn->status_code, msg, date, lm, etag, (int) mime_vec.len,
-            mime_vec.ptr, cl, "keep-alive", range, EXTRA_HTTP_HEADERS);
+  n = snprintf(headers, sizeof(headers),
+               "HTTP/1.1 %d %s\r\n"
+               "Date: %s\r\n"
+               "Last-Modified: %s\r\n"
+               "Etag: %s\r\n"
+               "Content-Type: %.*s\r\n"
+               "Content-Length: %" INT64_FMT "\r\n"
+               "Connection: %s\r\n"
+               "Accept-Ranges: bytes\r\n"
+               "%s%s\r\n",
+               status_code, msg, date, lm, etag, (int) mime_vec.len,
+               mime_vec.ptr, cl, "keep-alive", range, EXTRA_HTTP_HEADERS);
+  spool(&conn->remote_iobuf, headers, n);
 
   if (!strcmp(conn->mg_conn.request_method, "HEAD")) {
     conn->flags |= CONN_SPOOL_DONE;
@@ -1135,8 +1095,8 @@ static struct uri_handler *find_uri_handler(struct mg_server *server,
 // For given directory path, substitute it to valid index file.
 // Return 0 if index file has been found, -1 if not found.
 // If the file is found, it's stats is returned in stp.
-static int substitute_index_file(struct connection *conn, char *path,
-                                 size_t path_len, file_stat_t *stp) {
+static int find_index_file(struct connection *conn, char *path,
+                           size_t path_len, file_stat_t *stp) {
   const char *list = conn->server->config_options[INDEX_FILES];
   file_stat_t st;
   struct vec filename_vec;
@@ -1179,33 +1139,40 @@ static int substitute_index_file(struct connection *conn, char *path,
 }
 
 static void send_http_error(struct connection *conn, const char *fmt, ...) {
+  char buf[500];
+  int len;
   va_list ap;
+
   va_start(ap, fmt);
-  vspool(&conn->remote_iobuf, fmt, ap);
+  len = vsnprintf(buf, sizeof(buf), fmt, ap);
   va_end(ap);
+
+  spool(&conn->remote_iobuf, buf, len);
   conn->flags |= CONN_SPOOL_DONE;
 }
 
-static void exec_lua_script(struct connection *conn, const char *path) {
-  send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n");
+static void call_uri_handler_if_data_is_buffered(struct connection *conn) {
+  if (conn->local_iobuf.len >= conn->mg_conn.content_len) {
+    conn->endpoint.uh->handler(&conn->mg_conn);
+    close_local_endpoint(conn);
+  }
 }
 
 static void open_local_endpoint(struct connection *conn) {
   char path[MAX_PATH_SIZE] = {'\0'};
   file_stat_t st;
-  int uri_len, exists = 0, is_directory = 0;
-  struct uri_handler *uh;
+  int exists = 0, is_directory = 0;
+  const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
 
-  if ((conn->mg_conn.query_string = strchr(conn->mg_conn.uri, '?')) != NULL) {
-    * ((char *) conn->mg_conn.query_string++) = '\0';
-  }
-  uri_len = (int) strlen(conn->mg_conn.uri);
-  mg_url_decode(conn->mg_conn.uri, uri_len, (char *) conn->mg_conn.uri,
-                uri_len + 1, 0);
-  remove_double_dots_and_double_slashes((char *) conn->mg_conn.uri);
+  conn->mg_conn.content_len = cl_hdr == NULL ? 0 : to64(cl_hdr);
 
-  if ((uh = find_uri_handler(conn->server, conn->mg_conn.uri)) != NULL) {
+  // Call URI handler if one is registered for this URI
+  conn->endpoint.uh = find_uri_handler(conn->server, conn->mg_conn.uri);
+  if (conn->endpoint.uh != NULL) {
     conn->endpoint_type = EP_USER;
+    conn->mg_conn.content = conn->local_iobuf.buf;
+    call_uri_handler_if_data_is_buffered(conn);
+    return;
   }
 
   exists = convert_uri_to_file_name(conn, path, sizeof(path), &st);
@@ -1213,14 +1180,10 @@ static void open_local_endpoint(struct connection *conn) {
 
   if (!exists) {
     send_http_error(conn, "%s", "HTTP/1.1 404 Not Found\r\n\r\n");
-  } else if (is_directory && conn->mg_conn.uri[uri_len - 1] != '/') {
-    send_http_error(conn, "HTTP/1.1 301 Moved Permanently\r\n"
-                    "Location: %s/\r\n\r\n", conn->mg_conn.uri);
-  } else if (is_directory &&
-             !substitute_index_file(conn, path, sizeof(path), &st)) {
+  } else if (is_directory && !find_index_file(conn, path, sizeof(path), &st)) {
     send_http_error(conn, "%s", "HTTP/1.1 403 Listing Denied\r\n\r\n");
   } else if (match_prefix(LUA_SCRIPT_PATTERN, 6, path) > 0) {
-    exec_lua_script(conn, path);
+    send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n");
     conn->flags |= CONN_SPOOL_DONE;
   } else if (is_not_modified(conn, &st)) {
     send_http_error(conn, "%s", "HTTP/1.1 304 Not Modified\r\n\r\n");
@@ -1231,40 +1194,54 @@ static void open_local_endpoint(struct connection *conn) {
   }
 }
 
-static int io_space(const struct iobuf *io) {
-  return io->size - io->len;
+static void send_continue_if_expected(struct connection *conn) {
+  static const char expect_response[] = "HTTP/1.1 100 Continue\r\n\r\n";
+  const char *expect_hdr = mg_get_header(&conn->mg_conn, "Expect");
+
+  if (expect_hdr != NULL) {
+    spool(&conn->remote_iobuf, expect_response, sizeof(expect_response) - 1);
+  }
 }
 
 static void process_request(struct connection *conn) {
   struct iobuf *io = &conn->local_iobuf;
 
   //DBG(("parse_http_message(%d [%.*s])", io->len, io->len, io->buf));
-  if (conn->request_len == 0) {
-    conn->request_len = parse_http_message(io->buf, io->len, &conn->mg_conn);
+  if (conn->request_len == 0 &&
+      (conn->request_len = get_request_len((unsigned char  *) io->buf,
+                                           io->len)) > 0) {
+    // If request is buffered in, remove it from the iobuf. This is because
+    // iobuf could be reallocated, and pointers in parsed request could
+    // become ivalid.
+    conn->request = (char *) malloc(conn->request_len);
+    memcpy(conn->request, io->buf, conn->request_len);
+    memmove(io->buf, io->buf + conn->request_len, io->len - conn->request_len);
+    io->len -= conn->request_len;
+    conn->request_len = parse_http_message(conn->request, conn->request_len,
+                                           &conn->mg_conn);
+    DBG(("request_len = %d", conn->request_len));
   }
-  DBG(("parse_http_message() -> %d", conn->request_len));
 
   if (conn->request_len < 0 ||
-      (conn->request_len == 0 && io_space(io) <= 0)) {
+      (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) {
     // Invalid request, or request is too big: close the connection
     conn->flags |= CONN_CLOSE;
   } else if (conn->request_len > 0 && conn->endpoint_type == EP_NONE) {
+    send_continue_if_expected(conn);
     open_local_endpoint(conn);
+  } else if (conn->endpoint_type == EP_USER) {
+    call_uri_handler_if_data_is_buffered(conn);
   }
 }
 
 static void read_from_client(struct connection *conn) {
-  struct iobuf *io = &conn->local_iobuf;
-  int n = recv(conn->client_sock, io->buf + io->len, io->size - io->len, 0);
-
-  //DBG(("Read: %d [%.*s]", n, n, io->buf + io->len));
-  assert(io->len >= 0);
-  assert(io->len <= io->size);
+  char buf[IOBUF_SIZE];
+  int n = recv(conn->client_sock, buf, sizeof(buf), 0);
 
   if (is_error(n)) {
     conn->flags |= CONN_CLOSE;
   } else if (n > 0) {
-    io->len += n;
+    spool(&conn->local_iobuf, buf, n);
     process_request(conn);
   }
 }
@@ -1279,22 +1256,22 @@ static int should_keep_alive(const struct connection *conn) {
 }
 
 static void close_local_endpoint(struct connection *conn) {
-  struct iobuf *io = &conn->local_iobuf;
-  int keep_alive = should_keep_alive(conn);  // Must be done before memmove
+  int keep_alive = should_keep_alive(conn);  // Must be done before free()
+
+  //DBG(("Closing for conn %p", conn));
 
   // Close file descriptor
   switch (conn->endpoint_type) {
     case EP_FILE: close(conn->endpoint.fd); break;
+    case EP_USER: break;
     case EP_NONE: break;
     default: assert(0); break;
   }
 
-  // Get rid of that request from the buffer. NOTE: order is important here
-  assert(conn->request_len <= io->len);
-  memmove(io->buf, io->buf + conn->request_len, io->len - conn->request_len);
-  io->len -= conn->request_len;
   conn->endpoint_type = EP_NONE;
   conn->request_len = 0;
+  free(conn->request);
+  conn->request = NULL;
 
   if (keep_alive) {
     process_request(conn);  // Can call us recursively if pipelining is used
@@ -1304,16 +1281,13 @@ static void close_local_endpoint(struct connection *conn) {
 }
 
 static void transfer_file_data(struct connection *conn) {
-  struct iobuf *io = &conn->remote_iobuf;
-  int n, rem_space = io_space(io);
-
-  if (rem_space <= 0) return;
-  n = read(conn->endpoint.fd, io->buf + io->len, rem_space);
+  char buf[IOBUF_SIZE];
+  int n = read(conn->endpoint.fd, buf, sizeof(buf));
 
   if (is_error(n)) {
     close_local_endpoint(conn);
   } else if (n > 0) {
-    io->len += n;
+    spool(&conn->remote_iobuf, buf, n);
   }
 }
 
@@ -1488,22 +1462,6 @@ struct mg_server *mg_create_server(void *server_data) {
   server->server_data = server_data;
   server->listening_sock = INVALID_SOCKET;
 
-#if 0
-  while (opts != NULL && (name = *opts++) != NULL) {
-    if ((i = get_option_index(name)) == -1) {
-      snprintf(error_msg, sizeof(error_msg), "Invalid option: [%s]", name);
-    } else if ((value = *opts++) == NULL) {
-      snprintf(error_msg, sizeof(error_msg), "[%s] cannot be NULL", name);
-    } else {
-      if (server->config_options[i] != NULL) {
-        free(server->config_options[i]);
-      }
-      server->config_options[i] = mg_strdup(value);
-      DBG(("[%s] -> [%s]", name, value));
-    }
-  }
-#endif
-
   // Set default options values
   for (i = 0; static_config_options[i * 2] != NULL; i++) {
     value = static_config_options[i * 2 + 1];
@@ -1518,7 +1476,9 @@ struct mg_server *mg_create_server(void *server_data) {
 // End of library, start of the application code
 
 static void iterate_callback(struct mg_connection *c, void *param) {
-  mg_write(c, "%d", * (int *) param);
+  if (c->connection_param != NULL) {
+    mg_write(c, "%d", * (int *) param);
+  }
 }
 
 static void *timer_thread(void *param) {
@@ -1534,7 +1494,17 @@ static void *timer_thread(void *param) {
 }
 
 static int websocket_handler(struct mg_connection *conn) {
-  mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n:-)\n");
+  char headers[500], content[500];
+  int headers_len, content_len;
+
+  content_len = snprintf(content, sizeof(content), "%s %s, POST len %d\n",
+                         conn->request_method, conn->uri, conn->content_len);
+  headers_len = snprintf(headers, sizeof(headers), "HTTP/1.0 200 OK\r\n"
+                         "Content-Length: %d\r\n\r\n", content_len);
+
+  mg_write(conn, headers, headers_len);
+  mg_write(conn, content, content_len);
+
   return 1;
 }
 
@@ -1549,7 +1519,7 @@ int main(void) {
 
   printf("Started on port %s\n", mg_get_option(server, "listening_port"));
   for (;;) {
-    mg_poll_server(server, 1000);
+    mg_poll_server(server, 3000);
   }
 
   mg_destroy_server(&server);
diff --git a/build/src/core.h b/build/src/core.h
index f3fc54255..d6695796c 100644
--- a/build/src/core.h
+++ b/build/src/core.h
@@ -33,15 +33,19 @@ struct mg_connection {
   const char *uri;            // URL-decoded URI
   const char *http_version;   // E.g. "1.0", "1.1"
   const char *query_string;   // URL part after '?', not including '?', or NULL
+
   long remote_ip;             // Client's IP address
   int remote_port;            // Client's port
-  int is_ssl;                 // 1 if SSL-ed, 0 if not
+
   int num_headers;            // Number of HTTP headers
   struct mg_header {
     const char *name;         // HTTP header name
     const char *value;        // HTTP header value
   } http_headers[64];         // Maximum 64 headers
 
+  char *content;              // POST (or websocket message) data, or NULL
+  int content_len;            // content length
+
   void *server_param;         // Parameter passed to mg_add_uri_handler()
   void *connection_param;     // Placeholder for connection-specific data
 };
@@ -64,16 +68,16 @@ const char **mg_get_valid_option_names(void);
 const char *mg_get_option(const struct mg_server *server, const char *name);
 
 // Websocket functions
-void mg_websocket_handshake(struct mg_connection *);
-int mg_websocket_read(struct mg_connection *, int *bits, char **data);
-int mg_websocket_write(struct mg_connection* conn, int opcode,
-                       const char *data, size_t data_len);
+//void mg_websocket_handshake(struct mg_connection *);
+//int mg_websocket_read(struct mg_connection *, int *bits, char **data);
+//int mg_websocket_write(struct mg_connection* conn, int opcode,
+//                       const char *data, size_t data_len);
 
 // Connection management functions
 int mg_write(struct mg_connection *, const void *buf, int len);
-int mg_printf(struct mg_connection *, const char *fmt, ...);
 
 #if 0
+int mg_printf(struct mg_connection *, const char *fmt, ...);
 void mg_send_file(struct mg_connection *, const char *path);
 int mg_read(struct mg_connection *, void *buf, int len);
 const char *mg_get_header(const struct mg_connection *, const char *name);
-- 
GitLab