diff options
Diffstat (limited to 'src/http.c')
-rw-r--r-- | src/http.c | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/http.c b/src/http.c new file mode 100644 index 000000000..4d036428b --- /dev/null +++ b/src/http.c @@ -0,0 +1,591 @@ +/* Copyright (c) 2014, Vsevolod Stakhov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "http.h" +#include "utlist.h" + +struct rspamd_http_server_private { + GString *buf; + gboolean new_header; + struct rspamd_http_header *header; + struct http_parser parser; + struct http_parser_settings parser_cb; + struct event ev; + struct timeval tv; + struct timeval *ptv; + gboolean in_body; + struct rspamd_http_request *req; +}; + +#define HTTP_ERROR http_error_quark () +GQuark +http_error_quark (void) +{ + return g_quark_from_static_string ("http-error-quark"); +} + +static inline void +rspamd_http_check_date (struct rspamd_http_server_private *priv) +{ + if (g_ascii_strcasecmp (priv->header->name->str, "date") == 0) { + priv->req->date = rspamd_http_parse_date (priv->header->value->str, + priv->header->value->len); + } +} + +static gint +rspamd_http_on_url (http_parser* parser, const gchar *at, size_t length) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)parser->data; + struct rspamd_http_server_private *priv; + + priv = serv->priv; + + g_string_append_len (priv->req->url, at, length); + + return 0; +} + +static gint +rspamd_http_on_header_field (http_parser* parser, const gchar *at, size_t length) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)parser->data; + struct rspamd_http_server_private *priv; + + priv = serv->priv; + + if (priv->header == NULL) { + priv->header = g_slice_alloc (sizeof (struct rspamd_http_header)); + priv->header->name = g_string_sized_new (32); + priv->header->value = g_string_sized_new (32); + } + else if (priv->new_header) { + LL_PREPEND (priv->req->headers, priv->header); + rspamd_http_check_date (priv); + priv->header = g_slice_alloc (sizeof (struct rspamd_http_header)); + priv->header->name = g_string_sized_new (32); + priv->header->value = g_string_sized_new (32); + } + + priv->new_header = FALSE; + g_string_append_len (priv->header->name, at, length); + + return 0; +} + +static gint +rspamd_http_on_header_value (http_parser* parser, const gchar *at, size_t length) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)parser->data; + struct rspamd_http_server_private *priv; + + priv = serv->priv; + + if (priv->header == NULL) { + /* Should not happen */ + return -1; + } + + priv->new_header = TRUE; + g_string_append_len (priv->header->value, at, length); + + return 0; +} + +static int +rspamd_http_on_headers_complete (http_parser* parser) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)parser->data; + struct rspamd_http_server_private *priv; + + priv = serv->priv; + + if (priv->header != NULL) { + LL_PREPEND (priv->req->headers, priv->header); + rspamd_http_check_date (priv); + priv->header = NULL; + } + + priv->in_body = TRUE; + if (parser->content_length != 0 && parser->content_length != ULLONG_MAX) { + priv->req->body = g_string_sized_new (parser->content_length); + } + else { + priv->req->body = g_string_sized_new (BUFSIZ); + } + + return 0; +} + +static int +rspamd_http_on_body (http_parser* parser, const gchar *at, size_t length) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)parser->data; + + if (serv->opts & RSPAMD_HTTP_BODY_PARTIAL) { + return (serv->body_handler (serv, serv->priv->req, at, length)); + } + + return 0; +} + +static int +rspamd_http_on_message_complete (http_parser* parser) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)parser->data; + struct rspamd_http_server_private *priv; + int ret; + + priv = serv->priv; + + if (serv->opts & RSPAMD_HTTP_BODY_PARTIAL) { + ret = serv->body_handler (serv, priv->req, NULL, 0); + } + else { + ret = serv->body_handler (serv, priv->req, priv->req->body->str, priv->req->body->len); + } + + return ret; +} + +static void +rspamd_http_event_handler (int fd, short what, gpointer ud) +{ + struct rspamd_http_server *serv = (struct rspamd_http_server *)ud; + struct rspamd_http_server_private *priv; + GString *buf; + gchar *start; + gssize r; + gint64 remain; + GError *err; + + priv = serv->priv; + if (priv->in_body) { + buf = priv->req->body; + } + else { + priv->buf->len = 0; + buf = priv->buf; + } + + remain = buf->allocated_len - buf->len; + if (remain <= 0) { + /* Expand string */ + g_string_set_size (buf, buf->allocated_len * 2); + remain = buf->allocated_len - buf->len; + } + start = buf->str + buf->len; + r = read (fd, start, remain); + if (r == -1) { + err = g_error_new (HTTP_ERROR, errno, "IO read error: %s", strerror (errno)); + serv->error_handler (serv, err); + g_error_free (err); + } + else { + buf->len += r; + if (http_parser_execute (&priv->parser, &priv->parser_cb, start, r) != (size_t)r) { + err = g_error_new (HTTP_ERROR, priv->parser.http_errno, + "HTTP parser error: %s", http_errno_description (priv->parser.http_errno)); + serv->error_handler (serv, err); + g_error_free (err); + } + } +} + +struct rspamd_http_server* +rspamd_http_server_new (rspamd_http_body_handler body_handler, + rspamd_http_error_handler error_handler, enum rspamd_http_options opts) +{ + struct rspamd_http_server *new; + struct rspamd_http_server_private *priv; + + new = g_slice_alloc0 (sizeof (struct rspamd_http_server)); + new->opts = opts; + new->body_handler = body_handler; + new->error_handler = error_handler; + new->fd = -1; + + /* Init priv */ + priv = g_slice_alloc0 (sizeof (struct rspamd_http_server_private)); + http_parser_init (&priv->parser, HTTP_REQUEST); + priv->parser.data = new; + priv->parser_cb.on_url = rspamd_http_on_url; + priv->parser_cb.on_header_field = rspamd_http_on_header_field; + priv->parser_cb.on_header_value = rspamd_http_on_header_value; + priv->parser_cb.on_headers_complete = rspamd_http_on_headers_complete; + priv->parser_cb.on_body = rspamd_http_on_body; + priv->parser_cb.on_message_complete = rspamd_http_on_message_complete; + + new->priv = priv; + + return new; +} + +void +rspamd_http_server_reset (struct rspamd_http_server *server) +{ + struct rspamd_http_server_private *priv; + struct rspamd_http_request *req; + struct rspamd_http_header *hdr, *tmp_hdr; + + priv = server->priv; + req = priv->req; + + /* Clear request */ + if (req != NULL) { + LL_FOREACH_SAFE(req->headers, hdr, tmp_hdr) { + g_string_free (hdr->name, TRUE); + g_string_free (hdr->value, TRUE); + g_slice_free1 (sizeof (struct rspamd_http_header), hdr); + } + g_string_free (req->body, TRUE); + g_string_free (req->url, TRUE); + g_slice_free1 (sizeof (struct rspamd_http_request), req); + priv->req = NULL; + } + + /* Clear priv */ + event_del (&priv->ev); + if (priv->buf != NULL) { + g_string_free (priv->buf, TRUE); + priv->buf = NULL; + } + + /* Clear server itself */ + if (server->fd != -1) { + close (server->fd); + } +} + +void +rspamd_http_server_free (struct rspamd_http_server *server) +{ + struct rspamd_http_server_private *priv; + + priv = server->priv; + rspamd_http_server_reset (server); + g_slice_free1 (sizeof (struct rspamd_http_server_private), priv); + g_slice_free1 (sizeof (struct rspamd_http_server), server); +} + +void +rspamd_http_server_handle_request (struct rspamd_http_server *server, + gpointer ud, gint fd, struct timeval *timeout, struct event_base *base) +{ + struct rspamd_http_server_private *priv = server->priv; + struct rspamd_http_request *req; + + server->fd = fd; + server->ud = ud; + req = g_slice_alloc (sizeof (struct rspamd_http_request)); + req->url = g_string_sized_new (32); + req->headers = NULL; + req->date = 0; + priv->req = req; + + if (timeout == NULL) { + priv->ptv = NULL; + } + else { + memcpy (&priv->tv, timeout, sizeof (struct timeval)); + priv->ptv = &priv->tv; + } + priv->header = NULL; + priv->buf = g_string_sized_new (BUFSIZ); + priv->in_body = FALSE; + priv->new_header = TRUE; + + event_set (&priv->ev, fd, EV_READ | EV_PERSIST, rspamd_http_event_handler, server); + event_base_set (base, &priv->ev); + event_add (&priv->ev, priv->ptv); +} + + +/* + * Obtained from nginx + * Copyright (C) Igor Sysoev + */ +static guint mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + +time_t +rspamd_http_parse_date (const gchar *header, gsize len) +{ + const gchar *p, *end; + gint month; + guint day, year, hour, min, sec; + guint64 time; + enum { + no = 0, rfc822, /* Tue, 10 Nov 2002 23:50:13 */ + rfc850, /* Tuesday, 10-Dec-02 23:50:13 */ + isoc /* Tue Dec 10 23:50:13 2002 */ + } fmt; + + fmt = 0; + if (len > 0) { + end = header + len; + } + else { + end = header + strlen (header); + } + +#if (NGX_SUPPRESS_WARN) + day = 32; + year = 2038; +#endif + + for (p = header; p < end; p++) { + if (*p == ',') { + break; + } + + if (*p == ' ') { + fmt = isoc; + break; + } + } + + for (p++; p < end; p++) + if (*p != ' ') { + break; + } + + if (end - p < 18) { + return (time_t)-1; + } + + if (fmt != isoc) { + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { + return (time_t)-1; + } + + day = (*p - '0') * 10 + *(p + 1) - '0'; + p += 2; + + if (*p == ' ') { + if (end - p < 18) { + return (time_t)-1; + } + fmt = rfc822; + + } + else if (*p == '-') { + fmt = rfc850; + + } + else { + return (time_t)-1; + } + + p++; + } + + switch (*p) { + + case 'J': + month = *(p + 1) == 'a' ? 0 : *(p + 2) == 'n' ? 5 : 6; + break; + + case 'F': + month = 1; + break; + + case 'M': + month = *(p + 2) == 'r' ? 2 : 4; + break; + + case 'A': + month = *(p + 1) == 'p' ? 3 : 7; + break; + + case 'S': + month = 8; + break; + + case 'O': + month = 9; + break; + + case 'N': + month = 10; + break; + + case 'D': + month = 11; + break; + + default: + return (time_t)-1; + } + + p += 3; + + if ((fmt == rfc822 && *p != ' ') || (fmt == rfc850 && *p != '-')) { + return (time_t)-1; + } + + p++; + + if (fmt == rfc822) { + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9' + || *(p + 2) < '0' || *(p + 2) > '9' || *(p + 3) < '0' + || *(p + 3) > '9') { + return (time_t)-1; + } + + year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100 + + (*(p + 2) - '0') * 10 + *(p + 3) - '0'; + p += 4; + + } + else if (fmt == rfc850) { + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { + return (time_t)-1; + } + + year = (*p - '0') * 10 + *(p + 1) - '0'; + year += (year < 70) ? 2000 : 1900; + p += 2; + } + + if (fmt == isoc) { + if (*p == ' ') { + p++; + } + + if (*p < '0' || *p > '9') { + return (time_t)-1; + } + + day = *p++ - '0'; + + if (*p != ' ') { + if (*p < '0' || *p > '9') { + return (time_t)-1; + } + + day = day * 10 + *p++ - '0'; + } + + if (end - p < 14) { + return (time_t)-1; + } + } + + if (*p++ != ' ') { + return (time_t)-1; + } + + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { + return (time_t)-1; + } + + hour = (*p - '0') * 10 + *(p + 1) - '0'; + p += 2; + + if (*p++ != ':') { + return (time_t)-1; + } + + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { + return (time_t)-1; + } + + min = (*p - '0') * 10 + *(p + 1) - '0'; + p += 2; + + if (*p++ != ':') { + return (time_t)-1; + } + + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { + return (time_t)-1; + } + + sec = (*p - '0') * 10 + *(p + 1) - '0'; + + if (fmt == isoc) { + p += 2; + + if (*p++ != ' ') { + return (time_t)-1; + } + + if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9' + || *(p + 2) < '0' || *(p + 2) > '9' || *(p + 3) < '0' + || *(p + 3) > '9') { + return (time_t)-1; + } + + year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100 + + (*(p + 2) - '0') * 10 + *(p + 3) - '0'; + } + + if (hour > 23 || min > 59 || sec > 59) { + return (time_t)-1; + } + + if (day == 29 && month == 1) { + if ((year & 3) || ((year % 100 == 0) && (year % 400) != 0)) { + return (time_t)-1; + } + + } + else if (day > mday[month]) { + return (time_t)-1; + } + + /* + * shift new year to March 1 and start months from 1 (not 0), + * it is needed for Gauss' formula + */ + + if (--month <= 0) { + month += 12; + year -= 1; + } + + /* Gauss' formula for Gregorian days since March 1, 1 BC */ + + time = (guint64) ( + /* days in years including leap years since March 1, 1 BC */ + + 365 * year + year / 4 - year / 100 + year / 400 + + /* days before the month */ + + + 367 * month / 12 - 30 + + /* days before the day */ + + + day - 1 + + /* + * 719527 days were between March 1, 1 BC and March 1, 1970, + * 31 and 28 days were in January and February 1970 + */ + + - 719527 + 31 + 28) * 86400 + hour * 3600 + min * 60 + sec; + + return (time_t) time; +} |