aboutsummaryrefslogtreecommitdiffstats
path: root/src/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/http.c')
-rw-r--r--src/http.c591
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;
+}