From dfab99ace5f1193ca6df0ed7b704ea450a2fae95 Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sat, 26 Sep 2020 14:36:52 -0400 Subject: [PATCH] Serve files from root --- configure | 1 + include/mime.h | 6 ++++ src/mime.c | 30 ++++++++++++++++ src/server.c | 93 ++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 include/mime.h create mode 100644 src/mime.c diff --git a/configure b/configure index 5a5b7a7..16834c0 100755 --- a/configure +++ b/configure @@ -9,6 +9,7 @@ gmnisrv() { src/ini.c \ src/log.c \ src/main.c \ + src/mime.c \ src/server.c \ src/tls.c \ src/url.c \ diff --git a/include/mime.h b/include/mime.h new file mode 100644 index 0000000..33d36cb --- /dev/null +++ b/include/mime.h @@ -0,0 +1,6 @@ +#ifndef GMNISRV_MIME +#define GMNISRV_MIME + +const char *gmnisrv_mimetype_for_path(const char *path); + +#endif diff --git a/src/mime.c b/src/mime.c new file mode 100644 index 0000000..5722357 --- /dev/null +++ b/src/mime.c @@ -0,0 +1,30 @@ +#include +#include +#include "mime.h" + +static bool +has_suffix(const char *str, const char *suff) +{ + size_t a = strlen(str), b = strlen(suff); + if (a < b) { + return false; + } + return strncmp(&str[a - b], suff, b) == 0; +} + +const char * +gmnisrv_mimetype_for_path(const char *path) +{ + // TODO: Read /etc/mime.types + // TODO: Consider adding content-disposition fields like filename + if (has_suffix(path, ".gmi") || has_suffix(path, ".gemini")) { + return "text/gemini"; + } + if (has_suffix(path, ".txt")) { + return "text/plain"; + } + if (has_suffix(path, ".png")) { + return "image/png"; + } + return "application/octet-stream"; +} diff --git a/src/server.c b/src/server.c index f163e73..7e28280 100644 --- a/src/server.c +++ b/src/server.c @@ -13,6 +13,7 @@ #include "config.h" #include "gemini.h" #include "log.h" +#include "mime.h" #include "server.h" #include "tls.h" #include "url.h" @@ -307,6 +308,45 @@ exit: return true; } +static void +serve_request(struct gmnisrv_client *client) +{ + struct gmnisrv_host *host = client->host; + assert(host); + assert(host->root); // TODO: reverse proxy support + + char path[PATH_MAX + 1]; + int n = snprintf(path, sizeof(path), "%s%s", host->root, client->path); + if ((size_t)n >= sizeof(path)) { + client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE, + "Request path exceeds PATH_MAX", -1); + return; + } + + if (path[strlen(path) - 1] == '/') { + // TODO: Let user configure index file name? + strncat(path, "index.gmi", sizeof(path) - 1); + } + + int fd = open(path, O_RDONLY); + if (fd == -1) { + if (errno == ENOENT) { + client_submit_response(client, GEMINI_STATUS_NOT_FOUND, + "Not found", -1); + return; + } else { + client_error(&client->addr, "error opening %s: %s", + path, strerror(errno)); + client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE, + "Internal server error", -1); + return; + } + } + + const char *meta = gmnisrv_mimetype_for_path(path); + client_submit_response(client, GEMINI_STATUS_SUCCESS, meta, fd); +} + static void client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client) { @@ -347,10 +387,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client) return; } - // TODO: prep response - const char *error = "TODO: Finish implementation"; - client_submit_response(client, - GEMINI_STATUS_TEMPORARY_FAILURE, error, -1); + serve_request(client); } static void @@ -376,24 +413,58 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client) return; } client->status = GEMINI_STATUS_NONE; - client_error(&client->addr, "SSL read error %s, disconnecting", - ERR_error_string(r, NULL)); + client_error(&client->addr, + "header write error %s, disconnecting", + ERR_error_string(r, NULL)); disconnect_client(server, client); return; } client->bufix += r; if (client->bufix >= client->bufln) { - // TODO: Start sending response body as well - disconnect_client(server, client); + if (client->bodyfd == -1) { + disconnect_client(server, client); + } else { + client->state = RESPOND_BODY; + client->bufix = client->bufln = 0; + } return; } break; case RESPOND_BODY: - assert(0); + if (client->bufix >= client->bufln) { + n = read(client->bodyfd, + client->buf, sizeof(client->buf)); + if (n == -1) { + client_error(&client->addr, + "Error reading response body: %s", + strerror(errno)); + disconnect_client(server, client); + return; + } + if (n == 0) { + // EOF + disconnect_client(server, client); + return; + } + client->bufln = n; + client->bufix = 0; + } + r = BIO_write(client->sbio, &client->buf[client->bufix], + client->bufln - client->bufix); + if (r <= 0) { + r = SSL_get_error(client->ssl, r); + if (r == SSL_ERROR_WANT_WRITE) { + return; + } + client->status = GEMINI_STATUS_NONE; + client_error(&client->addr, "body write error %s, disconnecting", + ERR_error_string(r, NULL)); + disconnect_client(server, client); + return; + } + client->bufix += r; break; } - - (void)server; } static long