diff --git a/docs/c-api/util.h/intro.md b/docs/c-api/util.h/intro.md
index 62f2f5e6f3ed8df176d4554a1fba0f75a2787780..99d4e407506b5ffb59d7d8b6a6320aa711ade3ef 100644
--- a/docs/c-api/util.h/intro.md
+++ b/docs/c-api/util.h/intro.md
@@ -3,8 +3,6 @@ title: "Utility API"
 symbol_kind: "intro"
 decl_name: "util.h"
 items:
-  - { name: mg_asprintf.md }
-  - { name: mg_avprintf.md }
   - { name: mg_base64_decode.md }
   - { name: mg_base64_encode.md }
   - { name: mg_basic_auth_header.md }
diff --git a/docs/c-api/util.h/mg_asprintf.md b/docs/c-api/util.h/mg_asprintf.md
deleted file mode 100644
index 10dd17460336384aa6903ca58883585484d45e10..0000000000000000000000000000000000000000
--- a/docs/c-api/util.h/mg_asprintf.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-title: "mg_asprintf()"
-decl_name: "mg_asprintf"
-symbol_kind: "func"
-signature: |
-  int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
----
-
-Prints message to the buffer. If the buffer is large enough to hold the
-message, it returns buffer. If buffer is to small, it allocates a large
-enough buffer on heap and returns allocated buffer.
-This is a supposed use case:
-
-   char buf[5], *p = buf;
-   mg_avprintf(&p, sizeof(buf), "%s", "hi there");
-   use_p_somehow(p);
-   if (p != buf) {
-     free(p);
-   }
-
-The purpose of this is to avoid malloc-ing if generated strings are small. 
-
diff --git a/docs/c-api/util.h/mg_avprintf.md b/docs/c-api/util.h/mg_avprintf.md
deleted file mode 100644
index aff55db0126f1d5b6b49ab302616f64ae64e1144..0000000000000000000000000000000000000000
--- a/docs/c-api/util.h/mg_avprintf.md
+++ /dev/null
@@ -1,10 +0,0 @@
----
-title: "mg_avprintf()"
-decl_name: "mg_avprintf"
-symbol_kind: "func"
-signature: |
-  int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
----
-
-Same as mg_asprintf, but takes varargs list. 
-
diff --git a/mongoose.c b/mongoose.c
index c038df1aca623d9e41b2d39494ff5f1702ec8df1..236d5b272a4341b67d3fa23124c80d8bb6d25437 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -1480,6 +1480,14 @@ void cs_hmac_sha1(const unsigned char *key, size_t keylen,
 #define C_DISABLE_BUILTIN_SNPRINTF 0
 #endif
 
+#ifndef MG_MALLOC
+#define MG_MALLOC malloc
+#endif
+
+#ifndef MG_FREE
+#define MG_FREE free
+#endif
+
 size_t c_strnlen(const char *s, size_t maxlen) {
   size_t l = 0;
   for (; l < maxlen && s[l] != '\0'; l++) {
@@ -1812,6 +1820,51 @@ int mg_casecmp(const char *s1, const char *s2) {
   return mg_ncasecmp(s1, s2, (size_t) ~0);
 }
 
+int mg_asprintf(char **buf, size_t size, const char *fmt, ...) {
+  int ret;
+  va_list ap;
+  va_start(ap, fmt);
+  ret = mg_avprintf(buf, size, fmt, ap);
+  va_end(ap);
+  return ret;
+}
+
+int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) {
+  va_list ap_copy;
+  int len;
+
+  va_copy(ap_copy, ap);
+  len = vsnprintf(*buf, size, fmt, ap_copy);
+  va_end(ap_copy);
+
+  if (len < 0) {
+    /* eCos and Windows are not standard-compliant and return -1 when
+     * the buffer is too small. Keep allocating larger buffers until we
+     * succeed or out of memory. */
+    *buf = NULL; /* LCOV_EXCL_START */
+    while (len < 0) {
+      MG_FREE(*buf);
+      size *= 2;
+      if ((*buf = (char *) MG_MALLOC(size)) == NULL) break;
+      va_copy(ap_copy, ap);
+      len = vsnprintf(*buf, size, fmt, ap_copy);
+      va_end(ap_copy);
+    }
+    /* LCOV_EXCL_STOP */
+  } else if (len >= (int) size) {
+    /* Standard-compliant code path. Allocate a buffer that is large enough. */
+    if ((*buf = (char *) MG_MALLOC(len + 1)) == NULL) {
+      len = -1; /* LCOV_EXCL_LINE */
+    } else {    /* LCOV_EXCL_LINE */
+      va_copy(ap_copy, ap);
+      len = vsnprintf(*buf, len + 1, fmt, ap_copy);
+      va_end(ap_copy);
+    }
+  }
+
+  return len;
+}
+
 #endif /* EXCLUDE_COMMON */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/tun.h"
@@ -9123,51 +9176,6 @@ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
 }
 #endif
 
-int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap) {
-  va_list ap_copy;
-  int len;
-
-  va_copy(ap_copy, ap);
-  len = vsnprintf(*buf, size, fmt, ap_copy);
-  va_end(ap_copy);
-
-  if (len < 0) {
-    /* eCos and Windows are not standard-compliant and return -1 when
-     * the buffer is too small. Keep allocating larger buffers until we
-     * succeed or out of memory. */
-    *buf = NULL; /* LCOV_EXCL_START */
-    while (len < 0) {
-      MG_FREE(*buf);
-      size *= 2;
-      if ((*buf = (char *) MG_MALLOC(size)) == NULL) break;
-      va_copy(ap_copy, ap);
-      len = vsnprintf(*buf, size, fmt, ap_copy);
-      va_end(ap_copy);
-    }
-    /* LCOV_EXCL_STOP */
-  } else if (len >= (int) size) {
-    /* Standard-compliant code path. Allocate a buffer that is large enough. */
-    if ((*buf = (char *) MG_MALLOC(len + 1)) == NULL) {
-      len = -1; /* LCOV_EXCL_LINE */
-    } else {    /* LCOV_EXCL_LINE */
-      va_copy(ap_copy, ap);
-      len = vsnprintf(*buf, len + 1, fmt, ap_copy);
-      va_end(ap_copy);
-    }
-  }
-
-  return len;
-}
-
-int mg_asprintf(char **buf, size_t size, const char *fmt, ...) {
-  int ret;
-  va_list ap;
-  va_start(ap, fmt);
-  ret = mg_avprintf(buf, size, fmt, ap);
-  va_end(ap);
-  return ret;
-}
-
 int mg_is_big_endian(void) {
   static const int n = 1;
   /* TODO(mkm) use compiletime check with 4-byte char literal */
diff --git a/mongoose.h b/mongoose.h
index 57d13eed4969d0f49ba003dcc46d179df05dd2ad..4a6c13b63aa353919472790d0fdb1f5a08918b59 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -1869,6 +1869,26 @@ int mg_ncasecmp(const char *s1, const char *s2, size_t len);
  */
 int mg_casecmp(const char *s1, const char *s2);
 
+/*
+ * Prints message to the buffer. If the buffer is large enough to hold the
+ * message, it returns buffer. If buffer is to small, it allocates a large
+ * enough buffer on heap and returns allocated buffer.
+ * This is a supposed use case:
+ *
+ *    char buf[5], *p = buf;
+ *    mg_avprintf(&p, sizeof(buf), "%s", "hi there");
+ *    use_p_somehow(p);
+ *    if (p != buf) {
+ *      free(p);
+ *    }
+ *
+ * The purpose of this is to avoid malloc-ing if generated strings are small.
+ */
+int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
+
+/* Same as mg_asprintf, but takes varargs list. */
+int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
+
 #ifdef __cplusplus
 }
 #endif
@@ -3763,26 +3783,6 @@ void mg_hexdump_connection(struct mg_connection *nc, const char *path,
                            const void *buf, int num_bytes, int ev);
 #endif
 
-/*
- * Prints message to the buffer. If the buffer is large enough to hold the
- * message, it returns buffer. If buffer is to small, it allocates a large
- * enough buffer on heap and returns allocated buffer.
- * This is a supposed use case:
- *
- *    char buf[5], *p = buf;
- *    mg_avprintf(&p, sizeof(buf), "%s", "hi there");
- *    use_p_somehow(p);
- *    if (p != buf) {
- *      free(p);
- *    }
- *
- * The purpose of this is to avoid malloc-ing if generated strings are small.
- */
-int mg_asprintf(char **buf, size_t size, const char *fmt, ...);
-
-/* Same as mg_asprintf, but takes varargs list. */
-int mg_avprintf(char **buf, size_t size, const char *fmt, va_list ap);
-
 /*
  * Returns true if target platform is big endian.
  */