]> source.dussan.org Git - rspamd.git/commitdiff
[Rework] Finish http code split and cleanup
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Fri, 15 Feb 2019 18:23:40 +0000 (18:23 +0000)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Fri, 15 Feb 2019 18:23:40 +0000 (18:23 +0000)
18 files changed:
contrib/http-parser/http_parser.c
contrib/http-parser/http_parser.h
src/controller.c
src/fuzzy_storage.c
src/libserver/url.c
src/libserver/worker_util.c
src/libutil/CMakeLists.txt
src/libutil/http_connection.c
src/libutil/http_connection.h
src/libutil/http_message.c [new file with mode: 0644]
src/libutil/http_message.h [new file with mode: 0644]
src/libutil/http_private.h
src/libutil/http_router.c [new file with mode: 0644]
src/libutil/http_router.h [new file with mode: 0644]
src/libutil/http_util.c [new file with mode: 0644]
src/libutil/http_util.h [new file with mode: 0644]
src/plugins/fuzzy_check.c
src/rspamadm/lua_repl.c

index 0e75d964b25f61c061947be556bdc282e02e7638..c14ecc034ee94c36fd0e2221fc2da2ff40eb273c 100644 (file)
@@ -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));
index e2a0b4985f28709d3100b07b959f1f5ab344c09c..7b4ac497c4ee2938d803c2f38c47ede1d435ee3b 100644 (file)
@@ -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,
index 6a839b4dfb1c99ecaf2982c36d9f26a795e37a53..b19ac3db7c169298a8ef4977ef8d44efb1595e16 100644 (file)
@@ -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"
index 36b41113dd4cd870cd390f0d16be5d81a7a2c28e..bbd6b36ca4c2a1e68fb219e9e47bf23b1cdca584 100644 (file)
 #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 <math.h>
index 0effe4d6bb30e0d7cbce6952f04162a35f5b6d08..461c232af1d5ef60ebaf782b237ad3c9905f0a74 100644 (file)
@@ -46,6 +46,7 @@
 #include "message.h"
 #include "multipattern.h"
 #include "contrib/uthash/utlist.h"
+#include "contrib/http-parser/http_parser.h"
 #include <unicode/utf8.h>
 #include <unicode/uchar.h>
 
index a0e51192962ae21fa837e965bed32ab97fe17861..e10e25bc0d5b249f819c2e04223fdf94ed82d8c6 100644 (file)
@@ -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 <gperftools/profiler.h>
index aef2ed2685ee70623fd819bac197019853d9a75a..4d4f8c7bdd08db010b029b872f49a68d07e3d8df 100644 (file)
@@ -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
index a82fc24f7a3360701f765aa0348f30951fee7c5a..df283f752a538fbc9fa46a1914d251fcaf34448a 100644 (file)
@@ -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"
 #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 <openssl/err.h>
 
 #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)
 {
index 64a05bf656dce552e235ae974e0bfc82a852e013..1fa1170b2db68d9af1c414f17702b79abb7591d6 100644 (file)
  */
 
 #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 <event.h>
 
 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 (file)
index 0000000..b090d2f
--- /dev/null
@@ -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 (file)
index 0000000..c9e6abf
--- /dev/null
@@ -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
index 970956741fbb4e0e6d8029b740ca5a835bc4adf6..df1233d8b95740b0150377b4d34ff224404dd185 100644 (file)
@@ -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 (file)
index 0000000..0dc5977
--- /dev/null
@@ -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 (file)
index 0000000..7440c51
--- /dev/null
@@ -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 (file)
index 0000000..8e220ad
--- /dev/null
@@ -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 (file)
index 0000000..30d806a
--- /dev/null
@@ -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
index 7edb0168df5da99afcde88fccc132591504eeec3..760429ba21a812f953aefef1abf3184d35a33336 100644 (file)
@@ -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 <math.h>
 #include <src/libmime/message.h>
index 812d3fb96ad742113e6cd31cdc715f1219a77e05..6248f2aa267fa68f241fbb23cd582382adc52f86 100644 (file)
@@ -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"