summaryrefslogtreecommitdiffstats
path: root/src/libserver/http/http_router.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
commit133a45c109da5310add55824db21af5239951f93 (patch)
treeba6ac4c0a950a0dda56451944315d66409923918 /src/libserver/http/http_router.c
parentInitial commit. (diff)
downloadrspamd-133a45c109da5310add55824db21af5239951f93.tar.xz
rspamd-133a45c109da5310add55824db21af5239951f93.zip
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libserver/http/http_router.c')
-rw-r--r--src/libserver/http/http_router.c559
1 files changed, 559 insertions, 0 deletions
diff --git a/src/libserver/http/http_router.c b/src/libserver/http/http_router.c
new file mode 100644
index 0000000..2fdfe48
--- /dev/null
+++ b/src/libserver/http/http_router.c
@@ -0,0 +1,559 @@
+/*-
+ * Copyright 2019 Vsevolod Stakhov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "http_router.h"
+#include "http_connection.h"
+#include "http_private.h"
+#include "libutil/regexp.h"
+#include "libutil/printf.h"
+#include "libserver/logger.h"
+#include "utlist.h"
+#include "unix-std.h"
+
+enum http_magic_type {
+ HTTP_MAGIC_PLAIN = 0,
+ HTTP_MAGIC_HTML,
+ HTTP_MAGIC_CSS,
+ HTTP_MAGIC_JS,
+ HTTP_MAGIC_ICO,
+ HTTP_MAGIC_PNG,
+ HTTP_MAGIC_JPG
+};
+
+static const struct _rspamd_http_magic {
+ const gchar *ext;
+ const gchar *ct;
+} http_file_types[] = {
+ [HTTP_MAGIC_PLAIN] = {"txt", "text/plain"},
+ [HTTP_MAGIC_HTML] = {"html", "text/html"},
+ [HTTP_MAGIC_CSS] = {"css", "text/css"},
+ [HTTP_MAGIC_JS] = {"js", "application/javascript"},
+ [HTTP_MAGIC_ICO] = {"ico", "image/x-icon"},
+ [HTTP_MAGIC_PNG] = {"png", "image/png"},
+ [HTTP_MAGIC_JPG] = {"jpg", "image/jpeg"},
+};
+
+/*
+ * HTTP router functions
+ */
+
+static void
+rspamd_http_entry_free(struct rspamd_http_connection_entry *entry)
+{
+ if (entry != NULL) {
+ close(entry->conn->fd);
+ rspamd_http_connection_unref(entry->conn);
+ if (entry->rt->finish_handler) {
+ entry->rt->finish_handler(entry);
+ }
+
+ DL_DELETE(entry->rt->conns, entry);
+ g_free(entry);
+ }
+}
+
+static void
+rspamd_http_router_error_handler(struct rspamd_http_connection *conn,
+ GError *err)
+{
+ struct rspamd_http_connection_entry *entry = conn->ud;
+ struct rspamd_http_message *msg;
+
+ if (entry->is_reply) {
+ /* At this point we need to finish this session and close owned socket */
+ if (entry->rt->error_handler != NULL) {
+ entry->rt->error_handler(entry, err);
+ }
+ rspamd_http_entry_free(entry);
+ }
+ else {
+ /* Here we can write a reply to a client */
+ if (entry->rt->error_handler != NULL) {
+ entry->rt->error_handler(entry, err);
+ }
+ msg = rspamd_http_new_message(HTTP_RESPONSE);
+ msg->date = time(NULL);
+ msg->code = err->code;
+ rspamd_http_message_set_body(msg, err->message, strlen(err->message));
+ rspamd_http_connection_reset(entry->conn);
+ rspamd_http_connection_write_message(entry->conn,
+ msg,
+ NULL,
+ "text/plain",
+ entry,
+ entry->rt->timeout);
+ entry->is_reply = TRUE;
+ }
+}
+
+static const gchar *
+rspamd_http_router_detect_ct(const gchar *path)
+{
+ const gchar *dot;
+ guint i;
+
+ dot = strrchr(path, '.');
+ if (dot == NULL) {
+ return http_file_types[HTTP_MAGIC_PLAIN].ct;
+ }
+ dot++;
+
+ for (i = 0; i < G_N_ELEMENTS(http_file_types); i++) {
+ if (strcmp(http_file_types[i].ext, dot) == 0) {
+ return http_file_types[i].ct;
+ }
+ }
+
+ return http_file_types[HTTP_MAGIC_PLAIN].ct;
+}
+
+static gboolean
+rspamd_http_router_is_subdir(const gchar *parent, const gchar *sub)
+{
+ if (parent == NULL || sub == NULL || *parent == '\0') {
+ return FALSE;
+ }
+
+ while (*parent != '\0') {
+ if (*sub != *parent) {
+ return FALSE;
+ }
+ parent++;
+ sub++;
+ }
+
+ parent--;
+ if (*parent == G_DIR_SEPARATOR) {
+ return TRUE;
+ }
+
+ return (*sub == G_DIR_SEPARATOR || *sub == '\0');
+}
+
+static gboolean
+rspamd_http_router_try_file(struct rspamd_http_connection_entry *entry,
+ rspamd_ftok_t *lookup, gboolean expand_path)
+{
+ struct stat st;
+ gint fd;
+ gchar filebuf[PATH_MAX], realbuf[PATH_MAX], *dir;
+ struct rspamd_http_message *reply_msg;
+
+ rspamd_snprintf(filebuf, sizeof(filebuf), "%s%c%T",
+ entry->rt->default_fs_path, G_DIR_SEPARATOR, lookup);
+
+ if (realpath(filebuf, realbuf) == NULL ||
+ lstat(realbuf, &st) == -1) {
+ return FALSE;
+ }
+
+ if (S_ISDIR(st.st_mode) && expand_path) {
+ /* Try to append 'index.html' to the url */
+ rspamd_fstring_t *nlookup;
+ rspamd_ftok_t tok;
+ gboolean ret;
+
+ nlookup = rspamd_fstring_sized_new(lookup->len + sizeof("index.html"));
+ rspamd_printf_fstring(&nlookup, "%T%c%s", lookup, G_DIR_SEPARATOR,
+ "index.html");
+ tok.begin = nlookup->str;
+ tok.len = nlookup->len;
+ ret = rspamd_http_router_try_file(entry, &tok, FALSE);
+ rspamd_fstring_free(nlookup);
+
+ return ret;
+ }
+ else if (!S_ISREG(st.st_mode)) {
+ return FALSE;
+ }
+
+ /* We also need to ensure that file is inside the defined dir */
+ rspamd_strlcpy(filebuf, realbuf, sizeof(filebuf));
+ dir = dirname(filebuf);
+
+ if (dir == NULL ||
+ !rspamd_http_router_is_subdir(entry->rt->default_fs_path,
+ dir)) {
+ return FALSE;
+ }
+
+ fd = open(realbuf, O_RDONLY);
+ if (fd == -1) {
+ return FALSE;
+ }
+
+ reply_msg = rspamd_http_new_message(HTTP_RESPONSE);
+ reply_msg->date = time(NULL);
+ reply_msg->code = 200;
+ rspamd_http_router_insert_headers(entry->rt, reply_msg);
+
+ if (!rspamd_http_message_set_body_from_fd(reply_msg, fd)) {
+ rspamd_http_message_free(reply_msg);
+ close(fd);
+ return FALSE;
+ }
+
+ close(fd);
+
+ rspamd_http_connection_reset(entry->conn);
+
+ msg_debug("requested file %s", realbuf);
+ rspamd_http_connection_write_message(entry->conn, reply_msg, NULL,
+ rspamd_http_router_detect_ct(realbuf), entry,
+ entry->rt->timeout);
+
+ return TRUE;
+}
+
+static void
+rspamd_http_router_send_error(GError *err,
+ struct rspamd_http_connection_entry *entry)
+{
+ struct rspamd_http_message *err_msg;
+
+ err_msg = rspamd_http_new_message(HTTP_RESPONSE);
+ err_msg->date = time(NULL);
+ err_msg->code = err->code;
+ rspamd_http_message_set_body(err_msg, err->message,
+ strlen(err->message));
+ entry->is_reply = TRUE;
+ err_msg->status = rspamd_fstring_new_init(err->message, strlen(err->message));
+ rspamd_http_router_insert_headers(entry->rt, err_msg);
+ rspamd_http_connection_reset(entry->conn);
+ rspamd_http_connection_write_message(entry->conn,
+ err_msg,
+ NULL,
+ "text/plain",
+ entry,
+ entry->rt->timeout);
+}
+
+
+static int
+rspamd_http_router_finish_handler(struct rspamd_http_connection *conn,
+ struct rspamd_http_message *msg)
+{
+ struct rspamd_http_connection_entry *entry = conn->ud;
+ rspamd_http_router_handler_t handler = NULL;
+ gpointer found;
+
+ GError *err;
+ rspamd_ftok_t lookup;
+ const rspamd_ftok_t *encoding;
+ struct http_parser_url u;
+ guint i;
+ rspamd_regexp_t *re;
+ struct rspamd_http_connection_router *router;
+ gchar *pathbuf = NULL;
+
+ G_STATIC_ASSERT(sizeof(rspamd_http_router_handler_t) ==
+ sizeof(gpointer));
+
+ memset(&lookup, 0, sizeof(lookup));
+ router = entry->rt;
+
+ if (entry->is_reply) {
+ /* Request is finished, it is safe to free a connection */
+ rspamd_http_entry_free(entry);
+ }
+ else {
+ if (G_UNLIKELY(msg->method != HTTP_GET && msg->method != HTTP_POST)) {
+ if (router->unknown_method_handler) {
+ return router->unknown_method_handler(entry, msg);
+ }
+ else {
+ err = g_error_new(HTTP_ERROR, 500,
+ "Invalid method");
+ if (entry->rt->error_handler != NULL) {
+ entry->rt->error_handler(entry, err);
+ }
+
+ rspamd_http_router_send_error(err, entry);
+ g_error_free(err);
+
+ return 0;
+ }
+ }
+
+ /* Search for path */
+ if (msg->url != NULL && msg->url->len != 0) {
+
+ http_parser_parse_url(msg->url->str, msg->url->len, TRUE, &u);
+
+ if (u.field_set & (1 << UF_PATH)) {
+ gsize unnorm_len;
+
+ pathbuf = g_malloc(u.field_data[UF_PATH].len);
+ memcpy(pathbuf, msg->url->str + u.field_data[UF_PATH].off,
+ u.field_data[UF_PATH].len);
+ lookup.begin = pathbuf;
+ lookup.len = u.field_data[UF_PATH].len;
+
+ rspamd_normalize_path_inplace(pathbuf,
+ lookup.len,
+ &unnorm_len);
+ lookup.len = unnorm_len;
+ }
+ else {
+ lookup.begin = msg->url->str;
+ lookup.len = msg->url->len;
+ }
+
+ found = g_hash_table_lookup(entry->rt->paths, &lookup);
+ memcpy(&handler, &found, sizeof(found));
+ msg_debug("requested known path: %T", &lookup);
+ }
+ else {
+ err = g_error_new(HTTP_ERROR, 404,
+ "Empty path requested");
+ if (entry->rt->error_handler != NULL) {
+ entry->rt->error_handler(entry, err);
+ }
+
+ rspamd_http_router_send_error(err, entry);
+ g_error_free(err);
+
+ return 0;
+ }
+
+ entry->is_reply = TRUE;
+
+ encoding = rspamd_http_message_find_header(msg, "Accept-Encoding");
+
+ if (encoding && rspamd_substring_search(encoding->begin, encoding->len,
+ "gzip", 4) != -1) {
+ entry->support_gzip = TRUE;
+ }
+
+ if (handler != NULL) {
+ if (pathbuf) {
+ g_free(pathbuf);
+ }
+
+ return handler(entry, msg);
+ }
+ else {
+ /* Try regexps */
+ for (i = 0; i < router->regexps->len; i++) {
+ re = g_ptr_array_index(router->regexps, i);
+ if (rspamd_regexp_match(re, lookup.begin, lookup.len,
+ TRUE)) {
+ found = rspamd_regexp_get_ud(re);
+ memcpy(&handler, &found, sizeof(found));
+
+ if (pathbuf) {
+ g_free(pathbuf);
+ }
+
+ return handler(entry, msg);
+ }
+ }
+
+ /* Now try plain file */
+ if (entry->rt->default_fs_path == NULL || lookup.len == 0 ||
+ !rspamd_http_router_try_file(entry, &lookup, TRUE)) {
+
+ err = g_error_new(HTTP_ERROR, 404,
+ "Not found");
+ if (entry->rt->error_handler != NULL) {
+ entry->rt->error_handler(entry, err);
+ }
+
+ msg_info("path: %T not found", &lookup);
+ rspamd_http_router_send_error(err, entry);
+ g_error_free(err);
+ }
+ }
+ }
+
+ if (pathbuf) {
+ g_free(pathbuf);
+ }
+
+ return 0;
+}
+
+struct rspamd_http_connection_router *
+rspamd_http_router_new(rspamd_http_router_error_handler_t eh,
+ rspamd_http_router_finish_handler_t fh,
+ ev_tstamp timeout,
+ const char *default_fs_path,
+ struct rspamd_http_context *ctx)
+{
+ struct rspamd_http_connection_router *nrouter;
+ struct stat st;
+
+ nrouter = g_malloc0(sizeof(struct rspamd_http_connection_router));
+ nrouter->paths = g_hash_table_new_full(rspamd_ftok_icase_hash,
+ rspamd_ftok_icase_equal, rspamd_fstring_mapped_ftok_free, NULL);
+ nrouter->regexps = g_ptr_array_new();
+ nrouter->conns = NULL;
+ nrouter->error_handler = eh;
+ nrouter->finish_handler = fh;
+ nrouter->response_headers = g_hash_table_new_full(rspamd_strcase_hash,
+ rspamd_strcase_equal, g_free, g_free);
+ nrouter->event_loop = ctx->event_loop;
+ nrouter->timeout = timeout;
+ nrouter->default_fs_path = NULL;
+
+ if (default_fs_path != NULL) {
+ if (stat(default_fs_path, &st) == -1) {
+ msg_err("cannot stat %s", default_fs_path);
+ }
+ else {
+ if (!S_ISDIR(st.st_mode)) {
+ msg_err("path %s is not a directory", default_fs_path);
+ }
+ else {
+ nrouter->default_fs_path = realpath(default_fs_path, NULL);
+ }
+ }
+ }
+
+ nrouter->ctx = ctx;
+
+ return nrouter;
+}
+
+void rspamd_http_router_set_key(struct rspamd_http_connection_router *router,
+ struct rspamd_cryptobox_keypair *key)
+{
+ g_assert(key != NULL);
+
+ router->key = rspamd_keypair_ref(key);
+}
+
+void rspamd_http_router_add_path(struct rspamd_http_connection_router *router,
+ const gchar *path, rspamd_http_router_handler_t handler)
+{
+ gpointer ptr;
+ rspamd_ftok_t *key;
+ rspamd_fstring_t *storage;
+ G_STATIC_ASSERT(sizeof(rspamd_http_router_handler_t) ==
+ sizeof(gpointer));
+
+ if (path != NULL && handler != NULL && router != NULL) {
+ memcpy(&ptr, &handler, sizeof(ptr));
+ storage = rspamd_fstring_new_init(path, strlen(path));
+ key = g_malloc0(sizeof(*key));
+ key->begin = storage->str;
+ key->len = storage->len;
+ g_hash_table_insert(router->paths, key, ptr);
+ }
+}
+
+void rspamd_http_router_set_unknown_handler(struct rspamd_http_connection_router *router,
+ rspamd_http_router_handler_t handler)
+{
+ if (router != NULL) {
+ router->unknown_method_handler = handler;
+ }
+}
+
+void rspamd_http_router_add_header(struct rspamd_http_connection_router *router,
+ const gchar *name, const gchar *value)
+{
+ if (name != NULL && value != NULL && router != NULL) {
+ g_hash_table_replace(router->response_headers, g_strdup(name),
+ g_strdup(value));
+ }
+}
+
+void rspamd_http_router_insert_headers(struct rspamd_http_connection_router *router,
+ struct rspamd_http_message *msg)
+{
+ GHashTableIter it;
+ gpointer k, v;
+
+ if (router && msg) {
+ g_hash_table_iter_init(&it, router->response_headers);
+
+ while (g_hash_table_iter_next(&it, &k, &v)) {
+ rspamd_http_message_add_header(msg, k, v);
+ }
+ }
+}
+
+void rspamd_http_router_add_regexp(struct rspamd_http_connection_router *router,
+ struct rspamd_regexp_s *re, rspamd_http_router_handler_t handler)
+{
+ gpointer ptr;
+ G_STATIC_ASSERT(sizeof(rspamd_http_router_handler_t) ==
+ sizeof(gpointer));
+
+ if (re != NULL && handler != NULL && router != NULL) {
+ memcpy(&ptr, &handler, sizeof(ptr));
+ rspamd_regexp_set_ud(re, ptr);
+ g_ptr_array_add(router->regexps, rspamd_regexp_ref(re));
+ }
+}
+
+void rspamd_http_router_handle_socket(struct rspamd_http_connection_router *router,
+ gint fd, gpointer ud)
+{
+ struct rspamd_http_connection_entry *conn;
+
+ conn = g_malloc0(sizeof(struct rspamd_http_connection_entry));
+ conn->rt = router;
+ conn->ud = ud;
+ conn->is_reply = FALSE;
+
+ conn->conn = rspamd_http_connection_new_server(router->ctx,
+ fd,
+ NULL,
+ rspamd_http_router_error_handler,
+ rspamd_http_router_finish_handler,
+ 0);
+
+ if (router->key) {
+ rspamd_http_connection_set_key(conn->conn, router->key);
+ }
+
+ rspamd_http_connection_read_message(conn->conn, conn, router->timeout);
+ DL_PREPEND(router->conns, conn);
+}
+
+void rspamd_http_router_free(struct rspamd_http_connection_router *router)
+{
+ struct rspamd_http_connection_entry *conn, *tmp;
+ rspamd_regexp_t *re;
+ guint i;
+
+ if (router) {
+ DL_FOREACH_SAFE(router->conns, conn, tmp)
+ {
+ rspamd_http_entry_free(conn);
+ }
+
+ if (router->key) {
+ rspamd_keypair_unref(router->key);
+ }
+
+ if (router->default_fs_path != NULL) {
+ g_free(router->default_fs_path);
+ }
+
+ for (i = 0; i < router->regexps->len; i++) {
+ re = g_ptr_array_index(router->regexps, i);
+ rspamd_regexp_unref(re);
+ }
+
+ g_ptr_array_free(router->regexps, TRUE);
+ g_hash_table_unref(router->paths);
+ g_hash_table_unref(router->response_headers);
+ g_free(router);
+ }
+}