aboutsummaryrefslogtreecommitdiff
path: root/handler.c
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-01-09 01:22:54 +0100
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-07-20 23:52:47 +0200
commit2968c5f67daa1c571f5f9cf9445de907f9490636 (patch)
treed6919b2446976f4818c62ad419065e3d92061e25 /handler.c
Initial commit
Diffstat (limited to 'handler.c')
-rw-r--r--handler.c336
1 files changed, 336 insertions, 0 deletions
diff --git a/handler.c b/handler.c
new file mode 100644
index 0000000..b152cf1
--- /dev/null
+++ b/handler.c
@@ -0,0 +1,336 @@
+#include "handler.h"
+#include "http.h"
+#include "server.h"
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+struct handler
+{
+ const char *tmpdir;
+
+ struct handler_cfg
+ {
+ char *url;
+ enum http_op op;
+ handler_fn f;
+ void *user;
+ } *cfg;
+
+ struct server *server;
+ struct client
+ {
+ struct handler *h;
+ struct server_client *c;
+ struct http_ctx *http;
+ struct client *next;
+ } *clients;
+
+ size_t n_cfg;
+};
+
+static int on_read(void *const buf, const size_t n, void *const user)
+{
+ struct client *const c = user;
+
+ return server_read(buf, n, c->c);
+}
+
+static int on_write(const void *const buf, const size_t n, void *const user)
+{
+ struct client *const c = user;
+
+ return server_write(buf, n, c->c);
+}
+
+static int wildcard_cmp(const char *s, const char *p)
+{
+ while (*p && *s)
+ {
+ const char *const wc = strchr(p, '*');
+
+ if (!wc)
+ return strcmp(s, p);
+
+ const size_t n = wc - p;
+
+ if (n)
+ {
+ const int r = strncmp(s, p, n);
+
+ if (r)
+ return r;
+
+ p += n;
+ s += n;
+ }
+ else if (*(wc + 1) == *s)
+ {
+ p = wc + 1;
+ s += n;
+ }
+ else if (*(wc + 1) == '*')
+ p++;
+ else
+ {
+ s++;
+ p += n;
+ }
+ }
+
+ while (*p)
+ if (*p++ != '*')
+ return -1;
+
+ return 0;
+}
+
+static int on_payload(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ struct client *const c = user;
+ struct handler *const h = c->h;
+
+ for (size_t i = 0; i < h->n_cfg; i++)
+ {
+ const struct handler_cfg *const cfg = &h->cfg[i];
+
+ if (cfg->op == p->op && !wildcard_cmp(p->resource, cfg->url))
+ return cfg->f(p, r, cfg->user);
+ }
+
+ fprintf(stderr, "Not found: %s\n", p->resource);
+
+ *r = (const struct http_response)
+ {
+ .status = HTTP_STATUS_NOT_FOUND
+ };
+
+ return 0;
+}
+
+static struct client *find_or_alloc_client(struct handler *const h,
+ struct server_client *const c)
+{
+ for (struct client *cl = h->clients; cl; cl = cl->next)
+ {
+ if (cl->c == c)
+ return cl;
+ }
+
+ struct client *const ret = malloc(sizeof *ret);
+
+ if (!ret)
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return NULL;
+ }
+
+ const struct http_cfg cfg =
+ {
+ .read = on_read,
+ .write = on_write,
+ .payload = on_payload,
+ .user = ret,
+ .tmpdir = h->tmpdir
+ };
+
+ *ret = (const struct client)
+ {
+ .c = c,
+ .h = h,
+ .http = http_alloc(&cfg)
+ };
+
+ if (!ret->http)
+ {
+ fprintf(stderr, "%s: http_alloc failed\n", __func__);
+ return NULL;
+ }
+
+ if (!h->clients)
+ h->clients = ret;
+ else
+ {
+ for (struct client *c = h->clients; c; c = c->next)
+ if (!c->next)
+ {
+ c->next = ret;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static void client_free(struct client *const c)
+{
+ if (c)
+ http_free(c->http);
+
+ free(c);
+}
+
+static int remove_client_from_list(struct handler *const h,
+ struct client *const c)
+{
+ int ret = -1;
+
+ if (server_client_close(h->server, c->c))
+ {
+ fprintf(stderr, "%s: server_client_close failed\n",
+ __func__);
+ goto end;
+ }
+
+ for (struct client *cl = h->clients, *prev = NULL; cl;
+ prev = cl, cl = cl->next)
+ {
+ if (cl == c)
+ {
+ if (!prev)
+ h->clients = c->next;
+ else
+ prev->next = cl->next;
+
+ break;
+ }
+ }
+
+ ret = 0;
+
+end:
+ client_free(c);
+ return ret;
+}
+
+int handler_listen(struct handler *const h, const short port)
+{
+ if (!(h->server = server_init(port)))
+ {
+ fprintf(stderr, "%s: server_init failed\n", __func__);
+ return -1;
+ }
+
+ for (;;)
+ {
+ bool exit, io;
+ struct server_client *const c = server_select(h->server, &io, &exit);
+
+ if (exit)
+ {
+ printf("Exiting...\n");
+ break;
+ }
+ else if (!c)
+ {
+ fprintf(stderr, "%s: server_select failed\n", __func__);
+ return -1;
+ }
+
+ struct client *const cl = find_or_alloc_client(h, c);
+
+ if (!cl)
+ {
+ fprintf(stderr, "%s: find_or_alloc_client failed\n", __func__);
+ return -1;
+ }
+ else if (io)
+ {
+ bool write, close;
+ const int res = http_update(cl->http, &write, &close);
+
+ if (res || close)
+ {
+ if (res < 0)
+ {
+ fprintf(stderr, "%s: http_update failed\n", __func__);
+ return -1;
+ }
+ else if (remove_client_from_list(h, cl))
+ {
+ fprintf(stderr, "%s: remove_client_from_list failed\n",
+ __func__);
+ return -1;
+ }
+ }
+ else
+ server_client_write_pending(cl->c, write);
+ }
+ }
+
+ return 0;
+}
+
+static void free_clients(struct handler *const h)
+{
+ for (struct client *c = h->clients; c;)
+ {
+ struct client *const next = c->next;
+
+ server_client_close(h->server, c->c);
+ client_free(c);
+ c = next;
+ }
+}
+
+void handler_free(struct handler *const h)
+{
+ if (h)
+ {
+ for (size_t i = 0; i < h->n_cfg; i++)
+ free(h->cfg[i].url);
+
+ free(h->cfg);
+ free_clients(h);
+ server_close(h->server);
+ }
+
+ free(h);
+}
+
+struct handler *handler_alloc(const char *const tmpdir)
+{
+ struct handler *const h = malloc(sizeof *h);
+
+ if (!h)
+ {
+ fprintf(stderr, "%s: malloc(3) handler: %s\n",
+ __func__, strerror(errno));
+ return NULL;
+ }
+
+ *h = (const struct handler){.tmpdir = tmpdir};
+ return h;
+}
+
+int handler_add(struct handler *const h, const char *url,
+ const enum http_op op, const handler_fn f, void *const user)
+{
+ if (!h || !(h->cfg = realloc(h->cfg, (h->n_cfg + 1) * sizeof *h->cfg)))
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ char *const new = strdup(url);
+
+ if (!new)
+ {
+ fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
+ return -1;
+ }
+
+ h->cfg[h->n_cfg++] = (const struct handler_cfg)
+ {
+ .url = new,
+ .op = op,
+ .f = f,
+ .user = user
+ };
+
+ return 0;
+}