diff --git a/mongoose.c b/mongoose.c index 94fe4ffe0855f1f8cad89c551e2a382d9ed65cdd..ae528bea10bfbbc287542c4c7aa8f917ffe6f4f3 100644 --- a/mongoose.c +++ b/mongoose.c @@ -407,7 +407,7 @@ void mg_error(struct mg_connection *c, const char *fmt, ...) { } #ifdef MG_ENABLE_LINES -#line 1 "src/fs.c" +#line 1 "src/fs_packed.c" #endif @@ -423,58 +423,202 @@ const char *mg_unpack(const char *path, size_t *size) { return NULL; } -#if defined(MG_FOPENCOOKIE) -ssize_t packed_read(void *cookie, char *buf, size_t size) { - struct packed_file *fp = (struct packed_file *) cookie; - if (size > fp->size - fp->pos) size = fp->size - fp->pos; - memcpy(buf, &fp->data[fp->pos], size); - fp->pos += size; - return (ssize_t) size; +static char *packed_realpath(const char *path, char *resolved_path) { + if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + strcpy(resolved_path, path); + return resolved_path; } -ssize_t packed_write(void *cookie, const char *buf, size_t size) { - (void) cookie, (void) buf, (void) size; - return -1; +static int packed_stat(const char *path, size_t *size, unsigned *mtime) { + const char *data = mg_unpack(path, size); + if (mtime) *mtime = 0; + return data == NULL ? 0 : MG_FS_READ; } -int packed_seek(void *cookie, long *offset, int whence) { - struct packed_file *fp = (struct packed_file *) cookie; - if (whence == SEEK_SET) fp->pos = (size_t) *offset; - if (whence == SEEK_END) fp->pos = (size_t)((long) fp->size + *offset); - if (whence == SEEK_CUR) fp->pos = (size_t)((long) fp->pos + *offset); - if (fp->pos > fp->size) fp->pos = fp->size; - *offset = (long) fp->pos; - return 0; -} - -int packed_close(void *cookie) { - free(cookie); - return 0; +static void packed_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; } -FILE *mg_fopen_packed(const char *path, const char *mode) { - cookie_io_functions_t funcs = { - .read = packed_read, - .write = packed_write, - .seek = packed_seek, - .close = packed_close, - }; - struct packed_file *cookie = NULL; +static struct mg_fd *packed_open(const char *path, int flags) { size_t size = 0; const char *data = mg_unpack(path, &size); + struct packed_file *fp = NULL; + struct mg_fd *fd = NULL; if (data == NULL) return NULL; - if ((cookie = calloc(1, sizeof(*cookie))) == NULL) return NULL; - cookie->data = data; - cookie->size = size; - return fopencookie(cookie, mode, funcs); + if (flags & MG_FS_WRITE) return NULL; + fp = calloc(1, sizeof(*fp)); + fd = calloc(1, sizeof(*fd)); + fp->size = size; + fp->data = data; + fd->fd = fp; + fd->fs = &mg_fs_packed; + return fd; +} + +static void packed_close(struct mg_fd *fd) { + if (fd) free(fd->fd), free(fd); +} + +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; } + +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + 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}; + +#ifdef MG_ENABLE_LINES +#line 1 "src/fs_posix.c" +#endif + + +#if defined(O_READ) +static char *posix_realpath(const char *path, char *resolved_path) { +#ifdef _WIN32 + return _fullpath(path, resolved_path, PATH_MAX); +#else + return realpath(path, resolved_path); +#endif +} + +static int posix_stat(const char *path, size_t *size, unsigned *mtime) { +#ifdef _WIN32 + struct _stati64 st; + wchar_t tmp[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; #else -FILE *mg_fopen_packed(const char *path, const char *mode) { - (void) path, (void) mode; + struct stat st; + if (stat(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = (unsigned) st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); +} + +static void posix_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + // char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; + struct dirent *dp; + DIR *dirp; + + // while (p > dir && *p != '/') *p-- = '\0'; + if ((dirp = (opendir(dir))) != NULL) { + size_t off, n; + while ((dp = readdir(dirp)) != NULL) { + // Do not show current dir and hidden files + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, &st); + } + closedir(dirp); + } +} + +static struct mg_fd *posix_open(const char *path, int flags) { + const char *mode = + flags & (MG_FS_READ | MG_FS_WRITE) + ? "r+b" + : flags & MG_FS_READ ? "rb" : flags & MG_FS_WRITE ? "wb" : ""; + void *fp = NULL; + struct mg_fd *fd = NULL; +#ifdef _WIN32 + wchar_t b1[PATH_MAX], b2[10]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); + fp = (void *) _wfopen(b1, b2); +#else + fp = (void *) fopen(path, mode); +#endif + if (fp == NULL) return NULL; + fd = calloc(1, sizeof(*fd)); + fd->fd = fp; + fd->fs = &mg_fs_posix; + return fd; +} + +static void posix_close(struct mg_fd *fd) { + if (fd) fclose((FILE *) fd->fd), free(fd); +} + +static size_t posix_read(void *fp, void *buf, size_t len) { + return fread(buf, 1, len, (FILE *) fp); +} + +static size_t posix_write(void *fp, const void *buf, size_t len) { + return fwrite(buf, 1, len, (FILE *) fp); +} + +static size_t posix_seek(void *fp, size_t offset) { +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ + _XOPEN_SOURCE >= 600 + fseeko((FILE *) fp, (off_t) offset, SEEK_SET); +#else + fseek((FILE *) fp, (long) offset, SEEK_SET); +#endif + return (size_t) ftell((FILE *) fp); +} +#else +static char *posix_realpath(const char *path, char *resolved_path) { + (void) path, (void) resolved_path; + return NULL; +} + +static int posix_stat(const char *path, size_t *size, unsigned *mtime) { + (void) path, (void) size, (void) mtime; + return 0; +} + +static void posix_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} + +static struct mg_fd *posix_open(const char *path, int flags) { + (void) path, (void) flags; return NULL; } + +static void posix_close(struct mg_fd *fd) { + (void) fd; +} + +static size_t posix_read(void *fd, void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t posix_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t posix_seek(void *fd, size_t offset) { + (void) fd, (void) offset; + return (size_t) ~0; +} #endif +struct mg_fs mg_fs_posix = {posix_realpath, posix_stat, posix_list, + posix_open, posix_close, posix_read, + posix_write, posix_seek}; + #ifdef MG_ENABLE_LINES #line 1 "src/http.c" #endif @@ -900,73 +1044,83 @@ static void static_cb(struct mg_connection *c, int ev, void *ev_data, (void) ev_data; } -static const char *guess_content_type(const char *filename) { - size_t n = strlen(filename); -#define MIME_ENTRY(_ext, _type) \ - { _ext, sizeof(_ext) - 1, _type } - const struct { - const char *ext; - size_t ext_len; - const char *type; - } * t, types[] = { - MIME_ENTRY("html", "text/html; charset=utf-8"), - MIME_ENTRY("htm", "text/html; charset=utf-8"), - MIME_ENTRY("css", "text/css; charset=utf-8"), - MIME_ENTRY("js", "text/javascript; charset=utf-8"), - MIME_ENTRY("gif", "image/gif"), - MIME_ENTRY("png", "image/png"), - MIME_ENTRY("woff", "font/woff"), - MIME_ENTRY("ttf", "font/ttf"), - MIME_ENTRY("aac", "audio/aac"), - MIME_ENTRY("avi", "video/x-msvideo"), - MIME_ENTRY("azw", "application/vnd.amazon.ebook"), - MIME_ENTRY("bin", "application/octet-stream"), - MIME_ENTRY("bmp", "image/bmp"), - MIME_ENTRY("bz", "application/x-bzip"), - MIME_ENTRY("bz2", "application/x-bzip2"), - MIME_ENTRY("csv", "text/csv"), - MIME_ENTRY("doc", "application/msword"), - MIME_ENTRY("epub", "application/epub+zip"), - MIME_ENTRY("exe", "application/octet-stream"), - MIME_ENTRY("gz", "application/gzip"), - MIME_ENTRY("ico", "image/x-icon"), - MIME_ENTRY("json", "application/json"), - MIME_ENTRY("mid", "audio/mid"), - MIME_ENTRY("mjs", "text/javascript"), - MIME_ENTRY("mov", "video/quicktime"), - MIME_ENTRY("mp3", "audio/mpeg"), - MIME_ENTRY("mp4", "video/mp4"), - MIME_ENTRY("mpeg", "video/mpeg"), - MIME_ENTRY("mpg", "video/mpeg"), - MIME_ENTRY("ogg", "application/ogg"), - MIME_ENTRY("pdf", "application/pdf"), - MIME_ENTRY("rar", "application/rar"), - MIME_ENTRY("rtf", "application/rtf"), - MIME_ENTRY("shtml", "text/html; charset=utf-8"), - MIME_ENTRY("svg", "image/svg+xml"), - MIME_ENTRY("tar", "application/tar"), - MIME_ENTRY("tgz", "application/tar-gz"), - MIME_ENTRY("txt", "text/plain; charset=utf-8"), - MIME_ENTRY("wasm", "application/wasm"), - MIME_ENTRY("wav", "audio/wav"), - MIME_ENTRY("weba", "audio/webm"), - MIME_ENTRY("webm", "video/webm"), - MIME_ENTRY("webp", "image/webp"), - MIME_ENTRY("xls", "application/excel"), - MIME_ENTRY("xml", "application/xml"), - MIME_ENTRY("xsl", "application/xml"), - MIME_ENTRY("zip", "application/zip"), - MIME_ENTRY("3gp", "video/3gpp"), - MIME_ENTRY("7z", "application/x-7z-compressed"), - {NULL, 0, NULL}, - }; - - for (t = types; t->ext != NULL; t++) { - if (n < t->ext_len + 2) continue; - if (mg_ncasecmp(t->ext, &filename[n - t->ext_len], t->ext_len)) continue; - return t->type; - } - return "text/plain; charset=utf-8"; +static struct mg_str guess_content_type(struct mg_str path, const char *extra) { + // clang-format off + struct mimeentry { struct mg_str extension, value; }; + #define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }} + // clang-format on + const struct mimeentry tab[] = { + MIME_ENTRY("html", "text/html; charset=utf-8"), + MIME_ENTRY("htm", "text/html; charset=utf-8"), + MIME_ENTRY("css", "text/css; charset=utf-8"), + MIME_ENTRY("js", "text/javascript; charset=utf-8"), + MIME_ENTRY("gif", "image/gif"), + MIME_ENTRY("png", "image/png"), + MIME_ENTRY("woff", "font/woff"), + MIME_ENTRY("ttf", "font/ttf"), + MIME_ENTRY("aac", "audio/aac"), + MIME_ENTRY("avi", "video/x-msvideo"), + MIME_ENTRY("azw", "application/vnd.amazon.ebook"), + MIME_ENTRY("bin", "application/octet-stream"), + MIME_ENTRY("bmp", "image/bmp"), + MIME_ENTRY("bz", "application/x-bzip"), + MIME_ENTRY("bz2", "application/x-bzip2"), + MIME_ENTRY("csv", "text/csv"), + MIME_ENTRY("doc", "application/msword"), + MIME_ENTRY("epub", "application/epub+zip"), + MIME_ENTRY("exe", "application/octet-stream"), + MIME_ENTRY("gz", "application/gzip"), + MIME_ENTRY("ico", "image/x-icon"), + MIME_ENTRY("json", "application/json"), + MIME_ENTRY("mid", "audio/mid"), + MIME_ENTRY("mjs", "text/javascript"), + MIME_ENTRY("mov", "video/quicktime"), + MIME_ENTRY("mp3", "audio/mpeg"), + MIME_ENTRY("mp4", "video/mp4"), + MIME_ENTRY("mpeg", "video/mpeg"), + MIME_ENTRY("mpg", "video/mpeg"), + MIME_ENTRY("ogg", "application/ogg"), + MIME_ENTRY("pdf", "application/pdf"), + MIME_ENTRY("rar", "application/rar"), + MIME_ENTRY("rtf", "application/rtf"), + MIME_ENTRY("shtml", "text/html; charset=utf-8"), + MIME_ENTRY("svg", "image/svg+xml"), + MIME_ENTRY("tar", "application/tar"), + MIME_ENTRY("tgz", "application/tar-gz"), + MIME_ENTRY("txt", "text/plain; charset=utf-8"), + MIME_ENTRY("wasm", "application/wasm"), + MIME_ENTRY("wav", "audio/wav"), + MIME_ENTRY("weba", "audio/webm"), + MIME_ENTRY("webm", "video/webm"), + MIME_ENTRY("webp", "image/webp"), + MIME_ENTRY("xls", "application/excel"), + MIME_ENTRY("xml", "application/xml"), + MIME_ENTRY("xsl", "application/xml"), + MIME_ENTRY("zip", "application/zip"), + MIME_ENTRY("3gp", "video/3gpp"), + MIME_ENTRY("7z", "application/x-7z-compressed"), + MIME_ENTRY("7z", "application/x-7z-compressed"), + {{0, 0}, {0, 0}}, + }; + size_t i = 0; + struct mg_str k, v, s = mg_str(extra); + + // Shrink path to its extension only + while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; + path.ptr += path.len - i; + path.len = i; + + // Process user-provided mime type overrides, if any + while (mg_next_comma_entry(&s, &k, &v)) { + if (mg_strcmp(path, k) == 0) return v; + } + + // Process built-in mime types + for (i = 0; tab[i].extension.ptr != NULL; i++) { + if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value; + } + + return mg_str("text/plain; charset=utf-8"); } static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { @@ -990,7 +1144,7 @@ static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { } void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, - const char *path, const char *mime, const char *hdrs) { + const char *path, struct mg_http_serve_opts *opts) { struct mg_str *inm = mg_http_get_header(hm, "If-None-Match"); struct stat st; char etag[64]; @@ -1008,6 +1162,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, int n, status = 200; char range[100] = ""; int64_t r1 = 0, r2 = 0, cl = st.st_size; + struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); // Handle Range header struct mg_str *rh = mg_http_get_header(hm, "Range"); @@ -1038,10 +1193,10 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, } mg_printf(c, - "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n" + "HTTP/1.1 %d %s\r\nContent-Type: %.*s\r\n" "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n", - status, mg_http_status_code_str(status), mime, etag, cl, range, - hdrs ? hdrs : ""); + status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, + etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { fclose(fp); } else { @@ -1345,40 +1500,39 @@ static bool uri_to_local_path(struct mg_connection *c, void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts) { - char root_dir[MG_PATH_MAX], full_path[sizeof(root_dir)]; + char root_dir[MG_PATH_MAX], path[sizeof(root_dir)]; bool is_index = false, exists; struct stat st; - root_dir[0] = full_path[0] = '\0'; + root_dir[0] = path[0] = '\0'; - if (!uri_to_local_path(c, hm, opts, root_dir, sizeof(root_dir), full_path, - sizeof(full_path), &is_index)) + if (!uri_to_local_path(c, hm, opts, root_dir, sizeof(root_dir), path, + sizeof(path), &is_index)) return; - exists = stat(full_path, &st) == 0; + exists = stat(path, &st) == 0; #if MG_ENABLE_SSI if (is_index && !exists) { - char *p = full_path + strlen(full_path); - while (p > full_path && p[-1] != '/') p--; - strncpy(p, "index.shtml", (size_t)(&full_path[sizeof(full_path)] - p - 2)); - full_path[sizeof(full_path) - 1] = '\0'; - exists = stat(full_path, &st) == 0; + char *p = path + strlen(path); + while (p > path && p[-1] != '/') p--; + strncpy(p, "index.shtml", (size_t)(&path[sizeof(path)] - p - 2)); + path[sizeof(path) - 1] = '\0'; + exists = stat(path, &st) == 0; } #endif if (is_index && !exists) { #if MG_ENABLE_DIRECTORY_LISTING - listdir(c, hm, opts, full_path); + listdir(c, hm, opts, path); #else mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); #endif #if MG_ENABLE_SSI } else if (opts->ssi_pattern != NULL && - mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), - full_path, strlen(full_path))) { - mg_http_serve_ssi(c, root_dir, full_path); + mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), path, + strlen(path))) { + mg_http_serve_ssi(c, root_dir, path); #endif } else { - mg_http_serve_file(c, hm, full_path, guess_content_type(full_path), - opts->extra_headers); + mg_http_serve_file(c, hm, path, opts); } } diff --git a/mongoose.h b/mongoose.h index 4a09f906b2d3f935a9da49e642d793fe43fa79b5..24e35ed7363198188e2e9719f0694e3eb047c15c 100644 --- a/mongoose.h +++ b/mongoose.h @@ -606,14 +606,28 @@ void mg_usleep(unsigned long usecs); -FILE *mg_fopen_packed(const char *path, const char *mode); +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); + int (*stat)(const char *path, size_t *size, unsigned *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); +}; -#if (defined(__linux__) && defined(__GNUC__)) || defined(__NEWLIB__) -#define MG_ENABLE_PACKED_FS 1 -#define MG_FOPENCOOKIE -#else -#define MG_ENABLE_PACKED_FS 0 -#endif +// File descriptor +struct mg_fd { + void *fd; + struct mg_fs *fs; +}; + +extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek +extern struct mg_fs mg_fs_packed; // Packed FS, see examples/complete @@ -781,6 +795,7 @@ char *mg_ntoa(const struct mg_addr *addr, char *buf, size_t len); + struct mg_http_header { struct mg_str name; struct mg_str value; @@ -800,7 +815,8 @@ struct mg_http_serve_opts { const char *root_dir; // Web root directory, must be non-NULL const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml const char *extra_headers; // Extra HTTP headers to add in responses - bool use_packed_fs; // Serve files embedded into binary + const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. + struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX }; // Parameter for mg_http_next_multipart @@ -820,9 +836,9 @@ struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, mg_event_handler_t fn, void *fn_data); void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, - struct mg_http_serve_opts *); -void mg_http_serve_file(struct mg_connection *, struct mg_http_message *, - const char *, const char *mime, const char *headers); + struct mg_http_serve_opts *opts); +void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, + const char *path, struct mg_http_serve_opts *opts); void mg_http_reply(struct mg_connection *, int status_code, const char *headers, const char *body_fmt, ...); struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); diff --git a/src/fs.c b/src/fs.c deleted file mode 100644 index fe2064f7add5e21f71c9f3f9d08820442854e9f6..0000000000000000000000000000000000000000 --- a/src/fs.c +++ /dev/null @@ -1,65 +0,0 @@ -#include "fs.h" - -struct packed_file { - const char *data; - size_t size; - size_t pos; -}; - -const char *mg_unpack(const char *, size_t *) WEAK; -const char *mg_unpack(const char *path, size_t *size) { - (void) path, (void) size; - return NULL; -} - -#if defined(MG_FOPENCOOKIE) -ssize_t packed_read(void *cookie, char *buf, size_t size) { - struct packed_file *fp = (struct packed_file *) cookie; - if (size > fp->size - fp->pos) size = fp->size - fp->pos; - memcpy(buf, &fp->data[fp->pos], size); - fp->pos += size; - return (ssize_t) size; -} - -ssize_t packed_write(void *cookie, const char *buf, size_t size) { - (void) cookie, (void) buf, (void) size; - return -1; -} - -int packed_seek(void *cookie, long *offset, int whence) { - struct packed_file *fp = (struct packed_file *) cookie; - if (whence == SEEK_SET) fp->pos = (size_t) *offset; - if (whence == SEEK_END) fp->pos = (size_t)((long) fp->size + *offset); - if (whence == SEEK_CUR) fp->pos = (size_t)((long) fp->pos + *offset); - if (fp->pos > fp->size) fp->pos = fp->size; - *offset = (long) fp->pos; - return 0; -} - -int packed_close(void *cookie) { - free(cookie); - return 0; -} - -FILE *mg_fopen_packed(const char *path, const char *mode) { - cookie_io_functions_t funcs = { - .read = packed_read, - .write = packed_write, - .seek = packed_seek, - .close = packed_close, - }; - struct packed_file *cookie = NULL; - size_t size = 0; - const char *data = mg_unpack(path, &size); - if (data == NULL) return NULL; - if ((cookie = calloc(1, sizeof(*cookie))) == NULL) return NULL; - cookie->data = data; - cookie->size = size; - return fopencookie(cookie, mode, funcs); -} -#else -FILE *mg_fopen_packed(const char *path, const char *mode) { - (void) path, (void) mode; - return NULL; -} -#endif diff --git a/src/fs.h b/src/fs.h index 9fd0b957481dc98548a8e7fcb6bfef7e5c90a818..c7f862e6d54e538490bdb3bea7deef894e29699c 100644 --- a/src/fs.h +++ b/src/fs.h @@ -2,11 +2,25 @@ #include "arch.h" -FILE *mg_fopen_packed(const char *path, const char *mode); - -#if (defined(__linux__) && defined(__GNUC__)) || defined(__NEWLIB__) -#define MG_ENABLE_PACKED_FS 1 -#define MG_FOPENCOOKIE -#else -#define MG_ENABLE_PACKED_FS 0 -#endif +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); + int (*stat)(const char *path, size_t *size, unsigned *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); +}; + +// File descriptor +struct mg_fd { + void *fd; + struct mg_fs *fs; +}; + +extern struct mg_fs mg_fs_posix; // POSIX open/close/read/write/seek +extern struct mg_fs mg_fs_packed; // Packed FS, see examples/complete diff --git a/src/fs_packed.c b/src/fs_packed.c new file mode 100644 index 0000000000000000000000000000000000000000..75add662f0a301071fffd54ca929afdac840d4a6 --- /dev/null +++ b/src/fs_packed.c @@ -0,0 +1,74 @@ +#include "fs.h" + +struct packed_file { + const char *data; + size_t size; + size_t pos; +}; + +const char *mg_unpack(const char *, size_t *) WEAK; +const char *mg_unpack(const char *path, size_t *size) { + (void) path, (void) size; + return NULL; +} + +static char *packed_realpath(const char *path, char *resolved_path) { + if (resolved_path == NULL) resolved_path = malloc(strlen(path) + 1); + strcpy(resolved_path, path); + return resolved_path; +} + +static int packed_stat(const char *path, size_t *size, unsigned *mtime) { + const char *data = mg_unpack(path, size); + if (mtime) *mtime = 0; + return data == NULL ? 0 : MG_FS_READ; +} + +static void packed_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} + +static struct mg_fd *packed_open(const char *path, int flags) { + size_t size = 0; + const char *data = mg_unpack(path, &size); + struct packed_file *fp = NULL; + struct mg_fd *fd = NULL; + if (data == NULL) return NULL; + if (flags & MG_FS_WRITE) return NULL; + fp = calloc(1, sizeof(*fp)); + fd = calloc(1, sizeof(*fd)); + fp->size = size; + fp->data = data; + fd->fd = fp; + fd->fs = &mg_fs_packed; + return fd; +} + +static void packed_close(struct mg_fd *fd) { + if (fd) free(fd->fd), free(fd); +} + +static size_t packed_read(void *fd, void *buf, size_t len) { + struct packed_file *fp = (struct packed_file *) fd; + if (fp->pos + len > fp->size) len = fp->size - fp->pos; + memcpy(buf, &fp->data[fp->pos], len); + fp->pos += len; + return len; +} + +static size_t packed_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t packed_seek(void *fd, size_t offset) { + struct packed_file *fp = (struct packed_file *) fd; + fp->pos = offset; + if (fp->pos > fp->size) fp->pos = fp->size; + 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}; diff --git a/src/fs_posix.c b/src/fs_posix.c new file mode 100644 index 0000000000000000000000000000000000000000..d93f2c3b5301756e719bae24b9902cc40b1339ac --- /dev/null +++ b/src/fs_posix.c @@ -0,0 +1,131 @@ +#include "fs.h" + +#if defined(O_READ) +static char *posix_realpath(const char *path, char *resolved_path) { +#ifdef _WIN32 + return _fullpath(path, resolved_path, PATH_MAX); +#else + return realpath(path, resolved_path); +#endif +} + +static int posix_stat(const char *path, size_t *size, unsigned *mtime) { +#ifdef _WIN32 + struct _stati64 st; + wchar_t tmp[PATH_MAX]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, tmp, sizeof(tmp) / sizeof(tmp[0])); + if (_wstati64(tmp, &st) != 0) return 0; +#else + struct stat st; + if (stat(path, &st) != 0) return 0; +#endif + if (size) *size = (size_t) st.st_size; + if (mtime) *mtime = (unsigned) st.st_mtime; + return MG_FS_READ | MG_FS_WRITE | (S_ISDIR(st.st_mode) ? MG_FS_DIR : 0); +} + +static void posix_list(const char *dir, void (*fn)(const char *, void *), + void *userdata) { + // char path[MG_PATH_MAX], *p = &dir[strlen(dir) - 1], tmp[10]; + struct dirent *dp; + DIR *dirp; + + // while (p > dir && *p != '/') *p-- = '\0'; + if ((dirp = (opendir(dir))) != NULL) { + size_t off, n; + while ((dp = readdir(dirp)) != NULL) { + // Do not show current dir and hidden files + if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) continue; + fn(dp->d_name, &st); + } + closedir(dirp); + } +} + +static struct mg_fd *posix_open(const char *path, int flags) { + const char *mode = + flags & (MG_FS_READ | MG_FS_WRITE) + ? "r+b" + : flags & MG_FS_READ ? "rb" : flags & MG_FS_WRITE ? "wb" : ""; + void *fp = NULL; + struct mg_fd *fd = NULL; +#ifdef _WIN32 + wchar_t b1[PATH_MAX], b2[10]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, b1, sizeof(b1) / sizeof(b1[0])); + MultiByteToWideChar(CP_UTF8, 0, mode, -1, b2, sizeof(b2) / sizeof(b2[0])); + fp = (void *) _wfopen(b1, b2); +#else + fp = (void *) fopen(path, mode); +#endif + if (fp == NULL) return NULL; + fd = calloc(1, sizeof(*fd)); + fd->fd = fp; + fd->fs = &mg_fs_posix; + return fd; +} + +static void posix_close(struct mg_fd *fd) { + if (fd) fclose((FILE *) fd->fd), free(fd); +} + +static size_t posix_read(void *fp, void *buf, size_t len) { + return fread(buf, 1, len, (FILE *) fp); +} + +static size_t posix_write(void *fp, const void *buf, size_t len) { + return fwrite(buf, 1, len, (FILE *) fp); +} + +static size_t posix_seek(void *fp, size_t offset) { +#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \ + _XOPEN_SOURCE >= 600 + fseeko((FILE *) fp, (off_t) offset, SEEK_SET); +#else + fseek((FILE *) fp, (long) offset, SEEK_SET); +#endif + return (size_t) ftell((FILE *) fp); +} +#else +static char *posix_realpath(const char *path, char *resolved_path) { + (void) path, (void) resolved_path; + return NULL; +} + +static int posix_stat(const char *path, size_t *size, unsigned *mtime) { + (void) path, (void) size, (void) mtime; + return 0; +} + +static void posix_list(const char *path, void (*fn)(const char *, void *), + void *userdata) { + (void) path, (void) fn, (void) userdata; +} + +static struct mg_fd *posix_open(const char *path, int flags) { + (void) path, (void) flags; + return NULL; +} + +static void posix_close(struct mg_fd *fd) { + (void) fd; +} + +static size_t posix_read(void *fd, void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t posix_write(void *fd, const void *buf, size_t len) { + (void) fd, (void) buf, (void) len; + return 0; +} + +static size_t posix_seek(void *fd, size_t offset) { + (void) fd, (void) offset; + return (size_t) ~0; +} +#endif + +struct mg_fs mg_fs_posix = {posix_realpath, 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 196ba01ed05a89270b9801dd5e8fa8e90bc25700..3447f88a6dbe473b24db1932faacd815d47ef35d 100644 --- a/src/http.c +++ b/src/http.c @@ -420,73 +420,83 @@ static void static_cb(struct mg_connection *c, int ev, void *ev_data, (void) ev_data; } -static const char *guess_content_type(const char *filename) { - size_t n = strlen(filename); -#define MIME_ENTRY(_ext, _type) \ - { _ext, sizeof(_ext) - 1, _type } - const struct { - const char *ext; - size_t ext_len; - const char *type; - } * t, types[] = { - MIME_ENTRY("html", "text/html; charset=utf-8"), - MIME_ENTRY("htm", "text/html; charset=utf-8"), - MIME_ENTRY("css", "text/css; charset=utf-8"), - MIME_ENTRY("js", "text/javascript; charset=utf-8"), - MIME_ENTRY("gif", "image/gif"), - MIME_ENTRY("png", "image/png"), - MIME_ENTRY("woff", "font/woff"), - MIME_ENTRY("ttf", "font/ttf"), - MIME_ENTRY("aac", "audio/aac"), - MIME_ENTRY("avi", "video/x-msvideo"), - MIME_ENTRY("azw", "application/vnd.amazon.ebook"), - MIME_ENTRY("bin", "application/octet-stream"), - MIME_ENTRY("bmp", "image/bmp"), - MIME_ENTRY("bz", "application/x-bzip"), - MIME_ENTRY("bz2", "application/x-bzip2"), - MIME_ENTRY("csv", "text/csv"), - MIME_ENTRY("doc", "application/msword"), - MIME_ENTRY("epub", "application/epub+zip"), - MIME_ENTRY("exe", "application/octet-stream"), - MIME_ENTRY("gz", "application/gzip"), - MIME_ENTRY("ico", "image/x-icon"), - MIME_ENTRY("json", "application/json"), - MIME_ENTRY("mid", "audio/mid"), - MIME_ENTRY("mjs", "text/javascript"), - MIME_ENTRY("mov", "video/quicktime"), - MIME_ENTRY("mp3", "audio/mpeg"), - MIME_ENTRY("mp4", "video/mp4"), - MIME_ENTRY("mpeg", "video/mpeg"), - MIME_ENTRY("mpg", "video/mpeg"), - MIME_ENTRY("ogg", "application/ogg"), - MIME_ENTRY("pdf", "application/pdf"), - MIME_ENTRY("rar", "application/rar"), - MIME_ENTRY("rtf", "application/rtf"), - MIME_ENTRY("shtml", "text/html; charset=utf-8"), - MIME_ENTRY("svg", "image/svg+xml"), - MIME_ENTRY("tar", "application/tar"), - MIME_ENTRY("tgz", "application/tar-gz"), - MIME_ENTRY("txt", "text/plain; charset=utf-8"), - MIME_ENTRY("wasm", "application/wasm"), - MIME_ENTRY("wav", "audio/wav"), - MIME_ENTRY("weba", "audio/webm"), - MIME_ENTRY("webm", "video/webm"), - MIME_ENTRY("webp", "image/webp"), - MIME_ENTRY("xls", "application/excel"), - MIME_ENTRY("xml", "application/xml"), - MIME_ENTRY("xsl", "application/xml"), - MIME_ENTRY("zip", "application/zip"), - MIME_ENTRY("3gp", "video/3gpp"), - MIME_ENTRY("7z", "application/x-7z-compressed"), - {NULL, 0, NULL}, - }; - - for (t = types; t->ext != NULL; t++) { - if (n < t->ext_len + 2) continue; - if (mg_ncasecmp(t->ext, &filename[n - t->ext_len], t->ext_len)) continue; - return t->type; +static struct mg_str guess_content_type(struct mg_str path, const char *extra) { + // clang-format off + struct mimeentry { struct mg_str extension, value; }; + #define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }} + // clang-format on + const struct mimeentry tab[] = { + MIME_ENTRY("html", "text/html; charset=utf-8"), + MIME_ENTRY("htm", "text/html; charset=utf-8"), + MIME_ENTRY("css", "text/css; charset=utf-8"), + MIME_ENTRY("js", "text/javascript; charset=utf-8"), + MIME_ENTRY("gif", "image/gif"), + MIME_ENTRY("png", "image/png"), + MIME_ENTRY("woff", "font/woff"), + MIME_ENTRY("ttf", "font/ttf"), + MIME_ENTRY("aac", "audio/aac"), + MIME_ENTRY("avi", "video/x-msvideo"), + MIME_ENTRY("azw", "application/vnd.amazon.ebook"), + MIME_ENTRY("bin", "application/octet-stream"), + MIME_ENTRY("bmp", "image/bmp"), + MIME_ENTRY("bz", "application/x-bzip"), + MIME_ENTRY("bz2", "application/x-bzip2"), + MIME_ENTRY("csv", "text/csv"), + MIME_ENTRY("doc", "application/msword"), + MIME_ENTRY("epub", "application/epub+zip"), + MIME_ENTRY("exe", "application/octet-stream"), + MIME_ENTRY("gz", "application/gzip"), + MIME_ENTRY("ico", "image/x-icon"), + MIME_ENTRY("json", "application/json"), + MIME_ENTRY("mid", "audio/mid"), + MIME_ENTRY("mjs", "text/javascript"), + MIME_ENTRY("mov", "video/quicktime"), + MIME_ENTRY("mp3", "audio/mpeg"), + MIME_ENTRY("mp4", "video/mp4"), + MIME_ENTRY("mpeg", "video/mpeg"), + MIME_ENTRY("mpg", "video/mpeg"), + MIME_ENTRY("ogg", "application/ogg"), + MIME_ENTRY("pdf", "application/pdf"), + MIME_ENTRY("rar", "application/rar"), + MIME_ENTRY("rtf", "application/rtf"), + MIME_ENTRY("shtml", "text/html; charset=utf-8"), + MIME_ENTRY("svg", "image/svg+xml"), + MIME_ENTRY("tar", "application/tar"), + MIME_ENTRY("tgz", "application/tar-gz"), + MIME_ENTRY("txt", "text/plain; charset=utf-8"), + MIME_ENTRY("wasm", "application/wasm"), + MIME_ENTRY("wav", "audio/wav"), + MIME_ENTRY("weba", "audio/webm"), + MIME_ENTRY("webm", "video/webm"), + MIME_ENTRY("webp", "image/webp"), + MIME_ENTRY("xls", "application/excel"), + MIME_ENTRY("xml", "application/xml"), + MIME_ENTRY("xsl", "application/xml"), + MIME_ENTRY("zip", "application/zip"), + MIME_ENTRY("3gp", "video/3gpp"), + MIME_ENTRY("7z", "application/x-7z-compressed"), + MIME_ENTRY("7z", "application/x-7z-compressed"), + {{0, 0}, {0, 0}}, + }; + size_t i = 0; + struct mg_str k, v, s = mg_str(extra); + + // Shrink path to its extension only + while (i < path.len && path.ptr[path.len - i - 1] != '.') i++; + path.ptr += path.len - i; + path.len = i; + + // Process user-provided mime type overrides, if any + while (mg_next_comma_entry(&s, &k, &v)) { + if (mg_strcmp(path, k) == 0) return v; } - return "text/plain; charset=utf-8"; + + // Process built-in mime types + for (i = 0; tab[i].extension.ptr != NULL; i++) { + if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value; + } + + return mg_str("text/plain; charset=utf-8"); } static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { @@ -510,7 +520,7 @@ static int getrange(struct mg_str *s, int64_t *a, int64_t *b) { } void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, - const char *path, const char *mime, const char *hdrs) { + const char *path, struct mg_http_serve_opts *opts) { struct mg_str *inm = mg_http_get_header(hm, "If-None-Match"); struct stat st; char etag[64]; @@ -528,6 +538,7 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, int n, status = 200; char range[100] = ""; int64_t r1 = 0, r2 = 0, cl = st.st_size; + struct mg_str mime = guess_content_type(mg_str(path), opts->mime_types); // Handle Range header struct mg_str *rh = mg_http_get_header(hm, "Range"); @@ -558,10 +569,10 @@ void mg_http_serve_file(struct mg_connection *c, struct mg_http_message *hm, } mg_printf(c, - "HTTP/1.1 %d %s\r\nContent-Type: %s\r\n" + "HTTP/1.1 %d %s\r\nContent-Type: %.*s\r\n" "Etag: %s\r\nContent-Length: " MG_INT64_FMT "\r\n%s%s\r\n", - status, mg_http_status_code_str(status), mime, etag, cl, range, - hdrs ? hdrs : ""); + status, mg_http_status_code_str(status), (int) mime.len, mime.ptr, + etag, cl, range, opts->extra_headers ? opts->extra_headers : ""); if (mg_vcasecmp(&hm->method, "HEAD") == 0) { fclose(fp); } else { @@ -865,40 +876,39 @@ static bool uri_to_local_path(struct mg_connection *c, void mg_http_serve_dir(struct mg_connection *c, struct mg_http_message *hm, struct mg_http_serve_opts *opts) { - char root_dir[MG_PATH_MAX], full_path[sizeof(root_dir)]; + char root_dir[MG_PATH_MAX], path[sizeof(root_dir)]; bool is_index = false, exists; struct stat st; - root_dir[0] = full_path[0] = '\0'; + root_dir[0] = path[0] = '\0'; - if (!uri_to_local_path(c, hm, opts, root_dir, sizeof(root_dir), full_path, - sizeof(full_path), &is_index)) + if (!uri_to_local_path(c, hm, opts, root_dir, sizeof(root_dir), path, + sizeof(path), &is_index)) return; - exists = stat(full_path, &st) == 0; + exists = stat(path, &st) == 0; #if MG_ENABLE_SSI if (is_index && !exists) { - char *p = full_path + strlen(full_path); - while (p > full_path && p[-1] != '/') p--; - strncpy(p, "index.shtml", (size_t)(&full_path[sizeof(full_path)] - p - 2)); - full_path[sizeof(full_path) - 1] = '\0'; - exists = stat(full_path, &st) == 0; + char *p = path + strlen(path); + while (p > path && p[-1] != '/') p--; + strncpy(p, "index.shtml", (size_t)(&path[sizeof(path)] - p - 2)); + path[sizeof(path) - 1] = '\0'; + exists = stat(path, &st) == 0; } #endif if (is_index && !exists) { #if MG_ENABLE_DIRECTORY_LISTING - listdir(c, hm, opts, full_path); + listdir(c, hm, opts, path); #else mg_http_reply(c, 403, "", "%s", "Directory listing not supported"); #endif #if MG_ENABLE_SSI } else if (opts->ssi_pattern != NULL && - mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), - full_path, strlen(full_path))) { - mg_http_serve_ssi(c, root_dir, full_path); + mg_globmatch(opts->ssi_pattern, strlen(opts->ssi_pattern), path, + strlen(path))) { + mg_http_serve_ssi(c, root_dir, path); #endif } else { - mg_http_serve_file(c, hm, full_path, guess_content_type(full_path), - opts->extra_headers); + mg_http_serve_file(c, hm, path, opts); } } diff --git a/src/http.h b/src/http.h index 7e68af61b98d0d09b4cd0a554b2b587f4800bd60..a9ffbad19fee9c1b787fd2b4221aaf7278df45d6 100644 --- a/src/http.h +++ b/src/http.h @@ -1,6 +1,7 @@ #pragma once #include "config.h" +#include "fs.h" #include "net.h" #include "str.h" @@ -23,7 +24,8 @@ struct mg_http_serve_opts { const char *root_dir; // Web root directory, must be non-NULL const char *ssi_pattern; // SSI file name pattern, e.g. #.shtml const char *extra_headers; // Extra HTTP headers to add in responses - bool use_packed_fs; // Serve files embedded into binary + const char *mime_types; // Extra mime types, ext1=type1,ext2=type2,.. + struct mg_fs *fs; // Filesystem implementation. Use NULL for POSIX }; // Parameter for mg_http_next_multipart @@ -43,9 +45,9 @@ struct mg_connection *mg_http_listen(struct mg_mgr *, const char *url, struct mg_connection *mg_http_connect(struct mg_mgr *, const char *url, mg_event_handler_t fn, void *fn_data); void mg_http_serve_dir(struct mg_connection *, struct mg_http_message *hm, - struct mg_http_serve_opts *); -void mg_http_serve_file(struct mg_connection *, struct mg_http_message *, - const char *, const char *mime, const char *headers); + struct mg_http_serve_opts *opts); +void mg_http_serve_file(struct mg_connection *, struct mg_http_message *hm, + const char *path, struct mg_http_serve_opts *opts); void mg_http_reply(struct mg_connection *, int status_code, const char *headers, const char *body_fmt, ...); struct mg_str *mg_http_get_header(struct mg_http_message *, const char *name); diff --git a/test/mongoose_custom.h b/test/mongoose_custom.h index 5963078ede3c7ac158a5795334f8d6a728a983e7..8176b245b04ca6e3bdbd23cb28bc9b8f3a6b1917 100644 --- a/test/mongoose_custom.h +++ b/test/mongoose_custom.h @@ -22,6 +22,4 @@ #undef MG_ENABLE_SOCKET #define MG_ENABLE_SOCKET 0 -#define realpath(a, b) (a) - int clock_gettime(clockid_t clock_id, struct timespec *tp); diff --git a/test/unit_test.c b/test/unit_test.c index f37d9e89e7cf43315417fb3f0816f8b93f942a03..316912d2aabbd358536d8f8cbcb1923ca5058f84 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -363,6 +363,16 @@ static void eh1(struct mg_connection *c, int ev, void *ev_data, void *fn_data) { sopts.root_dir = "."; sopts.extra_headers = "A: B\r\nC: D\r\n"; mg_http_serve_dir(c, hm, &sopts); + } else if (mg_http_match_uri(hm, "/packed/#")) { + struct mg_http_serve_opts sopts; + memset(&sopts, 0, sizeof(sopts)); + sopts.root_dir = "."; + mg_http_serve_dir(c, hm, &sopts); + } else if (mg_http_match_uri(hm, "/servefile")) { + struct mg_http_serve_opts sopts; + memset(&sopts, 0, sizeof(sopts)); + sopts.mime_types = "foo=a/b,txt=c/d"; + mg_http_serve_file(c, hm, "test/data/a.txt", &sopts); } else { struct mg_http_serve_opts sopts; memset(&sopts, 0, sizeof(sopts)); @@ -501,6 +511,17 @@ static void test_http_server(void) { etag) == 304); } + // Text mime type override + ASSERT(fetch(&mgr, buf, url, "GET /servefile HTTP/1.0\n\n") == 200); + ASSERT(cmpbody(buf, "hello\n") == 0); + { + struct mg_http_message hm; + mg_http_parse(buf, strlen(buf), &hm); + ASSERT(mg_http_get_header(&hm, "Content-Type") != NULL); + ASSERT(mg_strcmp(*mg_http_get_header(&hm, "Content-Type"), mg_str("c/d")) == + 0); + } + ASSERT(fetch(&mgr, buf, url, "GET /foo/1 HTTP/1.0\r\n\n") == 200); // LOG(LL_INFO, ("%d %.*s", (int) hm.len, (int) hm.len, hm.buf)); ASSERT(cmpbody(buf, "uri: 1") == 0); @@ -1336,18 +1357,14 @@ static void test_multipart(void) { } static void test_packed(void) { - const char *path = "Makefile"; - FILE *fp = mg_fopen_packed(path, "r"); -#if MG_ENABLE_PACKED_FS - struct stat st; - ASSERT(stat(path, &st) == 0); - ASSERT(fp != NULL); - fseek(fp, 0, SEEK_END); - ASSERT(ftell(fp) == st.st_size); - fclose(fp); -#else - ASSERT(fp == NULL); -#endif + struct mg_mgr mgr; + const char *url = "http://127.0.0.1:12351"; + char buf[FETCH_BUF_SIZE]; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, url, eh1, NULL); + ASSERT(fetch(&mgr, buf, url, "GET /packed/ HTTP/1.0\n\n") == 404); + mg_mgr_free(&mgr); + ASSERT(mgr.conns == NULL); } int main(void) {