summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2020-09-26 15:51:28 -0400
committerDrew DeVault <sir@cmpwn.com>2020-09-26 15:51:28 -0400
commit6bc9c4deb90e8daa228d792b23a3e61b7bebdb78 (patch)
treee81beb74f44b1809ead547cbb5aad39d6594e65e /src
parent165e3c02fc9c9834b320c3a333c942ee87ffed1b (diff)
downloadgmnisrv-6bc9c4deb90e8daa228d792b23a3e61b7bebdb78.tar.gz
gmnisrv-6bc9c4deb90e8daa228d792b23a3e61b7bebdb78.tar.xz
gmnisrv-6bc9c4deb90e8daa228d792b23a3e61b7bebdb78.zip
Implement autoindex option
Diffstat (limited to 'src')
-rw-r--r--src/config.c20
-rw-r--r--src/serve.c163
-rw-r--r--src/server.c11
3 files changed, 169 insertions, 25 deletions
diff --git a/src/config.c b/src/config.c
index f3172d2..f146aa0 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,8 +1,10 @@
#include <assert.h>
#include <errno.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <strings.h>
#include "config.h"
#include "ini.h"
@@ -146,6 +148,13 @@ conf_ini_handler(void *user, const char *section,
char **value;
} host_strvars[] = {
{ "root", &host->root },
+ { "index", &host->index },
+ };
+ struct {
+ char *name;
+ bool *value;
+ } host_bvars[] = {
+ { "autoindex", &host->autoindex },
};
for (size_t i = 0; i < sizeof(host_strvars) / sizeof(host_strvars[0]); ++i) {
@@ -156,6 +165,17 @@ conf_ini_handler(void *user, const char *section,
return 1;
}
+ for (size_t i = 0; i < sizeof(host_bvars) / sizeof(host_bvars[0]); ++i) {
+ if (strcmp(host_bvars[i].name, name) != 0) {
+ continue;
+ }
+ *host_bvars[i].value =
+ strcasecmp(value, "yes") == 0 ||
+ strcasecmp(value, "true") == 0 ||
+ strcasecmp(value, "on") == 0;
+ return 1;
+ }
+
fprintf(stderr, "Unknown config option [%s]%s\n", section, name);
return 0;
}
diff --git a/src/serve.c b/src/serve.c
index 00d51b5..a856bc2 100644
--- a/src/serve.c
+++ b/src/serve.c
@@ -1,9 +1,12 @@
#include <assert.h>
+#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
#include "config.h"
#include "gemini.h"
#include "log.h"
@@ -13,11 +16,11 @@
void
client_submit_response(struct gmnisrv_client *client,
- enum gemini_status status, const char *meta, int bodyfd)
+ enum gemini_status status, const char *meta, FILE *body)
{
client->status = status;
client->meta = strdup(meta);
- client->bodyfd = bodyfd;
+ client->body = body;
client->pollfd->events = POLLOUT;
}
@@ -26,7 +29,92 @@ client_oom(struct gmnisrv_client *client)
{
const char *error = "Out of memory";
client_submit_response(client,
- GEMINI_STATUS_TEMPORARY_FAILURE, error, -1);
+ GEMINI_STATUS_TEMPORARY_FAILURE, error, NULL);
+}
+
+static int
+namecmp(const void *p1, const void *p2)
+{
+ return strcmp(*(char *const *)p1, *(char *const *)p2);
+}
+
+void
+serve_autoindex(struct gmnisrv_client *client, const char *path)
+{
+ size_t bufsz = 0;
+ size_t nameln = 0, namesz = 1024;
+ char **names = calloc(namesz, sizeof(char *));
+
+ DIR *dirp = opendir(path);
+ if (!dirp) {
+ goto internal_error;
+ }
+
+ bufsz += snprintf(NULL, 0, "# Index of %s\n\n", client->path);
+
+ struct dirent *ent;
+ errno = 0;
+ while ((ent = readdir(dirp)) != NULL) {
+ char fpath[PATH_MAX + 1];
+ strcpy(fpath, path);
+ strncat(fpath, ent->d_name, sizeof(fpath));
+
+ struct stat st;
+ if (stat(fpath, &st) != 0) {
+ goto internal_error;
+ }
+
+ if ((S_ISREG(st.st_mode) || S_ISLNK(st.st_mode) || S_ISDIR(st.st_mode))
+ && ent->d_name[0] != '.') {
+ if (nameln >= namesz) {
+ char **new = realloc(names, namesz * 2);
+ if (!new) {
+ goto internal_error;
+ }
+ namesz *= 2;
+ names = new;
+ }
+ names[nameln++] = strdup(ent->d_name);
+ bufsz += snprintf(NULL, 0, "=> %s\n", ent->d_name);
+ }
+
+ errno = 0;
+ }
+ if (errno != 0) {
+ goto internal_error;
+ }
+
+ qsort(names, nameln, sizeof(names[0]), namecmp);
+
+ FILE *buf = fmemopen(NULL, bufsz, "w+");
+ if (!buf) {
+ goto internal_error;
+ }
+ int r;
+ r = fprintf(buf, "# Index of %s\n\n", client->path);
+ assert(r > 0);
+ for (size_t i = 0; i < nameln; ++i) {
+ r = fprintf(buf, "=> %s\n", names[i]);
+ assert(r > 0);
+ }
+ r = fseek(buf, 0, SEEK_SET);
+ assert(r == 0);
+ client_submit_response(client, GEMINI_STATUS_SUCCESS,
+ "text/gemini", buf);
+
+exit:
+ closedir(dirp);
+ for (size_t i = 0; i < nameln; ++i) {
+ free(names[i]);
+ }
+ free(names);
+ return;
+
+internal_error:
+ server_error("Error reading %s: %s", path, strerror(errno));
+ client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
+ "Internal server error", NULL);
+ goto exit;
}
void
@@ -40,32 +128,69 @@ serve_request(struct gmnisrv_client *client)
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);
+ "Request path exceeds PATH_MAX", NULL);
return;
}
- if (path[strlen(path) - 1] == '/') {
- // TODO: Let user configure index file name?
- strncat(path, "index.gmi", sizeof(path) - 1);
+ int nlinks = 0;
+ struct stat st;
+ while (true) {
+ if ((n = stat(path, &st)) != 0) {
+ client_submit_response(client,
+ GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
+ return;
+ }
+
+ if (S_ISDIR(st.st_mode)) {
+ if (host->autoindex) {
+ serve_autoindex(client, path);
+ return;
+ } else {
+ strncat(path,
+ host->index ? host->index : "index.gmi",
+ sizeof(path) - 1);
+ }
+ } else if (S_ISLNK(st.st_mode)) {
+ ++nlinks;
+ if (nlinks > 3) {
+ server_error("Maximum redirects exceeded for %s",
+ client->path);
+ client_submit_response(client,
+ GEMINI_STATUS_NOT_FOUND,
+ "Not found", NULL);
+ return;
+ }
+ char path2[PATH_MAX + 1];
+ ssize_t s = readlink(path, path2, sizeof(path2));
+ assert(s != -1);
+ strcpy(path, path2);
+ } else if (S_ISREG(st.st_mode)) {
+ break;
+ } else {
+ // Don't serve special files
+ client_submit_response(client,
+ GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
+ return;
+ }
}
- int fd = open(path, O_RDONLY);
- if (fd == -1) {
+ FILE *body = fopen(path, "r");
+ if (!body) {
if (errno == ENOENT) {
- client_submit_response(client, GEMINI_STATUS_NOT_FOUND,
- "Not found", -1);
+ client_submit_response(client,
+ GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
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);
+ "Internal server error", NULL);
return;
}
}
const char *meta = gmnisrv_mimetype_for_path(path);
- client_submit_response(client, GEMINI_STATUS_SUCCESS, meta, fd);
+ client_submit_response(client, GEMINI_STATUS_SUCCESS, meta, body);
}
bool
@@ -79,7 +204,7 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_set(url, CURLUPART_URL, client->buf, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
}
@@ -87,13 +212,13 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_get(url, CURLUPART_SCHEME, &part, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL (expected scheme)";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
} else if (strcmp(part, "gemini") != 0) {
free(part);
const char *error = "Refusing proxy to non-gemini URL";
client_submit_response(client,
- GEMINI_STATUS_PROXY_REQUEST_REFUSED, error, -1);
+ GEMINI_STATUS_PROXY_REQUEST_REFUSED, error, NULL);
goto exit;
}
free(part);
@@ -101,13 +226,13 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_get(url, CURLUPART_HOST, &part, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL (expected host)";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
} else if (strcmp(part, client->host->hostname) != 0) {
free(part);
const char *error = "Protocol error: hostname does not match SNI";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
}
free(part);
@@ -115,7 +240,7 @@ request_validate(struct gmnisrv_client *client, char **path)
if (curl_url_get(url, CURLUPART_PATH, &part, 0) != CURLUE_OK) {
const char *error = "Protocol error: invalid URL (expected path)";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
goto exit;
}
// NOTE: curl_url_set(..., CURLUPART_URL, ..., 0) will consoldate .. and
diff --git a/src/server.c b/src/server.c
index e69e381..c818fbd 100644
--- a/src/server.c
+++ b/src/server.c
@@ -150,7 +150,6 @@ accept_client(struct gmnisrv_server *server, int fd)
client->pollfd = pollfd;
client->addrlen = addrlen;
client->server = server;
- client->bodyfd = -1;
clock_gettime(CLOCK_MONOTONIC, &client->ctime);
memcpy(&client->addr, &addr, sizeof(addr));
@@ -240,7 +239,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
if (!client->host) {
const char *error = "This server requires clients to support the TLS SNI (server name identification) extension";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
return;
}
@@ -261,7 +260,7 @@ client_readable(struct gmnisrv_server *server, struct gmnisrv_client *client)
if (!newline) {
const char *error = "Protocol error: malformed request";
client_submit_response(client,
- GEMINI_STATUS_BAD_REQUEST, error, -1);
+ GEMINI_STATUS_BAD_REQUEST, error, NULL);
return;
}
*newline = 0;
@@ -304,7 +303,7 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
}
client->bufix += r;
if (client->bufix >= client->bufln) {
- if (client->bodyfd == -1) {
+ if (!client->body) {
disconnect_client(server, client);
} else {
client->state = RESPOND_BODY;
@@ -315,8 +314,8 @@ client_writable(struct gmnisrv_server *server, struct gmnisrv_client *client)
break;
case RESPOND_BODY:
if (client->bufix >= client->bufln) {
- n = read(client->bodyfd,
- client->buf, sizeof(client->buf));
+ n = fread(client->buf, 1,
+ sizeof(client->buf), client->body);
if (n == -1) {
client_error(&client->addr,
"Error reading response body: %s",