diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
commit | 133a45c109da5310add55824db21af5239951f93 (patch) | |
tree | ba6ac4c0a950a0dda56451944315d66409923918 /src/libserver/http/http_router.c | |
parent | Initial commit. (diff) | |
download | rspamd-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.c | 559 |
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); + } +} |