diff --git a/build/Makefile b/build/Makefile
index e6da94815225b174aa651881fa67f31d31532472..8fcbb8965340a140afb63c94caf86c3aa8128638 100644
--- a/build/Makefile
+++ b/build/Makefile
@@ -27,7 +27,7 @@ VERSION = $(shell perl -lne \
 
 # The order in which files are listed is important
 SOURCES = src/internal.h src/string.c src/parse_date.c src/options.c \
-          src/mongoose.c
+          src/crypto.c src/auth.c src/mongoose.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/auth.c b/build/src/auth.c
new file mode 100644
index 0000000000000000000000000000000000000000..a527f2b55b025826f897309e7197fe7de4d4222c
--- /dev/null
+++ b/build/src/auth.c
@@ -0,0 +1,301 @@
+#include "internal.h"
+
+// Stringify binary data. Output buffer must be twice as big as input,
+// because each byte takes 2 bytes in string representation
+static void bin2str(char *to, const unsigned char *p, size_t len) {
+  static const char *hex = "0123456789abcdef";
+
+  for (; len--; p++) {
+    *to++ = hex[p[0] >> 4];
+    *to++ = hex[p[0] & 0x0f];
+  }
+  *to = '\0';
+}
+
+// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
+char *mg_md5(char buf[33], ...) {
+  unsigned char hash[16];
+  const char *p;
+  va_list ap;
+  MD5_CTX ctx;
+
+  MD5Init(&ctx);
+
+  va_start(ap, buf);
+  while ((p = va_arg(ap, const char *)) != NULL) {
+    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
+  }
+  va_end(ap);
+
+  MD5Final(hash, &ctx);
+  bin2str(buf, hash, sizeof(hash));
+  return buf;
+}
+
+// Check the user's password, return 1 if OK
+static int check_password(const char *method, const char *ha1, const char *uri,
+                          const char *nonce, const char *nc, const char *cnonce,
+                          const char *qop, const char *response) {
+  char ha2[32 + 1], expected_response[32 + 1];
+
+  // Some of the parameters may be NULL
+  if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
+      qop == NULL || response == NULL) {
+    return 0;
+  }
+
+  // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
+  // TODO(lsm): check for authentication timeout
+  if (// strcmp(dig->uri, c->ouri) != 0 ||
+      strlen(response) != 32
+      // || now - strtoul(dig->nonce, NULL, 10) > 3600
+      ) {
+    return 0;
+  }
+
+  mg_md5(ha2, method, ":", uri, NULL);
+  mg_md5(expected_response, ha1, ":", nonce, ":", nc,
+      ":", cnonce, ":", qop, ":", ha2, NULL);
+
+  return mg_strcasecmp(response, expected_response) == 0;
+}
+
+// Use the global passwords file, if specified by auth_gpass option,
+// or search for .htpasswd in the requested directory.
+static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
+  char name[PATH_MAX];
+  const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
+  struct file file = STRUCT_FILE_INITIALIZER;
+  FILE *fp = NULL;
+
+  if (gpass != NULL) {
+    // Use global passwords file
+    fp = mg_fopen(gpass, "r");
+    // Important: using local struct file to test path for is_directory flag.
+    // If filep is used, mg_stat() makes it appear as if auth file was opened.
+  } else if (mg_stat(path, &file) && file.is_directory) {
+    mg_snprintf(name, sizeof(name), "%s%c%s",
+                path, '/', PASSWORDS_FILE_NAME);
+    fp = mg_fopen(name, "r");
+  } else {
+     // Try to find .htpasswd in requested directory.
+    for (p = path, e = p + strlen(p) - 1; e > p; e--)
+      if (e[0] == '/')
+        break;
+    mg_snprintf(name, sizeof(name), "%.*s%c%s",
+                (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
+    fp = mg_fopen(name, "r");
+  }
+
+  return fp;
+}
+
+// Parsed Authorization header
+struct ah {
+  char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
+};
+
+// Return 1 on success. Always initializes the ah structure.
+static int parse_auth_header(struct mg_connection *conn, char *buf,
+                             size_t buf_size, struct ah *ah) {
+  char *name, *value, *s;
+  const char *auth_header;
+
+  (void) memset(ah, 0, sizeof(*ah));
+  if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
+      mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
+    return 0;
+  }
+
+  // Make modifiable copy of the auth header
+  (void) mg_strlcpy(buf, auth_header + 7, buf_size);
+  s = buf;
+
+  // Parse authorization header
+  for (;;) {
+    // Gobble initial spaces
+    while (isspace(* (unsigned char *) s)) {
+      s++;
+    }
+    name = skip_quoted(&s, "=", " ", 0);
+    // Value is either quote-delimited, or ends at first comma or space.
+    if (s[0] == '\"') {
+      s++;
+      value = skip_quoted(&s, "\"", " ", '\\');
+      if (s[0] == ',') {
+        s++;
+      }
+    } else {
+      value = skip_quoted(&s, ", ", " ", 0);  // IE uses commas, FF uses spaces
+    }
+    if (*name == '\0') {
+      break;
+    }
+
+    if (!strcmp(name, "username")) {
+      ah->user = value;
+    } else if (!strcmp(name, "cnonce")) {
+      ah->cnonce = value;
+    } else if (!strcmp(name, "response")) {
+      ah->response = value;
+    } else if (!strcmp(name, "uri")) {
+      ah->uri = value;
+    } else if (!strcmp(name, "qop")) {
+      ah->qop = value;
+    } else if (!strcmp(name, "nc")) {
+      ah->nc = value;
+    } else if (!strcmp(name, "nonce")) {
+      ah->nonce = value;
+    }
+  }
+
+  // CGI needs it as REMOTE_USER
+  if (ah->user != NULL) {
+    conn->request_info.remote_user = mg_strdup(ah->user);
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+// Authorize against the opened passwords file. Return 1 if authorized.
+static int authorize(struct mg_connection *conn, FILE *fp) {
+  struct ah ah;
+  char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
+
+  if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
+    return 0;
+  }
+
+  // Loop over passwords file
+  while (fgets(line, sizeof(line), fp) != NULL) {
+    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
+      continue;
+    }
+
+    if (!strcmp(ah.user, f_user) &&
+        !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
+      return check_password(conn->request_info.request_method, ha1, ah.uri,
+                            ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
+  }
+
+  return 0;
+}
+
+// Return 1 if request is authorised, 0 otherwise.
+static int check_authorization(struct mg_connection *conn, const char *path) {
+  char fname[PATH_MAX];
+  struct vec uri_vec, filename_vec;
+  const char *list;
+  FILE *fp = NULL;
+  int authorized = 1;
+
+  list = conn->ctx->config[PROTECT_URI];
+  while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
+    if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
+      mg_snprintf(fname, sizeof(fname), "%.*s",
+                  (int) filename_vec.len, filename_vec.ptr);
+      fp = mg_fopen(fname, "r");
+      break;
+    }
+  }
+
+  if (fp == NULL) {
+    fp = open_auth_file(conn, path);
+  }
+
+  if (fp != NULL) {
+    authorized = authorize(conn, fp);
+    fclose(fp);
+  }
+
+  return authorized;
+}
+
+static void send_authorization_request(struct mg_connection *conn) {
+  conn->status_code = 401;
+  mg_printf(conn,
+            "HTTP/1.1 401 Unauthorized\r\n"
+            "Content-Length: 0\r\n"
+            "WWW-Authenticate: Digest qop=\"auth\", "
+            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+            conn->ctx->config[AUTHENTICATION_DOMAIN],
+            (unsigned long) time(NULL));
+}
+
+static int is_authorized_for_put(struct mg_connection *conn) {
+  const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
+  FILE *fp;
+  int ret = 0;
+
+  if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
+    ret = authorize(conn, fp);
+    fclose(fp);
+  }
+
+  return ret;
+}
+
+int mg_modify_passwords_file(const char *fname, const char *domain,
+                             const char *user, const char *pass) {
+  int found;
+  char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
+  FILE *fp, *fp2;
+
+  found = 0;
+  fp = fp2 = NULL;
+
+  // Regard empty password as no password - remove user record.
+  if (pass != NULL && pass[0] == '\0') {
+    pass = NULL;
+  }
+
+  (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
+
+  // Create the file if does not exist
+  if ((fp = fopen(fname, "a+")) != NULL) {
+    fclose(fp);
+  }
+
+  // Open the given file and temporary file
+  if ((fp = fopen(fname, "r")) == NULL) {
+    return 0;
+  } else if ((fp2 = fopen(tmp, "w+")) == NULL) {
+    fclose(fp);
+    return 0;
+  }
+
+  // Copy the stuff to temporary file
+  while (fgets(line, sizeof(line), fp) != NULL) {
+    if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
+      continue;
+    }
+
+    if (!strcmp(u, user) && !strcmp(d, domain)) {
+      found++;
+      if (pass != NULL) {
+        mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+        fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+      }
+    } else {
+      fprintf(fp2, "%s", line);
+    }
+  }
+
+  // If new user, just add it
+  if (!found && pass != NULL) {
+    mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+    fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+  }
+
+  // Close files
+  fclose(fp);
+  fclose(fp2);
+
+  // Put the temp file in place of real file
+  remove(fname);
+  rename(tmp, fname);
+
+  return 1;
+}
diff --git a/build/src/crypto.c b/build/src/crypto.c
new file mode 100644
index 0000000000000000000000000000000000000000..ede02558f8ddce5ffe866577131db3ed155e0d6e
--- /dev/null
+++ b/build/src/crypto.c
@@ -0,0 +1,196 @@
+static int is_big_endian(void) {
+  static const int n = 1;
+  return ((char *) &n)[0] == 0;
+}
+
+#ifndef HAVE_MD5
+typedef struct MD5Context {
+  uint32_t buf[4];
+  uint32_t bits[2];
+  unsigned char in[64];
+} MD5_CTX;
+
+static void byteReverse(unsigned char *buf, unsigned longs) {
+  uint32_t t;
+
+  // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
+  if (is_big_endian()) {
+    do {
+      t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+        ((unsigned) buf[1] << 8 | buf[0]);
+      * (uint32_t *) buf = t;
+      buf += 4;
+    } while (--longs);
+  }
+}
+
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+#define MD5STEP(f, w, x, y, z, data, s) \
+  ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+// initialization constants.
+static void MD5Init(MD5_CTX *ctx) {
+  ctx->buf[0] = 0x67452301;
+  ctx->buf[1] = 0xefcdab89;
+  ctx->buf[2] = 0x98badcfe;
+  ctx->buf[3] = 0x10325476;
+
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
+}
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
+  register uint32_t a, b, c, d;
+
+  a = buf[0];
+  b = buf[1];
+  c = buf[2];
+  d = buf[3];
+
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
+}
+
+static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
+  uint32_t t;
+
+  t = ctx->bits[0];
+  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+    ctx->bits[1]++;
+  ctx->bits[1] += len >> 29;
+
+  t = (t >> 3) & 0x3f;
+
+  if (t) {
+    unsigned char *p = (unsigned char *) ctx->in + t;
+
+    t = 64 - t;
+    if (len < t) {
+      memcpy(p, buf, len);
+      return;
+    }
+    memcpy(p, buf, t);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += t;
+    len -= t;
+  }
+
+  while (len >= 64) {
+    memcpy(ctx->in, buf, 64);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += 64;
+    len -= 64;
+  }
+
+  memcpy(ctx->in, buf, len);
+}
+
+static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
+  unsigned count;
+  unsigned char *p;
+  uint32_t *a;
+
+  count = (ctx->bits[0] >> 3) & 0x3F;
+
+  p = ctx->in + count;
+  *p++ = 0x80;
+  count = 64 - 1 - count;
+  if (count < 8) {
+    memset(p, 0, count);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    memset(ctx->in, 0, 56);
+  } else {
+    memset(p, 0, count - 8);
+  }
+  byteReverse(ctx->in, 14);
+
+  a = (uint32_t *)ctx->in;
+  a[14] = ctx->bits[0];
+  a[15] = ctx->bits[1];
+
+  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+  byteReverse((unsigned char *) ctx->buf, 4);
+  memcpy(digest, ctx->buf, 16);
+  memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif // !HAVE_MD5
+
+
diff --git a/build/src/internal.h b/build/src/internal.h
index c75298ea915b469cb2b50d33708510d0d3701952..d03d98f0fb1415e95067e1039a2387a4c1f7b2d6 100644
--- a/build/src/internal.h
+++ b/build/src/internal.h
@@ -458,3 +458,5 @@ struct de {
   struct file file;
 };
 
+static FILE *mg_fopen(const char *path, const char *mode);
+static int mg_stat(const char *path, struct file *filep);
diff --git a/build/src/mongoose.c b/build/src/mongoose.c
index c68b09dcbe1c506469c19d9f3c636b6fc9bde561..d61b7d472e651e59bf5dcdc6674705dfb16c3b30 100644
--- a/build/src/mongoose.c
+++ b/build/src/mongoose.c
@@ -1205,505 +1205,6 @@ static void get_mime_type(struct mg_context *ctx, const char *path,
   vec->len = strlen(vec->ptr);
 }
 
-static int is_big_endian(void) {
-  static const int n = 1;
-  return ((char *) &n)[0] == 0;
-}
-
-#ifndef HAVE_MD5
-typedef struct MD5Context {
-  uint32_t buf[4];
-  uint32_t bits[2];
-  unsigned char in[64];
-} MD5_CTX;
-
-static void byteReverse(unsigned char *buf, unsigned longs) {
-  uint32_t t;
-
-  // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
-  if (is_big_endian()) {
-    do {
-      t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
-        ((unsigned) buf[1] << 8 | buf[0]);
-      * (uint32_t *) buf = t;
-      buf += 4;
-    } while (--longs);
-  }
-}
-
-#define F1(x, y, z) (z ^ (x & (y ^ z)))
-#define F2(x, y, z) F1(z, x, y)
-#define F3(x, y, z) (x ^ y ^ z)
-#define F4(x, y, z) (y ^ (x | ~z))
-
-#define MD5STEP(f, w, x, y, z, data, s) \
-  ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
-
-// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
-// initialization constants.
-static void MD5Init(MD5_CTX *ctx) {
-  ctx->buf[0] = 0x67452301;
-  ctx->buf[1] = 0xefcdab89;
-  ctx->buf[2] = 0x98badcfe;
-  ctx->buf[3] = 0x10325476;
-
-  ctx->bits[0] = 0;
-  ctx->bits[1] = 0;
-}
-
-static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
-  register uint32_t a, b, c, d;
-
-  a = buf[0];
-  b = buf[1];
-  c = buf[2];
-  d = buf[3];
-
-  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
-  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
-  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
-  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
-  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
-  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
-  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
-  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
-  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
-  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
-  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
-  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
-  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
-  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
-  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
-  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
-
-  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
-  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
-  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
-  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
-  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
-  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
-  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
-  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
-  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
-  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
-  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
-  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
-  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
-  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
-  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
-  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
-
-  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
-  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
-  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
-  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
-  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
-  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
-  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
-  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
-  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
-  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
-  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
-  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
-  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
-  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
-  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
-  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
-
-  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
-  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
-  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
-  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
-  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
-  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
-  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
-  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
-  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
-  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
-  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
-  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
-  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
-  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
-  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
-  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
-
-  buf[0] += a;
-  buf[1] += b;
-  buf[2] += c;
-  buf[3] += d;
-}
-
-static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
-  uint32_t t;
-
-  t = ctx->bits[0];
-  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
-    ctx->bits[1]++;
-  ctx->bits[1] += len >> 29;
-
-  t = (t >> 3) & 0x3f;
-
-  if (t) {
-    unsigned char *p = (unsigned char *) ctx->in + t;
-
-    t = 64 - t;
-    if (len < t) {
-      memcpy(p, buf, len);
-      return;
-    }
-    memcpy(p, buf, t);
-    byteReverse(ctx->in, 16);
-    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += t;
-    len -= t;
-  }
-
-  while (len >= 64) {
-    memcpy(ctx->in, buf, 64);
-    byteReverse(ctx->in, 16);
-    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += 64;
-    len -= 64;
-  }
-
-  memcpy(ctx->in, buf, len);
-}
-
-static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
-  unsigned count;
-  unsigned char *p;
-  uint32_t *a;
-
-  count = (ctx->bits[0] >> 3) & 0x3F;
-
-  p = ctx->in + count;
-  *p++ = 0x80;
-  count = 64 - 1 - count;
-  if (count < 8) {
-    memset(p, 0, count);
-    byteReverse(ctx->in, 16);
-    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-    memset(ctx->in, 0, 56);
-  } else {
-    memset(p, 0, count - 8);
-  }
-  byteReverse(ctx->in, 14);
-
-  a = (uint32_t *)ctx->in;
-  a[14] = ctx->bits[0];
-  a[15] = ctx->bits[1];
-
-  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-  byteReverse((unsigned char *) ctx->buf, 4);
-  memcpy(digest, ctx->buf, 16);
-  memset((char *) ctx, 0, sizeof(*ctx));
-}
-#endif // !HAVE_MD5
-
-// Stringify binary data. Output buffer must be twice as big as input,
-// because each byte takes 2 bytes in string representation
-static void bin2str(char *to, const unsigned char *p, size_t len) {
-  static const char *hex = "0123456789abcdef";
-
-  for (; len--; p++) {
-    *to++ = hex[p[0] >> 4];
-    *to++ = hex[p[0] & 0x0f];
-  }
-  *to = '\0';
-}
-
-// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
-char *mg_md5(char buf[33], ...) {
-  unsigned char hash[16];
-  const char *p;
-  va_list ap;
-  MD5_CTX ctx;
-
-  MD5Init(&ctx);
-
-  va_start(ap, buf);
-  while ((p = va_arg(ap, const char *)) != NULL) {
-    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
-  }
-  va_end(ap);
-
-  MD5Final(hash, &ctx);
-  bin2str(buf, hash, sizeof(hash));
-  return buf;
-}
-
-// Check the user's password, return 1 if OK
-static int check_password(const char *method, const char *ha1, const char *uri,
-                          const char *nonce, const char *nc, const char *cnonce,
-                          const char *qop, const char *response) {
-  char ha2[32 + 1], expected_response[32 + 1];
-
-  // Some of the parameters may be NULL
-  if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
-      qop == NULL || response == NULL) {
-    return 0;
-  }
-
-  // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
-  // TODO(lsm): check for authentication timeout
-  if (// strcmp(dig->uri, c->ouri) != 0 ||
-      strlen(response) != 32
-      // || now - strtoul(dig->nonce, NULL, 10) > 3600
-      ) {
-    return 0;
-  }
-
-  mg_md5(ha2, method, ":", uri, NULL);
-  mg_md5(expected_response, ha1, ":", nonce, ":", nc,
-      ":", cnonce, ":", qop, ":", ha2, NULL);
-
-  return mg_strcasecmp(response, expected_response) == 0;
-}
-
-// Use the global passwords file, if specified by auth_gpass option,
-// or search for .htpasswd in the requested directory.
-static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
-  char name[PATH_MAX];
-  const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
-  struct file file = STRUCT_FILE_INITIALIZER;
-  FILE *fp = NULL;
-
-  if (gpass != NULL) {
-    // Use global passwords file
-    if ((fp = mg_fopen(gpass, "r")) == NULL) {
-      cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO));
-    }
-    // Important: using local struct file to test path for is_directory flag.
-    // If filep is used, mg_stat() makes it appear as if auth file was opened.
-  } else if (mg_stat(path, &file) && file.is_directory) {
-    mg_snprintf(name, sizeof(name), "%s%c%s",
-                path, '/', PASSWORDS_FILE_NAME);
-    fp = mg_fopen(name, "r");
-  } else {
-     // Try to find .htpasswd in requested directory.
-    for (p = path, e = p + strlen(p) - 1; e > p; e--)
-      if (e[0] == '/')
-        break;
-    mg_snprintf(name, sizeof(name), "%.*s%c%s",
-                (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
-    fp = mg_fopen(name, "r");
-  }
-
-  return fp;
-}
-
-// Parsed Authorization header
-struct ah {
-  char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
-};
-
-// Return 1 on success. Always initializes the ah structure.
-static int parse_auth_header(struct mg_connection *conn, char *buf,
-                             size_t buf_size, struct ah *ah) {
-  char *name, *value, *s;
-  const char *auth_header;
-
-  (void) memset(ah, 0, sizeof(*ah));
-  if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
-      mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
-    return 0;
-  }
-
-  // Make modifiable copy of the auth header
-  (void) mg_strlcpy(buf, auth_header + 7, buf_size);
-  s = buf;
-
-  // Parse authorization header
-  for (;;) {
-    // Gobble initial spaces
-    while (isspace(* (unsigned char *) s)) {
-      s++;
-    }
-    name = skip_quoted(&s, "=", " ", 0);
-    // Value is either quote-delimited, or ends at first comma or space.
-    if (s[0] == '\"') {
-      s++;
-      value = skip_quoted(&s, "\"", " ", '\\');
-      if (s[0] == ',') {
-        s++;
-      }
-    } else {
-      value = skip_quoted(&s, ", ", " ", 0);  // IE uses commas, FF uses spaces
-    }
-    if (*name == '\0') {
-      break;
-    }
-
-    if (!strcmp(name, "username")) {
-      ah->user = value;
-    } else if (!strcmp(name, "cnonce")) {
-      ah->cnonce = value;
-    } else if (!strcmp(name, "response")) {
-      ah->response = value;
-    } else if (!strcmp(name, "uri")) {
-      ah->uri = value;
-    } else if (!strcmp(name, "qop")) {
-      ah->qop = value;
-    } else if (!strcmp(name, "nc")) {
-      ah->nc = value;
-    } else if (!strcmp(name, "nonce")) {
-      ah->nonce = value;
-    }
-  }
-
-  // CGI needs it as REMOTE_USER
-  if (ah->user != NULL) {
-    conn->request_info.remote_user = mg_strdup(ah->user);
-  } else {
-    return 0;
-  }
-
-  return 1;
-}
-
-// Authorize against the opened passwords file. Return 1 if authorized.
-static int authorize(struct mg_connection *conn, FILE *fp) {
-  struct ah ah;
-  char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
-
-  if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
-    return 0;
-  }
-
-  // Loop over passwords file
-  while (fgets(line, sizeof(line), fp) != NULL) {
-    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
-      continue;
-    }
-
-    if (!strcmp(ah.user, f_user) &&
-        !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
-      return check_password(conn->request_info.request_method, ha1, ah.uri,
-                            ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
-  }
-
-  return 0;
-}
-
-// Return 1 if request is authorised, 0 otherwise.
-static int check_authorization(struct mg_connection *conn, const char *path) {
-  char fname[PATH_MAX];
-  struct vec uri_vec, filename_vec;
-  const char *list;
-  FILE *fp = NULL;
-  int authorized = 1;
-
-  list = conn->ctx->config[PROTECT_URI];
-  while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
-    if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
-      mg_snprintf(fname, sizeof(fname), "%.*s",
-                  (int) filename_vec.len, filename_vec.ptr);
-      if ((fp = mg_fopen(fname, "r")) == NULL) {
-        cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
-      }
-      break;
-    }
-  }
-
-  if (fp == NULL) {
-    fp = open_auth_file(conn, path);
-  }
-
-  if (fp != NULL) {
-    authorized = authorize(conn, fp);
-    fclose(fp);
-  }
-
-  return authorized;
-}
-
-static void send_authorization_request(struct mg_connection *conn) {
-  conn->status_code = 401;
-  mg_printf(conn,
-            "HTTP/1.1 401 Unauthorized\r\n"
-            "Content-Length: 0\r\n"
-            "WWW-Authenticate: Digest qop=\"auth\", "
-            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
-            conn->ctx->config[AUTHENTICATION_DOMAIN],
-            (unsigned long) time(NULL));
-}
-
-static int is_authorized_for_put(struct mg_connection *conn) {
-  const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
-  FILE *fp;
-  int ret = 0;
-
-  if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
-    ret = authorize(conn, fp);
-    fclose(fp);
-  }
-
-  return ret;
-}
-
-int mg_modify_passwords_file(const char *fname, const char *domain,
-                             const char *user, const char *pass) {
-  int found;
-  char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
-  FILE *fp, *fp2;
-
-  found = 0;
-  fp = fp2 = NULL;
-
-  // Regard empty password as no password - remove user record.
-  if (pass != NULL && pass[0] == '\0') {
-    pass = NULL;
-  }
-
-  (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
-
-  // Create the file if does not exist
-  if ((fp = fopen(fname, "a+")) != NULL) {
-    fclose(fp);
-  }
-
-  // Open the given file and temporary file
-  if ((fp = fopen(fname, "r")) == NULL) {
-    return 0;
-  } else if ((fp2 = fopen(tmp, "w+")) == NULL) {
-    fclose(fp);
-    return 0;
-  }
-
-  // Copy the stuff to temporary file
-  while (fgets(line, sizeof(line), fp) != NULL) {
-    if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
-      continue;
-    }
-
-    if (!strcmp(u, user) && !strcmp(d, domain)) {
-      found++;
-      if (pass != NULL) {
-        mg_md5(ha1, user, ":", domain, ":", pass, NULL);
-        fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
-      }
-    } else {
-      fprintf(fp2, "%s", line);
-    }
-  }
-
-  // If new user, just add it
-  if (!found && pass != NULL) {
-    mg_md5(ha1, user, ":", domain, ":", pass, NULL);
-    fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
-  }
-
-  // Close files
-  fclose(fp);
-  fclose(fp2);
-
-  // Put the temp file in place of real file
-  remove(fname);
-  rename(tmp, fname);
-
-  return 1;
-}
-
 static SOCKET conn2(const char *host, int port, int use_ssl,
                     char *ebuf, size_t ebuf_len) {
   struct sockaddr_in sin;
diff --git a/mongoose.c b/mongoose.c
index 1a9ec6cba19e7fd325da732b5d89d564d8f89125..283fc4d4dc8e323cd1a18aaf91702bc56cd86748 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -458,6 +458,8 @@ struct de {
   struct file file;
 };
 
+static FILE *mg_fopen(const char *path, const char *mode);
+static int mg_stat(const char *path, struct file *filep);
 
 static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
   for (; *src != '\0' && n > 1; n--) {
@@ -806,1709 +808,1706 @@ const char *mg_get_option(const struct mg_context *ctx, const char *name) {
     return ctx->config[i];
   }
 }
-
-// Return number of bytes left to read for this connection
-static int64_t left_to_read(const struct mg_connection *conn) {
-  return conn->content_len + conn->request_len - conn->num_bytes_read;
-}
-
-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;
-    conn->event.type = type;
-    conn->event.event_param = p;
-    conn->event.request_info = &conn->request_info;
-    conn->event.conn = conn;
-  }
-  return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
-    0 : conn->ctx->event_handler(&conn->event);
-}
-
-static FILE *mg_fopen(const char *path, const char *mode) {
-#ifdef _WIN32
-  wchar_t wbuf[PATH_MAX], wmode[20];
-  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
-  MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
-  return _wfopen(wbuf, wmode);
-#else
-  return fopen(path, mode);
-#endif
-}
-
-static void sockaddr_to_string(char *buf, size_t len,
-                                     const union usa *usa) {
-  buf[0] = '\0';
-#if defined(USE_IPV6)
-  inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ?
-            (void *) &usa->sin.sin_addr :
-            (void *) &usa->sin6.sin6_addr, buf, len);
-#elif defined(_WIN32)
-  // Only Windoze Vista (and newer) have inet_ntop()
-  strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
-#else
-  inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
-#endif
-}
-
-static void cry(struct mg_connection *conn,
-                PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
-
-// Print error message to the opened error log stream.
-static void cry(struct mg_connection *conn, const char *fmt, ...) {
-  char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN];
-  va_list ap;
-  FILE *fp;
-  time_t timestamp;
-
-  va_start(ap, fmt);
-  (void) vsnprintf(buf, sizeof(buf), fmt, ap);
-  va_end(ap);
-
-  // Do not lock when getting the callback value, here and below.
-  // I suppose this is fine, since function cannot disappear in the
-  // same way string option can.
-  if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
-    fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
-      fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
-
-    if (fp != NULL) {
-      flockfile(fp);
-      timestamp = time(NULL);
-
-      sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
-      fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
-              src_addr);
-
-      if (conn->request_info.request_method != NULL) {
-        fprintf(fp, "%s %s: ", conn->request_info.request_method,
-                conn->request_info.uri);
-      }
-
-      fprintf(fp, "%s", buf);
-      fputc('\n', fp);
-      funlockfile(fp);
-      fclose(fp);
-    }
-  }
+static int is_big_endian(void) {
+  static const int n = 1;
+  return ((char *) &n)[0] == 0;
 }
 
-// Return fake connection structure. Used for logging, if connection
-// is not applicable at the moment of logging.
-static struct mg_connection *fc(struct mg_context *ctx) {
-  static struct mg_connection fake_connection;
-  fake_connection.ctx = ctx;
-  // See https://github.com/cesanta/mongoose/issues/236
-  fake_connection.event.user_data = ctx->user_data;
-  return &fake_connection;
-}
+#ifndef HAVE_MD5
+typedef struct MD5Context {
+  uint32_t buf[4];
+  uint32_t bits[2];
+  unsigned char in[64];
+} MD5_CTX;
 
-const char *mg_version(void) {
-  return MONGOOSE_VERSION;
-}
+static void byteReverse(unsigned char *buf, unsigned longs) {
+  uint32_t t;
 
-// HTTP 1.1 assumes keep alive if "Connection:" header is not set
-// This function must tolerate situations when connection info is not
-// set up, for example if request parsing failed.
-static int should_keep_alive(const struct mg_connection *conn) {
-  const char *http_version = conn->request_info.http_version;
-  const char *header = mg_get_header(conn, "Connection");
-  if (conn->must_close ||
-      conn->status_code == 401 ||
-      mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 ||
-      (header != NULL && mg_strcasecmp(header, "keep-alive") != 0) ||
-      (header == NULL && http_version && strcmp(http_version, "1.1"))) {
-    return 0;
+  // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
+  if (is_big_endian()) {
+    do {
+      t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+        ((unsigned) buf[1] << 8 | buf[0]);
+      * (uint32_t *) buf = t;
+      buf += 4;
+    } while (--longs);
   }
-  return 1;
-}
-
-static const char *suggest_connection_header(const struct mg_connection *conn) {
-  return should_keep_alive(conn) ? "keep-alive" : "close";
 }
 
-static void send_http_error(struct mg_connection *, int, const char *,
-                            PRINTF_FORMAT_STRING(const char *fmt), ...)
-  PRINTF_ARGS(4, 5);
-
-
-static void send_http_error(struct mg_connection *conn, int status,
-                            const char *reason, const char *fmt, ...) {
-  char buf[MG_BUF_LEN];
-  va_list ap;
-  int len = 0;
-
-  conn->status_code = status;
-  buf[0] = '\0';
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
 
-  // Errors 1xx, 204 and 304 MUST NOT send a body
-  if (status > 199 && status != 204 && status != 304) {
-    len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason);
-    buf[len++] = '\n';
+#define MD5STEP(f, w, x, y, z, data, s) \
+  ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
 
-    va_start(ap, fmt);
-    len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
-    va_end(ap);
-  }
-  DEBUG_TRACE(("[%s]", buf));
+// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+// initialization constants.
+static void MD5Init(MD5_CTX *ctx) {
+  ctx->buf[0] = 0x67452301;
+  ctx->buf[1] = 0xefcdab89;
+  ctx->buf[2] = 0x98badcfe;
+  ctx->buf[3] = 0x10325476;
 
-  mg_printf(conn, "HTTP/1.1 %d %s\r\n"
-            "Content-Length: %d\r\n"
-            "Connection: %s\r\n\r\n", status, reason, len,
-            suggest_connection_header(conn));
-  conn->num_bytes_sent += mg_printf(conn, "%s", buf);
+  ctx->bits[0] = 0;
+  ctx->bits[1] = 0;
 }
 
-#if defined(_WIN32) && !defined(__SYMBIAN32__)
-static pthread_t pthread_self(void) {
-  return GetCurrentThreadId();
-}
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
+  register uint32_t a, b, c, d;
 
-static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
-  (void) unused;
-  *mutex = CreateMutex(NULL, FALSE, NULL);
-  return *mutex == NULL ? -1 : 0;
-}
+  a = buf[0];
+  b = buf[1];
+  c = buf[2];
+  d = buf[3];
 
-static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
-  return CloseHandle(*mutex) == 0 ? -1 : 0;
-}
+  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
 
-static int pthread_mutex_lock(pthread_mutex_t *mutex) {
-  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
-}
+  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
 
-static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
-  return ReleaseMutex(*mutex) == 0 ? -1 : 0;
-}
+  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
 
-static int pthread_cond_init(pthread_cond_t *cv, const void *unused) {
-  (void) unused;
-  cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL);
-  cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL);
-  return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1;
-}
+  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
 
-static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
-  HANDLE handles[] = {cv->signal, cv->broadcast};
-  ReleaseMutex(*mutex);
-  WaitForMultipleObjects(2, handles, FALSE, INFINITE);
-  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+  buf[0] += a;
+  buf[1] += b;
+  buf[2] += c;
+  buf[3] += d;
 }
 
-static int pthread_cond_signal(pthread_cond_t *cv) {
-  return SetEvent(cv->signal) == 0 ? -1 : 0;
-}
+static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
+  uint32_t t;
 
-static int pthread_cond_broadcast(pthread_cond_t *cv) {
-  // Implementation with PulseEvent() has race condition, see
-  // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
-  return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
-}
+  t = ctx->bits[0];
+  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
+    ctx->bits[1]++;
+  ctx->bits[1] += len >> 29;
 
-static int pthread_cond_destroy(pthread_cond_t *cv) {
-  return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
-}
+  t = (t >> 3) & 0x3f;
 
-// For Windows, change all slashes to backslashes in path names.
-static void change_slashes_to_backslashes(char *path) {
-  int i;
+  if (t) {
+    unsigned char *p = (unsigned char *) ctx->in + t;
 
-  for (i = 0; path[i] != '\0'; i++) {
-    if (path[i] == '/')
-      path[i] = '\\';
-    // i > 0 check is to preserve UNC paths, like \\server\file.txt
-    if (path[i] == '\\' && i > 0)
-      while (path[i + 1] == '\\' || path[i + 1] == '/')
-        (void) memmove(path + i + 1,
-            path + i + 2, strlen(path + i + 1));
+    t = 64 - t;
+    if (len < t) {
+      memcpy(p, buf, len);
+      return;
+    }
+    memcpy(p, buf, t);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += t;
+    len -= t;
   }
-}
 
-// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
-// wbuf and wbuf_len is a target buffer and its length.
-static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
-  char buf[PATH_MAX * 2], buf2[PATH_MAX * 2];
-
-  mg_strlcpy(buf, path, sizeof(buf));
-  change_slashes_to_backslashes(buf);
-
-  // Convert to Unicode and back. If doubly-converted string does not
-  // match the original, something is fishy, reject.
-  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
-  MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
-  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
-                      NULL, NULL);
-  if (strcmp(buf, buf2) != 0) {
-    wbuf[0] = L'\0';
+  while (len >= 64) {
+    memcpy(ctx->in, buf, 64);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    buf += 64;
+    len -= 64;
   }
+
+  memcpy(ctx->in, buf, len);
 }
 
-#if defined(_WIN32_WCE)
-static time_t time(time_t *ptime) {
-  time_t t;
-  SYSTEMTIME st;
-  FILETIME ft;
+static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
+  unsigned count;
+  unsigned char *p;
+  uint32_t *a;
 
-  GetSystemTime(&st);
-  SystemTimeToFileTime(&st, &ft);
-  t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
+  count = (ctx->bits[0] >> 3) & 0x3F;
 
-  if (ptime != NULL) {
-    *ptime = t;
+  p = ctx->in + count;
+  *p++ = 0x80;
+  count = 64 - 1 - count;
+  if (count < 8) {
+    memset(p, 0, count);
+    byteReverse(ctx->in, 16);
+    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+    memset(ctx->in, 0, 56);
+  } else {
+    memset(p, 0, count - 8);
   }
+  byteReverse(ctx->in, 14);
 
-  return t;
-}
-
-static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
-  int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
-  FILETIME ft, lft;
-  SYSTEMTIME st;
-  TIME_ZONE_INFORMATION tzinfo;
+  a = (uint32_t *)ctx->in;
+  a[14] = ctx->bits[0];
+  a[15] = ctx->bits[1];
 
-  if (ptm == NULL) {
-    return NULL;
-  }
+  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
+  byteReverse((unsigned char *) ctx->buf, 4);
+  memcpy(digest, ctx->buf, 16);
+  memset((char *) ctx, 0, sizeof(*ctx));
+}
+#endif // !HAVE_MD5
 
-  * (int64_t *) &ft = t;
-  FileTimeToLocalFileTime(&ft, &lft);
-  FileTimeToSystemTime(&lft, &st);
-  ptm->tm_year = st.wYear - 1900;
-  ptm->tm_mon = st.wMonth - 1;
-  ptm->tm_wday = st.wDayOfWeek;
-  ptm->tm_mday = st.wDay;
-  ptm->tm_hour = st.wHour;
-  ptm->tm_min = st.wMinute;
-  ptm->tm_sec = st.wSecond;
-  ptm->tm_yday = 0; // hope nobody uses this
-  ptm->tm_isdst =
-    GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
 
-  return ptm;
-}
 
-static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
-  // FIXME(lsm): fix this.
-  return localtime(ptime, ptm);
-}
+// Stringify binary data. Output buffer must be twice as big as input,
+// because each byte takes 2 bytes in string representation
+static void bin2str(char *to, const unsigned char *p, size_t len) {
+  static const char *hex = "0123456789abcdef";
 
-static size_t strftime(char *dst, size_t dst_size, const char *fmt,
-                       const struct tm *tm) {
-  (void) snprintf(dst, dst_size, "implement strftime() for WinCE");
-  return 0;
+  for (; len--; p++) {
+    *to++ = hex[p[0] >> 4];
+    *to++ = hex[p[0] & 0x0f];
+  }
+  *to = '\0';
 }
-#endif
 
-// Windows happily opens files with some garbage at the end of file name.
-// For example, fopen("a.cgi    ", "r") on Windows successfully opens
-// "a.cgi", despite one would expect an error back.
-// This function returns non-0 if path ends with some garbage.
-static int path_cannot_disclose_cgi(const char *path) {
-  static const char *allowed_last_characters = "_-";
-  int last = path[strlen(path) - 1];
-  return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
-}
+// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
+char *mg_md5(char buf[33], ...) {
+  unsigned char hash[16];
+  const char *p;
+  va_list ap;
+  MD5_CTX ctx;
 
-static int mg_stat(const char *path, struct file *filep) {
-  wchar_t wbuf[PATH_MAX] = L"\\\\?\\";
-  WIN32_FILE_ATTRIBUTE_DATA info;
+  MD5Init(&ctx);
 
-  filep->modification_time = 0;
-  to_unicode(path, wbuf + 4, ARRAY_SIZE(wbuf) - 4);
-  if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
-    filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
-    filep->modification_time = SYS2UNIX_TIME(
-        info.ftLastWriteTime.dwLowDateTime,
-        info.ftLastWriteTime.dwHighDateTime);
-    filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
-    // If file name is fishy, reset the file structure and return error.
-    // Note it is important to reset, not just return the error, cause
-    // functions like is_file_opened() check the struct.
-    if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
-      memset(filep, 0, sizeof(*filep));
-    }
+  va_start(ap, buf);
+  while ((p = va_arg(ap, const char *)) != NULL) {
+    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
   }
+  va_end(ap);
 
-  return filep->modification_time != 0;
+  MD5Final(hash, &ctx);
+  bin2str(buf, hash, sizeof(hash));
+  return buf;
 }
 
-static int mg_remove(const char *path) {
-  wchar_t wbuf[PATH_MAX];
-  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
-  return DeleteFileW(wbuf) ? 0 : -1;
-}
+// Check the user's password, return 1 if OK
+static int check_password(const char *method, const char *ha1, const char *uri,
+                          const char *nonce, const char *nc, const char *cnonce,
+                          const char *qop, const char *response) {
+  char ha2[32 + 1], expected_response[32 + 1];
 
-static int mg_mkdir(const char *path, int mode) {
-  char buf[PATH_MAX];
-  wchar_t wbuf[PATH_MAX];
+  // Some of the parameters may be NULL
+  if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
+      qop == NULL || response == NULL) {
+    return 0;
+  }
 
-  (void) mode;
-  mg_strlcpy(buf, path, sizeof(buf));
-  change_slashes_to_backslashes(buf);
+  // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
+  // TODO(lsm): check for authentication timeout
+  if (// strcmp(dig->uri, c->ouri) != 0 ||
+      strlen(response) != 32
+      // || now - strtoul(dig->nonce, NULL, 10) > 3600
+      ) {
+    return 0;
+  }
 
-  (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
+  mg_md5(ha2, method, ":", uri, NULL);
+  mg_md5(expected_response, ha1, ":", nonce, ":", nc,
+      ":", cnonce, ":", qop, ":", ha2, NULL);
 
-  return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
+  return mg_strcasecmp(response, expected_response) == 0;
 }
 
-// Implementation of POSIX opendir/closedir/readdir for Windows.
-static DIR * opendir(const char *name) {
-  DIR *dir = NULL;
-  wchar_t wpath[PATH_MAX];
-  DWORD attrs;
+// Use the global passwords file, if specified by auth_gpass option,
+// or search for .htpasswd in the requested directory.
+static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
+  char name[PATH_MAX];
+  const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
+  struct file file = STRUCT_FILE_INITIALIZER;
+  FILE *fp = NULL;
 
-  if (name == NULL) {
-    SetLastError(ERROR_BAD_ARGUMENTS);
-  } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
-    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
+  if (gpass != NULL) {
+    // Use global passwords file
+    fp = mg_fopen(gpass, "r");
+    // Important: using local struct file to test path for is_directory flag.
+    // If filep is used, mg_stat() makes it appear as if auth file was opened.
+  } else if (mg_stat(path, &file) && file.is_directory) {
+    mg_snprintf(name, sizeof(name), "%s%c%s",
+                path, '/', PASSWORDS_FILE_NAME);
+    fp = mg_fopen(name, "r");
   } else {
-    to_unicode(name, wpath, ARRAY_SIZE(wpath));
-    attrs = GetFileAttributesW(wpath);
-    if (attrs != 0xFFFFFFFF &&
-        ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
-      (void) wcscat(wpath, L"\\*");
-      dir->handle = FindFirstFileW(wpath, &dir->info);
-      dir->result.d_name[0] = '\0';
-    } else {
-      free(dir);
-      dir = NULL;
-    }
+     // Try to find .htpasswd in requested directory.
+    for (p = path, e = p + strlen(p) - 1; e > p; e--)
+      if (e[0] == '/')
+        break;
+    mg_snprintf(name, sizeof(name), "%.*s%c%s",
+                (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
+    fp = mg_fopen(name, "r");
   }
 
-  return dir;
+  return fp;
 }
 
-static int closedir(DIR *dir) {
-  int result = 0;
+// Parsed Authorization header
+struct ah {
+  char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
+};
 
-  if (dir != NULL) {
-    if (dir->handle != INVALID_HANDLE_VALUE)
-      result = FindClose(dir->handle) ? 0 : -1;
+// Return 1 on success. Always initializes the ah structure.
+static int parse_auth_header(struct mg_connection *conn, char *buf,
+                             size_t buf_size, struct ah *ah) {
+  char *name, *value, *s;
+  const char *auth_header;
 
-    free(dir);
-  } else {
-    result = -1;
-    SetLastError(ERROR_BAD_ARGUMENTS);
+  (void) memset(ah, 0, sizeof(*ah));
+  if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
+      mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
+    return 0;
   }
 
-  return result;
-}
-
-static struct dirent *readdir(DIR *dir) {
-  struct dirent *result = 0;
-
-  if (dir) {
-    if (dir->handle != INVALID_HANDLE_VALUE) {
-      result = &dir->result;
-      (void) WideCharToMultiByte(CP_UTF8, 0,
-          dir->info.cFileName, -1, result->d_name,
-          sizeof(result->d_name), NULL, NULL);
+  // Make modifiable copy of the auth header
+  (void) mg_strlcpy(buf, auth_header + 7, buf_size);
+  s = buf;
 
-      if (!FindNextFileW(dir->handle, &dir->info)) {
-        (void) FindClose(dir->handle);
-        dir->handle = INVALID_HANDLE_VALUE;
+  // Parse authorization header
+  for (;;) {
+    // Gobble initial spaces
+    while (isspace(* (unsigned char *) s)) {
+      s++;
+    }
+    name = skip_quoted(&s, "=", " ", 0);
+    // Value is either quote-delimited, or ends at first comma or space.
+    if (s[0] == '\"') {
+      s++;
+      value = skip_quoted(&s, "\"", " ", '\\');
+      if (s[0] == ',') {
+        s++;
       }
-
     } else {
-      SetLastError(ERROR_FILE_NOT_FOUND);
+      value = skip_quoted(&s, ", ", " ", 0);  // IE uses commas, FF uses spaces
+    }
+    if (*name == '\0') {
+      break;
+    }
+
+    if (!strcmp(name, "username")) {
+      ah->user = value;
+    } else if (!strcmp(name, "cnonce")) {
+      ah->cnonce = value;
+    } else if (!strcmp(name, "response")) {
+      ah->response = value;
+    } else if (!strcmp(name, "uri")) {
+      ah->uri = value;
+    } else if (!strcmp(name, "qop")) {
+      ah->qop = value;
+    } else if (!strcmp(name, "nc")) {
+      ah->nc = value;
+    } else if (!strcmp(name, "nonce")) {
+      ah->nonce = value;
     }
+  }
+
+  // CGI needs it as REMOTE_USER
+  if (ah->user != NULL) {
+    conn->request_info.remote_user = mg_strdup(ah->user);
   } else {
-    SetLastError(ERROR_BAD_ARGUMENTS);
+    return 0;
   }
 
-  return result;
+  return 1;
 }
 
-#ifndef HAVE_POLL
-static int poll(struct pollfd *pfd, int n, int milliseconds) {
-  struct timeval tv;
-  fd_set set;
-  int i, result;
-  SOCKET maxfd = 0;
-
-  tv.tv_sec = milliseconds / 1000;
-  tv.tv_usec = (milliseconds % 1000) * 1000;
-  FD_ZERO(&set);
+// Authorize against the opened passwords file. Return 1 if authorized.
+static int authorize(struct mg_connection *conn, FILE *fp) {
+  struct ah ah;
+  char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
 
-  for (i = 0; i < n; i++) {
-    FD_SET((SOCKET) pfd[i].fd, &set);
-    pfd[i].revents = 0;
+  if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
+    return 0;
+  }
 
-    if (pfd[i].fd > maxfd) {
-        maxfd = pfd[i].fd;
+  // Loop over passwords file
+  while (fgets(line, sizeof(line), fp) != NULL) {
+    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
+      continue;
     }
+
+    if (!strcmp(ah.user, f_user) &&
+        !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
+      return check_password(conn->request_info.request_method, ha1, ah.uri,
+                            ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
   }
 
-  if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) {
-    for (i = 0; i < n; i++) {
-      if (FD_ISSET(pfd[i].fd, &set)) {
-        pfd[i].revents = POLLIN;
-      }
+  return 0;
+}
+
+// Return 1 if request is authorised, 0 otherwise.
+static int check_authorization(struct mg_connection *conn, const char *path) {
+  char fname[PATH_MAX];
+  struct vec uri_vec, filename_vec;
+  const char *list;
+  FILE *fp = NULL;
+  int authorized = 1;
+
+  list = conn->ctx->config[PROTECT_URI];
+  while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
+    if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
+      mg_snprintf(fname, sizeof(fname), "%.*s",
+                  (int) filename_vec.len, filename_vec.ptr);
+      fp = mg_fopen(fname, "r");
+      break;
     }
   }
 
-  return result;
-}
-#endif // HAVE_POLL
+  if (fp == NULL) {
+    fp = open_auth_file(conn, path);
+  }
 
-static void set_close_on_exec(SOCKET sock) {
-  (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
-}
+  if (fp != NULL) {
+    authorized = authorize(conn, fp);
+    fclose(fp);
+  }
 
-int mg_start_thread(mg_thread_func_t f, void *p) {
-  return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
+  return authorized;
 }
 
-static HANDLE dlopen(const char *dll_name, int flags) {
-  wchar_t wbuf[PATH_MAX];
-  (void) flags;
-  to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
-  return LoadLibraryW(wbuf);
+static void send_authorization_request(struct mg_connection *conn) {
+  conn->status_code = 401;
+  mg_printf(conn,
+            "HTTP/1.1 401 Unauthorized\r\n"
+            "Content-Length: 0\r\n"
+            "WWW-Authenticate: Digest qop=\"auth\", "
+            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
+            conn->ctx->config[AUTHENTICATION_DOMAIN],
+            (unsigned long) time(NULL));
 }
 
-#if !defined(NO_CGI)
-#define SIGKILL 0
-static int kill(pid_t pid, int sig_num) {
-  (void) TerminateProcess(pid, sig_num);
-  (void) CloseHandle(pid);
-  return 0;
-}
+static int is_authorized_for_put(struct mg_connection *conn) {
+  const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
+  FILE *fp;
+  int ret = 0;
 
-static void trim_trailing_whitespaces(char *s) {
-  char *e = s + strlen(s) - 1;
-  while (e > s && isspace(* (unsigned char *) e)) {
-    *e-- = '\0';
+  if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
+    ret = authorize(conn, fp);
+    fclose(fp);
   }
+
+  return ret;
 }
 
-static pid_t spawn_process(struct mg_connection *conn, const char *prog,
-                           char *envblk, char *envp[], int fdin,
-                           int fdout, const char *dir) {
-  HANDLE me;
-  char *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
-       cmdline[PATH_MAX], buf[PATH_MAX];
-  FILE *fp;
-  STARTUPINFOA si;
-  PROCESS_INFORMATION pi = { 0 };
+int mg_modify_passwords_file(const char *fname, const char *domain,
+                             const char *user, const char *pass) {
+  int found;
+  char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
+  FILE *fp, *fp2;
 
-  (void) envp;
+  found = 0;
+  fp = fp2 = NULL;
 
-  memset(&si, 0, sizeof(si));
-  si.cb = sizeof(si);
+  // Regard empty password as no password - remove user record.
+  if (pass != NULL && pass[0] == '\0') {
+    pass = NULL;
+  }
 
-  // TODO(lsm): redirect CGI errors to the error log file
-  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
-  si.wShowWindow = SW_HIDE;
+  (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
 
-  me = GetCurrentProcess();
-  DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
-                  &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
-  DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
-                  &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+  // Create the file if does not exist
+  if ((fp = fopen(fname, "a+")) != NULL) {
+    fclose(fp);
+  }
 
-  // If CGI file is a script, try to read the interpreter line
-  interp = conn->ctx->config[CGI_INTERPRETER];
-  if (interp == NULL) {
-    buf[0] = buf[1] = '\0';
+  // Open the given file and temporary file
+  if ((fp = fopen(fname, "r")) == NULL) {
+    return 0;
+  } else if ((fp2 = fopen(tmp, "w+")) == NULL) {
+    fclose(fp);
+    return 0;
+  }
 
-    // Read the first line of the script into the buffer
-    snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
-    if ((fp = mg_fopen(cmdline, "r")) != NULL) {
-      fgets(buf, sizeof(buf), fp);
-      fclose(fp);
-      buf[sizeof(buf) - 1] = '\0';
+  // Copy the stuff to temporary file
+  while (fgets(line, sizeof(line), fp) != NULL) {
+    if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
+      continue;
     }
 
-    if (buf[0] == '#' && buf[1] == '!') {
-      trim_trailing_whitespaces(buf + 2);
+    if (!strcmp(u, user) && !strcmp(d, domain)) {
+      found++;
+      if (pass != NULL) {
+        mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+        fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
+      }
     } else {
-      buf[2] = '\0';
+      fprintf(fp2, "%s", line);
     }
-    interp = buf + 2;
   }
 
-  if (interp[0] != '\0') {
-    GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL);
-    interp = full_interp;
+  // If new user, just add it
+  if (!found && pass != NULL) {
+    mg_md5(ha1, user, ":", domain, ":", pass, NULL);
+    fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
   }
-  GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL);
-
-  mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"",
-              interp, interp[0] == '\0' ? "" : " ", full_dir, prog);
 
-  DEBUG_TRACE(("Running [%s]", cmdline));
-  if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
-        CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
-    cry(conn, "%s: CreateProcess(%s): %ld",
-        __func__, cmdline, ERRNO);
-    pi.hProcess = (pid_t) -1;
-  }
+  // Close files
+  fclose(fp);
+  fclose(fp2);
 
-  (void) CloseHandle(si.hStdOutput);
-  (void) CloseHandle(si.hStdInput);
-  (void) CloseHandle(pi.hThread);
+  // Put the temp file in place of real file
+  remove(fname);
+  rename(tmp, fname);
 
-  return (pid_t) pi.hProcess;
+  return 1;
 }
-#endif // !NO_CGI
 
-static int set_non_blocking_mode(SOCKET sock) {
-  unsigned long on = 1;
-  return ioctlsocket(sock, FIONBIO, &on);
+// Return number of bytes left to read for this connection
+static int64_t left_to_read(const struct mg_connection *conn) {
+  return conn->content_len + conn->request_len - conn->num_bytes_read;
 }
 
-#else
-static int mg_stat(const char *path, struct file *filep) {
-  struct stat st;
-
-  filep->modification_time = (time_t) 0;
-  if (stat(path, &st) == 0) {
-    filep->size = st.st_size;
-    filep->modification_time = st.st_mtime;
-    filep->is_directory = S_ISDIR(st.st_mode);
-
-    // See https://github.com/cesanta/mongoose/issues/109
-    // Some filesystems report modification time as 0. Artificially
-    // bump it up to mark mg_stat() success.
-    if (filep->modification_time == (time_t) 0) {
-      filep->modification_time = (time_t) 1;
-    }
+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;
+    conn->event.type = type;
+    conn->event.event_param = p;
+    conn->event.request_info = &conn->request_info;
+    conn->event.conn = conn;
   }
-
-  return filep->modification_time != (time_t) 0;
+  return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
+    0 : conn->ctx->event_handler(&conn->event);
 }
 
-static void set_close_on_exec(int fd) {
-  fcntl(fd, F_SETFD, FD_CLOEXEC);
+static FILE *mg_fopen(const char *path, const char *mode) {
+#ifdef _WIN32
+  wchar_t wbuf[PATH_MAX], wmode[20];
+  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+  MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
+  return _wfopen(wbuf, wmode);
+#else
+  return fopen(path, mode);
+#endif
 }
 
-int mg_start_thread(mg_thread_func_t func, void *param) {
-  pthread_t thread_id;
-  pthread_attr_t attr;
-  int result;
-
-  (void) pthread_attr_init(&attr);
-  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
-
-#if USE_STACK_SIZE > 1
-  // Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
-  (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
+static void sockaddr_to_string(char *buf, size_t len,
+                                     const union usa *usa) {
+  buf[0] = '\0';
+#if defined(USE_IPV6)
+  inet_ntop(usa->sa.sa_family, usa->sa.sa_family == AF_INET ?
+            (void *) &usa->sin.sin_addr :
+            (void *) &usa->sin6.sin6_addr, buf, len);
+#elif defined(_WIN32)
+  // Only Windoze Vista (and newer) have inet_ntop()
+  strncpy(buf, inet_ntoa(usa->sin.sin_addr), len);
+#else
+  inet_ntop(usa->sa.sa_family, (void *) &usa->sin.sin_addr, buf, len);
 #endif
-
-  result = pthread_create(&thread_id, &attr, func, param);
-  pthread_attr_destroy(&attr);
-
-  return result;
 }
 
-#ifndef NO_CGI
-static pid_t spawn_process(struct mg_connection *conn, const char *prog,
-                           char *envblk, char *envp[], int fdin,
-                           int fdout, const char *dir) {
-  pid_t pid;
-  const char *interp;
+static void cry(struct mg_connection *conn,
+                PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
 
-  (void) envblk;
+// Print error message to the opened error log stream.
+static void cry(struct mg_connection *conn, const char *fmt, ...) {
+  char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN];
+  va_list ap;
+  FILE *fp;
+  time_t timestamp;
 
-  if ((pid = fork()) == -1) {
-    // Parent
-    send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
-  } else if (pid == 0) {
-    // Child
-    if (chdir(dir) != 0) {
-      cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
-    } else if (dup2(fdin, 0) == -1) {
-      cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
-    } else if (dup2(fdout, 1) == -1) {
-      cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
-    } else {
-      // Not redirecting stderr to stdout, to avoid output being littered
-      // with the error messages.
-      (void) close(fdin);
-      (void) close(fdout);
+  va_start(ap, fmt);
+  (void) vsnprintf(buf, sizeof(buf), fmt, ap);
+  va_end(ap);
 
-      // After exec, all signal handlers are restored to their default values,
-      // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
-      // implementation, SIGCHLD's handler will leave unchanged after exec
-      // if it was set to be ignored. Restore it to default action.
-      signal(SIGCHLD, SIG_DFL);
+  // Do not lock when getting the callback value, here and below.
+  // I suppose this is fine, since function cannot disappear in the
+  // same way string option can.
+  if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
+    fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
+      fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
 
-      interp = conn->ctx->config[CGI_INTERPRETER];
-      if (interp == NULL) {
-        (void) execle(prog, prog, NULL, envp);
-        cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
-      } else {
-        (void) execle(interp, interp, prog, NULL, envp);
-        cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
-            strerror(ERRNO));
+    if (fp != NULL) {
+      flockfile(fp);
+      timestamp = time(NULL);
+
+      sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+      fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
+              src_addr);
+
+      if (conn->request_info.request_method != NULL) {
+        fprintf(fp, "%s %s: ", conn->request_info.request_method,
+                conn->request_info.uri);
       }
+
+      fprintf(fp, "%s", buf);
+      fputc('\n', fp);
+      funlockfile(fp);
+      fclose(fp);
     }
-    exit(EXIT_FAILURE);
   }
+}
 
-  return pid;
+// Return fake connection structure. Used for logging, if connection
+// is not applicable at the moment of logging.
+static struct mg_connection *fc(struct mg_context *ctx) {
+  static struct mg_connection fake_connection;
+  fake_connection.ctx = ctx;
+  // See https://github.com/cesanta/mongoose/issues/236
+  fake_connection.event.user_data = ctx->user_data;
+  return &fake_connection;
 }
-#endif // !NO_CGI
 
-static int set_non_blocking_mode(SOCKET sock) {
-  int flags;
+const char *mg_version(void) {
+  return MONGOOSE_VERSION;
+}
 
-  flags = fcntl(sock, F_GETFL, 0);
-  (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
+// HTTP 1.1 assumes keep alive if "Connection:" header is not set
+// This function must tolerate situations when connection info is not
+// set up, for example if request parsing failed.
+static int should_keep_alive(const struct mg_connection *conn) {
+  const char *http_version = conn->request_info.http_version;
+  const char *header = mg_get_header(conn, "Connection");
+  if (conn->must_close ||
+      conn->status_code == 401 ||
+      mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 ||
+      (header != NULL && mg_strcasecmp(header, "keep-alive") != 0) ||
+      (header == NULL && http_version && strcmp(http_version, "1.1"))) {
+    return 0;
+  }
+  return 1;
+}
 
-  return 0;
+static const char *suggest_connection_header(const struct mg_connection *conn) {
+  return should_keep_alive(conn) ? "keep-alive" : "close";
 }
-#endif // _WIN32
 
-// Write data to the IO channel - opened file descriptor, socket or SSL
-// descriptor. Return number of bytes written.
-static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
-                    int64_t len) {
-  int64_t sent;
-  int n, k;
+static void send_http_error(struct mg_connection *, int, const char *,
+                            PRINTF_FORMAT_STRING(const char *fmt), ...)
+  PRINTF_ARGS(4, 5);
 
-  (void) ssl;  // Get rid of warning
-  sent = 0;
-  while (sent < len) {
 
-    // How many bytes we send in this iteration
-    k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
+static void send_http_error(struct mg_connection *conn, int status,
+                            const char *reason, const char *fmt, ...) {
+  char buf[MG_BUF_LEN];
+  va_list ap;
+  int len = 0;
 
-    if (ssl != NULL) {
-      n = SSL_write(ssl, buf + sent, k);
-    } else if (fp != NULL) {
-      n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
-      if (ferror(fp))
-        n = -1;
-    } else {
-      n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
-    }
+  conn->status_code = status;
+  buf[0] = '\0';
 
-    if (n <= 0)
-      break;
+  // Errors 1xx, 204 and 304 MUST NOT send a body
+  if (status > 199 && status != 204 && status != 304) {
+    len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason);
+    buf[len++] = '\n';
 
-    sent += n;
+    va_start(ap, fmt);
+    len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
+    va_end(ap);
   }
+  DEBUG_TRACE(("[%s]", buf));
 
-  return sent;
+  mg_printf(conn, "HTTP/1.1 %d %s\r\n"
+            "Content-Length: %d\r\n"
+            "Connection: %s\r\n\r\n", status, reason, len,
+            suggest_connection_header(conn));
+  conn->num_bytes_sent += mg_printf(conn, "%s", buf);
 }
 
-// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
-// Return negative value on error, or number of bytes read on success.
-static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
-  int nread;
+#if defined(_WIN32) && !defined(__SYMBIAN32__)
+static pthread_t pthread_self(void) {
+  return GetCurrentThreadId();
+}
 
-  if (len <= 0) return 0;
-  if (fp != NULL) {
-    // Use read() instead of fread(), because if we're reading from the CGI
-    // pipe, fread() may block until IO buffer is filled up. We cannot afford
-    // to block and must pass all read bytes immediately to the client.
-    nread = read(fileno(fp), buf, (size_t) len);
-#ifndef NO_SSL
-  } else if (conn->ssl != NULL) {
-    nread = SSL_read(conn->ssl, buf, len);
-#endif
-  } else {
-    nread = recv(conn->client.sock, buf, (size_t) len, 0);
-  }
-  if (nread > 0) {
-    conn->num_bytes_read += nread;
-  }
+static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
+  (void) unused;
+  *mutex = CreateMutex(NULL, FALSE, NULL);
+  return *mutex == NULL ? -1 : 0;
+}
 
-  return conn->ctx->stop_flag ? -1 : nread;
+static int pthread_mutex_destroy(pthread_mutex_t *mutex) {
+  return CloseHandle(*mutex) == 0 ? -1 : 0;
 }
 
-static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
-  int n, nread = 0;
+static int pthread_mutex_lock(pthread_mutex_t *mutex) {
+  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
 
-  while (len > 0 && conn->ctx->stop_flag == 0) {
-    n = pull(fp, conn, buf + nread, len);
-    if (n < 0) {
-      nread = n;  // Propagate the error
-      break;
-    } else if (n == 0) {
-      break;  // No more data to read
-    } else {
-      nread += n;
-      len -= n;
-    }
-  }
+static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
+  return ReleaseMutex(*mutex) == 0 ? -1 : 0;
+}
 
-  return nread;
+static int pthread_cond_init(pthread_cond_t *cv, const void *unused) {
+  (void) unused;
+  cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL);
+  cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL);
+  return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1;
 }
 
-int mg_read(struct mg_connection *conn, void *buf, int len) {
-  int n, buffered_len, nread = 0;
-  int64_t left;
+static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) {
+  HANDLE handles[] = {cv->signal, cv->broadcast};
+  ReleaseMutex(*mutex);
+  WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+  return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1;
+}
 
-  // 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;
-  }
+static int pthread_cond_signal(pthread_cond_t *cv) {
+  return SetEvent(cv->signal) == 0 ? -1 : 0;
+}
 
-  // conn->buf           body
-  //    |=================|==========|===============|
-  //    |<--request_len-->|                          |
-  //    |<-----------data_len------->|      conn->buf + conn->buf_size
+static int pthread_cond_broadcast(pthread_cond_t *cv) {
+  // Implementation with PulseEvent() has race condition, see
+  // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
+  return PulseEvent(cv->broadcast) == 0 ? -1 : 0;
+}
 
-  // First, check for data buffered in conn->buf by read_request().
-  if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
-    char *body = conn->buf + conn->request_len;
-    if (buffered_len > len) buffered_len = len;
-    if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
+static int pthread_cond_destroy(pthread_cond_t *cv) {
+  return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1;
+}
 
-    memcpy(buf, body, (size_t) buffered_len);
-    memmove(body, body + buffered_len,
-            &conn->buf[conn->data_len] - &body[buffered_len]);
-    len -= buffered_len;
-    conn->data_len -= buffered_len;
-    nread += buffered_len;
+// For Windows, change all slashes to backslashes in path names.
+static void change_slashes_to_backslashes(char *path) {
+  int i;
+
+  for (i = 0; path[i] != '\0'; i++) {
+    if (path[i] == '/')
+      path[i] = '\\';
+    // i > 0 check is to preserve UNC paths, like \\server\file.txt
+    if (path[i] == '\\' && i > 0)
+      while (path[i + 1] == '\\' || path[i + 1] == '/')
+        (void) memmove(path + i + 1,
+            path + i + 2, strlen(path + i + 1));
   }
+}
 
-  // Read data from the socket.
-  if (len > 0 && (left = left_to_read(conn)) > 0) {
-    if (left < len) {
-      len = (int) left;
-    }
-    n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
-    nread = n >= 0 ? nread + n : n;
+// Encode 'path' which is assumed UTF-8 string, into UNICODE string.
+// wbuf and wbuf_len is a target buffer and its length.
+static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) {
+  char buf[PATH_MAX * 2], buf2[PATH_MAX * 2];
+
+  mg_strlcpy(buf, path, sizeof(buf));
+  change_slashes_to_backslashes(buf);
+
+  // Convert to Unicode and back. If doubly-converted string does not
+  // match the original, something is fishy, reject.
+  memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
+  MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len);
+  WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2),
+                      NULL, NULL);
+  if (strcmp(buf, buf2) != 0) {
+    wbuf[0] = L'\0';
   }
-
-  return nread;
 }
 
-int mg_write(struct mg_connection *conn, const void *buf, int len) {
-  time_t now;
-  int64_t n, total, allowed;
+#if defined(_WIN32_WCE)
+static time_t time(time_t *ptime) {
+  time_t t;
+  SYSTEMTIME st;
+  FILETIME ft;
 
-  if (conn->throttle > 0) {
-    if ((now = time(NULL)) != conn->last_throttle_time) {
-      conn->last_throttle_time = now;
-      conn->last_throttle_bytes = 0;
-    }
-    allowed = conn->throttle - conn->last_throttle_bytes;
-    if (allowed > (int64_t) len) {
-      allowed = len;
-    }
-    if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
-                      (int64_t) allowed)) == allowed) {
-      buf = (char *) buf + total;
-      conn->last_throttle_bytes += total;
-      while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
-        allowed = conn->throttle > (int64_t) len - total ?
-          (int64_t) len - total : conn->throttle;
-        if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
-                      (int64_t) allowed)) != allowed) {
-          break;
-        }
-        sleep(1);
-        conn->last_throttle_bytes = allowed;
-        conn->last_throttle_time = time(NULL);
-        buf = (char *) buf + n;
-        total += n;
-      }
-    }
-  } else {
-    total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
-                 (int64_t) len);
+  GetSystemTime(&st);
+  SystemTimeToFileTime(&st, &ft);
+  t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime);
+
+  if (ptime != NULL) {
+    *ptime = t;
   }
-  return (int) total;
-}
 
-// Print message to buffer. If buffer is large enough to hold the message,
-// return buffer. If buffer is to small, allocate large enough buffer on heap,
-// and return allocated buffer.
-static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
-  va_list ap_copy;
-  int len;
+  return t;
+}
 
-  // Windows is not standard-compliant, and vsnprintf() returns -1 if
-  // buffer is too small. Also, older versions of msvcrt.dll do not have
-  // _vscprintf().  However, if size is 0, vsnprintf() behaves correctly.
-  // Therefore, we make two passes: on first pass, get required message length.
-  // On second pass, actually print the message.
-  va_copy(ap_copy, ap);
-  len = vsnprintf(NULL, 0, fmt, ap_copy);
+static struct tm *localtime(const time_t *ptime, struct tm *ptm) {
+  int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF;
+  FILETIME ft, lft;
+  SYSTEMTIME st;
+  TIME_ZONE_INFORMATION tzinfo;
 
-  if (len > (int) size &&
-      (size = len + 1) > 0 &&
-      (*buf = (char *) malloc(size)) == NULL) {
-    len = -1;  // Allocation failed, mark failure
-  } else {
-    va_copy(ap_copy, ap);
-    vsnprintf(*buf, size, fmt, ap_copy);
+  if (ptm == NULL) {
+    return NULL;
   }
 
-  return len;
-}
+  * (int64_t *) &ft = t;
+  FileTimeToLocalFileTime(&ft, &lft);
+  FileTimeToSystemTime(&lft, &st);
+  ptm->tm_year = st.wYear - 1900;
+  ptm->tm_mon = st.wMonth - 1;
+  ptm->tm_wday = st.wDayOfWeek;
+  ptm->tm_mday = st.wDay;
+  ptm->tm_hour = st.wHour;
+  ptm->tm_min = st.wMinute;
+  ptm->tm_sec = st.wSecond;
+  ptm->tm_yday = 0; // hope nobody uses this
+  ptm->tm_isdst =
+    GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0;
 
-int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
-  char mem[MG_BUF_LEN], *buf = mem;
-  int len;
+  return ptm;
+}
 
-  if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
-    len = mg_write(conn, buf, (size_t) len);
-  }
-  if (buf != mem && buf != NULL) {
-    free(buf);
-  }
+static struct tm *gmtime(const time_t *ptime, struct tm *ptm) {
+  // FIXME(lsm): fix this.
+  return localtime(ptime, ptm);
+}
 
-  return len;
+static size_t strftime(char *dst, size_t dst_size, const char *fmt,
+                       const struct tm *tm) {
+  (void) snprintf(dst, dst_size, "implement strftime() for WinCE");
+  return 0;
 }
+#endif
 
-int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
-  va_list ap;
-  va_start(ap, fmt);
-  return mg_vprintf(conn, fmt, ap);
+// Windows happily opens files with some garbage at the end of file name.
+// For example, fopen("a.cgi    ", "r") on Windows successfully opens
+// "a.cgi", despite one would expect an error back.
+// This function returns non-0 if path ends with some garbage.
+static int path_cannot_disclose_cgi(const char *path) {
+  static const char *allowed_last_characters = "_-";
+  int last = path[strlen(path) - 1];
+  return isalnum(last) || strchr(allowed_last_characters, last) != NULL;
 }
 
-static int mg_chunked_printf(struct mg_connection *conn, const char *fmt, ...) {
-  char mem[MG_BUF_LEN], *buf = mem;
-  int len;
+static int mg_stat(const char *path, struct file *filep) {
+  wchar_t wbuf[PATH_MAX] = L"\\\\?\\";
+  WIN32_FILE_ATTRIBUTE_DATA info;
 
-  va_list ap;
-  va_start(ap, fmt);
-  if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
-    len = mg_printf(conn, "%X\r\n%s\r\n", len, buf);
+  filep->modification_time = 0;
+  to_unicode(path, wbuf + 4, ARRAY_SIZE(wbuf) - 4);
+  if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) {
+    filep->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh);
+    filep->modification_time = SYS2UNIX_TIME(
+        info.ftLastWriteTime.dwLowDateTime,
+        info.ftLastWriteTime.dwHighDateTime);
+    filep->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+    // If file name is fishy, reset the file structure and return error.
+    // Note it is important to reset, not just return the error, cause
+    // functions like is_file_opened() check the struct.
+    if (!filep->is_directory && !path_cannot_disclose_cgi(path)) {
+      memset(filep, 0, sizeof(*filep));
+    }
   }
 
-  if (buf != mem && buf != NULL) {
-    free(buf);
-  }
+  return filep->modification_time != 0;
+}
 
-  return len;
+static int mg_remove(const char *path) {
+  wchar_t wbuf[PATH_MAX];
+  to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
+  return DeleteFileW(wbuf) ? 0 : -1;
 }
 
-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')
+static int mg_mkdir(const char *path, int mode) {
+  char buf[PATH_MAX];
+  wchar_t wbuf[PATH_MAX];
 
-  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];
-    }
-  }
+  (void) mode;
+  mg_strlcpy(buf, path, sizeof(buf));
+  change_slashes_to_backslashes(buf);
 
-  dst[j] = '\0'; // Null-terminate the destination
+  (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, ARRAY_SIZE(wbuf));
 
-  return i >= src_len ? j : -1;
+  return CreateDirectoryW(wbuf, NULL) ? 0 : -1;
 }
 
-int mg_get_var(const char *data, size_t data_len, const char *name,
-               char *dst, size_t dst_len) {
-  const char *p, *e, *s;
-  size_t name_len;
-  int len;
+// Implementation of POSIX opendir/closedir/readdir for Windows.
+static DIR * opendir(const char *name) {
+  DIR *dir = NULL;
+  wchar_t wpath[PATH_MAX];
+  DWORD attrs;
 
-  if (dst == NULL || dst_len == 0) {
-    len = -2;
-  } else if (data == NULL || name == NULL || data_len == 0) {
-    len = -1;
-    dst[0] = '\0';
+  if (name == NULL) {
+    SetLastError(ERROR_BAD_ARGUMENTS);
+  } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) {
+    SetLastError(ERROR_NOT_ENOUGH_MEMORY);
   } else {
-    name_len = strlen(name);
-    e = data + data_len;
-    len = -1;
-    dst[0] = '\0';
-
-    // data is "var1=val1&var2=val2...". Find variable first
-    for (p = data; p + name_len < e; p++) {
-      if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
-          !mg_strncasecmp(name, p, name_len)) {
+    to_unicode(name, wpath, ARRAY_SIZE(wpath));
+    attrs = GetFileAttributesW(wpath);
+    if (attrs != 0xFFFFFFFF &&
+        ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) {
+      (void) wcscat(wpath, L"\\*");
+      dir->handle = FindFirstFileW(wpath, &dir->info);
+      dir->result.d_name[0] = '\0';
+    } else {
+      free(dir);
+      dir = NULL;
+    }
+  }
 
-        // Point p to variable value
-        p += name_len + 1;
+  return dir;
+}
 
-        // Point s to the end of the value
-        s = (const char *) memchr(p, '&', (size_t)(e - p));
-        if (s == NULL) {
-          s = e;
-        }
-        assert(s >= p);
+static int closedir(DIR *dir) {
+  int result = 0;
 
-        // Decode variable into destination buffer
-        len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
+  if (dir != NULL) {
+    if (dir->handle != INVALID_HANDLE_VALUE)
+      result = FindClose(dir->handle) ? 0 : -1;
 
-        // Redirect error code from -1 to -2 (destination buffer too small).
-        if (len == -1) {
-          len = -2;
-        }
-        break;
-      }
-    }
+    free(dir);
+  } else {
+    result = -1;
+    SetLastError(ERROR_BAD_ARGUMENTS);
   }
 
-  return len;
+  return result;
 }
 
-int mg_get_cookie(const char *cookie_header, const char *var_name,
-                  char *dst, size_t dst_size) {
-  const char *s, *p, *end;
-  int name_len, len = -1;
+static struct dirent *readdir(DIR *dir) {
+  struct dirent *result = 0;
 
-  if (dst == NULL || dst_size == 0) {
-    len = -2;
-  } else if (var_name == NULL || (s = cookie_header) == NULL) {
-    len = -1;
-    dst[0] = '\0';
-  } else {
-    name_len = (int) strlen(var_name);
-    end = s + strlen(s);
-    dst[0] = '\0';
+  if (dir) {
+    if (dir->handle != INVALID_HANDLE_VALUE) {
+      result = &dir->result;
+      (void) WideCharToMultiByte(CP_UTF8, 0,
+          dir->info.cFileName, -1, result->d_name,
+          sizeof(result->d_name), NULL, NULL);
 
-    for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
-      if (s[name_len] == '=') {
-        s += name_len + 1;
-        if ((p = strchr(s, ' ')) == NULL)
-          p = end;
-        if (p[-1] == ';')
-          p--;
-        if (*s == '"' && p[-1] == '"' && p > s + 1) {
-          s++;
-          p--;
-        }
-        if ((size_t) (p - s) < dst_size) {
-          len = p - s;
-          mg_strlcpy(dst, s, (size_t) len + 1);
-        } else {
-          len = -3;
-        }
-        break;
+      if (!FindNextFileW(dir->handle, &dir->info)) {
+        (void) FindClose(dir->handle);
+        dir->handle = INVALID_HANDLE_VALUE;
       }
+
+    } else {
+      SetLastError(ERROR_FILE_NOT_FOUND);
     }
+  } else {
+    SetLastError(ERROR_BAD_ARGUMENTS);
   }
-  return len;
+
+  return result;
 }
 
-// Return 1 if real file has been found, 0 otherwise
-static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
-                                    size_t buf_len, struct file *filep) {
-  struct vec a, b;
-  const char *rewrite, *uri = conn->request_info.uri,
-        *root = conn->ctx->config[DOCUMENT_ROOT];
-  char *p;
-  int match_len;
-  char gz_path[PATH_MAX];
-  char const* accept_encoding;
+#ifndef HAVE_POLL
+static int poll(struct pollfd *pfd, int n, int milliseconds) {
+  struct timeval tv;
+  fd_set set;
+  int i, result;
+  SOCKET maxfd = 0;
 
-  // No filesystem access
-  if (root == NULL) {
-    return 0;
-  }
+  tv.tv_sec = milliseconds / 1000;
+  tv.tv_usec = (milliseconds % 1000) * 1000;
+  FD_ZERO(&set);
 
-  // Using buf_len - 1 because memmove() for PATH_INFO may shift part
-  // of the path one byte on the right.
-  // If document_root is NULL, leave the file empty.
-  mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
+  for (i = 0; i < n; i++) {
+    FD_SET((SOCKET) pfd[i].fd, &set);
+    pfd[i].revents = 0;
 
-  rewrite = conn->ctx->config[REWRITE];
-  while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
-    if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
-      mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
-                  uri + match_len);
-      break;
+    if (pfd[i].fd > maxfd) {
+        maxfd = pfd[i].fd;
     }
   }
 
-  if (mg_stat(buf, filep)) {
-    return 1;
-  }
-
-  // if we can't find the actual file, look for the file
-  // with the same name but a .gz extension. If we find it,
-  // use that and set the gzipped flag in the file struct
-  // to indicate that the response need to have the content-
-  // encoding: gzip header
-  // we can only do this if the browser declares support
-  if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
-    if (strstr(accept_encoding,"gzip") != NULL) {
-      snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
-      if (mg_stat(gz_path, filep)) {
-        filep->gzipped = 1;
-        return 1;
+  if ((result = select(maxfd + 1, &set, NULL, NULL, &tv)) > 0) {
+    for (i = 0; i < n; i++) {
+      if (FD_ISSET(pfd[i].fd, &set)) {
+        pfd[i].revents = POLLIN;
       }
     }
   }
 
-  // Support PATH_INFO for CGI scripts.
-  for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) {
-    if (*p == '/') {
-      *p = '\0';
-      if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
-                       strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
-          mg_stat(buf, filep)) {
-        // Shift PATH_INFO block one character right, e.g.
-        //  "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
-        // conn->path_info is pointing to the local variable "path" declared
-        // in handle_request(), so PATH_INFO is not valid after
-        // handle_request returns.
-        conn->path_info = p + 1;
-        memmove(p + 2, p + 1, strlen(p + 1) + 1);  // +1 is for trailing \0
-        p[1] = '/';
-        return 1;
-      } else {
-        *p = '/';
-      }
-    }
-  }
+  return result;
+}
+#endif // HAVE_POLL
 
-  return 0;
+static void set_close_on_exec(SOCKET sock) {
+  (void) SetHandleInformation((HANDLE) sock, HANDLE_FLAG_INHERIT, 0);
 }
 
-// Check whether full request is buffered. Return:
-//   -1  if request is malformed
-//    0  if request is not yet fully buffered
-//   >0  actual request length, including last \r\n\r\n
-static int get_request_len(const char *buf, int buf_len) {
-  int i;
+int mg_start_thread(mg_thread_func_t f, void *p) {
+  return (long)_beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0;
+}
 
-  for (i = 0; i < buf_len; i++) {
-    // Control characters are not allowed but >=128 is.
-    // Abort scan as soon as one malformed character is found;
-    // don't let subsequent \r\n\r\n win us over anyhow
-    if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
-        buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
-      return -1;
-    } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
-      return i + 2;
-    } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
-               buf[i + 2] == '\n') {
-      return i + 3;
-    }
-  }
+static HANDLE dlopen(const char *dll_name, int flags) {
+  wchar_t wbuf[PATH_MAX];
+  (void) flags;
+  to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf));
+  return LoadLibraryW(wbuf);
+}
 
+#if !defined(NO_CGI)
+#define SIGKILL 0
+static int kill(pid_t pid, int sig_num) {
+  (void) TerminateProcess(pid, sig_num);
+  (void) CloseHandle(pid);
   return 0;
 }
 
-// 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;
-        }
-      }
-    }
+static void trim_trailing_whitespaces(char *s) {
+  char *e = s + strlen(s) - 1;
+  while (e > s && isspace(* (unsigned char *) e)) {
+    *e-- = '\0';
   }
-  *p = '\0';
 }
 
-static const struct {
-  const char *extension;
-  size_t ext_len;
-  const char *mime_type;
-} builtin_mime_types[] = {
-  {".html", 5, "text/html"},
-  {".htm", 4, "text/html"},
-  {".shtm", 5, "text/html"},
-  {".shtml", 6, "text/html"},
-  {".css", 4, "text/css"},
-  {".js",  3, "application/x-javascript"},
-  {".ico", 4, "image/x-icon"},
-  {".gif", 4, "image/gif"},
-  {".jpg", 4, "image/jpeg"},
-  {".jpeg", 5, "image/jpeg"},
-  {".png", 4, "image/png"},
-  {".svg", 4, "image/svg+xml"},
-  {".txt", 4, "text/plain"},
-  {".torrent", 8, "application/x-bittorrent"},
-  {".wav", 4, "audio/x-wav"},
-  {".mp3", 4, "audio/x-mp3"},
-  {".mid", 4, "audio/mid"},
-  {".m3u", 4, "audio/x-mpegurl"},
-  {".ogg", 4, "application/ogg"},
-  {".ram", 4, "audio/x-pn-realaudio"},
-  {".xml", 4, "text/xml"},
-  {".json",  5, "text/json"},
-  {".xslt", 5, "application/xml"},
-  {".xsl", 4, "application/xml"},
-  {".ra",  3, "audio/x-pn-realaudio"},
-  {".doc", 4, "application/msword"},
-  {".exe", 4, "application/octet-stream"},
-  {".zip", 4, "application/x-zip-compressed"},
-  {".xls", 4, "application/excel"},
-  {".tgz", 4, "application/x-tar-gz"},
-  {".tar", 4, "application/x-tar"},
-  {".gz",  3, "application/x-gunzip"},
-  {".arj", 4, "application/x-arj-compressed"},
-  {".rar", 4, "application/x-arj-compressed"},
-  {".rtf", 4, "application/rtf"},
-  {".pdf", 4, "application/pdf"},
-  {".swf", 4, "application/x-shockwave-flash"},
-  {".mpg", 4, "video/mpeg"},
-  {".webm", 5, "video/webm"},
-  {".mpeg", 5, "video/mpeg"},
-  {".mov", 4, "video/quicktime"},
-  {".mp4", 4, "video/mp4"},
-  {".m4v", 4, "video/x-m4v"},
-  {".asf", 4, "video/x-ms-asf"},
-  {".avi", 4, "video/x-msvideo"},
-  {".bmp", 4, "image/bmp"},
-  {".ttf", 4, "application/x-font-ttf"},
-  {NULL,  0, NULL}
-};
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,
+                           char *envblk, char *envp[], int fdin,
+                           int fdout, const char *dir) {
+  HANDLE me;
+  char *interp, full_interp[PATH_MAX], full_dir[PATH_MAX],
+       cmdline[PATH_MAX], buf[PATH_MAX];
+  FILE *fp;
+  STARTUPINFOA si;
+  PROCESS_INFORMATION pi = { 0 };
 
-const char *mg_get_builtin_mime_type(const char *path) {
-  const char *ext;
-  size_t i, path_len;
+  (void) envp;
 
-  path_len = strlen(path);
+  memset(&si, 0, sizeof(si));
+  si.cb = sizeof(si);
 
-  for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
-    ext = path + (path_len - builtin_mime_types[i].ext_len);
-    if (path_len > builtin_mime_types[i].ext_len &&
-        mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
-      return builtin_mime_types[i].mime_type;
-    }
-  }
+  // TODO(lsm): redirect CGI errors to the error log file
+  si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+  si.wShowWindow = SW_HIDE;
 
-  return "text/plain";
-}
+  me = GetCurrentProcess();
+  DuplicateHandle(me, (HANDLE) _get_osfhandle(fdin), me,
+                  &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
+  DuplicateHandle(me, (HANDLE) _get_osfhandle(fdout), me,
+                  &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
 
-// Look at the "path" extension and figure what mime type it has.
-// Store mime type in the vector.
-static void get_mime_type(struct mg_context *ctx, const char *path,
-                          struct vec *vec) {
-  struct vec ext_vec, mime_vec;
-  const char *list, *ext;
-  size_t path_len;
+  // If CGI file is a script, try to read the interpreter line
+  interp = conn->ctx->config[CGI_INTERPRETER];
+  if (interp == NULL) {
+    buf[0] = buf[1] = '\0';
 
-  path_len = strlen(path);
+    // Read the first line of the script into the buffer
+    snprintf(cmdline, sizeof(cmdline), "%s%c%s", dir, '/', prog);
+    if ((fp = mg_fopen(cmdline, "r")) != NULL) {
+      fgets(buf, sizeof(buf), fp);
+      fclose(fp);
+      buf[sizeof(buf) - 1] = '\0';
+    }
 
-  // Scan user-defined mime types first, in case user wants to
-  // override default mime types.
-  list = ctx->config[EXTRA_MIME_TYPES];
-  while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
-    // ext now points to the path suffix
-    ext = path + path_len - ext_vec.len;
-    if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
-      *vec = mime_vec;
-      return;
+    if (buf[0] == '#' && buf[1] == '!') {
+      trim_trailing_whitespaces(buf + 2);
+    } else {
+      buf[2] = '\0';
     }
+    interp = buf + 2;
   }
 
-  vec->ptr = mg_get_builtin_mime_type(path);
-  vec->len = strlen(vec->ptr);
+  if (interp[0] != '\0') {
+    GetFullPathNameA(interp, sizeof(full_interp), full_interp, NULL);
+    interp = full_interp;
+  }
+  GetFullPathNameA(dir, sizeof(full_dir), full_dir, NULL);
+
+  mg_snprintf(cmdline, sizeof(cmdline), "%s%s\"%s\\%s\"",
+              interp, interp[0] == '\0' ? "" : " ", full_dir, prog);
+
+  DEBUG_TRACE(("Running [%s]", cmdline));
+  if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
+        CREATE_NEW_PROCESS_GROUP, envblk, NULL, &si, &pi) == 0) {
+    cry(conn, "%s: CreateProcess(%s): %ld",
+        __func__, cmdline, ERRNO);
+    pi.hProcess = (pid_t) -1;
+  }
+
+  (void) CloseHandle(si.hStdOutput);
+  (void) CloseHandle(si.hStdInput);
+  (void) CloseHandle(pi.hThread);
+
+  return (pid_t) pi.hProcess;
 }
+#endif // !NO_CGI
 
-static int is_big_endian(void) {
-  static const int n = 1;
-  return ((char *) &n)[0] == 0;
+static int set_non_blocking_mode(SOCKET sock) {
+  unsigned long on = 1;
+  return ioctlsocket(sock, FIONBIO, &on);
 }
 
-#ifndef HAVE_MD5
-typedef struct MD5Context {
-  uint32_t buf[4];
-  uint32_t bits[2];
-  unsigned char in[64];
-} MD5_CTX;
+#else
+static int mg_stat(const char *path, struct file *filep) {
+  struct stat st;
 
-static void byteReverse(unsigned char *buf, unsigned longs) {
-  uint32_t t;
+  filep->modification_time = (time_t) 0;
+  if (stat(path, &st) == 0) {
+    filep->size = st.st_size;
+    filep->modification_time = st.st_mtime;
+    filep->is_directory = S_ISDIR(st.st_mode);
 
-  // Forrest: MD5 expect LITTLE_ENDIAN, swap if BIG_ENDIAN
-  if (is_big_endian()) {
-    do {
-      t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
-        ((unsigned) buf[1] << 8 | buf[0]);
-      * (uint32_t *) buf = t;
-      buf += 4;
-    } while (--longs);
+    // See https://github.com/cesanta/mongoose/issues/109
+    // Some filesystems report modification time as 0. Artificially
+    // bump it up to mark mg_stat() success.
+    if (filep->modification_time == (time_t) 0) {
+      filep->modification_time = (time_t) 1;
+    }
   }
+
+  return filep->modification_time != (time_t) 0;
 }
 
-#define F1(x, y, z) (z ^ (x & (y ^ z)))
-#define F2(x, y, z) F1(z, x, y)
-#define F3(x, y, z) (x ^ y ^ z)
-#define F4(x, y, z) (y ^ (x | ~z))
+static void set_close_on_exec(int fd) {
+  fcntl(fd, F_SETFD, FD_CLOEXEC);
+}
 
-#define MD5STEP(f, w, x, y, z, data, s) \
-  ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+int mg_start_thread(mg_thread_func_t func, void *param) {
+  pthread_t thread_id;
+  pthread_attr_t attr;
+  int result;
 
-// Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
-// initialization constants.
-static void MD5Init(MD5_CTX *ctx) {
-  ctx->buf[0] = 0x67452301;
-  ctx->buf[1] = 0xefcdab89;
-  ctx->buf[2] = 0x98badcfe;
-  ctx->buf[3] = 0x10325476;
+  (void) pthread_attr_init(&attr);
+  (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
 
-  ctx->bits[0] = 0;
-  ctx->bits[1] = 0;
-}
+#if USE_STACK_SIZE > 1
+  // Compile-time option to control stack size, e.g. -DUSE_STACK_SIZE=16384
+  (void) pthread_attr_setstacksize(&attr, USE_STACK_SIZE);
+#endif
 
-static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) {
-  register uint32_t a, b, c, d;
+  result = pthread_create(&thread_id, &attr, func, param);
+  pthread_attr_destroy(&attr);
 
-  a = buf[0];
-  b = buf[1];
-  c = buf[2];
-  d = buf[3];
+  return result;
+}
 
-  MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
-  MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
-  MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
-  MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
-  MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
-  MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
-  MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
-  MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
-  MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
-  MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
-  MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
-  MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
-  MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
-  MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
-  MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
-  MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+#ifndef NO_CGI
+static pid_t spawn_process(struct mg_connection *conn, const char *prog,
+                           char *envblk, char *envp[], int fdin,
+                           int fdout, const char *dir) {
+  pid_t pid;
+  const char *interp;
 
-  MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
-  MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
-  MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
-  MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
-  MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
-  MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
-  MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
-  MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
-  MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
-  MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
-  MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
-  MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
-  MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
-  MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
-  MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
-  MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+  (void) envblk;
 
-  MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
-  MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
-  MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
-  MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
-  MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
-  MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
-  MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
-  MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
-  MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
-  MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
-  MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
-  MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
-  MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
-  MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
-  MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
-  MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+  if ((pid = fork()) == -1) {
+    // Parent
+    send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO));
+  } else if (pid == 0) {
+    // Child
+    if (chdir(dir) != 0) {
+      cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO));
+    } else if (dup2(fdin, 0) == -1) {
+      cry(conn, "%s: dup2(%d, 0): %s", __func__, fdin, strerror(ERRNO));
+    } else if (dup2(fdout, 1) == -1) {
+      cry(conn, "%s: dup2(%d, 1): %s", __func__, fdout, strerror(ERRNO));
+    } else {
+      // Not redirecting stderr to stdout, to avoid output being littered
+      // with the error messages.
+      (void) close(fdin);
+      (void) close(fdout);
 
-  MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
-  MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
-  MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
-  MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
-  MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
-  MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
-  MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
-  MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
-  MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
-  MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
-  MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
-  MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
-  MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
-  MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
-  MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
-  MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+      // After exec, all signal handlers are restored to their default values,
+      // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's
+      // implementation, SIGCHLD's handler will leave unchanged after exec
+      // if it was set to be ignored. Restore it to default action.
+      signal(SIGCHLD, SIG_DFL);
+
+      interp = conn->ctx->config[CGI_INTERPRETER];
+      if (interp == NULL) {
+        (void) execle(prog, prog, NULL, envp);
+        cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
+      } else {
+        (void) execle(interp, interp, prog, NULL, envp);
+        cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog,
+            strerror(ERRNO));
+      }
+    }
+    exit(EXIT_FAILURE);
+  }
 
-  buf[0] += a;
-  buf[1] += b;
-  buf[2] += c;
-  buf[3] += d;
+  return pid;
 }
+#endif // !NO_CGI
 
-static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) {
-  uint32_t t;
+static int set_non_blocking_mode(SOCKET sock) {
+  int flags;
 
-  t = ctx->bits[0];
-  if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t)
-    ctx->bits[1]++;
-  ctx->bits[1] += len >> 29;
+  flags = fcntl(sock, F_GETFL, 0);
+  (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK);
 
-  t = (t >> 3) & 0x3f;
+  return 0;
+}
+#endif // _WIN32
 
-  if (t) {
-    unsigned char *p = (unsigned char *) ctx->in + t;
+// Write data to the IO channel - opened file descriptor, socket or SSL
+// descriptor. Return number of bytes written.
+static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
+                    int64_t len) {
+  int64_t sent;
+  int n, k;
 
-    t = 64 - t;
-    if (len < t) {
-      memcpy(p, buf, len);
-      return;
+  (void) ssl;  // Get rid of warning
+  sent = 0;
+  while (sent < len) {
+
+    // How many bytes we send in this iteration
+    k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
+
+    if (ssl != NULL) {
+      n = SSL_write(ssl, buf + sent, k);
+    } else if (fp != NULL) {
+      n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
+      if (ferror(fp))
+        n = -1;
+    } else {
+      n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
     }
-    memcpy(p, buf, t);
-    byteReverse(ctx->in, 16);
-    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += t;
-    len -= t;
+
+    if (n <= 0)
+      break;
+
+    sent += n;
   }
 
-  while (len >= 64) {
-    memcpy(ctx->in, buf, 64);
-    byteReverse(ctx->in, 16);
-    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-    buf += 64;
-    len -= 64;
+  return sent;
+}
+
+// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
+// Return negative value on error, or number of bytes read on success.
+static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
+  int nread;
+
+  if (len <= 0) return 0;
+  if (fp != NULL) {
+    // Use read() instead of fread(), because if we're reading from the CGI
+    // pipe, fread() may block until IO buffer is filled up. We cannot afford
+    // to block and must pass all read bytes immediately to the client.
+    nread = read(fileno(fp), buf, (size_t) len);
+#ifndef NO_SSL
+  } else if (conn->ssl != NULL) {
+    nread = SSL_read(conn->ssl, buf, len);
+#endif
+  } else {
+    nread = recv(conn->client.sock, buf, (size_t) len, 0);
+  }
+  if (nread > 0) {
+    conn->num_bytes_read += nread;
   }
 
-  memcpy(ctx->in, buf, len);
+  return conn->ctx->stop_flag ? -1 : nread;
 }
 
-static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) {
-  unsigned count;
-  unsigned char *p;
-  uint32_t *a;
+static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
+  int n, nread = 0;
 
-  count = (ctx->bits[0] >> 3) & 0x3F;
+  while (len > 0 && conn->ctx->stop_flag == 0) {
+    n = pull(fp, conn, buf + nread, len);
+    if (n < 0) {
+      nread = n;  // Propagate the error
+      break;
+    } else if (n == 0) {
+      break;  // No more data to read
+    } else {
+      nread += n;
+      len -= n;
+    }
+  }
 
-  p = ctx->in + count;
-  *p++ = 0x80;
-  count = 64 - 1 - count;
-  if (count < 8) {
-    memset(p, 0, count);
-    byteReverse(ctx->in, 16);
-    MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-    memset(ctx->in, 0, 56);
-  } else {
-    memset(p, 0, count - 8);
+  return nread;
+}
+
+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;
   }
-  byteReverse(ctx->in, 14);
 
-  a = (uint32_t *)ctx->in;
-  a[14] = ctx->bits[0];
-  a[15] = ctx->bits[1];
+  // conn->buf           body
+  //    |=================|==========|===============|
+  //    |<--request_len-->|                          |
+  //    |<-----------data_len------->|      conn->buf + conn->buf_size
 
-  MD5Transform(ctx->buf, (uint32_t *) ctx->in);
-  byteReverse((unsigned char *) ctx->buf, 4);
-  memcpy(digest, ctx->buf, 16);
-  memset((char *) ctx, 0, sizeof(*ctx));
+  // First, check for data buffered in conn->buf by read_request().
+  if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
+    char *body = conn->buf + conn->request_len;
+    if (buffered_len > len) buffered_len = len;
+    if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
+
+    memcpy(buf, body, (size_t) buffered_len);
+    memmove(body, body + buffered_len,
+            &conn->buf[conn->data_len] - &body[buffered_len]);
+    len -= buffered_len;
+    conn->data_len -= buffered_len;
+    nread += buffered_len;
+  }
+
+  // Read data from the socket.
+  if (len > 0 && (left = left_to_read(conn)) > 0) {
+    if (left < len) {
+      len = (int) left;
+    }
+    n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
+    nread = n >= 0 ? nread + n : n;
+  }
+
+  return nread;
 }
-#endif // !HAVE_MD5
 
-// Stringify binary data. Output buffer must be twice as big as input,
-// because each byte takes 2 bytes in string representation
-static void bin2str(char *to, const unsigned char *p, size_t len) {
-  static const char *hex = "0123456789abcdef";
+int mg_write(struct mg_connection *conn, const void *buf, int len) {
+  time_t now;
+  int64_t n, total, allowed;
 
-  for (; len--; p++) {
-    *to++ = hex[p[0] >> 4];
-    *to++ = hex[p[0] & 0x0f];
+  if (conn->throttle > 0) {
+    if ((now = time(NULL)) != conn->last_throttle_time) {
+      conn->last_throttle_time = now;
+      conn->last_throttle_bytes = 0;
+    }
+    allowed = conn->throttle - conn->last_throttle_bytes;
+    if (allowed > (int64_t) len) {
+      allowed = len;
+    }
+    if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+                      (int64_t) allowed)) == allowed) {
+      buf = (char *) buf + total;
+      conn->last_throttle_bytes += total;
+      while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
+        allowed = conn->throttle > (int64_t) len - total ?
+          (int64_t) len - total : conn->throttle;
+        if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+                      (int64_t) allowed)) != allowed) {
+          break;
+        }
+        sleep(1);
+        conn->last_throttle_bytes = allowed;
+        conn->last_throttle_time = time(NULL);
+        buf = (char *) buf + n;
+        total += n;
+      }
+    }
+  } else {
+    total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
+                 (int64_t) len);
   }
-  *to = '\0';
+  return (int) total;
 }
 
-// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
-char *mg_md5(char buf[33], ...) {
-  unsigned char hash[16];
-  const char *p;
-  va_list ap;
-  MD5_CTX ctx;
+// Print message to buffer. If buffer is large enough to hold the message,
+// return buffer. If buffer is to small, allocate large enough buffer on heap,
+// and return allocated buffer.
+static int alloc_vprintf(char **buf, size_t size, const char *fmt, va_list ap) {
+  va_list ap_copy;
+  int len;
 
-  MD5Init(&ctx);
+  // Windows is not standard-compliant, and vsnprintf() returns -1 if
+  // buffer is too small. Also, older versions of msvcrt.dll do not have
+  // _vscprintf().  However, if size is 0, vsnprintf() behaves correctly.
+  // Therefore, we make two passes: on first pass, get required message length.
+  // On second pass, actually print the message.
+  va_copy(ap_copy, ap);
+  len = vsnprintf(NULL, 0, fmt, ap_copy);
 
-  va_start(ap, buf);
-  while ((p = va_arg(ap, const char *)) != NULL) {
-    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
+  if (len > (int) size &&
+      (size = len + 1) > 0 &&
+      (*buf = (char *) malloc(size)) == NULL) {
+    len = -1;  // Allocation failed, mark failure
+  } else {
+    va_copy(ap_copy, ap);
+    vsnprintf(*buf, size, fmt, ap_copy);
+  }
+
+  return len;
+}
+
+int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
+  char mem[MG_BUF_LEN], *buf = mem;
+  int len;
+
+  if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
+    len = mg_write(conn, buf, (size_t) len);
+  }
+  if (buf != mem && buf != NULL) {
+    free(buf);
   }
-  va_end(ap);
 
-  MD5Final(hash, &ctx);
-  bin2str(buf, hash, sizeof(hash));
-  return buf;
+  return len;
 }
 
-// Check the user's password, return 1 if OK
-static int check_password(const char *method, const char *ha1, const char *uri,
-                          const char *nonce, const char *nc, const char *cnonce,
-                          const char *qop, const char *response) {
-  char ha2[32 + 1], expected_response[32 + 1];
+int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
+  va_list ap;
+  va_start(ap, fmt);
+  return mg_vprintf(conn, fmt, ap);
+}
 
-  // Some of the parameters may be NULL
-  if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL ||
-      qop == NULL || response == NULL) {
-    return 0;
-  }
+static int mg_chunked_printf(struct mg_connection *conn, const char *fmt, ...) {
+  char mem[MG_BUF_LEN], *buf = mem;
+  int len;
 
-  // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
-  // TODO(lsm): check for authentication timeout
-  if (// strcmp(dig->uri, c->ouri) != 0 ||
-      strlen(response) != 32
-      // || now - strtoul(dig->nonce, NULL, 10) > 3600
-      ) {
-    return 0;
+  va_list ap;
+  va_start(ap, fmt);
+  if ((len = alloc_vprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
+    len = mg_printf(conn, "%X\r\n%s\r\n", len, buf);
   }
 
-  mg_md5(ha2, method, ":", uri, NULL);
-  mg_md5(expected_response, ha1, ":", nonce, ":", nc,
-      ":", cnonce, ":", qop, ":", ha2, NULL);
+  if (buf != mem && buf != NULL) {
+    free(buf);
+  }
 
-  return mg_strcasecmp(response, expected_response) == 0;
+  return len;
 }
 
-// Use the global passwords file, if specified by auth_gpass option,
-// or search for .htpasswd in the requested directory.
-static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
-  char name[PATH_MAX];
-  const char *p, *e, *gpass = conn->ctx->config[GLOBAL_PASSWORDS_FILE];
-  struct file file = STRUCT_FILE_INITIALIZER;
-  FILE *fp = NULL;
+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')
 
-  if (gpass != NULL) {
-    // Use global passwords file
-    if ((fp = mg_fopen(gpass, "r")) == NULL) {
-      cry(conn, "fopen(%s): %s", gpass, strerror(ERRNO));
+  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];
     }
-    // Important: using local struct file to test path for is_directory flag.
-    // If filep is used, mg_stat() makes it appear as if auth file was opened.
-  } else if (mg_stat(path, &file) && file.is_directory) {
-    mg_snprintf(name, sizeof(name), "%s%c%s",
-                path, '/', PASSWORDS_FILE_NAME);
-    fp = mg_fopen(name, "r");
-  } else {
-     // Try to find .htpasswd in requested directory.
-    for (p = path, e = p + strlen(p) - 1; e > p; e--)
-      if (e[0] == '/')
-        break;
-    mg_snprintf(name, sizeof(name), "%.*s%c%s",
-                (int) (e - p), p, '/', PASSWORDS_FILE_NAME);
-    fp = mg_fopen(name, "r");
   }
 
-  return fp;
+  dst[j] = '\0'; // Null-terminate the destination
+
+  return i >= src_len ? j : -1;
 }
 
-// Parsed Authorization header
-struct ah {
-  char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
-};
+int mg_get_var(const char *data, size_t data_len, const char *name,
+               char *dst, size_t dst_len) {
+  const char *p, *e, *s;
+  size_t name_len;
+  int len;
 
-// Return 1 on success. Always initializes the ah structure.
-static int parse_auth_header(struct mg_connection *conn, char *buf,
-                             size_t buf_size, struct ah *ah) {
-  char *name, *value, *s;
-  const char *auth_header;
+  if (dst == NULL || dst_len == 0) {
+    len = -2;
+  } else if (data == NULL || name == NULL || data_len == 0) {
+    len = -1;
+    dst[0] = '\0';
+  } else {
+    name_len = strlen(name);
+    e = data + data_len;
+    len = -1;
+    dst[0] = '\0';
 
-  (void) memset(ah, 0, sizeof(*ah));
-  if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
-      mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
-    return 0;
-  }
+    // data is "var1=val1&var2=val2...". Find variable first
+    for (p = data; p + name_len < e; p++) {
+      if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
+          !mg_strncasecmp(name, p, name_len)) {
 
-  // Make modifiable copy of the auth header
-  (void) mg_strlcpy(buf, auth_header + 7, buf_size);
-  s = buf;
+        // Point p to variable value
+        p += name_len + 1;
 
-  // Parse authorization header
-  for (;;) {
-    // Gobble initial spaces
-    while (isspace(* (unsigned char *) s)) {
-      s++;
-    }
-    name = skip_quoted(&s, "=", " ", 0);
-    // Value is either quote-delimited, or ends at first comma or space.
-    if (s[0] == '\"') {
-      s++;
-      value = skip_quoted(&s, "\"", " ", '\\');
-      if (s[0] == ',') {
-        s++;
-      }
-    } else {
-      value = skip_quoted(&s, ", ", " ", 0);  // IE uses commas, FF uses spaces
-    }
-    if (*name == '\0') {
-      break;
-    }
+        // Point s to the end of the value
+        s = (const char *) memchr(p, '&', (size_t)(e - p));
+        if (s == NULL) {
+          s = e;
+        }
+        assert(s >= p);
 
-    if (!strcmp(name, "username")) {
-      ah->user = value;
-    } else if (!strcmp(name, "cnonce")) {
-      ah->cnonce = value;
-    } else if (!strcmp(name, "response")) {
-      ah->response = value;
-    } else if (!strcmp(name, "uri")) {
-      ah->uri = value;
-    } else if (!strcmp(name, "qop")) {
-      ah->qop = value;
-    } else if (!strcmp(name, "nc")) {
-      ah->nc = value;
-    } else if (!strcmp(name, "nonce")) {
-      ah->nonce = value;
-    }
-  }
+        // Decode variable into destination buffer
+        len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
 
-  // CGI needs it as REMOTE_USER
-  if (ah->user != NULL) {
-    conn->request_info.remote_user = mg_strdup(ah->user);
-  } else {
-    return 0;
+        // Redirect error code from -1 to -2 (destination buffer too small).
+        if (len == -1) {
+          len = -2;
+        }
+        break;
+      }
+    }
   }
 
-  return 1;
+  return len;
 }
 
-// Authorize against the opened passwords file. Return 1 if authorized.
-static int authorize(struct mg_connection *conn, FILE *fp) {
-  struct ah ah;
-  char line[256], f_user[256], ha1[256], f_domain[256], buf[MG_BUF_LEN];
+int mg_get_cookie(const char *cookie_header, const char *var_name,
+                  char *dst, size_t dst_size) {
+  const char *s, *p, *end;
+  int name_len, len = -1;
 
-  if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
-    return 0;
-  }
+  if (dst == NULL || dst_size == 0) {
+    len = -2;
+  } else if (var_name == NULL || (s = cookie_header) == NULL) {
+    len = -1;
+    dst[0] = '\0';
+  } else {
+    name_len = (int) strlen(var_name);
+    end = s + strlen(s);
+    dst[0] = '\0';
 
-  // Loop over passwords file
-  while (fgets(line, sizeof(line), fp) != NULL) {
-    if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) {
-      continue;
+    for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
+      if (s[name_len] == '=') {
+        s += name_len + 1;
+        if ((p = strchr(s, ' ')) == NULL)
+          p = end;
+        if (p[-1] == ';')
+          p--;
+        if (*s == '"' && p[-1] == '"' && p > s + 1) {
+          s++;
+          p--;
+        }
+        if ((size_t) (p - s) < dst_size) {
+          len = p - s;
+          mg_strlcpy(dst, s, (size_t) len + 1);
+        } else {
+          len = -3;
+        }
+        break;
+      }
     }
-
-    if (!strcmp(ah.user, f_user) &&
-        !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
-      return check_password(conn->request_info.request_method, ha1, ah.uri,
-                            ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response);
   }
-
-  return 0;
+  return len;
 }
 
-// Return 1 if request is authorised, 0 otherwise.
-static int check_authorization(struct mg_connection *conn, const char *path) {
-  char fname[PATH_MAX];
-  struct vec uri_vec, filename_vec;
-  const char *list;
-  FILE *fp = NULL;
-  int authorized = 1;
+// Return 1 if real file has been found, 0 otherwise
+static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
+                                    size_t buf_len, struct file *filep) {
+  struct vec a, b;
+  const char *rewrite, *uri = conn->request_info.uri,
+        *root = conn->ctx->config[DOCUMENT_ROOT];
+  char *p;
+  int match_len;
+  char gz_path[PATH_MAX];
+  char const* accept_encoding;
+
+  // No filesystem access
+  if (root == NULL) {
+    return 0;
+  }
 
-  list = conn->ctx->config[PROTECT_URI];
-  while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
-    if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
-      mg_snprintf(fname, sizeof(fname), "%.*s",
-                  (int) filename_vec.len, filename_vec.ptr);
-      if ((fp = mg_fopen(fname, "r")) == NULL) {
-        cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno));
-      }
+  // Using buf_len - 1 because memmove() for PATH_INFO may shift part
+  // of the path one byte on the right.
+  // If document_root is NULL, leave the file empty.
+  mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
+
+  rewrite = conn->ctx->config[REWRITE];
+  while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
+    if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
+      mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
+                  uri + match_len);
       break;
     }
   }
 
-  if (fp == NULL) {
-    fp = open_auth_file(conn, path);
+  if (mg_stat(buf, filep)) {
+    return 1;
   }
 
-  if (fp != NULL) {
-    authorized = authorize(conn, fp);
-    fclose(fp);
+  // if we can't find the actual file, look for the file
+  // with the same name but a .gz extension. If we find it,
+  // use that and set the gzipped flag in the file struct
+  // to indicate that the response need to have the content-
+  // encoding: gzip header
+  // we can only do this if the browser declares support
+  if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
+    if (strstr(accept_encoding,"gzip") != NULL) {
+      snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
+      if (mg_stat(gz_path, filep)) {
+        filep->gzipped = 1;
+        return 1;
+      }
+    }
   }
 
-  return authorized;
-}
+  // Support PATH_INFO for CGI scripts.
+  for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) {
+    if (*p == '/') {
+      *p = '\0';
+      if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
+                       strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
+          mg_stat(buf, filep)) {
+        // Shift PATH_INFO block one character right, e.g.
+        //  "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
+        // conn->path_info is pointing to the local variable "path" declared
+        // in handle_request(), so PATH_INFO is not valid after
+        // handle_request returns.
+        conn->path_info = p + 1;
+        memmove(p + 2, p + 1, strlen(p + 1) + 1);  // +1 is for trailing \0
+        p[1] = '/';
+        return 1;
+      } else {
+        *p = '/';
+      }
+    }
+  }
 
-static void send_authorization_request(struct mg_connection *conn) {
-  conn->status_code = 401;
-  mg_printf(conn,
-            "HTTP/1.1 401 Unauthorized\r\n"
-            "Content-Length: 0\r\n"
-            "WWW-Authenticate: Digest qop=\"auth\", "
-            "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
-            conn->ctx->config[AUTHENTICATION_DOMAIN],
-            (unsigned long) time(NULL));
+  return 0;
 }
 
-static int is_authorized_for_put(struct mg_connection *conn) {
-  const char *passfile = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE];
-  FILE *fp;
-  int ret = 0;
+// Check whether full request is buffered. Return:
+//   -1  if request is malformed
+//    0  if request is not yet fully buffered
+//   >0  actual request length, including last \r\n\r\n
+static int get_request_len(const char *buf, int buf_len) {
+  int i;
 
-  if (passfile != NULL && (fp = mg_fopen(passfile, "r")) != NULL) {
-    ret = authorize(conn, fp);
-    fclose(fp);
+  for (i = 0; i < buf_len; i++) {
+    // Control characters are not allowed but >=128 is.
+    // Abort scan as soon as one malformed character is found;
+    // don't let subsequent \r\n\r\n win us over anyhow
+    if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
+        buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
+      return -1;
+    } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
+      return i + 2;
+    } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
+               buf[i + 2] == '\n') {
+      return i + 3;
+    }
   }
 
-  return ret;
+  return 0;
 }
 
-int mg_modify_passwords_file(const char *fname, const char *domain,
-                             const char *user, const char *pass) {
-  int found;
-  char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
-  FILE *fp, *fp2;
-
-  found = 0;
-  fp = fp2 = NULL;
+// Protect against directory disclosure attack by removing '..',
+// excessive '/' and '\' characters
+static void remove_double_dots_and_double_slashes(char *s) {
+  char *p = s;
 
-  // Regard empty password as no password - remove user record.
-  if (pass != NULL && pass[0] == '\0') {
-    pass = NULL;
+  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';
+}
 
-  (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
-
-  // Create the file if does not exist
-  if ((fp = fopen(fname, "a+")) != NULL) {
-    fclose(fp);
-  }
+static const struct {
+  const char *extension;
+  size_t ext_len;
+  const char *mime_type;
+} builtin_mime_types[] = {
+  {".html", 5, "text/html"},
+  {".htm", 4, "text/html"},
+  {".shtm", 5, "text/html"},
+  {".shtml", 6, "text/html"},
+  {".css", 4, "text/css"},
+  {".js",  3, "application/x-javascript"},
+  {".ico", 4, "image/x-icon"},
+  {".gif", 4, "image/gif"},
+  {".jpg", 4, "image/jpeg"},
+  {".jpeg", 5, "image/jpeg"},
+  {".png", 4, "image/png"},
+  {".svg", 4, "image/svg+xml"},
+  {".txt", 4, "text/plain"},
+  {".torrent", 8, "application/x-bittorrent"},
+  {".wav", 4, "audio/x-wav"},
+  {".mp3", 4, "audio/x-mp3"},
+  {".mid", 4, "audio/mid"},
+  {".m3u", 4, "audio/x-mpegurl"},
+  {".ogg", 4, "application/ogg"},
+  {".ram", 4, "audio/x-pn-realaudio"},
+  {".xml", 4, "text/xml"},
+  {".json",  5, "text/json"},
+  {".xslt", 5, "application/xml"},
+  {".xsl", 4, "application/xml"},
+  {".ra",  3, "audio/x-pn-realaudio"},
+  {".doc", 4, "application/msword"},
+  {".exe", 4, "application/octet-stream"},
+  {".zip", 4, "application/x-zip-compressed"},
+  {".xls", 4, "application/excel"},
+  {".tgz", 4, "application/x-tar-gz"},
+  {".tar", 4, "application/x-tar"},
+  {".gz",  3, "application/x-gunzip"},
+  {".arj", 4, "application/x-arj-compressed"},
+  {".rar", 4, "application/x-arj-compressed"},
+  {".rtf", 4, "application/rtf"},
+  {".pdf", 4, "application/pdf"},
+  {".swf", 4, "application/x-shockwave-flash"},
+  {".mpg", 4, "video/mpeg"},
+  {".webm", 5, "video/webm"},
+  {".mpeg", 5, "video/mpeg"},
+  {".mov", 4, "video/quicktime"},
+  {".mp4", 4, "video/mp4"},
+  {".m4v", 4, "video/x-m4v"},
+  {".asf", 4, "video/x-ms-asf"},
+  {".avi", 4, "video/x-msvideo"},
+  {".bmp", 4, "image/bmp"},
+  {".ttf", 4, "application/x-font-ttf"},
+  {NULL,  0, NULL}
+};
 
-  // Open the given file and temporary file
-  if ((fp = fopen(fname, "r")) == NULL) {
-    return 0;
-  } else if ((fp2 = fopen(tmp, "w+")) == NULL) {
-    fclose(fp);
-    return 0;
-  }
+const char *mg_get_builtin_mime_type(const char *path) {
+  const char *ext;
+  size_t i, path_len;
 
-  // Copy the stuff to temporary file
-  while (fgets(line, sizeof(line), fp) != NULL) {
-    if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) {
-      continue;
-    }
+  path_len = strlen(path);
 
-    if (!strcmp(u, user) && !strcmp(d, domain)) {
-      found++;
-      if (pass != NULL) {
-        mg_md5(ha1, user, ":", domain, ":", pass, NULL);
-        fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
-      }
-    } else {
-      fprintf(fp2, "%s", line);
+  for (i = 0; builtin_mime_types[i].extension != NULL; i++) {
+    ext = path + (path_len - builtin_mime_types[i].ext_len);
+    if (path_len > builtin_mime_types[i].ext_len &&
+        mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) {
+      return builtin_mime_types[i].mime_type;
     }
   }
 
-  // If new user, just add it
-  if (!found && pass != NULL) {
-    mg_md5(ha1, user, ":", domain, ":", pass, NULL);
-    fprintf(fp2, "%s:%s:%s\n", user, domain, ha1);
-  }
+  return "text/plain";
+}
 
-  // Close files
-  fclose(fp);
-  fclose(fp2);
+// Look at the "path" extension and figure what mime type it has.
+// Store mime type in the vector.
+static void get_mime_type(struct mg_context *ctx, const char *path,
+                          struct vec *vec) {
+  struct vec ext_vec, mime_vec;
+  const char *list, *ext;
+  size_t path_len;
 
-  // Put the temp file in place of real file
-  remove(fname);
-  rename(tmp, fname);
+  path_len = strlen(path);
 
-  return 1;
+  // Scan user-defined mime types first, in case user wants to
+  // override default mime types.
+  list = ctx->config[EXTRA_MIME_TYPES];
+  while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
+    // ext now points to the path suffix
+    ext = path + path_len - ext_vec.len;
+    if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) {
+      *vec = mime_vec;
+      return;
+    }
+  }
+
+  vec->ptr = mg_get_builtin_mime_type(path);
+  vec->len = strlen(vec->ptr);
 }
 
 static SOCKET conn2(const char *host, int port, int use_ssl,