diff --git a/build/Makefile b/build/Makefile
index d1c6dad6da1047cf4b72d4173a988854853e1d62..c78ee39fdc435b9e87152bb7f58d379d3a02f1c5 100644
--- a/build/Makefile
+++ b/build/Makefile
@@ -30,7 +30,7 @@ SOURCES = src/internal.h src/util.c src/string.c src/parse_date.c \
           src/options.c src/crypto.c src/auth.c src/win32.c src/unix.c \
           src/mg_printf.c src/ssl.c src/http_client.c src/mime.c \
           src/directory.c src/log.c src/parse_http.c src/io.c src/cgi.c \
-          src/mongoose.c src/lua.c
+          src/upload.c src/mongoose.c src/lua.c
 
 TINY_SOURCES = ../mongoose.c main.c
 LUA_SOURCES = $(TINY_SOURCES) sqlite3.c lsqlite3.c lua_5.2.1.c
diff --git a/build/src/mongoose.c b/build/src/mongoose.c
index df1bebf44703100446e57466073df7c406d0b2ec..6c448358b17272a450ae887c7360a43e55eae919 100644
--- a/build/src/mongoose.c
+++ b/build/src/mongoose.c
@@ -973,144 +973,6 @@ static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
   return len;
 }
 
-FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
-                char *path, int path_len) {
-  const char *content_type_header, *boundary_start;
-  char *buf, fname[1024], boundary[100], *s;
-  int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
-  FILE *fp;
-
-  // Request looks like this:
-  //
-  // POST /upload HTTP/1.1
-  // Host: 127.0.0.1:8080
-  // Content-Length: 244894
-  // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
-  //
-  // ------WebKitFormBoundaryRVr
-  // Content-Disposition: form-data; name="file"; filename="accum.png"
-  // Content-Type: image/png
-  //
-  //  <89>PNG
-  //  <PNG DATA>
-  // ------WebKitFormBoundaryRVr
-
-  // Extract boundary string from the Content-Type header
-  if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
-      (boundary_start = mg_strcasestr(content_type_header,
-                                      "boundary=")) == NULL ||
-      (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
-       sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
-      boundary[0] == '\0') {
-    return NULL;
-  }
-
-  boundary_len = strlen(boundary);
-  bl = boundary_len + 4;  // \r\n--<boundary>
-
-  //                     buf
-  // conn->buf            |<--------- buf_len ------>|
-  //    |=================|==========|===============|
-  //    |<--request_len-->|<--len--->|               |
-  //    |<-----------data_len------->|      conn->buf + conn->buf_size
-
-  buf = conn->buf + conn->request_len;
-  buf_len = conn->buf_size - conn->request_len;
-  len = conn->data_len - conn->request_len;
-
-  for (;;) {
-    // Pull in headers
-    assert(len >= 0 && len <= buf_len);
-    to_read = buf_len - len;
-    if (to_read > left_to_read(conn)) {
-      to_read = (int) left_to_read(conn);
-    }
-    while (len < buf_len &&
-           (n = pull(NULL, conn, buf + len, to_read)) > 0) {
-      len += n;
-    }
-    if ((headers_len = get_request_len(buf, len)) <= 0) {
-      break;
-    }
-
-    // Fetch file name.
-    fname[0] = '\0';
-    for (i = j = 0; i < headers_len; i++) {
-      if (buf[i] == '\r' && buf[i + 1] == '\n') {
-        buf[i] = buf[i + 1] = '\0';
-        // TODO(lsm): don't expect filename to be the 3rd field,
-        // parse the header properly instead.
-        sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
-               fname);
-        j = i + 2;
-      }
-    }
-
-    // Give up if the headers are not what we expect
-    if (fname[0] == '\0') {
-      break;
-    }
-
-    // Move data to the beginning of the buffer
-    assert(len >= headers_len);
-    memmove(buf, &buf[headers_len], len - headers_len);
-    len -= headers_len;
-    conn->data_len = conn->request_len + len;
-
-    // We open the file with exclusive lock held. This guarantee us
-    // there is no other thread can save into the same file simultaneously.
-    fp = NULL;
-
-    // Construct destination file name. Do not allow paths to have slashes.
-    if ((s = strrchr(fname, '/')) == NULL &&
-        (s = strrchr(fname, '\\')) == NULL) {
-      s = fname;
-    }
-
-    // Open file in binary mode. TODO: set an exclusive lock.
-    snprintf(path, path_len, "%s/%s", destination_dir, s);
-    if ((fp = fopen(path, "wb")) == NULL) {
-      break;
-    }
-
-    // Read POST data, write into file until boundary is found.
-    eof = n = 0;
-    do {
-      len += n;
-      for (i = 0; i < len - bl; i++) {
-        if (!memcmp(&buf[i], "\r\n--", 4) &&
-            !memcmp(&buf[i + 4], boundary, boundary_len)) {
-          // Found boundary, that's the end of file data.
-          fwrite(buf, 1, i, fp);
-          eof = 1;
-          memmove(buf, &buf[i + bl], len - (i + bl));
-          len -= i + bl;
-          break;
-        }
-      }
-      if (!eof && len > bl) {
-        fwrite(buf, 1, len - bl, fp);
-        memmove(buf, &buf[len - bl], bl);
-        len = bl;
-      }
-      to_read = buf_len - len;
-      if (to_read > left_to_read(conn)) {
-        to_read = (int) left_to_read(conn);
-      }
-    } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
-    conn->data_len = conn->request_len + len;
-
-    if (eof) {
-      rewind(fp);
-      return fp;
-    } else {
-      fclose(fp);
-    }
-  }
-
-  return NULL;
-}
-
 static int is_put_or_delete_request(const struct mg_connection *conn) {
   const char *s = conn->request_info.request_method;
   return s != NULL && (!strcmp(s, "PUT") ||
diff --git a/build/src/upload.c b/build/src/upload.c
new file mode 100644
index 0000000000000000000000000000000000000000..569f2ec2f5a6198669b3c9567d5cc84a7081addb
--- /dev/null
+++ b/build/src/upload.c
@@ -0,0 +1,141 @@
+#include "internal.h"
+
+FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
+                char *path, int path_len) {
+  const char *content_type_header, *boundary_start;
+  char *buf, fname[1024], boundary[100], *s;
+  int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
+  FILE *fp;
+
+  // Request looks like this:
+  //
+  // POST /upload HTTP/1.1
+  // Host: 127.0.0.1:8080
+  // Content-Length: 244894
+  // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
+  //
+  // ------WebKitFormBoundaryRVr
+  // Content-Disposition: form-data; name="file"; filename="accum.png"
+  // Content-Type: image/png
+  //
+  //  <89>PNG
+  //  <PNG DATA>
+  // ------WebKitFormBoundaryRVr
+
+  // Extract boundary string from the Content-Type header
+  if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
+      (boundary_start = mg_strcasestr(content_type_header,
+                                      "boundary=")) == NULL ||
+      (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
+       sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
+      boundary[0] == '\0') {
+    return NULL;
+  }
+
+  boundary_len = strlen(boundary);
+  bl = boundary_len + 4;  // \r\n--<boundary>
+
+  //                     buf
+  // conn->buf            |<--------- buf_len ------>|
+  //    |=================|==========|===============|
+  //    |<--request_len-->|<--len--->|               |
+  //    |<-----------data_len------->|      conn->buf + conn->buf_size
+
+  buf = conn->buf + conn->request_len;
+  buf_len = conn->buf_size - conn->request_len;
+  len = conn->data_len - conn->request_len;
+
+  for (;;) {
+    // Pull in headers
+    assert(len >= 0 && len <= buf_len);
+    to_read = buf_len - len;
+    if (to_read > left_to_read(conn)) {
+      to_read = (int) left_to_read(conn);
+    }
+    while (len < buf_len &&
+           (n = pull(NULL, conn, buf + len, to_read)) > 0) {
+      len += n;
+    }
+    if ((headers_len = get_request_len(buf, len)) <= 0) {
+      break;
+    }
+
+    // Fetch file name.
+    fname[0] = '\0';
+    for (i = j = 0; i < headers_len; i++) {
+      if (buf[i] == '\r' && buf[i + 1] == '\n') {
+        buf[i] = buf[i + 1] = '\0';
+        // TODO(lsm): don't expect filename to be the 3rd field,
+        // parse the header properly instead.
+        sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
+               fname);
+        j = i + 2;
+      }
+    }
+
+    // Give up if the headers are not what we expect
+    if (fname[0] == '\0') {
+      break;
+    }
+
+    // Move data to the beginning of the buffer
+    assert(len >= headers_len);
+    memmove(buf, &buf[headers_len], len - headers_len);
+    len -= headers_len;
+    conn->data_len = conn->request_len + len;
+
+    // We open the file with exclusive lock held. This guarantee us
+    // there is no other thread can save into the same file simultaneously.
+    fp = NULL;
+
+    // Construct destination file name. Do not allow paths to have slashes.
+    if ((s = strrchr(fname, '/')) == NULL &&
+        (s = strrchr(fname, '\\')) == NULL) {
+      s = fname;
+    }
+
+    // Open file in binary mode. TODO: set an exclusive lock.
+    snprintf(path, path_len, "%s/%s", destination_dir, s);
+    if ((fp = fopen(path, "wb")) == NULL) {
+      break;
+    }
+
+    // Read POST data, write into file until boundary is found.
+    eof = n = 0;
+    do {
+      len += n;
+      for (i = 0; i < len - bl; i++) {
+        if (!memcmp(&buf[i], "\r\n--", 4) &&
+            !memcmp(&buf[i + 4], boundary, boundary_len)) {
+          // Found boundary, that's the end of file data.
+          fwrite(buf, 1, i, fp);
+          eof = 1;
+          memmove(buf, &buf[i + bl], len - (i + bl));
+          len -= i + bl;
+          break;
+        }
+      }
+      if (!eof && len > bl) {
+        fwrite(buf, 1, len - bl, fp);
+        memmove(buf, &buf[len - bl], bl);
+        len = bl;
+      }
+      to_read = buf_len - len;
+      if (to_read > left_to_read(conn)) {
+        to_read = (int) left_to_read(conn);
+      }
+    } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
+    conn->data_len = conn->request_len + len;
+
+    if (eof) {
+      rewind(fp);
+      return fp;
+    } else {
+      fclose(fp);
+    }
+  }
+
+  return NULL;
+}
+
+
diff --git a/mongoose.c b/mongoose.c
index 1555d94a4a8e2c99a77269338efe84d26c1b81b9..6cb113e3ab51408efb2a8b96104d2e9f62988432 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -3401,6 +3401,146 @@ done:
 #endif // !NO_CGI
 
 
+FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
+                char *path, int path_len) {
+  const char *content_type_header, *boundary_start;
+  char *buf, fname[1024], boundary[100], *s;
+  int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
+  FILE *fp;
+
+  // Request looks like this:
+  //
+  // POST /upload HTTP/1.1
+  // Host: 127.0.0.1:8080
+  // Content-Length: 244894
+  // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
+  //
+  // ------WebKitFormBoundaryRVr
+  // Content-Disposition: form-data; name="file"; filename="accum.png"
+  // Content-Type: image/png
+  //
+  //  <89>PNG
+  //  <PNG DATA>
+  // ------WebKitFormBoundaryRVr
+
+  // Extract boundary string from the Content-Type header
+  if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
+      (boundary_start = mg_strcasestr(content_type_header,
+                                      "boundary=")) == NULL ||
+      (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
+       sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
+      boundary[0] == '\0') {
+    return NULL;
+  }
+
+  boundary_len = strlen(boundary);
+  bl = boundary_len + 4;  // \r\n--<boundary>
+
+  //                     buf
+  // conn->buf            |<--------- buf_len ------>|
+  //    |=================|==========|===============|
+  //    |<--request_len-->|<--len--->|               |
+  //    |<-----------data_len------->|      conn->buf + conn->buf_size
+
+  buf = conn->buf + conn->request_len;
+  buf_len = conn->buf_size - conn->request_len;
+  len = conn->data_len - conn->request_len;
+
+  for (;;) {
+    // Pull in headers
+    assert(len >= 0 && len <= buf_len);
+    to_read = buf_len - len;
+    if (to_read > left_to_read(conn)) {
+      to_read = (int) left_to_read(conn);
+    }
+    while (len < buf_len &&
+           (n = pull(NULL, conn, buf + len, to_read)) > 0) {
+      len += n;
+    }
+    if ((headers_len = get_request_len(buf, len)) <= 0) {
+      break;
+    }
+
+    // Fetch file name.
+    fname[0] = '\0';
+    for (i = j = 0; i < headers_len; i++) {
+      if (buf[i] == '\r' && buf[i + 1] == '\n') {
+        buf[i] = buf[i + 1] = '\0';
+        // TODO(lsm): don't expect filename to be the 3rd field,
+        // parse the header properly instead.
+        sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
+               fname);
+        j = i + 2;
+      }
+    }
+
+    // Give up if the headers are not what we expect
+    if (fname[0] == '\0') {
+      break;
+    }
+
+    // Move data to the beginning of the buffer
+    assert(len >= headers_len);
+    memmove(buf, &buf[headers_len], len - headers_len);
+    len -= headers_len;
+    conn->data_len = conn->request_len + len;
+
+    // We open the file with exclusive lock held. This guarantee us
+    // there is no other thread can save into the same file simultaneously.
+    fp = NULL;
+
+    // Construct destination file name. Do not allow paths to have slashes.
+    if ((s = strrchr(fname, '/')) == NULL &&
+        (s = strrchr(fname, '\\')) == NULL) {
+      s = fname;
+    }
+
+    // Open file in binary mode. TODO: set an exclusive lock.
+    snprintf(path, path_len, "%s/%s", destination_dir, s);
+    if ((fp = fopen(path, "wb")) == NULL) {
+      break;
+    }
+
+    // Read POST data, write into file until boundary is found.
+    eof = n = 0;
+    do {
+      len += n;
+      for (i = 0; i < len - bl; i++) {
+        if (!memcmp(&buf[i], "\r\n--", 4) &&
+            !memcmp(&buf[i + 4], boundary, boundary_len)) {
+          // Found boundary, that's the end of file data.
+          fwrite(buf, 1, i, fp);
+          eof = 1;
+          memmove(buf, &buf[i + bl], len - (i + bl));
+          len -= i + bl;
+          break;
+        }
+      }
+      if (!eof && len > bl) {
+        fwrite(buf, 1, len - bl, fp);
+        memmove(buf, &buf[len - bl], bl);
+        len = bl;
+      }
+      to_read = buf_len - len;
+      if (to_read > left_to_read(conn)) {
+        to_read = (int) left_to_read(conn);
+      }
+    } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
+    conn->data_len = conn->request_len + len;
+
+    if (eof) {
+      rewind(fp);
+      return fp;
+    } else {
+      fclose(fp);
+    }
+  }
+
+  return NULL;
+}
+
+
+
 static int call_user(int type, struct mg_connection *conn, void *p) {
   if (conn != NULL && conn->ctx != NULL) {
     conn->event.user_data = conn->ctx->user_data;
@@ -4374,144 +4514,6 @@ static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
   return len;
 }
 
-FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
-                char *path, int path_len) {
-  const char *content_type_header, *boundary_start;
-  char *buf, fname[1024], boundary[100], *s;
-  int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
-  FILE *fp;
-
-  // Request looks like this:
-  //
-  // POST /upload HTTP/1.1
-  // Host: 127.0.0.1:8080
-  // Content-Length: 244894
-  // Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRVr
-  //
-  // ------WebKitFormBoundaryRVr
-  // Content-Disposition: form-data; name="file"; filename="accum.png"
-  // Content-Type: image/png
-  //
-  //  <89>PNG
-  //  <PNG DATA>
-  // ------WebKitFormBoundaryRVr
-
-  // Extract boundary string from the Content-Type header
-  if ((content_type_header = mg_get_header(conn, "Content-Type")) == NULL ||
-      (boundary_start = mg_strcasestr(content_type_header,
-                                      "boundary=")) == NULL ||
-      (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
-       sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
-      boundary[0] == '\0') {
-    return NULL;
-  }
-
-  boundary_len = strlen(boundary);
-  bl = boundary_len + 4;  // \r\n--<boundary>
-
-  //                     buf
-  // conn->buf            |<--------- buf_len ------>|
-  //    |=================|==========|===============|
-  //    |<--request_len-->|<--len--->|               |
-  //    |<-----------data_len------->|      conn->buf + conn->buf_size
-
-  buf = conn->buf + conn->request_len;
-  buf_len = conn->buf_size - conn->request_len;
-  len = conn->data_len - conn->request_len;
-
-  for (;;) {
-    // Pull in headers
-    assert(len >= 0 && len <= buf_len);
-    to_read = buf_len - len;
-    if (to_read > left_to_read(conn)) {
-      to_read = (int) left_to_read(conn);
-    }
-    while (len < buf_len &&
-           (n = pull(NULL, conn, buf + len, to_read)) > 0) {
-      len += n;
-    }
-    if ((headers_len = get_request_len(buf, len)) <= 0) {
-      break;
-    }
-
-    // Fetch file name.
-    fname[0] = '\0';
-    for (i = j = 0; i < headers_len; i++) {
-      if (buf[i] == '\r' && buf[i + 1] == '\n') {
-        buf[i] = buf[i + 1] = '\0';
-        // TODO(lsm): don't expect filename to be the 3rd field,
-        // parse the header properly instead.
-        sscanf(&buf[j], "Content-Disposition: %*s %*s filename=\"%1023[^\"]",
-               fname);
-        j = i + 2;
-      }
-    }
-
-    // Give up if the headers are not what we expect
-    if (fname[0] == '\0') {
-      break;
-    }
-
-    // Move data to the beginning of the buffer
-    assert(len >= headers_len);
-    memmove(buf, &buf[headers_len], len - headers_len);
-    len -= headers_len;
-    conn->data_len = conn->request_len + len;
-
-    // We open the file with exclusive lock held. This guarantee us
-    // there is no other thread can save into the same file simultaneously.
-    fp = NULL;
-
-    // Construct destination file name. Do not allow paths to have slashes.
-    if ((s = strrchr(fname, '/')) == NULL &&
-        (s = strrchr(fname, '\\')) == NULL) {
-      s = fname;
-    }
-
-    // Open file in binary mode. TODO: set an exclusive lock.
-    snprintf(path, path_len, "%s/%s", destination_dir, s);
-    if ((fp = fopen(path, "wb")) == NULL) {
-      break;
-    }
-
-    // Read POST data, write into file until boundary is found.
-    eof = n = 0;
-    do {
-      len += n;
-      for (i = 0; i < len - bl; i++) {
-        if (!memcmp(&buf[i], "\r\n--", 4) &&
-            !memcmp(&buf[i + 4], boundary, boundary_len)) {
-          // Found boundary, that's the end of file data.
-          fwrite(buf, 1, i, fp);
-          eof = 1;
-          memmove(buf, &buf[i + bl], len - (i + bl));
-          len -= i + bl;
-          break;
-        }
-      }
-      if (!eof && len > bl) {
-        fwrite(buf, 1, len - bl, fp);
-        memmove(buf, &buf[len - bl], bl);
-        len = bl;
-      }
-      to_read = buf_len - len;
-      if (to_read > left_to_read(conn)) {
-        to_read = (int) left_to_read(conn);
-      }
-    } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
-    conn->data_len = conn->request_len + len;
-
-    if (eof) {
-      rewind(fp);
-      return fp;
-    } else {
-      fclose(fp);
-    }
-  }
-
-  return NULL;
-}
-
 static int is_put_or_delete_request(const struct mg_connection *conn) {
   const char *s = conn->request_info.request_method;
   return s != NULL && (!strcmp(s, "PUT") ||