summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile34
-rw-r--r--libphysfs-serve.c237
-rw-r--r--libphysfs-serve.h38
-rw-r--r--physfs-serve.c127
-rw-r--r--test1.zipbin0 -> 215 bytes
-rw-r--r--test2.zipbin0 -> 367 bytes
6 files changed, 436 insertions, 0 deletions
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 <physfs.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+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 <stdbool.h>
+#include <stddef.h>
+
+#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 <physfs.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <poll.h>
+#include <err.h>
+
+#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
--- /dev/null
+++ b/test1.zip
Binary files differ
diff --git a/test2.zip b/test2.zip
new file mode 100644
index 0000000..58f0fe6
--- /dev/null
+++ b/test2.zip
Binary files differ