#define KEEP_ALIVE "keep-alive" | #define KEEP_ALIVE "keep-alive" | ||||
#define CLOSE "close" | #define CLOSE "close" | ||||
enum rspamd_http_message_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; | |||||
static const char *method_strings[] = | static const char *method_strings[] = | ||||
{ | { | ||||
void | 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 */ | void *data = parser->data; /* preserve application data */ | ||||
memset(parser, 0, sizeof(*parser)); | memset(parser, 0, sizeof(*parser)); |
}; | }; | ||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; | |||||
/* Flag values for http_parser.flags field */ | /* Flag values for http_parser.flags field */ | ||||
enum flags | enum flags | ||||
*/ | */ | ||||
unsigned long http_parser_version(void); | 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, | size_t http_parser_execute(http_parser *parser, |
#include "libutil/map_helpers.h" | #include "libutil/map_helpers.h" | ||||
#include "libutil/map_private.h" | #include "libutil/map_private.h" | ||||
#include "libutil/http_private.h" | #include "libutil/http_private.h" | ||||
#include "libutil/http_router.h" | |||||
#include "libstat/stat_api.h" | #include "libstat/stat_api.h" | ||||
#include "rspamd.h" | #include "rspamd.h" | ||||
#include "libserver/worker_util.h" | #include "libserver/worker_util.h" |
#include "libcryptobox/keypairs_cache.h" | #include "libcryptobox/keypairs_cache.h" | ||||
#include "libcryptobox/keypair.h" | #include "libcryptobox/keypair.h" | ||||
#include "libserver/rspamd_control.h" | #include "libserver/rspamd_control.h" | ||||
#include "libutil/map_private.h" | |||||
#include "libutil/hash.h" | #include "libutil/hash.h" | ||||
#include "libutil/map_private.h" | |||||
#include "libutil/http_private.h" | #include "libutil/http_private.h" | ||||
#include "libutil/hash.h" | |||||
#include "libutil/http_router.h" | |||||
#include "unix-std.h" | #include "unix-std.h" | ||||
#include <math.h> | #include <math.h> |
#include "message.h" | #include "message.h" | ||||
#include "multipattern.h" | #include "multipattern.h" | ||||
#include "contrib/uthash/utlist.h" | #include "contrib/uthash/utlist.h" | ||||
#include "contrib/http-parser/http_parser.h" | |||||
#include <unicode/utf8.h> | #include <unicode/utf8.h> | ||||
#include <unicode/uchar.h> | #include <unicode/uchar.h> | ||||
#include "libutil/map.h" | #include "libutil/map.h" | ||||
#include "libutil/map_private.h" | #include "libutil/map_private.h" | ||||
#include "libutil/http_private.h" | #include "libutil/http_private.h" | ||||
#include "libutil/http_router.h" | |||||
#ifdef WITH_GPERF_TOOLS | #ifdef WITH_GPERF_TOOLS | ||||
#include <gperftools/profiler.h> | #include <gperftools/profiler.h> |
${CMAKE_CURRENT_SOURCE_DIR}/expression.c | ${CMAKE_CURRENT_SOURCE_DIR}/expression.c | ||||
${CMAKE_CURRENT_SOURCE_DIR}/fstring.c | ${CMAKE_CURRENT_SOURCE_DIR}/fstring.c | ||||
${CMAKE_CURRENT_SOURCE_DIR}/hash.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_connection.c | ||||
${CMAKE_CURRENT_SOURCE_DIR}/http_router.c | |||||
${CMAKE_CURRENT_SOURCE_DIR}/logger.c | ${CMAKE_CURRENT_SOURCE_DIR}/logger.c | ||||
${CMAKE_CURRENT_SOURCE_DIR}/map.c | ${CMAKE_CURRENT_SOURCE_DIR}/map.c | ||||
${CMAKE_CURRENT_SOURCE_DIR}/map_helpers.c | ${CMAKE_CURRENT_SOURCE_DIR}/map_helpers.c |
*/ | */ | ||||
#include "config.h" | #include "config.h" | ||||
#include "http_parser.h" | |||||
#include "keypair.h" | #include "keypair.h" | ||||
#include "keypairs_cache.h" | #include "keypairs_cache.h" | ||||
#include "fstring.h" | #include "fstring.h" | ||||
#include "ref.h" | #include "ref.h" | ||||
#include "http_message.h" | |||||
#include "http_util.h" | |||||
#include <event.h> | |||||
enum rspamd_http_connection_type { | enum rspamd_http_connection_type { | ||||
RSPAMD_HTTP_SERVER, | RSPAMD_HTTP_SERVER, | ||||
typedef int (*rspamd_http_finish_handler_t) (struct rspamd_http_connection *conn, | typedef int (*rspamd_http_finish_handler_t) (struct rspamd_http_connection *conn, | ||||
struct rspamd_http_message *msg); | 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 | * HTTP connection structure | ||||
*/ | */ | ||||
gint ref; | 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 | * Create new http connection | ||||
* @param handler_t handler_t for body | * @param handler_t handler_t for body | ||||
*/ | */ | ||||
void rspamd_http_connection_reset (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 | * Sets global maximum size for HTTP message being processed | ||||
* @param sz | * @param sz | ||||
void rspamd_http_connection_disable_encryption (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_ */ | #endif /* HTTP_H_ */ |
/*- | |||||
* 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; | |||||
} |
/*- | |||||
* 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 |
#define SRC_LIBUTIL_HTTP_PRIVATE_H_ | #define SRC_LIBUTIL_HTTP_PRIVATE_H_ | ||||
#include "http_connection.h" | #include "http_connection.h" | ||||
#include "http_parser.h" | |||||
#include "str_util.h" | #include "str_util.h" | ||||
#include "ref.h" | #include "ref.h" | ||||
#define HASH_CASELESS | #define HASH_CASELESS | ||||
time_t date; | time_t date; | ||||
time_t last_modified; | time_t last_modified; | ||||
unsigned port; | unsigned port; | ||||
enum http_parser_type type; | |||||
int type; | |||||
gint code; | gint code; | ||||
enum http_method method; | enum http_method method; | ||||
gint flags; | gint flags; | ||||
ref_entry_t ref; | 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_ */ | #endif /* SRC_LIBUTIL_HTTP_PRIVATE_H_ */ |
/*- | |||||
* 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); | |||||
} | |||||
} |
/*- | |||||
* 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 |
/*- | |||||
* 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); | |||||
} | |||||
} |
/*- | |||||
* 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 |
#include "lua/lua_common.h" | #include "lua/lua_common.h" | ||||
#include "unix-std.h" | #include "unix-std.h" | ||||
#include "libutil/http_private.h" | #include "libutil/http_private.h" | ||||
#include "libutil/http_router.h" | |||||
#include "libstat/stat_api.h" | #include "libstat/stat_api.h" | ||||
#include <math.h> | #include <math.h> | ||||
#include <src/libmime/message.h> | #include <src/libmime/message.h> |
#include "rspamadm.h" | #include "rspamadm.h" | ||||
#include "libutil/http_connection.h" | #include "libutil/http_connection.h" | ||||
#include "libutil/http_private.h" | #include "libutil/http_private.h" | ||||
#include "libutil/http_router.h" | |||||
#include "printf.h" | #include "printf.h" | ||||
#include "lua/lua_common.h" | #include "lua/lua_common.h" | ||||
#include "lua/lua_thread_pool.h" | #include "lua/lua_thread_pool.h" |