From 894a16883307cb6d236473e2f6fa2311e3fde747 Mon Sep 17 00:00:00 2001
From: Sergey Lyubka <valenok@gmail.com>
Date: Wed, 15 Sep 2021 07:43:48 +0100
Subject: [PATCH] Get rid of fs->realpath

---
 mongoose.c               | 140 +++++++++++++++++----------------------
 mongoose.h               |  47 ++++++++-----
 src/arch_freertos_lwip.h |   7 +-
 src/fs.h                 |  13 ++--
 src/fs_packed.c          |  13 +---
 src/fs_posix.c           |  18 +----
 src/http.c               | 106 +++++++++++++++--------------
 src/http.h               |   4 +-
 src/mqtt.h               |  14 ++--
 src/str.h                |   4 +-
 src/util.c               |   3 +-
 src/ws.h                 |   4 +-
 test/unit_test.c         |  19 ++++--
 13 files changed, 191 insertions(+), 201 deletions(-)

diff --git a/mongoose.c b/mongoose.c
index 046dd1490..ff9d50ff1 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -431,13 +431,6 @@ const char *mg_unlist(size_t no) {
 }
 #endif
 
-static char *packed_realpath(const char *path, char *resolved_path) {
-  if (resolved_path == NULL) resolved_path = (char *) malloc(strlen(path) + 1);
-  // while (*path == '.' || *path == '/') path++;
-  strcpy(resolved_path, path);
-  return resolved_path;
-}
-
 static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
   return n < strlen(path) && memcmp(prefix, path, n) == 0 && path[n] == '/';
   //(n == 0 || path[n] == MG_DIRSEP);
@@ -515,9 +508,9 @@ static size_t packed_seek(void *fd, size_t offset) {
   return fp->pos;
 }
 
-struct mg_fs mg_fs_packed = {packed_realpath, packed_stat,  packed_list,
-                             packed_open,     packed_close, packed_read,
-                             packed_write,    packed_seek};
+struct mg_fs mg_fs_packed = {packed_stat,  packed_list, packed_open,
+                             packed_close, packed_read, packed_write,
+                             packed_seek};
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/fs_posix.c"
@@ -525,19 +518,6 @@ struct mg_fs mg_fs_packed = {packed_realpath, packed_stat,  packed_list,
 
 
 #if defined(FOPEN_MAX)
-static char *posix_realpath(const char *path, char *resolved_path) {
-#ifdef _WIN32
-  return _fullpath(resolved_path, path, _MAX_PATH);
-#elif MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \
-    MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP
-  if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1);
-  strcpy(resolved_path, path);
-  return resolved_path;
-#else
-  return realpath(path, resolved_path);
-#endif
-}
-
 static int posix_stat(const char *path, size_t *size, time_t *mtime) {
 #ifdef _WIN32
   struct _stati64 st;
@@ -762,9 +742,8 @@ static size_t posix_seek(void *fd, size_t offset) {
 }
 #endif
 
-struct mg_fs mg_fs_posix = {posix_realpath, posix_stat,  posix_list,
-                            posix_open,     posix_close, posix_read,
-                            posix_write,    posix_seek};
+struct mg_fs mg_fs_posix = {posix_stat, posix_list,  posix_open, posix_close,
+                            posix_read, posix_write, posix_seek};
 
 #ifdef MG_ENABLE_LINES
 #line 1 "src/http.c"
@@ -1458,49 +1437,50 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
   memcpy(c->send.buf + off - 10, tmp, n);  // Set content length
 }
 
+static void remove_double_dots(char *s) {
+  char *p = s;
+  while (*s != '\0') {
+    *p++ = *s++;
+    if (s[-1] == '/' || s[-1] == '\\') {
+      while (s[0] != '\0') {
+        if (s[0] == '/' || s[0] == '\\') {
+          s++;
+        } else if (s[0] == '.' && s[1] == '.') {
+          s += 2;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+  *p = '\0';
+}
+
+// Resolve requested file into `path` and return its fs->stat() result
 static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
-                       struct mg_http_serve_opts *opts, char *root_dir,
-                       size_t rlen, char *path, size_t plen) {
+                       struct mg_http_serve_opts *opts, char *path,
+                       size_t path_size) {
   struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
   int flags = 0, tmp;
-  if (fs->realpath(opts->root_dir, root_dir) == NULL) {
-    LOG(LL_ERROR, ("realpath(%s): %d", opts->root_dir, errno));
-    mg_http_reply(c, 400, "", "Bad web root [%s]\n", opts->root_dir);
-  } else if (!(fs->stat(root_dir, NULL, NULL) & MG_FS_DIR)) {
-    mg_http_reply(c, 400, "", "Invalid web root [%s]\n", root_dir);
-  } else {
-    // NOTE(lsm): Xilinx snprintf does not 0-terminate the destination for
-    // the %.*s specifier, if the length is zero. Make sure hm->uri.len > 0
-    size_t n1 = strlen(root_dir), n2;
-    // Temporarily append URI to the root_dir: that is the unresolved path
-    mg_url_decode(hm->uri.ptr, hm->uri.len, root_dir + n1, rlen - n1, 0);
-    root_dir[rlen - 1] = '\0';
-    n2 = strlen(root_dir);
-    while (n2 > 0 && root_dir[n2 - 1] == '/') root_dir[--n2] = 0;
-    // Try to resolve it...
-    if (fs->realpath(root_dir, path) == NULL ||
-        (flags = fs->stat(path, NULL, NULL)) == 0) {
-      mg_http_reply(c, 404, "", "Not found\n");
+  // Append URI to the root_dir, and sanitize it
+  size_t n = (size_t) snprintf(path, path_size, "%s", opts->root_dir);
+  if (n > path_size) n = path_size;
+  mg_url_decode(hm->uri.ptr, hm->uri.len, path + n, path_size - n, 0);
+  path[path_size - 1] = '\0';  // Double-check
+  remove_double_dots(path);
+  n = strlen(path);
+  while (n > 0 && path[n - 1] == '/') path[--n] = 0;  // Strip trailing slashes
+  flags = fs->stat(path, NULL, NULL);                 // Does it exist?
+  if (flags == 0) {
+    mg_http_reply(c, 404, "", "Not found\n");  // Does not exist, doh
+  } else if (flags & MG_FS_DIR) {
+    if (((snprintf(path + n, path_size - n, "/index.html") > 0 &&
+          (tmp = fs->stat(path, NULL, NULL)) != 0) ||
+         (snprintf(path + n, path_size - n, "/index.shtml") > 0 &&
+          (tmp = fs->stat(path, NULL, NULL)) != 0))) {
+      flags = tmp;
     } else {
-      // Path is resolved successfully. It it is a directory, try to
-      // serve index.html in it
-      root_dir[n1] = '\0';  // Restore root_dir - remove appended URI
-      n2 = strlen(path);    // Memorise path length
-      if ((flags & MG_FS_DIR) &&
-          ((snprintf(path + n2, plen - n2, "/index.html") > 0 &&
-            (tmp = fs->stat(path, NULL, NULL)) != 0) ||
-           (snprintf(path + n2, plen - n2, "/index.shtml") > 0 &&
-            (tmp = fs->stat(path, NULL, NULL)) != 0))) {
-        flags = tmp;
-      } else {
-        path[n2] = '\0';  // Remove appended index file name
-      }
-    }
-    // Check that the resolved file is located inside root directory
-    if (strlen(path) < n1 || memcmp(root_dir, path, n1) != 0) {
-      mg_http_reply(c, 404, "", "Invalid URI [%.*s]\n", (int) hm->uri.len,
-                    hm->uri.ptr);
-      flags = 0;
+      path[n] = '\0';  // Remove appended index file name
     }
   }
   return flags;
@@ -1508,19 +1488,22 @@ static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
 
 void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
                        struct mg_http_serve_opts *opts) {
-  char root[MG_PATH_MAX] = "", path[sizeof(root)] = "";
-  int flags = uri_to_path(c, hm, opts, root, sizeof(root), path, sizeof(path));
-
-  if (flags == 0) return;
-  // LOG(LL_DEBUG, ("root [%s], path [%s] %d", root, path, flags));
-  if (flags & MG_FS_DIR) {
-    listdir(c, hm, opts, path);
-  } else if (opts->ssi_pattern != NULL &&
-             mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), path,
-                          strlen(path))) {
-    mg_http_serve_ssi(c, root, path);
+  char path[MG_PATH_MAX] = "";
+  const char *sp = opts->ssi_pattern;
+  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
+  if ((fs->stat(opts->root_dir, NULL, NULL) & MG_FS_DIR) == 0) {
+    mg_http_reply(c, 400, "", "Invalid web root [%s]\n", opts->root_dir);
   } else {
-    mg_http_serve_file(c, hm, path, opts);
+    int flags = uri_to_path(c, hm, opts, path, sizeof(path));
+    if (flags == 0) return;
+    LOG(LL_DEBUG, ("%.*s %s %d", (int) hm->uri.len, hm->uri.ptr, path, flags));
+    if (flags & MG_FS_DIR) {
+      listdir(c, hm, opts, path);
+    } else if (sp != NULL && mg_globmatch(sp, strlen(sp), path, strlen(path))) {
+      mg_http_serve_ssi(c, opts->root_dir, path);
+    } else {
+      mg_http_serve_file(c, hm, path, opts);
+    }
   }
 }
 
@@ -4213,9 +4196,10 @@ bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) {
       i++, j++;
     } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
       ni = i, nj = j + 1, i++;
-    } else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
+    } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) {
       i = ni, j = nj;
     } else {
+      // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj);
       return false;
     }
   }
diff --git a/mongoose.h b/mongoose.h
index 59c89101a..b2b2fadf4 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -214,13 +214,14 @@ struct timeval {
 #include <lwip/sockets.h>
 
 #if LWIP_SOCKET != 1
-// Sockets support disabled in LWIP by default 
+// Sockets support disabled in LWIP by default
 #error Set LWIP_SOCKET variable to 1 (in lwipopts.h)
 #endif
 
 #if LWIP_POSIX_SOCKETS_IO_NAMES != 0
-// LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS enviroment
-// (freertos mimics to one) otherwise names like `read` and `write` conflict
+// LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS
+// enviroment (freertos mimics to one) otherwise names like `read` and `write`
+// conflict
 #error LWIP_POSIX_SOCKETS_IO_NAMES must be set to 0 (in lwipopts.h) for FreeRTOS
 #endif
 
@@ -467,8 +468,8 @@ static __inline struct tm *localtime_r(time_t *t, struct tm *tm) {
 #include <string.h>
 
 struct mg_str {
-  const char *ptr;
-  size_t len;
+  const char *ptr;  // Pointer to string data
+  size_t len;       // String len
 };
 
 #define MG_NULL_STR \
@@ -596,13 +597,25 @@ enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 };
 
 // Filesystem API functions
 struct mg_fs {
-  char *(*realpath)(const char *path, char *resolved_path);
+  // Return MG_FS_* flags, and populate file size and modification time
   int (*stat)(const char *path, size_t *size, time_t *mtime);
+
+  // Enumerates objects in directory
   void (*list)(const char *path, void (*fn)(const char *, void *), void *);
+
+  // Open file
   struct mg_fd *(*open)(const char *path, int flags);
+
+  // Close file
   void (*close)(struct mg_fd *fd);
+
+  // Read file
   size_t (*read)(void *fd, void *buf, size_t len);
+
+  // Write file
   size_t (*write)(void *fd, const void *buf, size_t len);
+
+  // Seek file
   size_t (*seek)(void *fd, size_t offset);
 };
 
@@ -786,8 +799,8 @@ void mg_mgr_wakeup(struct mg_connection *pipe);
 
 
 struct mg_http_header {
-  struct mg_str name;
-  struct mg_str value;
+  struct mg_str name;   // Header name
+  struct mg_str value;  // Header value
 };
 
 struct mg_http_message {
@@ -873,8 +886,8 @@ void mg_tls_handshake(struct mg_connection *);
 
 
 struct mg_ws_message {
-  struct mg_str data;
-  uint8_t flags;  // Websocket message flags
+  struct mg_str data;  // Websocket message data
+  uint8_t flags;       // Websocket message flags
 };
 
 struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url,
@@ -917,13 +930,13 @@ int mg_sntp_parse(const unsigned char *buf, size_t len, struct timeval *tv);
 #define MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1)
 
 struct mg_mqtt_opts {
-  struct mg_str client_id;
-  struct mg_str will_topic;
-  struct mg_str will_message;
-  uint8_t qos;         // Quality of service
-  bool will_retain;    // Retain last will
-  bool clean;          // Use clean session, 0 or 1
-  uint16_t keepalive;  // Keep-alive timer in seconds
+  struct mg_str client_id;     // Client ID
+  struct mg_str will_topic;    // Will topic
+  struct mg_str will_message;  // Will message
+  uint8_t qos;                 // Quality of service
+  bool will_retain;            // Retain last will
+  bool clean;                  // Use clean session, 0 or 1
+  uint16_t keepalive;          // Keep-alive timer in seconds
 };
 
 struct mg_mqtt_message {
diff --git a/src/arch_freertos_lwip.h b/src/arch_freertos_lwip.h
index 56213b972..88d08ff4d 100644
--- a/src/arch_freertos_lwip.h
+++ b/src/arch_freertos_lwip.h
@@ -24,13 +24,14 @@ struct timeval {
 #include <lwip/sockets.h>
 
 #if LWIP_SOCKET != 1
-// Sockets support disabled in LWIP by default 
+// Sockets support disabled in LWIP by default
 #error Set LWIP_SOCKET variable to 1 (in lwipopts.h)
 #endif
 
 #if LWIP_POSIX_SOCKETS_IO_NAMES != 0
-// LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS enviroment
-// (freertos mimics to one) otherwise names like `read` and `write` conflict
+// LWIP_POSIX_SOCKETS_IO_NAMES must be disabled in posix-compatible OS
+// enviroment (freertos mimics to one) otherwise names like `read` and `write`
+// conflict
 #error LWIP_POSIX_SOCKETS_IO_NAMES must be set to 0 (in lwipopts.h) for FreeRTOS
 #endif
 
diff --git a/src/fs.h b/src/fs.h
index 8afb2f56d..ecd179f54 100644
--- a/src/fs.h
+++ b/src/fs.h
@@ -5,15 +5,16 @@
 enum { MG_FS_READ = 1, MG_FS_WRITE = 2, MG_FS_DIR = 4 };
 
 // Filesystem API functions
+// stat() returns MG_FS_* flags and populates file size and modification time
+// list() calls fn() for every directory entry, allowing to list a directory
 struct mg_fs {
-  char *(*realpath)(const char *path, char *resolved_path);
   int (*stat)(const char *path, size_t *size, time_t *mtime);
   void (*list)(const char *path, void (*fn)(const char *, void *), void *);
-  struct mg_fd *(*open)(const char *path, int flags);
-  void (*close)(struct mg_fd *fd);
-  size_t (*read)(void *fd, void *buf, size_t len);
-  size_t (*write)(void *fd, const void *buf, size_t len);
-  size_t (*seek)(void *fd, size_t offset);
+  struct mg_fd *(*open)(const char *path, int flags);      // Open file
+  void (*close)(struct mg_fd *fd);                         // Close file
+  size_t (*read)(void *fd, void *buf, size_t len);         // Read file
+  size_t (*write)(void *fd, const void *buf, size_t len);  // Write file
+  size_t (*seek)(void *fd, size_t offset);                 // Set file position
 };
 
 // File descriptor
diff --git a/src/fs_packed.c b/src/fs_packed.c
index 905312456..23a21a205 100644
--- a/src/fs_packed.c
+++ b/src/fs_packed.c
@@ -20,13 +20,6 @@ const char *mg_unlist(size_t no) {
 }
 #endif
 
-static char *packed_realpath(const char *path, char *resolved_path) {
-  if (resolved_path == NULL) resolved_path = (char *) malloc(strlen(path) + 1);
-  // while (*path == '.' || *path == '/') path++;
-  strcpy(resolved_path, path);
-  return resolved_path;
-}
-
 static int is_dir_prefix(const char *prefix, size_t n, const char *path) {
   return n < strlen(path) && memcmp(prefix, path, n) == 0 && path[n] == '/';
   //(n == 0 || path[n] == MG_DIRSEP);
@@ -104,6 +97,6 @@ static size_t packed_seek(void *fd, size_t offset) {
   return fp->pos;
 }
 
-struct mg_fs mg_fs_packed = {packed_realpath, packed_stat,  packed_list,
-                             packed_open,     packed_close, packed_read,
-                             packed_write,    packed_seek};
+struct mg_fs mg_fs_packed = {packed_stat,  packed_list, packed_open,
+                             packed_close, packed_read, packed_write,
+                             packed_seek};
diff --git a/src/fs_posix.c b/src/fs_posix.c
index 308a62e2c..beb5c0d2c 100644
--- a/src/fs_posix.c
+++ b/src/fs_posix.c
@@ -1,19 +1,6 @@
 #include "fs.h"
 
 #if defined(FOPEN_MAX)
-static char *posix_realpath(const char *path, char *resolved_path) {
-#ifdef _WIN32
-  return _fullpath(resolved_path, path, _MAX_PATH);
-#elif MG_ARCH == MG_ARCH_ESP32 || MG_ARCH == MG_ARCH_ESP8266 || \
-    MG_ARCH == MG_ARCH_FREERTOS_TCP || MG_ARCH == MG_ARCH_FREERTOS_LWIP
-  if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1);
-  strcpy(resolved_path, path);
-  return resolved_path;
-#else
-  return realpath(path, resolved_path);
-#endif
-}
-
 static int posix_stat(const char *path, size_t *size, time_t *mtime) {
 #ifdef _WIN32
   struct _stati64 st;
@@ -238,6 +225,5 @@ static size_t posix_seek(void *fd, size_t offset) {
 }
 #endif
 
-struct mg_fs mg_fs_posix = {posix_realpath, posix_stat,  posix_list,
-                            posix_open,     posix_close, posix_read,
-                            posix_write,    posix_seek};
+struct mg_fs mg_fs_posix = {posix_stat, posix_list,  posix_open, posix_close,
+                            posix_read, posix_write, posix_seek};
diff --git a/src/http.c b/src/http.c
index d11811967..d8d882ab7 100644
--- a/src/http.c
+++ b/src/http.c
@@ -687,49 +687,50 @@ static void listdir(struct mg_connection *c, struct mg_http_message *hm,
   memcpy(c->send.buf + off - 10, tmp, n);  // Set content length
 }
 
+static void remove_double_dots(char *s) {
+  char *p = s;
+  while (*s != '\0') {
+    *p++ = *s++;
+    if (s[-1] == '/' || s[-1] == '\\') {
+      while (s[0] != '\0') {
+        if (s[0] == '/' || s[0] == '\\') {
+          s++;
+        } else if (s[0] == '.' && s[1] == '.') {
+          s += 2;
+        } else {
+          break;
+        }
+      }
+    }
+  }
+  *p = '\0';
+}
+
+// Resolve requested file into `path` and return its fs->stat() result
 static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
-                       struct mg_http_serve_opts *opts, char *root_dir,
-                       size_t rlen, char *path, size_t plen) {
+                       struct mg_http_serve_opts *opts, char *path,
+                       size_t path_size) {
   struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
   int flags = 0, tmp;
-  if (fs->realpath(opts->root_dir, root_dir) == NULL) {
-    LOG(LL_ERROR, ("realpath(%s): %d", opts->root_dir, errno));
-    mg_http_reply(c, 400, "", "Bad web root [%s]\n", opts->root_dir);
-  } else if (!(fs->stat(root_dir, NULL, NULL) & MG_FS_DIR)) {
-    mg_http_reply(c, 400, "", "Invalid web root [%s]\n", root_dir);
-  } else {
-    // NOTE(lsm): Xilinx snprintf does not 0-terminate the destination for
-    // the %.*s specifier, if the length is zero. Make sure hm->uri.len > 0
-    size_t n1 = strlen(root_dir), n2;
-    // Temporarily append URI to the root_dir: that is the unresolved path
-    mg_url_decode(hm->uri.ptr, hm->uri.len, root_dir + n1, rlen - n1, 0);
-    root_dir[rlen - 1] = '\0';
-    n2 = strlen(root_dir);
-    while (n2 > 0 && root_dir[n2 - 1] == '/') root_dir[--n2] = 0;
-    // Try to resolve it...
-    if (fs->realpath(root_dir, path) == NULL ||
-        (flags = fs->stat(path, NULL, NULL)) == 0) {
-      mg_http_reply(c, 404, "", "Not found\n");
+  // Append URI to the root_dir, and sanitize it
+  size_t n = (size_t) snprintf(path, path_size, "%s", opts->root_dir);
+  if (n > path_size) n = path_size;
+  mg_url_decode(hm->uri.ptr, hm->uri.len, path + n, path_size - n, 0);
+  path[path_size - 1] = '\0';  // Double-check
+  remove_double_dots(path);
+  n = strlen(path);
+  while (n > 0 && path[n - 1] == '/') path[--n] = 0;  // Strip trailing slashes
+  flags = fs->stat(path, NULL, NULL);                 // Does it exist?
+  if (flags == 0) {
+    mg_http_reply(c, 404, "", "Not found\n");  // Does not exist, doh
+  } else if (flags & MG_FS_DIR) {
+    if (((snprintf(path + n, path_size - n, "/index.html") > 0 &&
+          (tmp = fs->stat(path, NULL, NULL)) != 0) ||
+         (snprintf(path + n, path_size - n, "/index.shtml") > 0 &&
+          (tmp = fs->stat(path, NULL, NULL)) != 0))) {
+      flags = tmp;
     } else {
-      // Path is resolved successfully. It it is a directory, try to
-      // serve index.html in it
-      root_dir[n1] = '\0';  // Restore root_dir - remove appended URI
-      n2 = strlen(path);    // Memorise path length
-      if ((flags & MG_FS_DIR) &&
-          ((snprintf(path + n2, plen - n2, "/index.html") > 0 &&
-            (tmp = fs->stat(path, NULL, NULL)) != 0) ||
-           (snprintf(path + n2, plen - n2, "/index.shtml") > 0 &&
-            (tmp = fs->stat(path, NULL, NULL)) != 0))) {
-        flags = tmp;
-      } else {
-        path[n2] = '\0';  // Remove appended index file name
-      }
-    }
-    // Check that the resolved file is located inside root directory
-    if (strlen(path) < n1 || memcmp(root_dir, path, n1) != 0) {
-      mg_http_reply(c, 404, "", "Invalid URI [%.*s]\n", (int) hm->uri.len,
-                    hm->uri.ptr);
-      flags = 0;
+      path[n] = '\0';  // Remove appended index file name
     }
   }
   return flags;
@@ -737,19 +738,22 @@ static int uri_to_path(struct mg_connection *c, struct mg_http_message *hm,
 
 void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm,
                        struct mg_http_serve_opts *opts) {
-  char root[MG_PATH_MAX] = "", path[sizeof(root)] = "";
-  int flags = uri_to_path(c, hm, opts, root, sizeof(root), path, sizeof(path));
-
-  if (flags == 0) return;
-  // LOG(LL_DEBUG, ("root [%s], path [%s] %d", root, path, flags));
-  if (flags & MG_FS_DIR) {
-    listdir(c, hm, opts, path);
-  } else if (opts->ssi_pattern != NULL &&
-             mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), path,
-                          strlen(path))) {
-    mg_http_serve_ssi(c, root, path);
+  char path[MG_PATH_MAX] = "";
+  const char *sp = opts->ssi_pattern;
+  struct mg_fs *fs = opts->fs == NULL ? &mg_fs_posix : opts->fs;
+  if ((fs->stat(opts->root_dir, NULL, NULL) & MG_FS_DIR) == 0) {
+    mg_http_reply(c, 400, "", "Invalid web root [%s]\n", opts->root_dir);
   } else {
-    mg_http_serve_file(c, hm, path, opts);
+    int flags = uri_to_path(c, hm, opts, path, sizeof(path));
+    if (flags == 0) return;
+    LOG(LL_DEBUG, ("%.*s %s %d", (int) hm->uri.len, hm->uri.ptr, path, flags));
+    if (flags & MG_FS_DIR) {
+      listdir(c, hm, opts, path);
+    } else if (sp != NULL && mg_globmatch(sp, strlen(sp), path, strlen(path))) {
+      mg_http_serve_ssi(c, opts->root_dir, path);
+    } else {
+      mg_http_serve_file(c, hm, path, opts);
+    }
   }
 }
 
diff --git a/src/http.h b/src/http.h
index a9ffbad19..d65019e12 100644
--- a/src/http.h
+++ b/src/http.h
@@ -6,8 +6,8 @@
 #include "str.h"
 
 struct mg_http_header {
-  struct mg_str name;
-  struct mg_str value;
+  struct mg_str name;   // Header name
+  struct mg_str value;  // Header value
 };
 
 struct mg_http_message {
diff --git a/src/mqtt.h b/src/mqtt.h
index 8f88cc720..29c1fecf6 100644
--- a/src/mqtt.h
+++ b/src/mqtt.h
@@ -23,13 +23,13 @@
 #define MQTT_SET_QOS(flags, qos) (flags) = ((flags) & ~0x6) | ((qos) << 1)
 
 struct mg_mqtt_opts {
-  struct mg_str client_id;
-  struct mg_str will_topic;
-  struct mg_str will_message;
-  uint8_t qos;         // Quality of service
-  bool will_retain;    // Retain last will
-  bool clean;          // Use clean session, 0 or 1
-  uint16_t keepalive;  // Keep-alive timer in seconds
+  struct mg_str client_id;     // Client ID
+  struct mg_str will_topic;    // Will topic
+  struct mg_str will_message;  // Will message
+  uint8_t qos;                 // Quality of service
+  bool will_retain;            // Retain last will
+  bool clean;                  // Use clean session, 0 or 1
+  uint16_t keepalive;          // Keep-alive timer in seconds
 };
 
 struct mg_mqtt_message {
diff --git a/src/str.h b/src/str.h
index 3d9633bf4..80a995b1e 100644
--- a/src/str.h
+++ b/src/str.h
@@ -4,8 +4,8 @@
 #include <string.h>
 
 struct mg_str {
-  const char *ptr;
-  size_t len;
+  const char *ptr;  // Pointer to string data
+  size_t len;       // String len
 };
 
 #define MG_NULL_STR \
diff --git a/src/util.c b/src/util.c
index 8e3626a87..f070939f1 100644
--- a/src/util.c
+++ b/src/util.c
@@ -86,9 +86,10 @@ bool mg_globmatch(const char *s1, size_t n1, const char *s2, size_t n2) {
       i++, j++;
     } else if (i < n1 && (s1[i] == '*' || s1[i] == '#')) {
       ni = i, nj = j + 1, i++;
-    } else if (nj > 0 && nj <= n2 && (s1[i - 1] == '#' || s2[j] != '/')) {
+    } else if (nj > 0 && nj <= n2 && (s1[ni] == '#' || s2[j] != '/')) {
       i = ni, j = nj;
     } else {
+      // printf(">>: [%s] [%s] %d %d %d %d\n", s1, s2, i, j, ni, nj);
       return false;
     }
   }
diff --git a/src/ws.h b/src/ws.h
index a5ab1cc06..a2ae41578 100644
--- a/src/ws.h
+++ b/src/ws.h
@@ -10,8 +10,8 @@
 #include "http.h"
 
 struct mg_ws_message {
-  struct mg_str data;
-  uint8_t flags;  // Websocket message flags
+  struct mg_str data;  // Websocket message data
+  uint8_t flags;       // Websocket message flags
 };
 
 struct mg_connection *mg_ws_connect(struct mg_mgr *, const char *url,
diff --git a/test/unit_test.c b/test/unit_test.c
index f3f8c2b9c..8bdfab228 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -36,6 +36,18 @@ static void test_globmatch(void) {
   ASSERT(mg_globmatch("/api/*", 6, "/api/foo", 8) == 1);
   ASSERT(mg_globmatch("/api/*", 6, "/api/log/static", 15) == 0);
   ASSERT(mg_globmatch("/api/#", 6, "/api/log/static", 15) == 1);
+  ASSERT(mg_globmatch("#.shtml", 7, "/ssi/index.shtml", 16) == 1);
+  ASSERT(mg_globmatch("#.c", 3, ".c", 2) == 1);
+  ASSERT(mg_globmatch("abc", 3, "ab", 2) == 0);
+  ASSERT(mg_globmatch("#.c", 3, "a.c", 3) == 1);
+  ASSERT(mg_globmatch("#.c", 3, "..c", 3) == 1);
+  ASSERT(mg_globmatch("#.c", 3, "/.c", 3) == 1);
+  ASSERT(mg_globmatch("#.c", 3, "//a.c", 5) == 1);
+  ASSERT(mg_globmatch("#.c", 3, "x/a.c", 5) == 1);
+  ASSERT(mg_globmatch("#.c", 3, "./a.c", 5) == 1);
+  ASSERT(mg_globmatch("#.shtml", 7, "./ssi/index.shtml", 17) == 1);
+  ASSERT(mg_globmatch("#aa#bb#", 7, "caabba", 6) == 1);
+  ASSERT(mg_globmatch("#aa#bb#", 7, "caabxa", 6) == 0);
 }
 
 static void test_commalist(void) {
@@ -575,12 +587,7 @@ static void test_http_server(void) {
   }
 
   ASSERT(fetch(&mgr, buf, url, "GET /badroot HTTP/1.0\r\n\n") == 400);
-#if MG_ARCH == MG_ARCH_WIN32
-  ASSERT(cmpbody(buf, "Invalid web root [Z:\\BAAADDD!]\n") == 0);
-#else
-  // LOG(LL_INFO, ("--> [%s]", buf));
-  ASSERT(cmpbody(buf, "Bad web root [/BAAADDD!]\n") == 0);
-#endif
+  ASSERT(cmpbody(buf, "Invalid web root [/BAAADDD!]\n") == 0);
 
   {
     char *data = mg_file_read("./test/data/ca.pem", NULL);
-- 
GitLab