From a841d419c9d230681a46900cac8629b576b2dfe8 Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Fri, 15 Feb 2019 18:23:40 +0000 Subject: [PATCH] [Rework] Finish http code split and cleanup --- contrib/http-parser/http_parser.c | 3 +- contrib/http-parser/http_parser.h | 4 +- src/controller.c | 1 + src/fuzzy_storage.c | 4 +- src/libserver/url.c | 1 + src/libserver/worker_util.c | 1 + src/libutil/CMakeLists.txt | 3 + src/libutil/http_connection.c | 1272 +---------------------------- src/libutil/http_connection.h | 338 +------- src/libutil/http_message.c | 465 +++++++++++ src/libutil/http_message.h | 221 +++++ src/libutil/http_private.h | 9 +- src/libutil/http_router.c | 564 +++++++++++++ src/libutil/http_router.h | 139 ++++ src/libutil/http_util.c | 247 ++++++ src/libutil/http_util.h | 48 ++ src/plugins/fuzzy_check.c | 1 + src/rspamadm/lua_repl.c | 1 + 18 files changed, 1738 insertions(+), 1584 deletions(-) create mode 100644 src/libutil/http_message.c create mode 100644 src/libutil/http_message.h create mode 100644 src/libutil/http_router.c create mode 100644 src/libutil/http_router.h create mode 100644 src/libutil/http_util.c create mode 100644 src/libutil/http_util.h diff --git a/contrib/http-parser/http_parser.c b/contrib/http-parser/http_parser.c index 0e75d964b..c14ecc034 100644 --- a/contrib/http-parser/http_parser.c +++ b/contrib/http-parser/http_parser.c @@ -122,6 +122,7 @@ do { \ #define KEEP_ALIVE "keep-alive" #define CLOSE "close" +enum rspamd_http_message_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; static const char *method_strings[] = { @@ -1981,7 +1982,7 @@ http_method_str (enum http_method m) void -http_parser_init (http_parser *parser, enum http_parser_type t) +http_parser_init (http_parser *parser, int t) { void *data = parser->data; /* preserve application data */ memset(parser, 0, sizeof(*parser)); diff --git a/contrib/http-parser/http_parser.h b/contrib/http-parser/http_parser.h index e2a0b4985..7b4ac497c 100644 --- a/contrib/http-parser/http_parser.h +++ b/contrib/http-parser/http_parser.h @@ -124,8 +124,6 @@ enum http_method }; -enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; - /* Flag values for http_parser.flags field */ enum flags @@ -280,7 +278,7 @@ struct http_parser_url { */ unsigned long http_parser_version(void); -void http_parser_init(http_parser *parser, enum http_parser_type type); +void http_parser_init(http_parser *parser, int type); size_t http_parser_execute(http_parser *parser, diff --git a/src/controller.c b/src/controller.c index 6a839b4df..b19ac3db7 100644 --- a/src/controller.c +++ b/src/controller.c @@ -21,6 +21,7 @@ #include "libutil/map_helpers.h" #include "libutil/map_private.h" #include "libutil/http_private.h" +#include "libutil/http_router.h" #include "libstat/stat_api.h" #include "rspamd.h" #include "libserver/worker_util.h" diff --git a/src/fuzzy_storage.c b/src/fuzzy_storage.c index 36b41113d..bbd6b36ca 100644 --- a/src/fuzzy_storage.c +++ b/src/fuzzy_storage.c @@ -34,10 +34,10 @@ #include "libcryptobox/keypairs_cache.h" #include "libcryptobox/keypair.h" #include "libserver/rspamd_control.h" -#include "libutil/map_private.h" #include "libutil/hash.h" +#include "libutil/map_private.h" #include "libutil/http_private.h" -#include "libutil/hash.h" +#include "libutil/http_router.h" #include "unix-std.h" #include diff --git a/src/libserver/url.c b/src/libserver/url.c index 0effe4d6b..461c232af 100644 --- a/src/libserver/url.c +++ b/src/libserver/url.c @@ -46,6 +46,7 @@ #include "message.h" #include "multipattern.h" #include "contrib/uthash/utlist.h" +#include "contrib/http-parser/http_parser.h" #include #include diff --git a/src/libserver/worker_util.c b/src/libserver/worker_util.c index a0e511929..e10e25bc0 100644 --- a/src/libserver/worker_util.c +++ b/src/libserver/worker_util.c @@ -24,6 +24,7 @@ #include "libutil/map.h" #include "libutil/map_private.h" #include "libutil/http_private.h" +#include "libutil/http_router.h" #ifdef WITH_GPERF_TOOLS #include diff --git a/src/libutil/CMakeLists.txt b/src/libutil/CMakeLists.txt index aef2ed268..4d4f8c7bd 100644 --- a/src/libutil/CMakeLists.txt +++ b/src/libutil/CMakeLists.txt @@ -6,7 +6,10 @@ SET(LIBRSPAMDUTILSRC ${CMAKE_CURRENT_SOURCE_DIR}/expression.c ${CMAKE_CURRENT_SOURCE_DIR}/fstring.c ${CMAKE_CURRENT_SOURCE_DIR}/hash.c + ${CMAKE_CURRENT_SOURCE_DIR}/http_util.c + ${CMAKE_CURRENT_SOURCE_DIR}/http_message.c ${CMAKE_CURRENT_SOURCE_DIR}/http_connection.c + ${CMAKE_CURRENT_SOURCE_DIR}/http_router.c ${CMAKE_CURRENT_SOURCE_DIR}/logger.c ${CMAKE_CURRENT_SOURCE_DIR}/map.c ${CMAKE_CURRENT_SOURCE_DIR}/map_helpers.c diff --git a/src/libutil/http_connection.c b/src/libutil/http_connection.c index a82fc24f7..df283f752 100644 --- a/src/libutil/http_connection.c +++ b/src/libutil/http_connection.c @@ -14,8 +14,9 @@ * limitations under the License. */ #include "config.h" -#include "../../contrib/mumhash/mum.h" +#include "http_connection.h" #include "http_private.h" +#include "http_message.h" #include "utlist.h" #include "util.h" #include "printf.h" @@ -24,11 +25,13 @@ #include "ottery.h" #include "keypair_private.h" #include "cryptobox.h" -#include "unix-std.h" #include "libutil/ssl_util.h" -#include "libutil/regexp.h" #include "libserver/url.h" +#include "contrib/mumhash/mum.h" +#include "contrib/http-parser/http_parser.h" +#include "unix-std.h" + #include #define ENCRYPTED_VERSION " HTTP/1.0" @@ -71,30 +74,6 @@ struct rspamd_http_connection_private { gsize wr_total; }; -enum http_magic_type { - HTTP_MAGIC_PLAIN = 0, - HTTP_MAGIC_HTML, - HTTP_MAGIC_CSS, - HTTP_MAGIC_JS, - 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_PNG] = { "png", "image/png" }, - [HTTP_MAGIC_JPG] = { "jpg", "image/jpeg" }, -}; - -static const gchar *http_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; -static const gchar *http_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const rspamd_ftok_t key_header = { .begin = "Key", .len = 3 @@ -108,9 +87,7 @@ static const rspamd_ftok_t last_modified_header = { .len = 13 }; -static void rspamd_http_message_storage_cleanup (struct rspamd_http_message *msg); -static gboolean rspamd_http_message_grow_body (struct rspamd_http_message *msg, - gsize len); + #define HTTP_ERROR http_error_quark () GQuark @@ -1767,18 +1744,14 @@ rspamd_http_message_write_header (const gchar* mime_type, gboolean encrypted, { gchar datebuf[64]; gint meth_len = 0; - struct tm t; if (conn->type == RSPAMD_HTTP_SERVER) { /* Format reply */ if (msg->method < HTTP_SYMBOLS) { rspamd_ftok_t status; - rspamd_gmtime (msg->date, &t); - rspamd_snprintf (datebuf, sizeof(datebuf), - "%s, %02d %s %4d %02d:%02d:%02d GMT", http_week[t.tm_wday], - t.tm_mday, http_month[t.tm_mon], t.tm_year + 1900, - t.tm_hour, t.tm_min, t.tm_sec); + rspamd_http_date_format (datebuf, sizeof (datebuf), msg->date); + if (mime_type == NULL) { mime_type = encrypted ? "application/octet-stream" : "text/plain"; @@ -2381,449 +2354,6 @@ rspamd_http_connection_write_message_shared (struct rspamd_http_connection *conn ud, fd, timeout, base, TRUE); } -struct rspamd_http_message * -rspamd_http_new_message (enum http_parser_type type) -{ - struct rspamd_http_message *new; - - new = g_malloc0 (sizeof (struct rspamd_http_message)); - - if (type == HTTP_REQUEST) { - new->url = rspamd_fstring_new (); - } - else { - new->url = NULL; - new->code = 200; - } - - new->port = 80; - new->type = type; - new->method = HTTP_INVALID; - - REF_INIT_RETAIN (new, rspamd_http_message_free); - - return new; -} - -struct rspamd_http_message* -rspamd_http_message_from_url (const gchar *url) -{ - struct http_parser_url pu; - struct rspamd_http_message *msg; - const gchar *host, *path; - size_t pathlen, urllen; - guint flags = 0; - - if (url == NULL) { - return NULL; - } - - urllen = strlen (url); - memset (&pu, 0, sizeof (pu)); - - if (http_parser_parse_url (url, urllen, FALSE, &pu) != 0) { - msg_warn ("cannot parse URL: %s", url); - return NULL; - } - - if ((pu.field_set & (1 << UF_HOST)) == 0) { - msg_warn ("no host argument in URL: %s", url); - return NULL; - } - - if ((pu.field_set & (1 << UF_SCHEMA))) { - if (pu.field_data[UF_SCHEMA].len == sizeof ("https") - 1 && - memcmp (url + pu.field_data[UF_SCHEMA].off, "https", 5) == 0) { - flags |= RSPAMD_HTTP_FLAG_SSL; - } - } - - if ((pu.field_set & (1 << UF_PATH)) == 0) { - path = "/"; - pathlen = 1; - } - else { - path = url + pu.field_data[UF_PATH].off; - pathlen = urllen - pu.field_data[UF_PATH].off; - } - - msg = rspamd_http_new_message (HTTP_REQUEST); - host = url + pu.field_data[UF_HOST].off; - msg->flags = flags; - - if ((pu.field_set & (1 << UF_PORT)) != 0) { - msg->port = pu.port; - } - else { - /* XXX: magic constant */ - if (flags & RSPAMD_HTTP_FLAG_SSL) { - msg->port = 443; - } - else { - msg->port = 80; - } - } - - msg->host = rspamd_fstring_new_init (host, pu.field_data[UF_HOST].len); - msg->url = rspamd_fstring_append (msg->url, path, pathlen); - - REF_INIT_RETAIN (msg, rspamd_http_message_free); - - return msg; -} - -const gchar * -rspamd_http_message_get_body (struct rspamd_http_message *msg, - gsize *blen) -{ - const gchar *ret = NULL; - - if (msg->body_buf.len > 0) { - ret = msg->body_buf.begin; - } - - if (blen) { - *blen = msg->body_buf.len; - } - - return ret; -} - -static void -rspamd_http_shname_dtor (void *p) -{ - struct rspamd_storage_shmem *n = p; - -#ifdef HAVE_SANE_SHMEM - shm_unlink (n->shm_name); -#else - unlink (n->shm_name); -#endif - g_free (n->shm_name); - g_free (n); -} - -struct rspamd_storage_shmem * -rspamd_http_message_shmem_ref (struct rspamd_http_message *msg) -{ - if ((msg->flags & RSPAMD_HTTP_FLAG_SHMEM) && msg->body_buf.c.shared.name) { - REF_RETAIN (msg->body_buf.c.shared.name); - return msg->body_buf.c.shared.name; - } - - return NULL; -} - -guint -rspamd_http_message_get_flags (struct rspamd_http_message *msg) -{ - return msg->flags; -} - -void -rspamd_http_message_shmem_unref (struct rspamd_storage_shmem *p) -{ - REF_RELEASE (p); -} - -gboolean -rspamd_http_message_set_body (struct rspamd_http_message *msg, - const gchar *data, gsize len) -{ - union _rspamd_storage_u *storage; - storage = &msg->body_buf.c; - - rspamd_http_message_storage_cleanup (msg); - - if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { - storage->shared.name = g_malloc (sizeof (*storage->shared.name)); - REF_INIT_RETAIN (storage->shared.name, rspamd_http_shname_dtor); -#ifdef HAVE_SANE_SHMEM -#if defined(__DragonFly__) - // DragonFly uses regular files for shm. User rspamd is not allowed to create - // files in the root. - storage->shared.name->shm_name = g_strdup ("/tmp/rhm.XXXXXXXXXXXXXXXXXXXX"); -#else - storage->shared.name->shm_name = g_strdup ("/rhm.XXXXXXXXXXXXXXXXXXXX"); -#endif - storage->shared.shm_fd = rspamd_shmem_mkstemp (storage->shared.name->shm_name); -#else - /* XXX: assume that tempdir is /tmp */ - storage->shared.name->shm_name = g_strdup ("/tmp/rhm.XXXXXXXXXXXXXXXXXXXX"); - storage->shared.shm_fd = mkstemp (storage->shared.name->shm_name); -#endif - - if (storage->shared.shm_fd == -1) { - return FALSE; - } - - if (len != 0 && len != ULLONG_MAX) { - if (ftruncate (storage->shared.shm_fd, len) == -1) { - return FALSE; - } - - msg->body_buf.str = mmap (NULL, len, - PROT_WRITE|PROT_READ, MAP_SHARED, - storage->shared.shm_fd, 0); - - if (msg->body_buf.str == MAP_FAILED) { - return FALSE; - } - - msg->body_buf.begin = msg->body_buf.str; - msg->body_buf.allocated_len = len; - - if (data != NULL) { - memcpy (msg->body_buf.str, data, len); - msg->body_buf.len = len; - } - } - else { - msg->body_buf.len = 0; - msg->body_buf.begin = NULL; - msg->body_buf.str = NULL; - msg->body_buf.allocated_len = 0; - } - } - else { - if (len != 0 && len != ULLONG_MAX) { - if (data == NULL) { - storage->normal = rspamd_fstring_sized_new (len); - msg->body_buf.len = 0; - } - else { - storage->normal = rspamd_fstring_new_init (data, len); - msg->body_buf.len = len; - } - } - else { - storage->normal = rspamd_fstring_new (); - } - - msg->body_buf.begin = storage->normal->str; - msg->body_buf.str = storage->normal->str; - msg->body_buf.allocated_len = storage->normal->allocated; - } - - msg->flags |= RSPAMD_HTTP_FLAG_HAS_BODY; - - return TRUE; -} - -void -rspamd_http_message_set_method (struct rspamd_http_message *msg, - const gchar *method) -{ - gint i; - - /* Linear search: not very efficient method */ - for (i = 0; i < HTTP_METHOD_MAX; i ++) { - if (g_ascii_strcasecmp (method, http_method_str (i)) == 0) { - msg->method = i; - } - } -} - -gboolean -rspamd_http_message_set_body_from_fd (struct rspamd_http_message *msg, - gint fd) -{ - union _rspamd_storage_u *storage; - struct stat st; - - rspamd_http_message_storage_cleanup (msg); - - storage = &msg->body_buf.c; - msg->flags |= RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE; - - storage->shared.shm_fd = dup (fd); - msg->body_buf.str = MAP_FAILED; - - if (storage->shared.shm_fd == -1) { - return FALSE; - } - - if (fstat (storage->shared.shm_fd, &st) == -1) { - return FALSE; - } - - msg->body_buf.str = mmap (NULL, st.st_size, - PROT_READ, MAP_SHARED, - storage->shared.shm_fd, 0); - - if (msg->body_buf.str == MAP_FAILED) { - return FALSE; - } - - msg->body_buf.begin = msg->body_buf.str; - msg->body_buf.len = st.st_size; - msg->body_buf.allocated_len = st.st_size; - - return TRUE; -} - -gboolean -rspamd_http_message_set_body_from_fstring_steal (struct rspamd_http_message *msg, - rspamd_fstring_t *fstr) -{ - union _rspamd_storage_u *storage; - - rspamd_http_message_storage_cleanup (msg); - - storage = &msg->body_buf.c; - msg->flags &= ~(RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE); - - storage->normal = fstr; - msg->body_buf.str = fstr->str; - msg->body_buf.begin = msg->body_buf.str; - msg->body_buf.len = fstr->len; - msg->body_buf.allocated_len = fstr->allocated; - - return TRUE; -} - -gboolean -rspamd_http_message_set_body_from_fstring_copy (struct rspamd_http_message *msg, - const rspamd_fstring_t *fstr) -{ - union _rspamd_storage_u *storage; - - rspamd_http_message_storage_cleanup (msg); - - storage = &msg->body_buf.c; - msg->flags &= ~(RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE); - - storage->normal = rspamd_fstring_new_init (fstr->str, fstr->len); - msg->body_buf.str = storage->normal->str; - msg->body_buf.begin = msg->body_buf.str; - msg->body_buf.len = storage->normal->len; - msg->body_buf.allocated_len = storage->normal->allocated; - - return TRUE; -} - - -static gboolean -rspamd_http_message_grow_body (struct rspamd_http_message *msg, gsize len) -{ - struct stat st; - union _rspamd_storage_u *storage; - gsize newlen; - - storage = &msg->body_buf.c; - - if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { - if (storage->shared.shm_fd == -1) { - return FALSE; - } - - if (fstat (storage->shared.shm_fd, &st) == -1) { - return FALSE; - } - - /* Check if we need to grow */ - if ((gsize)st.st_size < msg->body_buf.len + len) { - /* Need to grow */ - newlen = rspamd_fstring_suggest_size (msg->body_buf.len, st.st_size, - len); - /* Unmap as we need another size of segment */ - if (msg->body_buf.str != MAP_FAILED) { - munmap (msg->body_buf.str, st.st_size); - } - - if (ftruncate (storage->shared.shm_fd, newlen) == -1) { - return FALSE; - } - - msg->body_buf.str = mmap (NULL, newlen, - PROT_WRITE|PROT_READ, MAP_SHARED, - storage->shared.shm_fd, 0); - if (msg->body_buf.str == MAP_FAILED) { - return FALSE; - } - - msg->body_buf.begin = msg->body_buf.str; - msg->body_buf.allocated_len = newlen; - } - } - else { - storage->normal = rspamd_fstring_grow (storage->normal, len); - - /* Append might cause realloc */ - msg->body_buf.begin = storage->normal->str; - msg->body_buf.len = storage->normal->len; - msg->body_buf.str = storage->normal->str; - msg->body_buf.allocated_len = storage->normal->allocated; - } - - return TRUE; -} - -gboolean -rspamd_http_message_append_body (struct rspamd_http_message *msg, - const gchar *data, gsize len) -{ - union _rspamd_storage_u *storage; - - storage = &msg->body_buf.c; - - if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { - if (!rspamd_http_message_grow_body (msg, len)) { - return FALSE; - } - - memcpy (msg->body_buf.str + msg->body_buf.len, data, len); - msg->body_buf.len += len; - } - else { - storage->normal = rspamd_fstring_append (storage->normal, data, len); - - /* Append might cause realloc */ - msg->body_buf.begin = storage->normal->str; - msg->body_buf.len = storage->normal->len; - msg->body_buf.str = storage->normal->str; - msg->body_buf.allocated_len = storage->normal->allocated; - } - - return TRUE; -} - -static void -rspamd_http_message_storage_cleanup (struct rspamd_http_message *msg) -{ - union _rspamd_storage_u *storage; - struct stat st; - - if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { - storage = &msg->body_buf.c; - - if (storage->shared.shm_fd > 0) { - g_assert (fstat (storage->shared.shm_fd, &st) != -1); - - if (msg->body_buf.str != MAP_FAILED) { - munmap (msg->body_buf.str, st.st_size); - } - - close (storage->shared.shm_fd); - } - - if (storage->shared.name != NULL) { - REF_RELEASE (storage->shared.name); - } - - storage->shared.shm_fd = -1; - msg->body_buf.str = MAP_FAILED; - } - else { - if (msg->body_buf.c.normal) { - rspamd_fstring_free (msg->body_buf.c.normal); - } - - msg->body_buf.c.normal = NULL; - } - - msg->body_buf.len = 0; -} void rspamd_http_connection_set_max_size (struct rspamd_http_connection *conn, @@ -3027,557 +2557,38 @@ rspamd_http_message_remove_header (struct rspamd_http_message *msg, return res; } -/* - * HTTP router functions - */ - -static void -rspamd_http_entry_free (struct rspamd_http_connection_entry *entry) +void +rspamd_http_connection_set_key (struct rspamd_http_connection *conn, + struct rspamd_cryptobox_keypair *key) { - if (entry != NULL) { - close (entry->conn->fd); - rspamd_http_connection_unref (entry->conn); - if (entry->rt->finish_handler) { - entry->rt->finish_handler (entry); - } + struct rspamd_http_connection_private *priv = conn->priv; - DL_DELETE (entry->rt->conns, entry); - g_free (entry); - } + g_assert (key != NULL); + priv->local_key = rspamd_keypair_ref (key); } -static void -rspamd_http_router_error_handler (struct rspamd_http_connection *conn, - GError *err) +const struct rspamd_cryptobox_pubkey* +rspamd_http_connection_get_peer_key (struct rspamd_http_connection *conn) { - struct rspamd_http_connection_entry *entry = conn->ud; - struct rspamd_http_message *msg; + struct rspamd_http_connection_private *priv = conn->priv; - 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); + if (priv->peer_key) { + return priv->peer_key; } - 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->conn->fd, - entry->rt->ptv, - entry->rt->ev_base); - entry->is_reply = TRUE; + else if (priv->msg) { + return priv->msg->peer_key; } + + return NULL; } -static const gchar * -rspamd_http_router_detect_ct (const gchar *path) +gboolean +rspamd_http_connection_is_encrypted (struct rspamd_http_connection *conn) { - const gchar *dot; - guint i; + struct rspamd_http_connection_private *priv = conn->priv; - 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)) { - 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->conn->fd, - entry->rt->ptv, entry->rt->ev_base); - - 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->conn->fd, - entry->rt->ptv, - entry->rt->ev_base); -} - - -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; - - 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)) { - guint unnorm_len; - lookup.begin = msg->url->str + u.field_data[UF_PATH].off; - lookup.len = u.field_data[UF_PATH].len; - - rspamd_http_normalize_path_inplace ((gchar *)lookup.begin, - 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) { - 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)); - - 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); - } - } - } - - 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, - struct timeval *timeout, struct event_base *base, - const char *default_fs_path, - struct rspamd_keypair_cache *cache) -{ - struct rspamd_http_connection_router * new; - struct stat st; - - new = g_malloc0 (sizeof (struct rspamd_http_connection_router)); - new->paths = g_hash_table_new_full (rspamd_ftok_icase_hash, - rspamd_ftok_icase_equal, rspamd_fstring_mapped_ftok_free, NULL); - new->regexps = g_ptr_array_new (); - new->conns = NULL; - new->error_handler = eh; - new->finish_handler = fh; - new->ev_base = base; - new->response_headers = g_hash_table_new_full (rspamd_strcase_hash, - rspamd_strcase_equal, g_free, g_free); - - if (timeout) { - new->tv = *timeout; - new->ptv = &new->tv; - } - else { - new->ptv = NULL; - } - - new->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 { - new->default_fs_path = realpath (default_fs_path, NULL); - } - } - } - - new->cache = cache; - - return new; -} - -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 (NULL, - rspamd_http_router_error_handler, - rspamd_http_router_finish_handler, - 0, - RSPAMD_HTTP_SERVER, - router->cache, - NULL); - - if (router->key) { - rspamd_http_connection_set_key (conn->conn, router->key); - } - - rspamd_http_connection_read_message (conn->conn, conn, fd, router->ptv, - router->ev_base); - 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->cache) { - rspamd_keypair_cache_destroy (router->cache); - } - - 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); - } -} - -void -rspamd_http_connection_set_key (struct rspamd_http_connection *conn, - struct rspamd_cryptobox_keypair *key) -{ - struct rspamd_http_connection_private *priv = conn->priv; - - g_assert (key != NULL); - priv->local_key = rspamd_keypair_ref (key); -} - -const struct rspamd_cryptobox_pubkey* -rspamd_http_connection_get_peer_key (struct rspamd_http_connection *conn) -{ - struct rspamd_http_connection_private *priv = conn->priv; - - if (priv->peer_key) { - return priv->peer_key; - } - else if (priv->msg) { - return priv->msg->peer_key; - } - - return NULL; -} - -gboolean -rspamd_http_connection_is_encrypted (struct rspamd_http_connection *conn) -{ - struct rspamd_http_connection_private *priv = conn->priv; - - if (priv->peer_key != NULL) { - return TRUE; + if (priv->peer_key != NULL) { + return TRUE; } else if (priv->msg) { return priv->msg->peer_key != NULL; @@ -3710,19 +2721,6 @@ rspamd_http_message_parse_query (struct rspamd_http_message *msg) } -glong -rspamd_http_date_format (gchar *buf, gsize len, time_t time) -{ - struct tm tms; - - rspamd_gmtime (time, &tms); - - return rspamd_snprintf (buf, len, "%s, %02d %s %4d %02d:%02d:%02d GMT", - http_week[tms.tm_wday], tms.tm_mday, - http_month[tms.tm_mon], tms.tm_year + 1900, - tms.tm_hour, tms.tm_min, tms.tm_sec); -} - struct rspamd_http_message * rspamd_http_message_ref (struct rspamd_http_message *msg) { @@ -3737,218 +2735,6 @@ rspamd_http_message_unref (struct rspamd_http_message *msg) REF_RELEASE (msg); } - -void -rspamd_http_normalize_path_inplace (gchar *path, guint len, guint *nlen) -{ - const gchar *p, *end, *slash = NULL, *dot = NULL; - gchar *o; - enum { - st_normal = 0, - st_got_dot, - st_got_dot_dot, - st_got_slash, - st_got_slash_slash, - } state = st_normal; - - p = path; - end = path + len; - o = path; - - while (p < end) { - switch (state) { - case st_normal: - if (G_UNLIKELY (*p == '/')) { - state = st_got_slash; - slash = p; - } - else if (G_UNLIKELY (*p == '.')) { - state = st_got_dot; - dot = p; - } - else { - *o++ = *p; - } - p ++; - break; - case st_got_slash: - if (G_UNLIKELY (*p == '/')) { - /* Ignore double slash */ - *o++ = *p; - state = st_got_slash_slash; - } - else if (G_UNLIKELY (*p == '.')) { - dot = p; - state = st_got_dot; - } - else { - *o++ = '/'; - *o++ = *p; - slash = NULL; - dot = NULL; - state = st_normal; - } - p ++; - break; - case st_got_slash_slash: - if (G_LIKELY (*p != '/')) { - slash = p - 1; - dot = NULL; - state = st_normal; - continue; - } - p ++; - break; - case st_got_dot: - if (G_UNLIKELY (*p == '/')) { - /* Remove any /./ or ./ paths */ - if (((o > path && *(o - 1) != '/') || (o == path)) && slash) { - /* Preserve one slash */ - *o++ = '/'; - } - - slash = p; - dot = NULL; - /* Ignore last slash */ - state = st_normal; - } - else if (*p == '.') { - /* Double dot character */ - state = st_got_dot_dot; - } - else { - /* We have something like .some or /.some */ - if (dot && p > dot) { - if (slash == dot - 1 && (o > path && *(o - 1) != '/')) { - /* /.blah */ - memmove (o, slash, p - slash); - o += p - slash; - } - else { - memmove (o, dot, p - dot); - o += p - dot; - } - } - - slash = NULL; - dot = NULL; - state = st_normal; - continue; - } - - p ++; - break; - case st_got_dot_dot: - if (*p == '/') { - /* We have something like /../ or ../ */ - if (slash) { - /* We need to remove the last component from o if it is there */ - if (o > path + 2 && *(o - 1) == '/') { - slash = rspamd_memrchr (path, '/', o - path - 2); - } - else if (o > path + 1) { - slash = rspamd_memrchr (path, '/', o - path - 1); - } - else { - slash = NULL; - } - - if (slash) { - o = (gchar *)slash; - } - /* Otherwise we keep these dots */ - slash = p; - state = st_got_slash; - } - else { - /* We have something like bla../, so we need to copy it as is */ - if (o > path && dot && p > dot) { - memmove (o, dot, p - dot); - o += p - dot; - } - - slash = NULL; - dot = NULL; - state = st_normal; - continue; - } - } - else { - /* We have something like ..bla or ... */ - if (slash) { - *o ++ = '/'; - } - - if (dot && p > dot) { - memmove (o, dot, p - dot); - o += p - dot; - } - - slash = NULL; - dot = NULL; - state = st_normal; - continue; - } - - p ++; - break; - } - } - - /* Leftover */ - switch (state) { - case st_got_dot_dot: - /* Trailing .. */ - if (slash) { - /* We need to remove the last component from o if it is there */ - if (o > path + 2 && *(o - 1) == '/') { - slash = rspamd_memrchr (path, '/', o - path - 2); - } - else if (o > path + 1) { - slash = rspamd_memrchr (path, '/', o - path - 1); - } - else { - if (o == path) { - /* Corner case */ - *o++ = '/'; - } - - slash = NULL; - } - - if (slash) { - /* Remove last / */ - o = (gchar *)slash; - } - } - else { - /* Corner case */ - if (o == path) { - *o++ = '/'; - } - else { - if (dot && p > dot) { - memmove (o, dot, p - dot); - o += p - dot; - } - } - } - break; - case st_got_slash: - *o++ = '/'; - break; - default: - if (o > path + 1 && *(o - 1) == '/') { - o --; - } - break; - } - - if (nlen) { - *nlen = (o - path); - } -} - void rspamd_http_connection_disable_encryption (struct rspamd_http_connection *conn) { diff --git a/src/libutil/http_connection.h b/src/libutil/http_connection.h index 64a05bf65..1fa1170b2 100644 --- a/src/libutil/http_connection.h +++ b/src/libutil/http_connection.h @@ -24,11 +24,14 @@ */ #include "config.h" -#include "http_parser.h" #include "keypair.h" #include "keypairs_cache.h" #include "fstring.h" #include "ref.h" +#include "http_message.h" +#include "http_util.h" + +#include enum rspamd_http_connection_type { RSPAMD_HTTP_SERVER, @@ -93,15 +96,6 @@ typedef void (*rspamd_http_error_handler_t) (struct rspamd_http_connection *conn typedef int (*rspamd_http_finish_handler_t) (struct rspamd_http_connection *conn, struct rspamd_http_message *msg); -typedef int (*rspamd_http_router_handler_t) (struct rspamd_http_connection_entry - *conn_ent, - struct rspamd_http_message *msg); -typedef void (*rspamd_http_router_error_handler_t) (struct - rspamd_http_connection_entry *conn_ent, - GError *err); -typedef void (*rspamd_http_router_finish_handler_t) (struct - rspamd_http_connection_entry *conn_ent); - /** * HTTP connection structure */ @@ -120,31 +114,6 @@ struct rspamd_http_connection { gint ref; }; -struct rspamd_http_connection_entry { - struct rspamd_http_connection_router *rt; - struct rspamd_http_connection *conn; - gpointer ud; - gboolean is_reply; - gboolean support_gzip; - struct rspamd_http_connection_entry *prev, *next; -}; - -struct rspamd_http_connection_router { - struct rspamd_http_connection_entry *conns; - GHashTable *paths; - GHashTable *response_headers; - GPtrArray *regexps; - struct timeval tv; - struct timeval *ptv; - struct event_base *ev_base; - struct rspamd_keypair_cache *cache; - gchar *default_fs_path; - rspamd_http_router_handler_t unknown_method_handler; - struct rspamd_cryptobox_keypair *key; - rspamd_http_router_error_handler_t error_handler; - rspamd_http_router_finish_handler_t finish_handler; -}; - /** * Create new http connection * @param handler_t handler_t for body @@ -267,171 +236,6 @@ rspamd_http_connection_unref (struct rspamd_http_connection *conn) */ void rspamd_http_connection_reset (struct rspamd_http_connection *conn); -/** - * Extract the current message from a connection to deal with separately - * @param conn - * @return - */ -struct rspamd_http_message * rspamd_http_connection_steal_msg ( - struct rspamd_http_connection *conn); - -/** - * Copy the current message from a connection to deal with separately - * @param conn - * @return - */ -struct rspamd_http_message * rspamd_http_connection_copy_msg ( - struct rspamd_http_message *msg, GError **err); - -/** - * Create new HTTP message - * @param type request or response - * @return new http message - */ -struct rspamd_http_message * rspamd_http_new_message (enum http_parser_type type); - -/** - * Increase refcount number for an HTTP message - * @param msg message to use - * @return - */ -struct rspamd_http_message * rspamd_http_message_ref (struct rspamd_http_message *msg); -/** - * Decrease number of refcounts for http message - * @param msg - */ -void rspamd_http_message_unref (struct rspamd_http_message *msg); - -/** - * Sets a key for peer - * @param msg - * @param pk - */ -void rspamd_http_message_set_peer_key (struct rspamd_http_message *msg, - struct rspamd_cryptobox_pubkey *pk); -/** - * Create HTTP message from URL - * @param url - * @return new message or NULL - */ -struct rspamd_http_message* rspamd_http_message_from_url (const gchar *url); - -/** - * Returns body for a message - * @param msg - * @param blen pointer where to save body length - * @return pointer to body start - */ -const gchar *rspamd_http_message_get_body (struct rspamd_http_message *msg, - gsize *blen); - -/** - * Set message's body from the string - * @param msg - * @param data - * @param len - * @return TRUE if a message's body has been set - */ -gboolean rspamd_http_message_set_body (struct rspamd_http_message *msg, - const gchar *data, gsize len); - -/** - * Set message's method by name - * @param msg - * @param method - */ -void rspamd_http_message_set_method (struct rspamd_http_message *msg, - const gchar *method); -/** - * Maps fd as message's body - * @param msg - * @param fd - * @return TRUE if a message's body has been set - */ -gboolean rspamd_http_message_set_body_from_fd (struct rspamd_http_message *msg, - gint fd); - -/** - * Uses rspamd_fstring_t as message's body, string is consumed by this operation - * @param msg - * @param fstr - * @return TRUE if a message's body has been set - */ -gboolean rspamd_http_message_set_body_from_fstring_steal (struct rspamd_http_message *msg, - rspamd_fstring_t *fstr); - -/** - * Uses rspamd_fstring_t as message's body, string is copied by this operation - * @param msg - * @param fstr - * @return TRUE if a message's body has been set - */ -gboolean rspamd_http_message_set_body_from_fstring_copy (struct rspamd_http_message *msg, - const rspamd_fstring_t *fstr); - -/** - * Appends data to message's body - * @param msg - * @param data - * @param len - * @return TRUE if a message's body has been set - */ -gboolean rspamd_http_message_append_body (struct rspamd_http_message *msg, - const gchar *data, gsize len); - -/** - * Append a header to http message - * @param rep - * @param name - * @param value - */ -void rspamd_http_message_add_header (struct rspamd_http_message *msg, - const gchar *name, - const gchar *value); - -void rspamd_http_message_add_header_len (struct rspamd_http_message *msg, - const gchar *name, - const gchar *value, - gsize len); - -void rspamd_http_message_add_header_fstr (struct rspamd_http_message *msg, - const gchar *name, - rspamd_fstring_t *value); - -/** - * Search for a specified header in message - * @param msg message - * @param name name of header - */ -const rspamd_ftok_t * rspamd_http_message_find_header ( - struct rspamd_http_message *msg, - const gchar *name); - -/** - * Search for a header that has multiple values - * @param msg - * @param name - * @return list of rspamd_ftok_t * with values - */ -GPtrArray* rspamd_http_message_find_header_multiple ( - struct rspamd_http_message *msg, - const gchar *name); - -/** - * Remove specific header from a message - * @param msg - * @param name - * @return - */ -gboolean rspamd_http_message_remove_header (struct rspamd_http_message *msg, - const gchar *name); - -/** - * Free HTTP message - * @param msg - */ -void rspamd_http_message_free (struct rspamd_http_message *msg); - /** * Sets global maximum size for HTTP message being processed * @param sz @@ -441,138 +245,4 @@ void rspamd_http_connection_set_max_size (struct rspamd_http_connection *conn, void rspamd_http_connection_disable_encryption (struct rspamd_http_connection *conn); -/** - * Increase refcount for shared file (if any) to prevent early memory unlinking - * @param msg - */ -struct rspamd_storage_shmem* rspamd_http_message_shmem_ref (struct rspamd_http_message *msg); -/** - * Decrease external ref for shmem segment associated with a message - * @param msg - */ -void rspamd_http_message_shmem_unref (struct rspamd_storage_shmem *p); - -/** - * Returns message's flags - * @param msg - * @return - */ -guint rspamd_http_message_get_flags (struct rspamd_http_message *msg); - -/** - * Parse HTTP date header and return it as time_t - * @param header HTTP date header - * @param len length of header - * @return time_t or (time_t)-1 in case of error - */ -time_t rspamd_http_parse_date (const gchar *header, gsize len); - -/** - * Create new http connection router and the associated HTTP connection - * @param eh error handler callback - * @param fh finish handler callback - * @param default_fs_path if not NULL try to serve static files from - * the specified directory - * @return - */ -struct rspamd_http_connection_router * rspamd_http_router_new ( - rspamd_http_router_error_handler_t eh, - rspamd_http_router_finish_handler_t fh, - struct timeval *timeout, - struct event_base *base, - const char *default_fs_path, - struct rspamd_keypair_cache *cache); - -/** - * Set encryption key for the HTTP router - * @param router router structure - * @param key opaque key structure - */ -void rspamd_http_router_set_key (struct rspamd_http_connection_router *router, - struct rspamd_cryptobox_keypair *key); - -/** - * Add new path to the router - */ -void rspamd_http_router_add_path (struct rspamd_http_connection_router *router, - const gchar *path, rspamd_http_router_handler_t handler); - -/** - * Add custom header to append to router replies - * @param router - * @param name - * @param value - */ -void rspamd_http_router_add_header (struct rspamd_http_connection_router *router, - const gchar *name, const gchar *value); - -/** - * Sets method to handle unknown request methods - * @param router - * @param handler - */ -void rspamd_http_router_set_unknown_handler (struct rspamd_http_connection_router *router, - rspamd_http_router_handler_t handler); - -/** - * Inserts router headers to the outbound message - * @param router - * @param msg - */ -void rspamd_http_router_insert_headers (struct rspamd_http_connection_router *router, - struct rspamd_http_message *msg); - -struct rspamd_regexp_s; -/** - * Adds new pattern to router, regexp object is refcounted by this function - * @param router - * @param re - * @param handler - */ -void rspamd_http_router_add_regexp (struct rspamd_http_connection_router *router, - struct rspamd_regexp_s *re, rspamd_http_router_handler_t handler); -/** - * Handle new accepted socket - * @param router router object - * @param fd server socket - * @param ud opaque userdata - */ -void rspamd_http_router_handle_socket ( - struct rspamd_http_connection_router *router, - gint fd, - gpointer ud); - -/** - * Free router and all connections associated - * @param router - */ -void rspamd_http_router_free (struct rspamd_http_connection_router *router); - -/** - * Extract arguments from a message's URI contained inside query string decoding - * them if needed - * @param msg HTTP request message - * @return new GHashTable which maps rspamd_ftok_t* to rspamd_ftok_t* - * (table must be freed by a caller) - */ -GHashTable* rspamd_http_message_parse_query (struct rspamd_http_message *msg); - -/** - * Prints HTTP date from `time` to `buf` using standard HTTP date format - * @param buf date buffer - * @param len length of buffer - * @param time time in unix seconds - * @return number of bytes written - */ -glong rspamd_http_date_format (gchar *buf, gsize len, time_t time); - -/** - * Normalize HTTP path removing dot sequences and repeating '/' symbols as - * per rfc3986#section-5.2 - * @param path - * @param len - * @param nlen - */ -void rspamd_http_normalize_path_inplace (gchar *path, guint len, guint *nlen); - #endif /* HTTP_H_ */ diff --git a/src/libutil/http_message.c b/src/libutil/http_message.c new file mode 100644 index 000000000..b090d2f71 --- /dev/null +++ b/src/libutil/http_message.c @@ -0,0 +1,465 @@ +/*- + * 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_message.h" +#include "libutil/http_connection.h" +#include "libutil/http_private.h" +#include "libutil/printf.h" +#include "libutil/logger.h" +#include "unix-std.h" + +struct rspamd_http_message * +rspamd_http_new_message (enum rspamd_http_message_type type) +{ + struct rspamd_http_message *new; + + new = g_malloc0 (sizeof (struct rspamd_http_message)); + + if (type == HTTP_REQUEST) { + new->url = rspamd_fstring_new (); + } + else { + new->url = NULL; + new->code = 200; + } + + new->port = 80; + new->type = type; + new->method = HTTP_INVALID; + + REF_INIT_RETAIN (new, rspamd_http_message_free); + + return new; +} + +struct rspamd_http_message* +rspamd_http_message_from_url (const gchar *url) +{ + struct http_parser_url pu; + struct rspamd_http_message *msg; + const gchar *host, *path; + size_t pathlen, urllen; + guint flags = 0; + + if (url == NULL) { + return NULL; + } + + urllen = strlen (url); + memset (&pu, 0, sizeof (pu)); + + if (http_parser_parse_url (url, urllen, FALSE, &pu) != 0) { + msg_warn ("cannot parse URL: %s", url); + return NULL; + } + + if ((pu.field_set & (1 << UF_HOST)) == 0) { + msg_warn ("no host argument in URL: %s", url); + return NULL; + } + + if ((pu.field_set & (1 << UF_SCHEMA))) { + if (pu.field_data[UF_SCHEMA].len == sizeof ("https") - 1 && + memcmp (url + pu.field_data[UF_SCHEMA].off, "https", 5) == 0) { + flags |= RSPAMD_HTTP_FLAG_SSL; + } + } + + if ((pu.field_set & (1 << UF_PATH)) == 0) { + path = "/"; + pathlen = 1; + } + else { + path = url + pu.field_data[UF_PATH].off; + pathlen = urllen - pu.field_data[UF_PATH].off; + } + + msg = rspamd_http_new_message (HTTP_REQUEST); + host = url + pu.field_data[UF_HOST].off; + msg->flags = flags; + + if ((pu.field_set & (1 << UF_PORT)) != 0) { + msg->port = pu.port; + } + else { + /* XXX: magic constant */ + if (flags & RSPAMD_HTTP_FLAG_SSL) { + msg->port = 443; + } + else { + msg->port = 80; + } + } + + msg->host = rspamd_fstring_new_init (host, pu.field_data[UF_HOST].len); + msg->url = rspamd_fstring_append (msg->url, path, pathlen); + + REF_INIT_RETAIN (msg, rspamd_http_message_free); + + return msg; +} + +const gchar * +rspamd_http_message_get_body (struct rspamd_http_message *msg, + gsize *blen) +{ + const gchar *ret = NULL; + + if (msg->body_buf.len > 0) { + ret = msg->body_buf.begin; + } + + if (blen) { + *blen = msg->body_buf.len; + } + + return ret; +} + +static void +rspamd_http_shname_dtor (void *p) +{ + struct rspamd_storage_shmem *n = p; + +#ifdef HAVE_SANE_SHMEM + shm_unlink (n->shm_name); +#else + unlink (n->shm_name); +#endif + g_free (n->shm_name); + g_free (n); +} + +struct rspamd_storage_shmem * +rspamd_http_message_shmem_ref (struct rspamd_http_message *msg) +{ + if ((msg->flags & RSPAMD_HTTP_FLAG_SHMEM) && msg->body_buf.c.shared.name) { + REF_RETAIN (msg->body_buf.c.shared.name); + return msg->body_buf.c.shared.name; + } + + return NULL; +} + +guint +rspamd_http_message_get_flags (struct rspamd_http_message *msg) +{ + return msg->flags; +} + +void +rspamd_http_message_shmem_unref (struct rspamd_storage_shmem *p) +{ + REF_RELEASE (p); +} + +gboolean +rspamd_http_message_set_body (struct rspamd_http_message *msg, + const gchar *data, gsize len) +{ + union _rspamd_storage_u *storage; + storage = &msg->body_buf.c; + + rspamd_http_message_storage_cleanup (msg); + + if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { + storage->shared.name = g_malloc (sizeof (*storage->shared.name)); + REF_INIT_RETAIN (storage->shared.name, rspamd_http_shname_dtor); +#ifdef HAVE_SANE_SHMEM + #if defined(__DragonFly__) + // DragonFly uses regular files for shm. User rspamd is not allowed to create + // files in the root. + storage->shared.name->shm_name = g_strdup ("/tmp/rhm.XXXXXXXXXXXXXXXXXXXX"); +#else + storage->shared.name->shm_name = g_strdup ("/rhm.XXXXXXXXXXXXXXXXXXXX"); +#endif + storage->shared.shm_fd = rspamd_shmem_mkstemp (storage->shared.name->shm_name); +#else + /* XXX: assume that tempdir is /tmp */ + storage->shared.name->shm_name = g_strdup ("/tmp/rhm.XXXXXXXXXXXXXXXXXXXX"); + storage->shared.shm_fd = mkstemp (storage->shared.name->shm_name); +#endif + + if (storage->shared.shm_fd == -1) { + return FALSE; + } + + if (len != 0 && len != ULLONG_MAX) { + if (ftruncate (storage->shared.shm_fd, len) == -1) { + return FALSE; + } + + msg->body_buf.str = mmap (NULL, len, + PROT_WRITE|PROT_READ, MAP_SHARED, + storage->shared.shm_fd, 0); + + if (msg->body_buf.str == MAP_FAILED) { + return FALSE; + } + + msg->body_buf.begin = msg->body_buf.str; + msg->body_buf.allocated_len = len; + + if (data != NULL) { + memcpy (msg->body_buf.str, data, len); + msg->body_buf.len = len; + } + } + else { + msg->body_buf.len = 0; + msg->body_buf.begin = NULL; + msg->body_buf.str = NULL; + msg->body_buf.allocated_len = 0; + } + } + else { + if (len != 0 && len != ULLONG_MAX) { + if (data == NULL) { + storage->normal = rspamd_fstring_sized_new (len); + msg->body_buf.len = 0; + } + else { + storage->normal = rspamd_fstring_new_init (data, len); + msg->body_buf.len = len; + } + } + else { + storage->normal = rspamd_fstring_new (); + } + + msg->body_buf.begin = storage->normal->str; + msg->body_buf.str = storage->normal->str; + msg->body_buf.allocated_len = storage->normal->allocated; + } + + msg->flags |= RSPAMD_HTTP_FLAG_HAS_BODY; + + return TRUE; +} + +void +rspamd_http_message_set_method (struct rspamd_http_message *msg, + const gchar *method) +{ + gint i; + + /* Linear search: not very efficient method */ + for (i = 0; i < HTTP_METHOD_MAX; i ++) { + if (g_ascii_strcasecmp (method, http_method_str (i)) == 0) { + msg->method = i; + } + } +} + +gboolean +rspamd_http_message_set_body_from_fd (struct rspamd_http_message *msg, + gint fd) +{ + union _rspamd_storage_u *storage; + struct stat st; + + rspamd_http_message_storage_cleanup (msg); + + storage = &msg->body_buf.c; + msg->flags |= RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE; + + storage->shared.shm_fd = dup (fd); + msg->body_buf.str = MAP_FAILED; + + if (storage->shared.shm_fd == -1) { + return FALSE; + } + + if (fstat (storage->shared.shm_fd, &st) == -1) { + return FALSE; + } + + msg->body_buf.str = mmap (NULL, st.st_size, + PROT_READ, MAP_SHARED, + storage->shared.shm_fd, 0); + + if (msg->body_buf.str == MAP_FAILED) { + return FALSE; + } + + msg->body_buf.begin = msg->body_buf.str; + msg->body_buf.len = st.st_size; + msg->body_buf.allocated_len = st.st_size; + + return TRUE; +} + +gboolean +rspamd_http_message_set_body_from_fstring_steal (struct rspamd_http_message *msg, + rspamd_fstring_t *fstr) +{ + union _rspamd_storage_u *storage; + + rspamd_http_message_storage_cleanup (msg); + + storage = &msg->body_buf.c; + msg->flags &= ~(RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE); + + storage->normal = fstr; + msg->body_buf.str = fstr->str; + msg->body_buf.begin = msg->body_buf.str; + msg->body_buf.len = fstr->len; + msg->body_buf.allocated_len = fstr->allocated; + + return TRUE; +} + +gboolean +rspamd_http_message_set_body_from_fstring_copy (struct rspamd_http_message *msg, + const rspamd_fstring_t *fstr) +{ + union _rspamd_storage_u *storage; + + rspamd_http_message_storage_cleanup (msg); + + storage = &msg->body_buf.c; + msg->flags &= ~(RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE); + + storage->normal = rspamd_fstring_new_init (fstr->str, fstr->len); + msg->body_buf.str = storage->normal->str; + msg->body_buf.begin = msg->body_buf.str; + msg->body_buf.len = storage->normal->len; + msg->body_buf.allocated_len = storage->normal->allocated; + + return TRUE; +} + + +gboolean +rspamd_http_message_grow_body (struct rspamd_http_message *msg, gsize len) +{ + struct stat st; + union _rspamd_storage_u *storage; + gsize newlen; + + storage = &msg->body_buf.c; + + if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { + if (storage->shared.shm_fd == -1) { + return FALSE; + } + + if (fstat (storage->shared.shm_fd, &st) == -1) { + return FALSE; + } + + /* Check if we need to grow */ + if ((gsize)st.st_size < msg->body_buf.len + len) { + /* Need to grow */ + newlen = rspamd_fstring_suggest_size (msg->body_buf.len, st.st_size, + len); + /* Unmap as we need another size of segment */ + if (msg->body_buf.str != MAP_FAILED) { + munmap (msg->body_buf.str, st.st_size); + } + + if (ftruncate (storage->shared.shm_fd, newlen) == -1) { + return FALSE; + } + + msg->body_buf.str = mmap (NULL, newlen, + PROT_WRITE|PROT_READ, MAP_SHARED, + storage->shared.shm_fd, 0); + if (msg->body_buf.str == MAP_FAILED) { + return FALSE; + } + + msg->body_buf.begin = msg->body_buf.str; + msg->body_buf.allocated_len = newlen; + } + } + else { + storage->normal = rspamd_fstring_grow (storage->normal, len); + + /* Append might cause realloc */ + msg->body_buf.begin = storage->normal->str; + msg->body_buf.len = storage->normal->len; + msg->body_buf.str = storage->normal->str; + msg->body_buf.allocated_len = storage->normal->allocated; + } + + return TRUE; +} + +gboolean +rspamd_http_message_append_body (struct rspamd_http_message *msg, + const gchar *data, gsize len) +{ + union _rspamd_storage_u *storage; + + storage = &msg->body_buf.c; + + if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { + if (!rspamd_http_message_grow_body (msg, len)) { + return FALSE; + } + + memcpy (msg->body_buf.str + msg->body_buf.len, data, len); + msg->body_buf.len += len; + } + else { + storage->normal = rspamd_fstring_append (storage->normal, data, len); + + /* Append might cause realloc */ + msg->body_buf.begin = storage->normal->str; + msg->body_buf.len = storage->normal->len; + msg->body_buf.str = storage->normal->str; + msg->body_buf.allocated_len = storage->normal->allocated; + } + + return TRUE; +} + +void +rspamd_http_message_storage_cleanup (struct rspamd_http_message *msg) +{ + union _rspamd_storage_u *storage; + struct stat st; + + if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { + storage = &msg->body_buf.c; + + if (storage->shared.shm_fd > 0) { + g_assert (fstat (storage->shared.shm_fd, &st) != -1); + + if (msg->body_buf.str != MAP_FAILED) { + munmap (msg->body_buf.str, st.st_size); + } + + close (storage->shared.shm_fd); + } + + if (storage->shared.name != NULL) { + REF_RELEASE (storage->shared.name); + } + + storage->shared.shm_fd = -1; + msg->body_buf.str = MAP_FAILED; + } + else { + if (msg->body_buf.c.normal) { + rspamd_fstring_free (msg->body_buf.c.normal); + } + + msg->body_buf.c.normal = NULL; + } + + msg->body_buf.len = 0; +} diff --git a/src/libutil/http_message.h b/src/libutil/http_message.h new file mode 100644 index 000000000..c9e6abfce --- /dev/null +++ b/src/libutil/http_message.h @@ -0,0 +1,221 @@ +/*- + * 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. + */ +#ifndef RSPAMD_HTTP_MESSAGE_H +#define RSPAMD_HTTP_MESSAGE_H + +#include "config.h" +#include "keypair.h" +#include "keypairs_cache.h" +#include "fstring.h" +#include "ref.h" + +struct rspamd_http_connection; + +enum rspamd_http_message_type { HTTP_REQUEST = 0, HTTP_RESPONSE }; + +/** + * Extract the current message from a connection to deal with separately + * @param conn + * @return + */ +struct rspamd_http_message * rspamd_http_connection_steal_msg ( + struct rspamd_http_connection *conn); + +/** + * Copy the current message from a connection to deal with separately + * @param conn + * @return + */ +struct rspamd_http_message * rspamd_http_connection_copy_msg ( + struct rspamd_http_message *msg, GError **err); + +/** + * Create new HTTP message + * @param type request or response + * @return new http message + */ +struct rspamd_http_message * rspamd_http_new_message (enum rspamd_http_message_type type); + +/** + * Increase refcount number for an HTTP message + * @param msg message to use + * @return + */ +struct rspamd_http_message * rspamd_http_message_ref (struct rspamd_http_message *msg); +/** + * Decrease number of refcounts for http message + * @param msg + */ +void rspamd_http_message_unref (struct rspamd_http_message *msg); + +/** + * Sets a key for peer + * @param msg + * @param pk + */ +void rspamd_http_message_set_peer_key (struct rspamd_http_message *msg, + struct rspamd_cryptobox_pubkey *pk); +/** + * Create HTTP message from URL + * @param url + * @return new message or NULL + */ +struct rspamd_http_message* rspamd_http_message_from_url (const gchar *url); + +/** + * Returns body for a message + * @param msg + * @param blen pointer where to save body length + * @return pointer to body start + */ +const gchar *rspamd_http_message_get_body (struct rspamd_http_message *msg, + gsize *blen); + +/** + * Set message's body from the string + * @param msg + * @param data + * @param len + * @return TRUE if a message's body has been set + */ +gboolean rspamd_http_message_set_body (struct rspamd_http_message *msg, + const gchar *data, gsize len); + +/** + * Set message's method by name + * @param msg + * @param method + */ +void rspamd_http_message_set_method (struct rspamd_http_message *msg, + const gchar *method); +/** + * Maps fd as message's body + * @param msg + * @param fd + * @return TRUE if a message's body has been set + */ +gboolean rspamd_http_message_set_body_from_fd (struct rspamd_http_message *msg, + gint fd); + +/** + * Uses rspamd_fstring_t as message's body, string is consumed by this operation + * @param msg + * @param fstr + * @return TRUE if a message's body has been set + */ +gboolean rspamd_http_message_set_body_from_fstring_steal (struct rspamd_http_message *msg, + rspamd_fstring_t *fstr); + +/** + * Uses rspamd_fstring_t as message's body, string is copied by this operation + * @param msg + * @param fstr + * @return TRUE if a message's body has been set + */ +gboolean rspamd_http_message_set_body_from_fstring_copy (struct rspamd_http_message *msg, + const rspamd_fstring_t *fstr); + +/** + * Appends data to message's body + * @param msg + * @param data + * @param len + * @return TRUE if a message's body has been set + */ +gboolean rspamd_http_message_append_body (struct rspamd_http_message *msg, + const gchar *data, gsize len); + +/** + * Append a header to http message + * @param rep + * @param name + * @param value + */ +void rspamd_http_message_add_header (struct rspamd_http_message *msg, + const gchar *name, + const gchar *value); + +void rspamd_http_message_add_header_len (struct rspamd_http_message *msg, + const gchar *name, + const gchar *value, + gsize len); + +void rspamd_http_message_add_header_fstr (struct rspamd_http_message *msg, + const gchar *name, + rspamd_fstring_t *value); + +/** + * Search for a specified header in message + * @param msg message + * @param name name of header + */ +const rspamd_ftok_t * rspamd_http_message_find_header ( + struct rspamd_http_message *msg, + const gchar *name); + +/** + * Search for a header that has multiple values + * @param msg + * @param name + * @return list of rspamd_ftok_t * with values + */ +GPtrArray* rspamd_http_message_find_header_multiple ( + struct rspamd_http_message *msg, + const gchar *name); + +/** + * Remove specific header from a message + * @param msg + * @param name + * @return + */ +gboolean rspamd_http_message_remove_header (struct rspamd_http_message *msg, + const gchar *name); + +/** + * Free HTTP message + * @param msg + */ +void rspamd_http_message_free (struct rspamd_http_message *msg); + +/** + * Extract arguments from a message's URI contained inside query string decoding + * them if needed + * @param msg HTTP request message + * @return new GHashTable which maps rspamd_ftok_t* to rspamd_ftok_t* + * (table must be freed by a caller) + */ +GHashTable* rspamd_http_message_parse_query (struct rspamd_http_message *msg); + +/** + * Increase refcount for shared file (if any) to prevent early memory unlinking + * @param msg + */ +struct rspamd_storage_shmem* rspamd_http_message_shmem_ref (struct rspamd_http_message *msg); +/** + * Decrease external ref for shmem segment associated with a message + * @param msg + */ +void rspamd_http_message_shmem_unref (struct rspamd_storage_shmem *p); + +/** + * Returns message's flags + * @param msg + * @return + */ +guint rspamd_http_message_get_flags (struct rspamd_http_message *msg); + +#endif diff --git a/src/libutil/http_private.h b/src/libutil/http_private.h index 970956741..df1233d8b 100644 --- a/src/libutil/http_private.h +++ b/src/libutil/http_private.h @@ -17,6 +17,7 @@ #define SRC_LIBUTIL_HTTP_PRIVATE_H_ #include "http_connection.h" +#include "http_parser.h" #include "str_util.h" #include "ref.h" #define HASH_CASELESS @@ -66,12 +67,18 @@ struct rspamd_http_message { time_t date; time_t last_modified; unsigned port; - enum http_parser_type type; + int type; gint code; enum http_method method; gint flags; ref_entry_t ref; }; +#define HTTP_ERROR http_error_quark () +GQuark http_error_quark (void); + +void rspamd_http_message_storage_cleanup (struct rspamd_http_message *msg); +gboolean rspamd_http_message_grow_body (struct rspamd_http_message *msg, + gsize len); #endif /* SRC_LIBUTIL_HTTP_PRIVATE_H_ */ diff --git a/src/libutil/http_router.c b/src/libutil/http_router.c new file mode 100644 index 000000000..0dc597788 --- /dev/null +++ b/src/libutil/http_router.c @@ -0,0 +1,564 @@ +/*- + * 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 "libutil/http_router.h" +#include "libutil/http_connection.h" +#include "libutil/http_private.h" +#include "libutil/regexp.h" +#include "libutil/printf.h" +#include "libutil/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_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_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->conn->fd, + entry->rt->ptv, + entry->rt->ev_base); + 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)) { + 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->conn->fd, + entry->rt->ptv, entry->rt->ev_base); + + 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->conn->fd, + entry->rt->ptv, + entry->rt->ev_base); +} + + +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; + + 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)) { + guint unnorm_len; + lookup.begin = msg->url->str + u.field_data[UF_PATH].off; + lookup.len = u.field_data[UF_PATH].len; + + rspamd_http_normalize_path_inplace ((gchar *)lookup.begin, + 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) { + 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)); + + 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); + } + } + } + + 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, + struct timeval *timeout, struct event_base *base, + const char *default_fs_path, + struct rspamd_keypair_cache *cache) +{ + struct rspamd_http_connection_router * new; + struct stat st; + + new = g_malloc0 (sizeof (struct rspamd_http_connection_router)); + new->paths = g_hash_table_new_full (rspamd_ftok_icase_hash, + rspamd_ftok_icase_equal, rspamd_fstring_mapped_ftok_free, NULL); + new->regexps = g_ptr_array_new (); + new->conns = NULL; + new->error_handler = eh; + new->finish_handler = fh; + new->ev_base = base; + new->response_headers = g_hash_table_new_full (rspamd_strcase_hash, + rspamd_strcase_equal, g_free, g_free); + + if (timeout) { + new->tv = *timeout; + new->ptv = &new->tv; + } + else { + new->ptv = NULL; + } + + new->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 { + new->default_fs_path = realpath (default_fs_path, NULL); + } + } + } + + new->cache = cache; + + return new; +} + +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 (NULL, + rspamd_http_router_error_handler, + rspamd_http_router_finish_handler, + 0, + RSPAMD_HTTP_SERVER, + router->cache, + NULL); + + if (router->key) { + rspamd_http_connection_set_key (conn->conn, router->key); + } + + rspamd_http_connection_read_message (conn->conn, conn, fd, router->ptv, + router->ev_base); + 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->cache) { + rspamd_keypair_cache_destroy (router->cache); + } + + 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); + } +} \ No newline at end of file diff --git a/src/libutil/http_router.h b/src/libutil/http_router.h new file mode 100644 index 000000000..7440c519e --- /dev/null +++ b/src/libutil/http_router.h @@ -0,0 +1,139 @@ +/*- + * 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. + */ +#ifndef RSPAMD_HTTP_ROUTER_H +#define RSPAMD_HTTP_ROUTER_H + +#include "config.h" +#include "http_connection.h" + +struct rspamd_http_connection_router; +struct rspamd_http_connection_entry; + +typedef int (*rspamd_http_router_handler_t) (struct rspamd_http_connection_entry + *conn_ent, + struct rspamd_http_message *msg); +typedef void (*rspamd_http_router_error_handler_t) (struct rspamd_http_connection_entry *conn_ent, + GError *err); +typedef void (*rspamd_http_router_finish_handler_t) (struct rspamd_http_connection_entry *conn_ent); + + +struct rspamd_http_connection_entry { + struct rspamd_http_connection_router *rt; + struct rspamd_http_connection *conn; + gpointer ud; + gboolean is_reply; + gboolean support_gzip; + struct rspamd_http_connection_entry *prev, *next; +}; + +struct rspamd_http_connection_router { + struct rspamd_http_connection_entry *conns; + GHashTable *paths; + GHashTable *response_headers; + GPtrArray *regexps; + struct timeval tv; + struct timeval *ptv; + struct event_base *ev_base; + struct rspamd_keypair_cache *cache; + gchar *default_fs_path; + rspamd_http_router_handler_t unknown_method_handler; + struct rspamd_cryptobox_keypair *key; + rspamd_http_router_error_handler_t error_handler; + rspamd_http_router_finish_handler_t finish_handler; +}; + +/** + * Create new http connection router and the associated HTTP connection + * @param eh error handler callback + * @param fh finish handler callback + * @param default_fs_path if not NULL try to serve static files from + * the specified directory + * @return + */ +struct rspamd_http_connection_router * rspamd_http_router_new ( + rspamd_http_router_error_handler_t eh, + rspamd_http_router_finish_handler_t fh, + struct timeval *timeout, + struct event_base *base, + const char *default_fs_path, + struct rspamd_keypair_cache *cache); + +/** + * Set encryption key for the HTTP router + * @param router router structure + * @param key opaque key structure + */ +void rspamd_http_router_set_key (struct rspamd_http_connection_router *router, + struct rspamd_cryptobox_keypair *key); + +/** + * Add new path to the router + */ +void rspamd_http_router_add_path (struct rspamd_http_connection_router *router, + const gchar *path, rspamd_http_router_handler_t handler); + +/** + * Add custom header to append to router replies + * @param router + * @param name + * @param value + */ +void rspamd_http_router_add_header (struct rspamd_http_connection_router *router, + const gchar *name, const gchar *value); + +/** + * Sets method to handle unknown request methods + * @param router + * @param handler + */ +void rspamd_http_router_set_unknown_handler (struct rspamd_http_connection_router *router, + rspamd_http_router_handler_t handler); + +/** + * Inserts router headers to the outbound message + * @param router + * @param msg + */ +void rspamd_http_router_insert_headers (struct rspamd_http_connection_router *router, + struct rspamd_http_message *msg); + +struct rspamd_regexp_s; +/** + * Adds new pattern to router, regexp object is refcounted by this function + * @param router + * @param re + * @param handler + */ +void rspamd_http_router_add_regexp (struct rspamd_http_connection_router *router, + struct rspamd_regexp_s *re, rspamd_http_router_handler_t handler); +/** + * Handle new accepted socket + * @param router router object + * @param fd server socket + * @param ud opaque userdata + */ +void rspamd_http_router_handle_socket ( + struct rspamd_http_connection_router *router, + gint fd, + gpointer ud); + +/** + * Free router and all connections associated + * @param router + */ +void rspamd_http_router_free (struct rspamd_http_connection_router *router); + +#endif diff --git a/src/libutil/http_util.c b/src/libutil/http_util.c new file mode 100644 index 000000000..8e220adfa --- /dev/null +++ b/src/libutil/http_util.c @@ -0,0 +1,247 @@ +/*- + * 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 "libutil/http_util.h" +#include "libutil/printf.h" +#include "libutil/util.h" + +static const gchar *http_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +static const gchar *http_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +glong +rspamd_http_date_format (gchar *buf, gsize len, time_t time) +{ + struct tm tms; + + rspamd_gmtime (time, &tms); + + return rspamd_snprintf (buf, len, "%s, %02d %s %4d %02d:%02d:%02d GMT", + http_week[tms.tm_wday], tms.tm_mday, + http_month[tms.tm_mon], tms.tm_year + 1900, + tms.tm_hour, tms.tm_min, tms.tm_sec); +} + +void +rspamd_http_normalize_path_inplace (gchar *path, guint len, guint *nlen) +{ + const gchar *p, *end, *slash = NULL, *dot = NULL; + gchar *o; + enum { + st_normal = 0, + st_got_dot, + st_got_dot_dot, + st_got_slash, + st_got_slash_slash, + } state = st_normal; + + p = path; + end = path + len; + o = path; + + while (p < end) { + switch (state) { + case st_normal: + if (G_UNLIKELY (*p == '/')) { + state = st_got_slash; + slash = p; + } + else if (G_UNLIKELY (*p == '.')) { + state = st_got_dot; + dot = p; + } + else { + *o++ = *p; + } + p ++; + break; + case st_got_slash: + if (G_UNLIKELY (*p == '/')) { + /* Ignore double slash */ + *o++ = *p; + state = st_got_slash_slash; + } + else if (G_UNLIKELY (*p == '.')) { + dot = p; + state = st_got_dot; + } + else { + *o++ = '/'; + *o++ = *p; + slash = NULL; + dot = NULL; + state = st_normal; + } + p ++; + break; + case st_got_slash_slash: + if (G_LIKELY (*p != '/')) { + slash = p - 1; + dot = NULL; + state = st_normal; + continue; + } + p ++; + break; + case st_got_dot: + if (G_UNLIKELY (*p == '/')) { + /* Remove any /./ or ./ paths */ + if (((o > path && *(o - 1) != '/') || (o == path)) && slash) { + /* Preserve one slash */ + *o++ = '/'; + } + + slash = p; + dot = NULL; + /* Ignore last slash */ + state = st_normal; + } + else if (*p == '.') { + /* Double dot character */ + state = st_got_dot_dot; + } + else { + /* We have something like .some or /.some */ + if (dot && p > dot) { + if (slash == dot - 1 && (o > path && *(o - 1) != '/')) { + /* /.blah */ + memmove (o, slash, p - slash); + o += p - slash; + } + else { + memmove (o, dot, p - dot); + o += p - dot; + } + } + + slash = NULL; + dot = NULL; + state = st_normal; + continue; + } + + p ++; + break; + case st_got_dot_dot: + if (*p == '/') { + /* We have something like /../ or ../ */ + if (slash) { + /* We need to remove the last component from o if it is there */ + if (o > path + 2 && *(o - 1) == '/') { + slash = rspamd_memrchr (path, '/', o - path - 2); + } + else if (o > path + 1) { + slash = rspamd_memrchr (path, '/', o - path - 1); + } + else { + slash = NULL; + } + + if (slash) { + o = (gchar *)slash; + } + /* Otherwise we keep these dots */ + slash = p; + state = st_got_slash; + } + else { + /* We have something like bla../, so we need to copy it as is */ + if (o > path && dot && p > dot) { + memmove (o, dot, p - dot); + o += p - dot; + } + + slash = NULL; + dot = NULL; + state = st_normal; + continue; + } + } + else { + /* We have something like ..bla or ... */ + if (slash) { + *o ++ = '/'; + } + + if (dot && p > dot) { + memmove (o, dot, p - dot); + o += p - dot; + } + + slash = NULL; + dot = NULL; + state = st_normal; + continue; + } + + p ++; + break; + } + } + + /* Leftover */ + switch (state) { + case st_got_dot_dot: + /* Trailing .. */ + if (slash) { + /* We need to remove the last component from o if it is there */ + if (o > path + 2 && *(o - 1) == '/') { + slash = rspamd_memrchr (path, '/', o - path - 2); + } + else if (o > path + 1) { + slash = rspamd_memrchr (path, '/', o - path - 1); + } + else { + if (o == path) { + /* Corner case */ + *o++ = '/'; + } + + slash = NULL; + } + + if (slash) { + /* Remove last / */ + o = (gchar *)slash; + } + } + else { + /* Corner case */ + if (o == path) { + *o++ = '/'; + } + else { + if (dot && p > dot) { + memmove (o, dot, p - dot); + o += p - dot; + } + } + } + break; + case st_got_slash: + *o++ = '/'; + break; + default: + if (o > path + 1 && *(o - 1) == '/') { + o --; + } + break; + } + + if (nlen) { + *nlen = (o - path); + } +} \ No newline at end of file diff --git a/src/libutil/http_util.h b/src/libutil/http_util.h new file mode 100644 index 000000000..30d806ae0 --- /dev/null +++ b/src/libutil/http_util.h @@ -0,0 +1,48 @@ +/*- + * 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. + */ + +#ifndef RSPAMD_HTTP_UTIL_H +#define RSPAMD_HTTP_UTIL_H + +#include "config.h" + +/** + * Parse HTTP date header and return it as time_t + * @param header HTTP date header + * @param len length of header + * @return time_t or (time_t)-1 in case of error + */ +time_t rspamd_http_parse_date (const gchar *header, gsize len); + +/** + * Prints HTTP date from `time` to `buf` using standard HTTP date format + * @param buf date buffer + * @param len length of buffer + * @param time time in unix seconds + * @return number of bytes written + */ +glong rspamd_http_date_format (gchar *buf, gsize len, time_t time); + +/** + * Normalize HTTP path removing dot sequences and repeating '/' symbols as + * per rfc3986#section-5.2 + * @param path + * @param len + * @param nlen + */ +void rspamd_http_normalize_path_inplace (gchar *path, guint len, guint *nlen); + +#endif diff --git a/src/plugins/fuzzy_check.c b/src/plugins/fuzzy_check.c index 7edb0168d..760429ba2 100644 --- a/src/plugins/fuzzy_check.c +++ b/src/plugins/fuzzy_check.c @@ -43,6 +43,7 @@ #include "lua/lua_common.h" #include "unix-std.h" #include "libutil/http_private.h" +#include "libutil/http_router.h" #include "libstat/stat_api.h" #include #include diff --git a/src/rspamadm/lua_repl.c b/src/rspamadm/lua_repl.c index 812d3fb96..6248f2aa2 100644 --- a/src/rspamadm/lua_repl.c +++ b/src/rspamadm/lua_repl.c @@ -18,6 +18,7 @@ #include "rspamadm.h" #include "libutil/http_connection.h" #include "libutil/http_private.h" +#include "libutil/http_router.h" #include "printf.h" #include "lua/lua_common.h" #include "lua/lua_thread_pool.h" -- 2.39.5