From 37095b599613c64825b33025b72251aaa97cf466 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Mon, 16 Mar 2020 20:41:55 +0200 Subject: Initial commit --- GNUmakefile | 34 ++++++++ libphysfs-serve.c | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ libphysfs-serve.h | 38 +++++++++ physfs-serve.c | 127 +++++++++++++++++++++++++++++ test1.zip | Bin 0 -> 215 bytes test2.zip | Bin 0 -> 367 bytes 6 files changed, 436 insertions(+) create mode 100644 GNUmakefile create mode 100644 libphysfs-serve.c create mode 100644 libphysfs-serve.h create mode 100644 physfs-serve.c create mode 100644 test1.zip create mode 100644 test2.zip diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..4e6007a --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,34 @@ +PREFIX ?= /usr/local +includedir ?= /include +bindir ?= /bin +mandir ?= /share/man/man1 +docdir ?= /share/doc + +MAKEFLAGS += --no-builtin-rules + +WARNINGS = -Wall -Wextra -Wpedantic -Wformat=2 -Wstrict-aliasing=3 -Wstrict-overflow=5 -Wstack-usage=12500 \ + -Wfloat-equal -Wcast-align -Wpointer-arith -Wchar-subscripts -Warray-bounds=2 -Wno-unknown-warning-option + +override CFLAGS ?= -g -O2 $(WANRINGS) +override CFLAGS += -std=c99 +override CPPFLAGS ?= -D_FORTIFY_SOURCE=2 +override CPPFLAGS += -D_DEFAULT_SOURCE + +all: physfs-serve + +%.a: %.c + $(LINK.c) -c $(filter %.c,$^) $(LDLIBS) -o $@ + +libphysfs-serve.a: private override CPPFLAGS += $(shell pkg-config --cflags-only-I physfs) +libphysfs-serve.a: libphysfs-serve.h + +physfs-serve: private override LDLIBS += $(shell pkg-config --libs physfs) +physfs-serve: libphysfs-serve.a physfs-serve.c + $(LINK.c) $(filter %.c %.a,$^) $(LDLIBS) -o $@ + +install: all + install -Dm 755 physfs-serve $(DESTDIR)$(PREFIX)$(bindir)/physfs-serve + install -Dm 644 libphysfs-serve.a $(DESTDIR)$(PREFIX)$(libdir)/libphysfs-serve.a + +clean: + $(RM) physfs-serve libphysfs-serve.a diff --git a/libphysfs-serve.c b/libphysfs-serve.c new file mode 100644 index 0000000..eed3429 --- /dev/null +++ b/libphysfs-serve.c @@ -0,0 +1,237 @@ +#include "libphysfs-serve.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void ignore_ret(bool b, ...) {}; +#define ignore_ret(x) ignore_ret(true, x); + +static void +response_501(struct physfs_serve *serve, const int fd) { + const char buf[] = "HTTP/1.1 501 Not Implemented\r\n" \ + "Server: libphysfs-serve\r\n" \ + "Connection: close\r\n"; + ignore_ret(write(fd, buf, sizeof(buf) - 1)); +} + +static void +response_404(struct physfs_serve *serve, const int fd) { + const char buf[] = "HTTP/1.1 404 Not Found\r\n" \ + "Server: libphysfs-serve\r\n" \ + "Connection: close\r\n"; + ignore_ret(write(fd, buf, sizeof(buf) - 1)); +} + +static bool +get(struct physfs_serve *serve, const int fd, const char *path) { + struct PHYSFS_Stat st = {0}; + const int exists = PHYSFS_stat(path, &st); + st.filetype = (exists ? st.filetype : -1); + + char fullpath[1024]; + snprintf(fullpath, sizeof(fullpath), "%s", path); + if (serve->path_rewrite(serve, fullpath, sizeof(fullpath), &st)) { + if (!PHYSFS_stat(fullpath, &st)) { + response_404(serve, fd); + return false; + } + } + + PHYSFS_File *f; + if (!(f = PHYSFS_openRead(fullpath))) { + response_404(serve, fd); + return false; + } + + char buf[1024]; + snprintf(buf, sizeof(buf), + "HTTP/1.1 200 OK\r\n" \ + "Server: libphysfs-serve\r\n" \ + "Content-Type: %s\r\n" \ + "Content-Length: %zu\r\n\r\n", serve->content_type(serve, fullpath), (size_t)st.filesize); + ignore_ret(write(fd, buf, strlen(buf))); + for (int ret = 0; (ret = PHYSFS_readBytes(f, buf, sizeof(buf))) > 0;) + ignore_ret(write(fd, buf, ret)); + + PHYSFS_close(f); + return true; +} + +static char* +strchr_safe(const char *s, int c) { + char *ret = strchr(s, c); + return (ret ? ret : (char*)s + strlen(s)); +} + +static bool +urldecode(const char *in, char *out) { + static const char tbl[256] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,10,11,12,13,14,15,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1 + }; + for (char c; (c = *in++); *out++ = c) { + if (c != '%') continue; + char v1, v2; + if ((v1 = tbl[(uint8_t)*in++]) < 0 || (v2 = tbl[(uint8_t)*in++]) < 0) + return false; + c = (v1 << 4) | v2; + } + *out = 0; + return true; +} + +static bool +handle_request(struct physfs_serve *serve, const int fd) { + char buf[1024]; + size_t len; + if (!(len = read(fd, buf, sizeof(buf) - 1))) + return false; + + buf[len] = 0; + const char method[] = "GET"; + if (strncmp(method, buf, sizeof(method) - 1)) { + response_501(serve, fd); + return false; + } else { + const char *path = buf + sizeof(method); + *strchr_safe(path, ' ') = 0; + char decoded[1024]; + assert(strlen(path) < sizeof(decoded)); + urldecode(path, decoded); + *strchr_safe(decoded, '?') = 0; + get(serve, fd, decoded); + } + return true; +} + +union sockaddr_u { + struct sockaddr out; + struct sockaddr_in in; +}; + +bool +physfs_serve_init(struct physfs_serve *serve, const unsigned int port, const char *addr) { + int listen_fd, conn_fd; + if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { + warn("socket"); + goto fail; + } + + union sockaddr_u server = {0}; + inet_pton(AF_INET, addr, &server.in); + server.in.sin_port = htons(port); + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, (int[]){1}, sizeof(int)); + + if (bind(listen_fd, &server.out, sizeof(server.out)) == -1) { + warn("bind"); + goto fail2; + } + + if (listen(listen_fd, 0) == -1) { + warn("listen"); + goto fail2; + } + + if (!serve->content_type) serve->content_type = physfs_default_content_type; + if (!serve->path_rewrite) serve->path_rewrite = physfs_default_path_rewrite; + memset(serve->fd, -1, sizeof(serve->fd)); + serve->fd[serve->nfds++] = listen_fd; + return true; + +fail2: + close(listen_fd); +fail: + PHYSFS_deinit(); + return false; +} + +bool +physfs_serve_event(struct physfs_serve *serve, const int index) { + if (index == 0) { + if (serve->nfds >= ARRAY_SIZE(serve->fd)) { + warnx("max connections reached"); + return true; + } + union sockaddr_u client = {0}; + if ((serve->fd[serve->nfds++] = accept(serve->fd[index], &client.out, (socklen_t[]){sizeof(client.out)})) == -1) { + warn("accept"); + return false; + } + } else { + assert(serve->nfds > 1); + if (!handle_request(serve, serve->fd[index])) { + close(serve->fd[index]); + serve->fd[index] = serve->fd[--serve->nfds]; + serve->fd[serve->nfds] = -1; + } + } + return true; +} + +void +physfs_serve_free(struct physfs_serve *serve) { + if (!serve) return; + for (int i = 0; i < ARRAY_SIZE(serve->fd); ++i) + if (serve->fd[i] != -1) close(serve->fd[i]); + *serve = (struct physfs_serve){0}; + memset(serve->fd, -1, sizeof(serve->fd)); +} + +const char* +physfs_default_content_type(struct physfs_serve *serve, const char *path) { + if (physfs_ends_with(path, ".html")) return "text/html"; + else if (physfs_ends_with(path, ".js")) return "application/javascript"; + else if (physfs_ends_with(path, ".json")) return "application/json"; + else if (physfs_ends_with(path, ".css")) return "text/css"; + else if (physfs_ends_with(path, ".ttf")) return "font/ttf"; + else if (physfs_ends_with(path, ".ico")) return "image/x-icon"; + else if (physfs_ends_with(path, ".png")) return "image/png"; + else if (physfs_ends_with(path, ".jpg")) return "image/jpg"; + return ""; // let browser do its thing +} + +bool +physfs_default_path_rewrite(struct physfs_serve *serve, char buf[], const size_t bufsz, struct PHYSFS_Stat *st) { + if (st->filetype == PHYSFS_FILETYPE_DIRECTORY) { + physfs_append(buf, bufsz, "/index.html"); + return true; + } + return false; +} + +bool +physfs_ends_with(const char *str, const char *suf) { + const size_t strn = strlen(str), sufn = strlen(suf); + if (strn < sufn) return false; + return !strcmp(str + strn - sufn, suf); +} + +void +physfs_append(char buf[], const size_t bufsz, const char *str) { + const size_t len = strlen(buf), left = bufsz - len, want = strlen(str); + const size_t need = (left > want ? want : left); + memcpy(buf + len, str, need); + buf[len + need] = 0; +} diff --git a/libphysfs-serve.h b/libphysfs-serve.h new file mode 100644 index 0000000..bd9a2ae --- /dev/null +++ b/libphysfs-serve.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#endif + +struct PHYSFS_Stat; + +struct physfs_serve { + int fd[16], nfds; + const char* (*content_type)(struct physfs_serve *serve, const char *path); + bool (*path_rewrite)(struct physfs_serve *serve, char buf[], const size_t bufsz, struct PHYSFS_Stat *st); +}; + +bool +physfs_serve_init(struct physfs_serve *serve, const unsigned int port, const char *addr); + +bool +physfs_serve_event(struct physfs_serve *serve, const int index); + +void +physfs_serve_free(struct physfs_serve *serve); + +const char* +physfs_default_content_type(struct physfs_serve *serve, const char *path); + +bool +physfs_default_path_rewrite(struct physfs_serve *serve, char buf[], const size_t bufsz, struct PHYSFS_Stat *st); + +bool +physfs_ends_with(const char *str, const char *suf); + +inline void +physfs_append(char buf[], const size_t bufsz, const char *str); + diff --git a/physfs-serve.c b/physfs-serve.c new file mode 100644 index 0000000..55e4dce --- /dev/null +++ b/physfs-serve.c @@ -0,0 +1,127 @@ +#include "libphysfs-serve.h" +#include +#include +#include +#include +#include +#include + +#ifndef SERVE_INIT +# define SERVE_INIT 0 +#endif + +static char* +strrchr_safe(const char *s, int c) { + char *ret = strrchr(s, c); + return (ret ? ret : (char*)s + strlen(s)); +} + +static void +dirname(char *path) { + char *ret; + if (!(ret = strrchr(path, '/'))) { + *path = 0; + return; + } + *ret = 0; +} + +const bool +find_file(const char *root, const char *file, char res_buf[], size_t res_buf_sz) { + char **rc; + if (!(rc = PHYSFS_enumerateFiles(root))) + return false; + + for (char **i = rc; *i != NULL; ++i) { + char fullpath[1024]; + snprintf(fullpath, sizeof(fullpath), "%s/%s", root, *i); + struct PHYSFS_Stat st = {0}; + PHYSFS_stat(fullpath, &st); + if (st.filetype == PHYSFS_FILETYPE_DIRECTORY) { + if (find_file(fullpath, file, res_buf, res_buf_sz)) { + PHYSFS_freeList(rc); + return true; + } + } else { + if (strcmp(file, *i)) continue; + snprintf(res_buf, res_buf_sz, "%s", fullpath); + PHYSFS_freeList(rc); + return true; + } + } + + PHYSFS_freeList(rc); + return false; +} + +struct args { + const char *path[32]; + unsigned int npaths; + unsigned int port; + const char *addr; +}; + +bool +parse_arg(char *argv[], int argc, struct args *args) { + if (*argv[0] != '-') { + args->path[args->npaths++] = argv[0]; + } else { + const char *next = (argc > 1 ? argv[1] : ""); + for (const char *s = argv[0] + 1; *s; ++s) { + switch (*s) { + case 'i': args->addr = next; return true; + case 'p': args->port = strtoul(next, NULL, 10); return true; + } + } + } + return false; +} + +int +main(int argc, char *argv[]) { + if (argc < 2) + errx(EXIT_FAILURE, "usage: [-i addr] [-p port] archive ..."); + + struct args args = { .addr = "0.0.0.0", .port = 1337 }; + for (int i = 1; i < argc; ++i) + i += parse_arg(&argv[i], argc - i, &args); + + if (!PHYSFS_init(argv[0])) + errx(EXIT_FAILURE, "PHYSFS_init failed"); + + for (unsigned int i = 0; i < args.npaths; ++i) { + if (!PHYSFS_mount(args.path[i], NULL, 0)) + errx(EXIT_FAILURE, "failed to mount archive: %s", args.path[i]); + + if (i == 0) { + char root[1024]; + if (!find_file("", "index.html", root, sizeof(root))) + errx(EXIT_FAILURE, "no index.html found in the root archive"); + + dirname(root); + PHYSFS_setRoot(args.path[i], root); + } + } + + while (1) { + struct physfs_serve serve = { SERVE_INIT }; + if (!physfs_serve_init(&serve, args.port, args.addr)) + errx(EXIT_FAILURE, "couldn't listen"); + + while (1) { + struct pollfd pfd[ARRAY_SIZE(serve.fd)]; + for (int i = 0; i < ARRAY_SIZE(serve.fd); ++i) + pfd[i] = (struct pollfd){ .fd = serve.fd[i], .events = POLLIN }; + + poll(pfd, serve.nfds, -1); + for (int i = 0; i < serve.nfds; ++i) + if (pfd[i].revents & POLLIN && !physfs_serve_event(&serve, i)) + break; + } + + physfs_serve_free(&serve); + } + + PHYSFS_deinit(); + return EXIT_SUCCESS; +} diff --git a/test1.zip b/test1.zip new file mode 100644 index 0000000..39358bc Binary files /dev/null and b/test1.zip differ diff --git a/test2.zip b/test2.zip new file mode 100644 index 0000000..58f0fe6 Binary files /dev/null and b/test2.zip differ -- cgit v1.2.3