Browse Source

[Rework] Finish http code split and cleanup

tags/1.9.0
Vsevolod Stakhov 5 years ago
parent
commit
a841d419c9

+ 2
- 1
contrib/http-parser/http_parser.c View 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));

+ 1
- 3
contrib/http-parser/http_parser.h View 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,

+ 1
- 0
src/controller.c View 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"

+ 2
- 2
src/fuzzy_storage.c View File

@@ -34,10 +34,10 @@
#include "libcryptobox/keypairs_cache.h"
#include "libcryptobox/keypair.h"
#include "libserver/rspamd_control.h"
#include "libutil/map_private.h"
#include "libutil/hash.h"
#include "libutil/map_private.h"
#include "libutil/http_private.h"
#include "libutil/hash.h"
#include "libutil/http_router.h"
#include "unix-std.h"

#include <math.h>

+ 1
- 0
src/libserver/url.c View 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>


+ 1
- 0
src/libserver/worker_util.c View 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>

+ 3
- 0
src/libutil/CMakeLists.txt View 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

+ 29
- 1243
src/libutil/http_connection.c
File diff suppressed because it is too large
View File


+ 4
- 334
src/libutil/http_connection.h View File

@@ -24,11 +24,14 @@
*/

#include "config.h"
#include "http_parser.h"
#include "keypair.h"
#include "keypairs_cache.h"
#include "fstring.h"
#include "ref.h"
#include "http_message.h"
#include "http_util.h"

#include <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_ */

+ 465
- 0
src/libutil/http_message.c View File

@@ -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;
}

+ 221
- 0
src/libutil/http_message.h View File

@@ -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

+ 8
- 1
src/libutil/http_private.h View 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_ */

+ 564
- 0
src/libutil/http_router.c View File

@@ -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);
}
}

+ 139
- 0
src/libutil/http_router.h View File

@@ -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

+ 247
- 0
src/libutil/http_util.c View File

@@ -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);
}
}

+ 48
- 0
src/libutil/http_util.h View File

@@ -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

+ 1
- 0
src/plugins/fuzzy_check.c View 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>

+ 1
- 0
src/rspamadm/lua_repl.c View 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"

Loading…
Cancel
Save