aboutsummaryrefslogtreecommitdiffstats
path: root/src/libutil/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libutil/http.c')
-rw-r--r--src/libutil/http.c1222
1 files changed, 1222 insertions, 0 deletions
diff --git a/src/libutil/http.c b/src/libutil/http.c
new file mode 100644
index 000000000..491468352
--- /dev/null
+++ b/src/libutil/http.c
@@ -0,0 +1,1222 @@
+/* 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"
+#include "util.h"
+#include "printf.h"
+#include "logger.h"
+
+struct rspamd_http_connection_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;
+ struct rspamd_http_message *msg;
+ struct iovec *out;
+ guint outlen;
+ gsize wr_pos;
+ gsize wr_total;
+};
+
+enum http_magic_type {
+ HTTP_MAGIC_PLAIN = 0,
+ HTTP_MAGIC_HTML,
+ HTTP_MAGIC_CSS,
+ HTTP_MAGIC_JS,
+ HTTP_MAGIC_PNG,
+ HTTP_MAGIC_JPG
+};
+
+static const struct _rspamd_http_magic {
+ const gchar *ext;
+ const gchar *ct;
+} http_file_types[] = {
+ [HTTP_MAGIC_PLAIN] = { "txt", "text/plain" },
+ [HTTP_MAGIC_HTML] = { "html", "text/html" },
+ [HTTP_MAGIC_CSS] = { "css", "text/css" },
+ [HTTP_MAGIC_JS] = { "js", "application/javascript" },
+ [HTTP_MAGIC_PNG] = { "png", "image/png" },
+ [HTTP_MAGIC_JPG] = { "jpg", "image/jpeg" },
+};
+
+static gchar *http_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+static gchar *http_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+
+#define HTTP_ERROR http_error_quark ()
+GQuark
+http_error_quark (void)
+{
+ return g_quark_from_static_string ("http-error-quark");
+}
+
+static const gchar *
+rspamd_http_code_to_str (gint code)
+{
+ if (code == 200) {
+ return "OK";
+ }
+ else if (code == 404) {
+ return "Not found";
+ }
+ else if (code == 403 || code == 401) {
+ return "Not authorized";
+ }
+ else if (code >= 400 && code < 500) {
+ return "Bad request";
+ }
+ else if (code >= 300 && code < 400) {
+ return "See Other";
+ }
+ else if (code >= 500 && code < 600) {
+ return "Internal server error";
+ }
+
+ return "Unknown error";
+}
+
+/*
+ * 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;
+}
+
+static inline void
+rspamd_http_check_date (struct rspamd_http_connection_private *priv)
+{
+ if (g_ascii_strcasecmp (priv->header->name->str, "date") == 0) {
+ priv->msg->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_connection *conn = (struct rspamd_http_connection *)parser->data;
+ struct rspamd_http_connection_private *priv;
+
+ priv = conn->priv;
+
+ g_string_append_len (priv->msg->url, at, length);
+
+ return 0;
+}
+
+static gint
+rspamd_http_on_header_field (http_parser* parser, const gchar *at, size_t length)
+{
+ struct rspamd_http_connection *conn = (struct rspamd_http_connection *)parser->data;
+ struct rspamd_http_connection_private *priv;
+
+ priv = conn->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) {
+ DL_APPEND (priv->msg->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_connection *conn = (struct rspamd_http_connection *)parser->data;
+ struct rspamd_http_connection_private *priv;
+
+ priv = conn->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_connection *conn = (struct rspamd_http_connection *)parser->data;
+ struct rspamd_http_connection_private *priv;
+
+ priv = conn->priv;
+
+ if (priv->header != NULL) {
+ DL_APPEND (priv->msg->headers, priv->header);
+ rspamd_http_check_date (priv);
+ priv->header = NULL;
+ }
+
+ if (parser->content_length != 0 && parser->content_length != ULLONG_MAX) {
+ priv->msg->body = g_string_sized_new (parser->content_length + 1);
+ }
+ else {
+ priv->msg->body = g_string_sized_new (BUFSIZ);
+ }
+
+ priv->msg->method = parser->method;
+
+ return 0;
+}
+
+static int
+rspamd_http_on_body (http_parser* parser, const gchar *at, size_t length)
+{
+ struct rspamd_http_connection *conn = (struct rspamd_http_connection *)parser->data;
+ struct rspamd_http_connection_private *priv;
+
+ priv = conn->priv;
+
+ g_string_append_len (priv->msg->body, at, length);
+
+ if (conn->opts & RSPAMD_HTTP_BODY_PARTIAL) {
+ return (conn->body_handler (conn, priv->msg, at, length));
+ }
+
+ return 0;
+}
+
+static int
+rspamd_http_on_message_complete (http_parser* parser)
+{
+ struct rspamd_http_connection *conn = (struct rspamd_http_connection *)parser->data;
+ struct rspamd_http_connection_private *priv;
+ int ret = 0;
+
+ priv = conn->priv;
+
+ if (conn->body_handler != NULL) {
+ rspamd_http_connection_ref (conn);
+ if (conn->opts & RSPAMD_HTTP_BODY_PARTIAL) {
+ ret = conn->body_handler (conn, priv->msg, NULL, 0);
+ }
+ else {
+ ret = conn->body_handler (conn, priv->msg, priv->msg->body->str, priv->msg->body->len);
+ }
+ rspamd_http_connection_unref (conn);
+ }
+
+ if (ret == 0) {
+ rspamd_http_connection_ref (conn);
+ ret = conn->finish_handler (conn, priv->msg);
+ rspamd_http_connection_unref (conn);
+ }
+
+ return ret;
+}
+
+static void
+rspamd_http_write_helper (struct rspamd_http_connection *conn)
+{
+ struct rspamd_http_connection_private *priv;
+ struct iovec *start;
+ guint niov, i;
+ gsize remain;
+ gssize r;
+ GError *err;
+
+ priv = conn->priv;
+
+ if (priv->wr_pos == priv->wr_total) {
+ rspamd_http_connection_ref (conn);
+ conn->finish_handler (conn, priv->msg);
+ rspamd_http_connection_unref (conn);
+ return;
+ }
+
+ start = &priv->out[0];
+ niov = priv->outlen;
+ remain = priv->wr_pos;
+ for (i = 0; i < priv->outlen && remain > 0; i ++) {
+ /* Find out the first iov required */
+ start = &priv->out[i];
+ if (start->iov_len <= remain) {
+ remain -= start->iov_len;
+ start = &priv->out[i + 1];
+ niov --;
+ }
+ else {
+ start->iov_base = (void *)((char *)start->iov_base + remain);
+ start->iov_len -= remain;
+ remain = 0;
+ }
+ }
+
+ r = writev (conn->fd, start, MIN (IOV_MAX, niov));
+
+ if (r == -1) {
+ err = g_error_new (HTTP_ERROR, errno, "IO write error: %s", strerror (errno));
+ rspamd_http_connection_ref (conn);
+ conn->error_handler (conn, err);
+ rspamd_http_connection_unref (conn);
+ g_error_free (err);
+ return;
+ }
+ else {
+ priv->wr_pos += r;
+ }
+
+ if (priv->wr_pos >= priv->wr_total) {
+ rspamd_http_connection_ref (conn);
+ conn->finish_handler (conn, priv->msg);
+ rspamd_http_connection_unref (conn);
+ }
+ else {
+ /* Want to write more */
+ event_add (&priv->ev, priv->ptv);
+ }
+}
+
+static void
+rspamd_http_event_handler (int fd, short what, gpointer ud)
+{
+ struct rspamd_http_connection *conn = (struct rspamd_http_connection *)ud;
+ struct rspamd_http_connection_private *priv;
+ GString *buf;
+ gssize r;
+ GError *err;
+
+ priv = conn->priv;
+ buf = priv->buf;
+
+ if (what == EV_READ) {
+ r = read (fd, buf->str, buf->allocated_len);
+ if (r == -1) {
+ err = g_error_new (HTTP_ERROR, errno, "IO read error: %s", strerror (errno));
+ conn->error_handler (conn, err);
+ g_error_free (err);
+ return;
+ }
+ else {
+ buf->len = r;
+ rspamd_http_connection_ref (conn);
+ if (http_parser_execute (&priv->parser, &priv->parser_cb, buf->str, 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));
+ conn->error_handler (conn, err);
+ g_error_free (err);
+ rspamd_http_connection_unref (conn);
+ return;
+ }
+ rspamd_http_connection_unref (conn);
+ }
+ }
+ else if (what == EV_TIMEOUT) {
+ err = g_error_new (HTTP_ERROR, ETIMEDOUT,
+ "IO timeout");
+ rspamd_http_connection_ref (conn);
+ conn->error_handler (conn, err);
+ rspamd_http_connection_unref (conn);
+ g_error_free (err);
+ return;
+ }
+ else if (what == EV_WRITE) {
+ rspamd_http_write_helper (conn);
+ }
+}
+
+struct rspamd_http_connection*
+rspamd_http_connection_new (rspamd_http_body_handler_t body_handler,
+ rspamd_http_error_handler_t error_handler,
+ rspamd_http_finish_handler_t finish_handler,
+ enum rspamd_http_options opts,
+ enum rspamd_http_connection_type type)
+{
+ struct rspamd_http_connection *new;
+ struct rspamd_http_connection_private *priv;
+
+ if (error_handler == NULL || finish_handler == NULL) {
+ return NULL;
+ }
+
+ new = g_slice_alloc0 (sizeof (struct rspamd_http_connection));
+ new->opts = opts;
+ new->type = type;
+ new->body_handler = body_handler;
+ new->error_handler = error_handler;
+ new->finish_handler = finish_handler;
+ new->fd = -1;
+ new->ref = 1;
+
+ /* Init priv */
+ priv = g_slice_alloc0 (sizeof (struct rspamd_http_connection_private));
+ http_parser_init (&priv->parser, type == RSPAMD_HTTP_SERVER ? HTTP_REQUEST : HTTP_RESPONSE);
+ 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_connection_reset (struct rspamd_http_connection *conn)
+{
+ struct rspamd_http_connection_private *priv;
+ struct rspamd_http_message *msg;
+
+ priv = conn->priv;
+ msg = priv->msg;
+
+ /* Clear request */
+ if (msg != NULL) {
+ rspamd_http_message_free (msg);
+ priv->msg = NULL;
+ }
+
+ /* Clear priv */
+ event_del (&priv->ev);
+ if (priv->buf != NULL) {
+ g_string_free (priv->buf, TRUE);
+ priv->buf = NULL;
+ }
+ if (priv->out != NULL) {
+ g_slice_free1 (sizeof (struct iovec) * priv->outlen, priv->out);
+ priv->out = NULL;
+ }
+}
+
+void
+rspamd_http_connection_free (struct rspamd_http_connection *conn)
+{
+ struct rspamd_http_connection_private *priv;
+
+ priv = conn->priv;
+ rspamd_http_connection_reset (conn);
+ g_slice_free1 (sizeof (struct rspamd_http_connection_private), priv);
+ g_slice_free1 (sizeof (struct rspamd_http_connection), conn);
+}
+
+void
+rspamd_http_connection_read_message (struct rspamd_http_connection *conn,
+ gpointer ud, gint fd, struct timeval *timeout, struct event_base *base)
+{
+ struct rspamd_http_connection_private *priv = conn->priv;
+ struct rspamd_http_message *req;
+
+ conn->fd = fd;
+ conn->ud = ud;
+ req = rspamd_http_new_message (conn->type == RSPAMD_HTTP_SERVER ? HTTP_REQUEST : HTTP_RESPONSE);
+ priv->msg = 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->new_header = TRUE;
+
+ event_set (&priv->ev, fd, EV_READ | EV_PERSIST, rspamd_http_event_handler, conn);
+ event_base_set (base, &priv->ev);
+ event_add (&priv->ev, priv->ptv);
+}
+
+void
+rspamd_http_connection_write_message (struct rspamd_http_connection *conn,
+ struct rspamd_http_message *msg, const gchar *host, const gchar *mime_type,
+ gpointer ud, gint fd, struct timeval *timeout, struct event_base *base)
+{
+ struct rspamd_http_connection_private *priv = conn->priv;
+ struct rspamd_http_header *hdr;
+ struct tm t, *ptm;
+ gchar datebuf[64], *pbody;
+ gint i;
+ gsize bodylen;
+
+ conn->fd = fd;
+ conn->ud = ud;
+ priv->msg = msg;
+
+ 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 (128);
+
+ if (msg->method < HTTP_SYMBOLS) {
+ if (msg->body == NULL || msg->body->len == 0) {
+ pbody = NULL;
+ bodylen = 0;
+ priv->outlen = 2;
+ msg->method = HTTP_GET;
+ }
+ else {
+ pbody = msg->body->str;
+ bodylen = msg->body->len;
+ priv->outlen = 3;
+ msg->method = HTTP_POST;
+ }
+ }
+ else if (msg->body != NULL) {
+ pbody = msg->body->str;
+ bodylen = msg->body->len;
+ priv->outlen = 2;
+ }
+ else {
+ /* Invalid body for spamc method */
+ return;
+ }
+
+ if (conn->type == RSPAMD_HTTP_SERVER) {
+ /* Format reply */
+ if (msg->method < HTTP_SYMBOLS) {
+ ptm = gmtime (&msg->date);
+ t = *ptm;
+ rspamd_snprintf (datebuf, sizeof (datebuf), "%s, %02d %s %4d %02d:%02d:%02d GMT",
+ http_week[t.tm_wday],
+ t.tm_mday,
+ http_month[t.tm_mon],
+ t.tm_year + 1900,
+ t.tm_hour,
+ t.tm_min,
+ t.tm_sec);
+ if (mime_type == NULL) {
+ mime_type = "text/plain";
+ }
+ rspamd_printf_gstring (priv->buf, "HTTP/1.1 %d %s\r\n"
+ "Connection: close\r\n"
+ "Server: %s\r\n"
+ "Date: %s\r\n"
+ "Content-Length: %z\r\n"
+ "Content-Type: %s\r\n",
+ msg->code, rspamd_http_code_to_str (msg->code),
+ "rspamd/" RVERSION,
+ datebuf,
+ msg->body->len,
+ mime_type);
+ }
+ else {
+ /* Legacy spamd reply */
+ rspamd_printf_gstring (priv->buf, "RSPAMD/1.3 0 EX_OK\r\n");
+ }
+ }
+ else {
+ /* Format request */
+ if (host != NULL) {
+ rspamd_printf_gstring (priv->buf, "%s %v HTTP/1.1\r\n"
+ "Connection: close\r\n"
+ "Host: %s\r\n"
+ "Content-Length: %z\r\n",
+ http_method_str (msg->method), msg->url, host, msg->body->len);
+ }
+ else {
+ /* Fallback to HTTP/1.0 */
+ rspamd_printf_gstring (priv->buf, "%s %v HTTP/1.0\r\n"
+ "Content-Length: %z\r\n",
+ http_method_str (msg->method), msg->url, msg->body->len);
+ }
+ }
+ /* Allocate iov */
+ priv->wr_total = bodylen + priv->buf->len + 2;
+ DL_FOREACH (msg->headers, hdr) {
+ /* <name><: ><value><\r\n> */
+ priv->wr_total += hdr->name->len + hdr->value->len + 4;
+ priv->outlen += 4;
+ }
+ priv->out = g_slice_alloc (sizeof (struct iovec) * priv->outlen);
+ priv->wr_pos = 0;
+
+ /* Now set up all iov */
+ priv->out[0].iov_base = priv->buf->str;
+ priv->out[0].iov_len = priv->buf->len;
+ i = 1;
+ LL_FOREACH (msg->headers, hdr) {
+ priv->out[i].iov_base = hdr->name->str;
+ priv->out[i++].iov_len = hdr->name->len;
+ priv->out[i].iov_base = ": ";
+ priv->out[i++].iov_len = 2;
+ priv->out[i].iov_base = hdr->value->str;
+ priv->out[i++].iov_len = hdr->value->len;
+ priv->out[i].iov_base = "\r\n";
+ priv->out[i++].iov_len = 2;
+ }
+ if (msg->method < HTTP_SYMBOLS) {
+ priv->out[i].iov_base = "\r\n";
+ priv->out[i++].iov_len = 2;
+ }
+ else {
+ /* No CRLF for compatibility reply */
+ priv->wr_total -= 2;
+ }
+ if (msg->body != NULL) {
+ priv->out[i].iov_base = pbody;
+ priv->out[i++].iov_len = bodylen;
+ }
+
+ event_set (&priv->ev, fd, EV_WRITE, rspamd_http_event_handler, conn);
+ event_base_set (base, &priv->ev);
+ event_add (&priv->ev, priv->ptv);
+}
+
+struct rspamd_http_message*
+rspamd_http_new_message (enum http_parser_type type)
+{
+ struct rspamd_http_message *new;
+
+ new = g_slice_alloc (sizeof (struct rspamd_http_message));
+ if (type == HTTP_REQUEST) {
+ new->url = g_string_sized_new (32);
+ }
+ else {
+ new->url = NULL;
+ new->code = 200;
+ }
+ new->headers = NULL;
+ new->date = 0;
+ new->body = NULL;
+ new->type = type;
+ new->method = HTTP_GET;
+
+ return new;
+}
+
+void
+rspamd_http_message_free (struct rspamd_http_message *msg)
+{
+ struct rspamd_http_header *hdr, *tmp_hdr;
+
+ LL_FOREACH_SAFE (msg->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);
+ }
+ if (msg->body != NULL) {
+ g_string_free (msg->body, TRUE);
+ }
+ if (msg->url != NULL) {
+ g_string_free (msg->url, TRUE);
+ }
+ g_slice_free1 (sizeof (struct rspamd_http_message), msg);
+}
+
+void rspamd_http_message_add_header (struct rspamd_http_message *msg,
+ const gchar *name,
+ const gchar *value)
+{
+ struct rspamd_http_header *hdr;
+
+ if (msg != NULL && name != NULL && value != NULL) {
+ hdr = g_slice_alloc (sizeof (struct rspamd_http_header));
+ hdr->name = g_string_new (name);
+ hdr->value = g_string_new (value);
+ DL_APPEND (msg->headers, hdr);
+ }
+}
+
+const gchar*
+rspamd_http_message_find_header (struct rspamd_http_message *msg, const gchar *name)
+{
+ struct rspamd_http_header *hdr;
+ const gchar *res = NULL;
+ guint slen = strlen (name);
+
+ if (msg != NULL) {
+ LL_FOREACH (msg->headers, hdr) {
+ if (hdr->name->len == slen) {
+ if (memcmp (hdr->name->str, name, slen) == 0) {
+ res = hdr->value->str;
+ break;
+ }
+ }
+ }
+ }
+
+ return res;
+}
+
+/*
+ * 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);
+ g_slice_free1 (sizeof (struct rspamd_http_connection_entry), entry);
+ if (entry->rt->finish_handler) {
+ entry->rt->finish_handler (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;
+ msg->body = g_string_new (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_try_file (struct rspamd_http_connection_entry *entry,
+ struct rspamd_http_message *msg, gboolean expand_path)
+{
+ struct stat st;
+ gint fd;
+ gchar filebuf[PATH_MAX], realbuf[PATH_MAX], *dir;
+ struct rspamd_http_message *reply_msg;
+
+ /* XXX: filter filename component only */
+ if (expand_path) {
+ rspamd_snprintf (filebuf, sizeof (filebuf), "%s%c%v",
+ entry->rt->default_fs_path, G_DIR_SEPARATOR, msg->url);
+ }
+ else {
+ rspamd_snprintf (filebuf, sizeof (filebuf), "%v",
+ msg->url);
+ }
+
+ 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 */
+ g_string_append_printf (msg->url, "%c%s", G_DIR_SEPARATOR,
+ "index.html");
+ return rspamd_http_router_try_file (entry, msg, FALSE);
+ }
+ else if (!S_ISREG (st.st_mode)) {
+ return FALSE;
+ }
+
+ /* We also need to ensure that file is inside the defined dir */
+ dir = dirname (realbuf);
+ if (dir == NULL || strncmp (dir, entry->rt->default_fs_path,
+ strlen (entry->rt->default_fs_path)) != 0) {
+ 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;
+ reply_msg->body = g_string_sized_new (st.st_size);
+
+ if (read (fd, reply_msg->body->str, st.st_size) != st.st_size) {
+ close (fd);
+ rspamd_http_message_free (reply_msg);
+ return FALSE;
+ }
+
+ reply_msg->body->len = st.st_size;
+ reply_msg->body->str[st.st_size] = '\0';
+ close (fd);
+
+ rspamd_http_connection_reset (entry->conn);
+
+ /* XXX: detect content type */
+ 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 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;
+ struct rspamd_http_message *err_msg;
+ GError *err;
+
+ G_STATIC_ASSERT (sizeof (rspamd_http_router_handler_t) == sizeof (gpointer));
+
+ if (entry->is_reply) {
+ /* Request is finished, it is safe to free a connection */
+ rspamd_http_entry_free (entry);
+ }
+ else {
+ /* Search for path */
+ if (msg->url != NULL && msg->url->len != 0) {
+ found = g_hash_table_lookup (entry->rt->paths, msg->url->str);
+ memcpy (&handler, &found, sizeof (found));
+ }
+ entry->is_reply = TRUE;
+ if (handler != NULL) {
+ return handler (entry, msg);
+ }
+ else {
+ if (entry->rt->default_fs_path == NULL ||
+ rspamd_http_router_try_file (entry, msg, TRUE)) {
+ err = g_error_new (HTTP_ERROR, 404,
+ "Not found");
+ if (entry->rt->error_handler != NULL) {
+ entry->rt->error_handler (entry, err);
+ }
+ err_msg = rspamd_http_new_message (HTTP_RESPONSE);
+ err_msg->date = time (NULL);
+ err_msg->code = err->code;
+ err_msg->body = g_string_new (err->message);
+ 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);
+ 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_http_connection_router* new;
+ struct stat st;
+
+ new = g_slice_alloc (sizeof (struct rspamd_http_connection_router));
+ new->paths = g_hash_table_new (rspamd_strcase_hash, rspamd_strcase_equal);
+ new->conns = NULL;
+ new->error_handler = eh;
+ new->finish_handler = fh;
+ new->ev_base = base;
+ 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 = g_strdup (default_fs_path);
+ }
+ }
+ }
+
+ return new;
+}
+
+void
+rspamd_http_router_add_path (struct rspamd_http_connection_router *router,
+ const gchar *path, rspamd_http_router_handler_t handler)
+{
+ gpointer ptr;
+ G_STATIC_ASSERT (sizeof (rspamd_http_router_handler_t) == sizeof (gpointer));
+
+ if (path != NULL && handler != NULL && router != NULL) {
+ memcpy (&ptr, &handler, sizeof (ptr));
+ g_hash_table_insert (router->paths, (gpointer)path, ptr);
+ }
+}
+
+void
+rspamd_http_router_handle_socket (struct rspamd_http_connection_router *router,
+ gint fd, gpointer ud)
+{
+ struct rspamd_http_connection_entry *conn;
+
+ conn = g_slice_alloc (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);
+
+ rspamd_http_connection_read_message (conn->conn, conn, fd, router->ptv,
+ router->ev_base);
+ LL_PREPEND (router->conns, conn);
+}
+
+void
+rspamd_http_router_free (struct rspamd_http_connection_router *router)
+{
+ struct rspamd_http_connection_entry *conn, *tmp;
+
+ if (router) {
+ LL_FOREACH_SAFE (router->conns, conn, tmp) {
+ rspamd_http_entry_free (conn);
+ }
+
+ if (router->default_fs_path != NULL) {
+ g_free (router->default_fs_path);
+ }
+ g_hash_table_unref (router->paths);
+ g_slice_free1 (sizeof (struct rspamd_http_connection_router), router);
+ }
+}