mirror of
https://git.sr.ht/~sircmpwn/gmnisrv
synced 2024-11-03 06:07:17 -05:00
Implement autoindex option
This commit is contained in:
parent
165e3c02fc
commit
6bc9c4deb9
@ -2,6 +2,7 @@
|
||||
#define GMNISRV_CONFIG
|
||||
#include <arpa/inet.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct gmnisrv_tls {
|
||||
char *store;
|
||||
@ -13,8 +14,12 @@ struct gmnisrv_tls {
|
||||
struct gmnisrv_host {
|
||||
char *hostname;
|
||||
char *root;
|
||||
char *index;
|
||||
bool autoindex;
|
||||
|
||||
X509 *x509;
|
||||
EVP_PKEY *pkey;
|
||||
|
||||
struct gmnisrv_host *next;
|
||||
};
|
||||
|
||||
|
@ -33,7 +33,7 @@ struct gmnisrv_client {
|
||||
enum response_state state;
|
||||
enum gemini_status status;
|
||||
char *meta;
|
||||
int bodyfd;
|
||||
FILE *body;
|
||||
size_t bbytes;
|
||||
|
||||
struct gmnisrv_host *host;
|
||||
@ -70,7 +70,7 @@ void disconnect_client(struct gmnisrv_server *server,
|
||||
void serve_request(struct gmnisrv_client *client);
|
||||
bool request_validate(struct gmnisrv_client *client, char **path);
|
||||
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);
|
||||
void client_oom(struct gmnisrv_client *client);
|
||||
|
||||
#endif
|
||||
|
20
src/config.c
20
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;
|
||||
}
|
||||
|
163
src/serve.c
163
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
|
||||
|
11
src/server.c
11
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",
|
||||
|
Loading…
Reference in New Issue
Block a user