diff options
Diffstat (limited to 'libphysfs-serve.c')
-rw-r--r-- | libphysfs-serve.c | 237 |
1 files changed, 237 insertions, 0 deletions
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; +} |