diff --git a/docs/c-api/mg_http_server.h/intro.md b/docs/c-api/mg_http_server.h/intro.md
index 03b026d9fc786deced5c669a8e20a12296264d98..1a6055be5c8f1101d64b372a8cd7b3dba785ac05 100644
--- a/docs/c-api/mg_http_server.h/intro.md
+++ b/docs/c-api/mg_http_server.h/intro.md
@@ -10,6 +10,7 @@ items:
   - { name: mg_get_http_var.md }
   - { name: mg_http_check_digest_auth.md }
   - { name: mg_http_parse_header.md }
+  - { name: mg_http_parse_header2.md }
   - { name: mg_http_reverse_proxy.md }
   - { name: mg_http_send_error.md }
   - { name: mg_http_send_redirect.md }
diff --git a/docs/c-api/mg_http_server.h/mg_http_parse_header.md b/docs/c-api/mg_http_server.h/mg_http_parse_header.md
index f86564536ab146012c4b3582eba071812a00d0ff..969a13c94fc0fbf600b7bb98c61d65529c8f0559 100644
--- a/docs/c-api/mg_http_server.h/mg_http_parse_header.md
+++ b/docs/c-api/mg_http_server.h/mg_http_parse_header.md
@@ -7,17 +7,9 @@ signature: |
                            size_t buf_size);
 ---
 
-Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value
-in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero
-otherwise.
+DEPRECATED: use mg_http_parse_header2() instead.
 
-This function is supposed to parse cookies, authentication headers, etc.
-Example (error handling omitted):
-
-    char user[20];
-    struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
-    mg_http_parse_header(hdr, "username", user, sizeof(user));
-
-Returns the length of the variable's value. If buffer is not large enough,
-or variable not found, 0 is returned. 
+Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of
+`char **`), and thus it cannot allocate a new buffer if the provided one
+is not enough, and just returns 0 in that case. 
 
diff --git a/docs/c-api/mg_http_server.h/mg_http_parse_header2.md b/docs/c-api/mg_http_server.h/mg_http_parse_header2.md
new file mode 100644
index 0000000000000000000000000000000000000000..4f1e62ed50d5e603923ba024f43869ef8a7696e9
--- /dev/null
+++ b/docs/c-api/mg_http_server.h/mg_http_parse_header2.md
@@ -0,0 +1,30 @@
+---
+title: "mg_http_parse_header2()"
+decl_name: "mg_http_parse_header2"
+symbol_kind: "func"
+signature: |
+  int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
+                            size_t buf_size);
+---
+
+Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value
+in the buffer `*buf`, `buf_size`. If the buffer size is not enough,
+allocates a buffer of required size and writes it to `*buf`, similar to
+asprintf(). The caller should always check whether the buffer was updated,
+and free it if so.
+
+This function is supposed to parse cookies, authentication headers, etc.
+Example (error handling omitted):
+
+    char user_buf[20];
+    char *user = user_buf;
+    struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
+    mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf));
+    // ... do something useful with user
+    if (user != user_buf) {
+      free(user);
+    }
+
+Returns the length of the variable's value. If variable is not found, 0 is
+returned. 
+
diff --git a/examples/cookie_auth/cookie_auth.c b/examples/cookie_auth/cookie_auth.c
index d0bc672ce8198e191ea5d168c696c6d48cdda354..6111e62aa4cf05cd4cd59e566af5cc59178e9c8d 100644
--- a/examples/cookie_auth/cookie_auth.c
+++ b/examples/cookie_auth/cookie_auth.c
@@ -59,20 +59,29 @@ static int check_pass(const char *user, const char *pass) {
  */
 static struct session *get_session(struct http_message *hm) {
   struct mg_str *cookie_header = mg_get_http_header(hm, "cookie");
-  if (cookie_header == NULL) return NULL;
-  char ssid[21];
-  if (!mg_http_parse_header(cookie_header, SESSION_COOKIE_NAME, ssid,
-                            sizeof(ssid))) {
-    return NULL;
+  if (cookie_header == NULL) goto clean;
+  char ssid_buf[21];
+  char *ssid = ssid_buf;
+  struct session *ret = NULL;
+  if (!mg_http_parse_header2(cookie_header, SESSION_COOKIE_NAME, &ssid,
+                             sizeof(ssid_buf))) {
+    goto clean;
   }
   uint64_t sid = strtoull(ssid, NULL, 16);
-  for (int i = 0; i < NUM_SESSIONS; i++) {
+  int i;
+  for (i = 0; i < NUM_SESSIONS; i++) {
     if (s_sessions[i].id == sid) {
       s_sessions[i].last_used = mg_time();
-      return &s_sessions[i];
+      ret = &s_sessions[i];
+      goto clean;
     }
   }
-  return NULL;
+
+clean:
+  if (ssid != ssid_buf) {
+    free(ssid);
+  }
+  return ret;
 }
 
 /*
@@ -91,7 +100,8 @@ static struct session *create_session(const char *user,
   /* Find first available slot or use the oldest one. */
   struct session *s = NULL;
   struct session *oldest_s = s_sessions;
-  for (int i = 0; i < NUM_SESSIONS; i++) {
+  int i;
+  for (i = 0; i < NUM_SESSIONS; i++) {
     if (s_sessions[i].id == 0) {
       s = &s_sessions[i];
       break;
@@ -176,7 +186,8 @@ static void logout_handler(struct mg_connection *nc, int ev, void *p) {
 /* Cleans up sessions that have been idle for too long. */
 void check_sessions(void) {
   double threshold = mg_time() - SESSION_TTL;
-  for (int i = 0; i < NUM_SESSIONS; i++) {
+  int i;
+  for (i = 0; i < NUM_SESSIONS; i++) {
     struct session *s = &s_sessions[i];
     if (s->id != 0 && s->last_used < threshold) {
       fprintf(stderr, "Session %" INT64_X_FMT " (%s) closed due to idleness.\n",
diff --git a/mongoose.c b/mongoose.c
index 3de8b98d1c1357ca0a5dd8a72ac7fac59bb06bd6..141e501762054b8de40e3253276b99b8260bc207 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -5373,6 +5373,86 @@ out:
 /* Amalgamated: #include "mg_internal.h" */
 /* Amalgamated: #include "mg_util.h" */
 
+/* altbuf {{{ */
+
+/*
+ * Alternate buffer: fills the client-provided buffer with data; and if it's
+ * not large enough, allocates another buffer (via mbuf), similar to asprintf.
+ */
+struct altbuf {
+  struct mbuf m;
+  char *user_buf;
+  size_t len;
+  size_t user_buf_size;
+};
+
+/*
+ * Initializes altbuf; `buf`, `buf_size` is the client-provided buffer.
+ */
+MG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) {
+  mbuf_init(&ab->m, 0);
+  ab->user_buf = buf;
+  ab->user_buf_size = buf_size;
+  ab->len = 0;
+}
+
+/*
+ * Appends a single char to the altbuf.
+ */
+MG_INTERNAL void altbuf_append(struct altbuf *ab, char c) {
+  if (ab->len < ab->user_buf_size) {
+    /* The data fits into the original buffer */
+    ab->user_buf[ab->len++] = c;
+  } else {
+    /* The data can't fit into the original buffer, so write it to mbuf.  */
+
+    /*
+     * First of all, see if that's the first byte which overflows the original
+     * buffer: if so, copy the existing data from there to a newly allocated
+     * mbuf.
+     */
+    if (ab->len > 0 && ab->m.len == 0) {
+      mbuf_append(&ab->m, ab->user_buf, ab->len);
+    }
+
+    mbuf_append(&ab->m, &c, 1);
+    ab->len = ab->m.len;
+  }
+}
+
+/*
+ * Resets any data previously appended to altbuf.
+ */
+MG_INTERNAL void altbuf_reset(struct altbuf *ab) {
+  mbuf_free(&ab->m);
+  ab->len = 0;
+}
+
+/*
+ * Returns whether the additional buffer was allocated (and thus the data
+ * is in the mbuf, not the client-provided buffer)
+ */
+MG_INTERNAL int altbuf_reallocated(struct altbuf *ab) {
+  return ab->len > ab->user_buf_size;
+}
+
+/*
+ * Returns the actual buffer with data, either the client-provided or a newly
+ * allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first.
+ */
+MG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) {
+  if (altbuf_reallocated(ab)) {
+    if (trim) {
+      mbuf_trim(&ab->m);
+    }
+    return ab->m.buf;
+  } else {
+    return ab->user_buf;
+  }
+}
+
+/* }}} */
+
 static const char *mg_version_header = "Mongoose/" MG_VERSION;
 
 enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT };
@@ -6182,7 +6262,8 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
   struct mg_str *ct;
   struct mbuf *io = &nc->recv_mbuf;
 
-  char boundary[100];
+  char boundary_buf[100];
+  char *boundary = boundary_buf;
   int boundary_len;
 
   ct = mg_get_http_header(hm, "Content-Type");
@@ -6197,7 +6278,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
   }
 
   boundary_len =
-      mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
+      mg_http_parse_header2(ct, "boundary", &boundary, sizeof(boundary_buf));
   if (boundary_len == 0) {
     /*
      * Content type is multipart, but there is no boundary,
@@ -6234,7 +6315,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
     mbuf_remove(io, req_len);
   }
 exit_mp:
-  ;
+  if (boundary != boundary_buf) MG_FREE(boundary);
 }
 
 #define CONTENT_DISPOSITION "Content-Disposition: "
@@ -6316,17 +6397,24 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
   return 1;
 }
 
+static void mg_http_parse_header_internal(struct mg_str *hdr,
+                                          const char *var_name,
+                                          struct altbuf *ab);
+
 static int mg_http_multipart_process_boundary(struct mg_connection *c) {
   int data_size;
   const char *boundary, *block_begin;
   struct mbuf *io = &c->recv_mbuf;
   struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-  char file_name[100], var_name[100];
+  struct altbuf ab_file_name, ab_var_name;
   int line_len;
   boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
   block_begin = boundary + pd->mp_stream.boundary_len + 2;
   data_size = io->len - (block_begin - io->buf);
 
+  altbuf_init(&ab_file_name, NULL, 0);
+  altbuf_init(&ab_var_name, NULL, 0);
+
   while (data_size > 0 &&
          (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
     if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
@@ -6336,11 +6424,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
 
       header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
       header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
-      mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
-      mg_http_parse_header(&header, "filename", file_name,
-                           sizeof(file_name) - 2);
+
+      altbuf_reset(&ab_var_name);
+      mg_http_parse_header_internal(&header, "name", &ab_var_name);
+
+      altbuf_reset(&ab_file_name);
+      mg_http_parse_header_internal(&header, "filename", &ab_file_name);
+
       block_begin += line_len;
       data_size -= line_len;
+
       continue;
     }
 
@@ -6351,10 +6444,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
         mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
       }
 
+      /* Reserve 2 bytes for "\r\n" in file_name and var_name */
+      altbuf_append(&ab_file_name, '\0');
+      altbuf_append(&ab_file_name, '\0');
+      altbuf_append(&ab_var_name, '\0');
+      altbuf_append(&ab_var_name, '\0');
+
       MG_FREE((void *) pd->mp_stream.file_name);
-      pd->mp_stream.file_name = strdup(file_name);
+      pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */);
       MG_FREE((void *) pd->mp_stream.var_name);
-      pd->mp_stream.var_name = strdup(var_name);
+      pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */);
 
       mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
       pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
@@ -6367,6 +6466,9 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
 
   pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
 
+  altbuf_reset(&ab_var_name);
+  altbuf_reset(&ab_file_name);
+
   return 0;
 }
 
@@ -6921,14 +7023,12 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) {
   /* LCOV_EXCL_STOP */
 }
 
-int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
-                         size_t buf_size) {
-  int ch = ' ', ch1 = ',', len = 0, n = strlen(var_name);
+static void mg_http_parse_header_internal(struct mg_str *hdr,
+                                          const char *var_name,
+                                          struct altbuf *ab) {
+  int ch = ' ', ch1 = ',', n = strlen(var_name);
   const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;
 
-  if (buf != NULL && buf_size > 0) buf[0] = '\0';
-  if (hdr == NULL) return 0;
-
   /* Find where variable starts */
   for (s = hdr->p; s != NULL && s + n < end; s++) {
     if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') &&
@@ -6942,19 +7042,51 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
       ch = ch1 = *s++;
     }
     p = s;
-    while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) {
+    while (p < end && p[0] != ch && p[0] != ch1) {
       if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++;
-      buf[len++] = *p++;
+      altbuf_append(ab, *p++);
     }
-    if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
-      len = 0;
-    } else {
-      if (len > 0 && s[len - 1] == ',') len--;
-      if (len > 0 && s[len - 1] == ';') len--;
-      buf[len] = '\0';
+
+    if (ch != ' ' && *p != ch) {
+      altbuf_reset(ab);
     }
   }
 
+  /* If there is some data, append a NUL. */
+  if (ab->len > 0) {
+    altbuf_append(ab, '\0');
+  }
+}
+
+int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
+                          size_t buf_size) {
+  struct altbuf ab;
+  altbuf_init(&ab, *buf, buf_size);
+  if (hdr == NULL) return 0;
+  if (*buf != NULL && buf_size > 0) *buf[0] = '\0';
+
+  mg_http_parse_header_internal(hdr, var_name, &ab);
+
+  /*
+   * Get a (trimmed) buffer, and return a len without a NUL byte which might
+   * have been added.
+   */
+  *buf = altbuf_get_buf(&ab, 1 /* trim */);
+  return ab.len > 0 ? ab.len - 1 : 0;
+}
+
+int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
+                         size_t buf_size) {
+  char *buf2 = buf;
+
+  int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size);
+
+  if (buf2 != buf) {
+    /* Buffer was not enough and was reallocated: free it and just return 0 */
+    MG_FREE(buf2);
+    return 0;
+  }
+
   return len;
 }
 
@@ -7092,27 +7224,34 @@ static int mg_check_nonce(const char *nonce) {
 
 int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
                               FILE *fp) {
+  int ret = 0;
   struct mg_str *hdr;
-  char username[50], cnonce[64], response[40], uri[200], qop[20], nc[20],
-      nonce[30];
+  char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200],
+      qop_buf[20], nc_buf[20], nonce_buf[16];
+
+  char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf,
+       *uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf;
 
   /* Parse "Authorization:" header, fail fast on parse error */
   if (hm == NULL || fp == NULL ||
       (hdr = mg_get_http_header(hm, "Authorization")) == NULL ||
-      mg_http_parse_header(hdr, "username", username, sizeof(username)) == 0 ||
-      mg_http_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce)) == 0 ||
-      mg_http_parse_header(hdr, "response", response, sizeof(response)) == 0 ||
-      mg_http_parse_header(hdr, "uri", uri, sizeof(uri)) == 0 ||
-      mg_http_parse_header(hdr, "qop", qop, sizeof(qop)) == 0 ||
-      mg_http_parse_header(hdr, "nc", nc, sizeof(nc)) == 0 ||
-      mg_http_parse_header(hdr, "nonce", nonce, sizeof(nonce)) == 0 ||
+      mg_http_parse_header2(hdr, "username", &username, sizeof(username_buf)) ==
+          0 ||
+      mg_http_parse_header2(hdr, "cnonce", &cnonce, sizeof(cnonce_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "response", &response, sizeof(response_buf)) ==
+          0 ||
+      mg_http_parse_header2(hdr, "uri", &uri, sizeof(uri_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "qop", &qop, sizeof(qop_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "nc", &nc, sizeof(nc_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "nonce", &nonce, sizeof(nonce_buf)) == 0 ||
       mg_check_nonce(nonce) == 0) {
-    return 0;
+    ret = 0;
+    goto clean;
   }
 
   /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
 
-  return mg_check_digest_auth(
+  ret = mg_check_digest_auth(
       hm->method,
       mg_mk_str_n(
           hm->uri.p,
@@ -7120,6 +7259,17 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
       mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response),
       mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain),
       fp);
+
+clean:
+  if (username != username_buf) MG_FREE(username);
+  if (cnonce != cnonce_buf) MG_FREE(cnonce);
+  if (response != response_buf) MG_FREE(response);
+  if (uri != uri_buf) MG_FREE(uri);
+  if (qop != qop_buf) MG_FREE(qop);
+  if (nc != nc_buf) MG_FREE(nc);
+  if (nonce != nonce_buf) MG_FREE(nonce);
+
+  return ret;
 }
 
 int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
@@ -8189,8 +8339,24 @@ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
       struct mg_str header;
       header.p = buf + n + cdl;
       header.len = ll - (cdl + 2);
-      mg_http_parse_header(&header, "name", var_name, var_name_len);
-      mg_http_parse_header(&header, "filename", file_name, file_name_len);
+      {
+        char *var_name2 = var_name;
+        mg_http_parse_header2(&header, "name", &var_name2, var_name_len);
+        /* TODO: handle reallocated buffer correctly */
+        if (var_name2 != var_name) {
+          MG_FREE(var_name2);
+          var_name[0] = '\0';
+        }
+      }
+      {
+        char *file_name2 = file_name;
+        mg_http_parse_header2(&header, "filename", &file_name2, file_name_len);
+        /* TODO: handle reallocated buffer correctly */
+        if (file_name2 != file_name) {
+          MG_FREE(file_name2);
+          file_name[0] = '\0';
+        }
+      }
     }
   }
 
diff --git a/mongoose.h b/mongoose.h
index 15d54fe637e21e20c3eb01c9c7b749da1f6b2fb2..3104507f47a132eb137be07812b8dec3f6f6b391 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -4647,21 +4647,42 @@ struct mg_str *mg_get_http_header(struct http_message *hm, const char *name);
 
 /*
  * Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value
- * in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero
- * otherwise.
+ * in the buffer `*buf`, `buf_size`. If the buffer size is not enough,
+ * allocates a buffer of required size and writes it to `*buf`, similar to
+ * asprintf(). The caller should always check whether the buffer was updated,
+ * and free it if so.
  *
  * This function is supposed to parse cookies, authentication headers, etc.
  * Example (error handling omitted):
  *
- *     char user[20];
+ *     char user_buf[20];
+ *     char *user = user_buf;
  *     struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
- *     mg_http_parse_header(hdr, "username", user, sizeof(user));
+ *     mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf));
+ *     // ... do something useful with user
+ *     if (user != user_buf) {
+ *       free(user);
+ *     }
  *
- * Returns the length of the variable's value. If buffer is not large enough,
- * or variable not found, 0 is returned.
+ * Returns the length of the variable's value. If variable is not found, 0 is
+ * returned.
+ */
+int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
+                          size_t buf_size);
+
+/*
+ * DEPRECATED: use mg_http_parse_header2() instead.
+ *
+ * Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of
+ * `char **`), and thus it cannot allocate a new buffer if the provided one
+ * is not enough, and just returns 0 in that case.
  */
 int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
-                         size_t buf_size);
+                         size_t buf_size)
+#ifdef __GNUC__
+    __attribute__((deprecated));
+#endif
+;
 
 /*
  * Gets and parses the Authorization: Basic header
diff --git a/src/mg_http.c b/src/mg_http.c
index 547b5d13eb2eb4fa44e2ecf151dbc79992bc6936..068d2083077942b5a5f340d90c0f9901a9b2ace7 100644
--- a/src/mg_http.c
+++ b/src/mg_http.c
@@ -9,6 +9,86 @@
 #include "mg_internal.h"
 #include "mg_util.h"
 
+/* altbuf {{{ */
+
+/*
+ * Alternate buffer: fills the client-provided buffer with data; and if it's
+ * not large enough, allocates another buffer (via mbuf), similar to asprintf.
+ */
+struct altbuf {
+  struct mbuf m;
+  char *user_buf;
+  size_t len;
+  size_t user_buf_size;
+};
+
+/*
+ * Initializes altbuf; `buf`, `buf_size` is the client-provided buffer.
+ */
+MG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) {
+  mbuf_init(&ab->m, 0);
+  ab->user_buf = buf;
+  ab->user_buf_size = buf_size;
+  ab->len = 0;
+}
+
+/*
+ * Appends a single char to the altbuf.
+ */
+MG_INTERNAL void altbuf_append(struct altbuf *ab, char c) {
+  if (ab->len < ab->user_buf_size) {
+    /* The data fits into the original buffer */
+    ab->user_buf[ab->len++] = c;
+  } else {
+    /* The data can't fit into the original buffer, so write it to mbuf.  */
+
+    /*
+     * First of all, see if that's the first byte which overflows the original
+     * buffer: if so, copy the existing data from there to a newly allocated
+     * mbuf.
+     */
+    if (ab->len > 0 && ab->m.len == 0) {
+      mbuf_append(&ab->m, ab->user_buf, ab->len);
+    }
+
+    mbuf_append(&ab->m, &c, 1);
+    ab->len = ab->m.len;
+  }
+}
+
+/*
+ * Resets any data previously appended to altbuf.
+ */
+MG_INTERNAL void altbuf_reset(struct altbuf *ab) {
+  mbuf_free(&ab->m);
+  ab->len = 0;
+}
+
+/*
+ * Returns whether the additional buffer was allocated (and thus the data
+ * is in the mbuf, not the client-provided buffer)
+ */
+MG_INTERNAL int altbuf_reallocated(struct altbuf *ab) {
+  return ab->len > ab->user_buf_size;
+}
+
+/*
+ * Returns the actual buffer with data, either the client-provided or a newly
+ * allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first.
+ */
+MG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) {
+  if (altbuf_reallocated(ab)) {
+    if (trim) {
+      mbuf_trim(&ab->m);
+    }
+    return ab->m.buf;
+  } else {
+    return ab->user_buf;
+  }
+}
+
+/* }}} */
+
 static const char *mg_version_header = "Mongoose/" MG_VERSION;
 
 enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT };
@@ -818,7 +898,8 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
   struct mg_str *ct;
   struct mbuf *io = &nc->recv_mbuf;
 
-  char boundary[100];
+  char boundary_buf[100];
+  char *boundary = boundary_buf;
   int boundary_len;
 
   ct = mg_get_http_header(hm, "Content-Type");
@@ -833,7 +914,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
   }
 
   boundary_len =
-      mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
+      mg_http_parse_header2(ct, "boundary", &boundary, sizeof(boundary_buf));
   if (boundary_len == 0) {
     /*
      * Content type is multipart, but there is no boundary,
@@ -870,7 +951,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc,
     mbuf_remove(io, req_len);
   }
 exit_mp:
-  ;
+  if (boundary != boundary_buf) MG_FREE(boundary);
 }
 
 #define CONTENT_DISPOSITION "Content-Disposition: "
@@ -952,17 +1033,24 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
   return 1;
 }
 
+static void mg_http_parse_header_internal(struct mg_str *hdr,
+                                          const char *var_name,
+                                          struct altbuf *ab);
+
 static int mg_http_multipart_process_boundary(struct mg_connection *c) {
   int data_size;
   const char *boundary, *block_begin;
   struct mbuf *io = &c->recv_mbuf;
   struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-  char file_name[100], var_name[100];
+  struct altbuf ab_file_name, ab_var_name;
   int line_len;
   boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
   block_begin = boundary + pd->mp_stream.boundary_len + 2;
   data_size = io->len - (block_begin - io->buf);
 
+  altbuf_init(&ab_file_name, NULL, 0);
+  altbuf_init(&ab_var_name, NULL, 0);
+
   while (data_size > 0 &&
          (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
     if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
@@ -972,11 +1060,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
 
       header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
       header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
-      mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
-      mg_http_parse_header(&header, "filename", file_name,
-                           sizeof(file_name) - 2);
+
+      altbuf_reset(&ab_var_name);
+      mg_http_parse_header_internal(&header, "name", &ab_var_name);
+
+      altbuf_reset(&ab_file_name);
+      mg_http_parse_header_internal(&header, "filename", &ab_file_name);
+
       block_begin += line_len;
       data_size -= line_len;
+
       continue;
     }
 
@@ -987,10 +1080,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
         mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
       }
 
+      /* Reserve 2 bytes for "\r\n" in file_name and var_name */
+      altbuf_append(&ab_file_name, '\0');
+      altbuf_append(&ab_file_name, '\0');
+      altbuf_append(&ab_var_name, '\0');
+      altbuf_append(&ab_var_name, '\0');
+
       MG_FREE((void *) pd->mp_stream.file_name);
-      pd->mp_stream.file_name = strdup(file_name);
+      pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */);
       MG_FREE((void *) pd->mp_stream.var_name);
-      pd->mp_stream.var_name = strdup(var_name);
+      pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */);
 
       mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
       pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
@@ -1003,6 +1102,9 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) {
 
   pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
 
+  altbuf_reset(&ab_var_name);
+  altbuf_reset(&ab_file_name);
+
   return 0;
 }
 
@@ -1557,14 +1659,12 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) {
   /* LCOV_EXCL_STOP */
 }
 
-int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
-                         size_t buf_size) {
-  int ch = ' ', ch1 = ',', len = 0, n = strlen(var_name);
+static void mg_http_parse_header_internal(struct mg_str *hdr,
+                                          const char *var_name,
+                                          struct altbuf *ab) {
+  int ch = ' ', ch1 = ',', n = strlen(var_name);
   const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;
 
-  if (buf != NULL && buf_size > 0) buf[0] = '\0';
-  if (hdr == NULL) return 0;
-
   /* Find where variable starts */
   for (s = hdr->p; s != NULL && s + n < end; s++) {
     if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') &&
@@ -1578,19 +1678,51 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
       ch = ch1 = *s++;
     }
     p = s;
-    while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) {
+    while (p < end && p[0] != ch && p[0] != ch1) {
       if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++;
-      buf[len++] = *p++;
+      altbuf_append(ab, *p++);
     }
-    if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
-      len = 0;
-    } else {
-      if (len > 0 && s[len - 1] == ',') len--;
-      if (len > 0 && s[len - 1] == ';') len--;
-      buf[len] = '\0';
+
+    if (ch != ' ' && *p != ch) {
+      altbuf_reset(ab);
     }
   }
 
+  /* If there is some data, append a NUL. */
+  if (ab->len > 0) {
+    altbuf_append(ab, '\0');
+  }
+}
+
+int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
+                          size_t buf_size) {
+  struct altbuf ab;
+  altbuf_init(&ab, *buf, buf_size);
+  if (hdr == NULL) return 0;
+  if (*buf != NULL && buf_size > 0) *buf[0] = '\0';
+
+  mg_http_parse_header_internal(hdr, var_name, &ab);
+
+  /*
+   * Get a (trimmed) buffer, and return a len without a NUL byte which might
+   * have been added.
+   */
+  *buf = altbuf_get_buf(&ab, 1 /* trim */);
+  return ab.len > 0 ? ab.len - 1 : 0;
+}
+
+int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
+                         size_t buf_size) {
+  char *buf2 = buf;
+
+  int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size);
+
+  if (buf2 != buf) {
+    /* Buffer was not enough and was reallocated: free it and just return 0 */
+    MG_FREE(buf2);
+    return 0;
+  }
+
   return len;
 }
 
@@ -1728,27 +1860,34 @@ static int mg_check_nonce(const char *nonce) {
 
 int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
                               FILE *fp) {
+  int ret = 0;
   struct mg_str *hdr;
-  char username[50], cnonce[64], response[40], uri[200], qop[20], nc[20],
-      nonce[30];
+  char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200],
+      qop_buf[20], nc_buf[20], nonce_buf[16];
+
+  char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf,
+       *uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf;
 
   /* Parse "Authorization:" header, fail fast on parse error */
   if (hm == NULL || fp == NULL ||
       (hdr = mg_get_http_header(hm, "Authorization")) == NULL ||
-      mg_http_parse_header(hdr, "username", username, sizeof(username)) == 0 ||
-      mg_http_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce)) == 0 ||
-      mg_http_parse_header(hdr, "response", response, sizeof(response)) == 0 ||
-      mg_http_parse_header(hdr, "uri", uri, sizeof(uri)) == 0 ||
-      mg_http_parse_header(hdr, "qop", qop, sizeof(qop)) == 0 ||
-      mg_http_parse_header(hdr, "nc", nc, sizeof(nc)) == 0 ||
-      mg_http_parse_header(hdr, "nonce", nonce, sizeof(nonce)) == 0 ||
+      mg_http_parse_header2(hdr, "username", &username, sizeof(username_buf)) ==
+          0 ||
+      mg_http_parse_header2(hdr, "cnonce", &cnonce, sizeof(cnonce_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "response", &response, sizeof(response_buf)) ==
+          0 ||
+      mg_http_parse_header2(hdr, "uri", &uri, sizeof(uri_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "qop", &qop, sizeof(qop_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "nc", &nc, sizeof(nc_buf)) == 0 ||
+      mg_http_parse_header2(hdr, "nonce", &nonce, sizeof(nonce_buf)) == 0 ||
       mg_check_nonce(nonce) == 0) {
-    return 0;
+    ret = 0;
+    goto clean;
   }
 
   /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
 
-  return mg_check_digest_auth(
+  ret = mg_check_digest_auth(
       hm->method,
       mg_mk_str_n(
           hm->uri.p,
@@ -1756,6 +1895,17 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
       mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response),
       mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain),
       fp);
+
+clean:
+  if (username != username_buf) MG_FREE(username);
+  if (cnonce != cnonce_buf) MG_FREE(cnonce);
+  if (response != response_buf) MG_FREE(response);
+  if (uri != uri_buf) MG_FREE(uri);
+  if (qop != qop_buf) MG_FREE(qop);
+  if (nc != nc_buf) MG_FREE(nc);
+  if (nonce != nonce_buf) MG_FREE(nonce);
+
+  return ret;
 }
 
 int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
@@ -2825,8 +2975,24 @@ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
       struct mg_str header;
       header.p = buf + n + cdl;
       header.len = ll - (cdl + 2);
-      mg_http_parse_header(&header, "name", var_name, var_name_len);
-      mg_http_parse_header(&header, "filename", file_name, file_name_len);
+      {
+        char *var_name2 = var_name;
+        mg_http_parse_header2(&header, "name", &var_name2, var_name_len);
+        /* TODO: handle reallocated buffer correctly */
+        if (var_name2 != var_name) {
+          MG_FREE(var_name2);
+          var_name[0] = '\0';
+        }
+      }
+      {
+        char *file_name2 = file_name;
+        mg_http_parse_header2(&header, "filename", &file_name2, file_name_len);
+        /* TODO: handle reallocated buffer correctly */
+        if (file_name2 != file_name) {
+          MG_FREE(file_name2);
+          file_name[0] = '\0';
+        }
+      }
     }
   }
 
diff --git a/src/mg_http_server.h b/src/mg_http_server.h
index 62f298f86409dbb3bb5c5b0f915d725477d26155..c4dfee4da21b55f0e1656c3bddb7e5b95ad67a78 100644
--- a/src/mg_http_server.h
+++ b/src/mg_http_server.h
@@ -31,21 +31,42 @@ struct mg_str *mg_get_http_header(struct http_message *hm, const char *name);
 
 /*
  * Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value
- * in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero
- * otherwise.
+ * in the buffer `*buf`, `buf_size`. If the buffer size is not enough,
+ * allocates a buffer of required size and writes it to `*buf`, similar to
+ * asprintf(). The caller should always check whether the buffer was updated,
+ * and free it if so.
  *
  * This function is supposed to parse cookies, authentication headers, etc.
  * Example (error handling omitted):
  *
- *     char user[20];
+ *     char user_buf[20];
+ *     char *user = user_buf;
  *     struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
- *     mg_http_parse_header(hdr, "username", user, sizeof(user));
+ *     mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf));
+ *     // ... do something useful with user
+ *     if (user != user_buf) {
+ *       free(user);
+ *     }
+ *
+ * Returns the length of the variable's value. If variable is not found, 0 is
+ * returned.
+ */
+int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
+                          size_t buf_size);
+
+/*
+ * DEPRECATED: use mg_http_parse_header2() instead.
  *
- * Returns the length of the variable's value. If buffer is not large enough,
- * or variable not found, 0 is returned.
+ * Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of
+ * `char **`), and thus it cannot allocate a new buffer if the provided one
+ * is not enough, and just returns 0 in that case.
  */
 int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
-                         size_t buf_size);
+                         size_t buf_size)
+#ifdef __GNUC__
+    __attribute__((deprecated));
+#endif
+;
 
 /*
  * Gets and parses the Authorization: Basic header
diff --git a/test/Makefile b/test/Makefile
index 4795243d01635c37f83b88b799df511c2b6cf539..89223ab842205789216a24353bfdd3a17f4d92ae 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -45,7 +45,9 @@ COMMON_FEATURE_FLAGS = \
 	-DMG_ENABLE_SNTP -DMG_SNTP_NO_DELAY_CORRECTION \
 	-DMG_ENABLE_HTTP_STREAMING_MULTIPART
 UNIX_FEATURE_FLAGS=-DMG_ENABLE_IPV6 -DMG_ENABLE_SSL
-CFLAGS = -W -Wall -Wundef -Werror -g -O0 -Wno-multichar -D__USE_MISC \
+# TODO: remove -Wno-deprecated-declarations once deprecated
+# `mg_http_parse_header()` is removed from mongoose.
+CFLAGS = -W -Wall -Wundef -Werror -Wno-deprecated-declarations -g -O0 -Wno-multichar -D__USE_MISC \
 	 $(COMMON_FEATURE_FLAGS) $(UNIX_FEATURE_FLAGS) \
 	 $(patsubst %,-I%,$(subst :, ,$(VPATH))) \
 	 -include unit_test.h -pthread $(CFLAGS_EXTRA)
diff --git a/test/unit_test.c b/test/unit_test.c
index 9c7f284cd00000bddcc39d25d65cf8bcf6e3a6ca..ff080c58a6ec0063bcf4ec2068b3b294efe54712 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -4941,12 +4941,34 @@ static const char *test_http_parse_header(void) {
       "xx=1 kl yy, ert=234 kl=123, "
       "uri=\"/?naii=x,y\";ii=\"12\\\"34\" zz='aa bb',tt=2,gf=\"xx d=1234");
   char buf[20];
+  char *buf2;
 
   ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, sizeof(buf)), 3);
   ASSERT_STREQ(buf, "234");
+
   ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 2), 0);
   ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 3), 0);
   ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 4), 3);
+
+  buf2 = buf;
+  ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 2), 3);
+  ASSERT(buf2 != buf);
+  free(buf2);
+
+  buf2 = buf;
+  ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 3), 3);
+  ASSERT(buf2 != buf);
+  free(buf2);
+
+  buf2 = buf;
+  ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 4), 3);
+  ASSERT(buf2 == buf);
+
+  buf2 = NULL;
+  ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 0), 3);
+  ASSERT_STREQ(buf2, "234");
+  free(buf2);
+
   ASSERT_EQ(mg_http_parse_header(&h, "gf", buf, sizeof(buf)), 0);
   ASSERT_EQ(mg_http_parse_header(&h, "zz", buf, sizeof(buf)), 5);
   ASSERT_STREQ(buf, "aa bb");