summaryrefslogtreecommitdiffstats
path: root/src/lua/lua_http.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
commit133a45c109da5310add55824db21af5239951f93 (patch)
treeba6ac4c0a950a0dda56451944315d66409923918 /src/lua/lua_http.c
parentInitial commit. (diff)
downloadrspamd-upstream.tar.xz
rspamd-upstream.zip
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/lua/lua_http.c')
-rw-r--r--src/lua/lua_http.c1270
1 files changed, 1270 insertions, 0 deletions
diff --git a/src/lua/lua_http.c b/src/lua/lua_http.c
new file mode 100644
index 0000000..713082a
--- /dev/null
+++ b/src/lua/lua_http.c
@@ -0,0 +1,1270 @@
+/*
+ * Copyright 2023 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 "lua_common.h"
+#include "lua_thread_pool.h"
+#include "libserver/http/http_private.h"
+#include "libutil/upstream.h"
+#include "ref.h"
+#include "unix-std.h"
+#include "zlib.h"
+#include "utlist.h"
+
+/***
+ * @module rspamd_http
+ * Rspamd HTTP module represents HTTP asynchronous client available from LUA code.
+ * This module hides all complexity: DNS resolving, sessions management, zero-copy
+ * text transfers and so on under the hood.
+ * @example
+local rspamd_http = require "rspamd_http"
+
+local function symbol_callback(task)
+ local function http_callback(err_message, code, body, headers)
+ task:insert_result('SYMBOL', 1) -- task is available via closure
+ end
+
+ rspamd_http.request({
+ task=task,
+ url='http://example.com/data',
+ body=task:get_content(),
+ callback=http_callback,
+ headers={Header='Value', OtherHeader='Value'},
+ mime_type='text/plain',
+ })
+ end
+ */
+
+#define MAX_HEADERS_SIZE 8192
+
+static const gchar *M = "rspamd lua http";
+
+LUA_FUNCTION_DEF(http, request);
+
+static const struct luaL_reg httplib_m[] = {
+ LUA_INTERFACE_DEF(http, request),
+ {"__tostring", rspamd_lua_class_tostring},
+ {NULL, NULL}};
+
+#define RSPAMD_LUA_HTTP_FLAG_TEXT (1 << 0)
+#define RSPAMD_LUA_HTTP_FLAG_NOVERIFY (1 << 1)
+#define RSPAMD_LUA_HTTP_FLAG_RESOLVED (1 << 2)
+#define RSPAMD_LUA_HTTP_FLAG_KEEP_ALIVE (1 << 3)
+#define RSPAMD_LUA_HTTP_FLAG_YIELDED (1 << 4)
+
+struct lua_http_cbdata {
+ struct rspamd_http_connection *conn;
+ struct rspamd_async_session *session;
+ struct rspamd_symcache_dynamic_item *item;
+ struct rspamd_http_message *msg;
+ struct ev_loop *event_loop;
+ struct rspamd_config *cfg;
+ struct rspamd_task *task;
+ ev_tstamp timeout;
+ struct rspamd_cryptobox_keypair *local_kp;
+ struct rspamd_cryptobox_pubkey *peer_pk;
+ rspamd_inet_addr_t *addr;
+ gchar *mime_type;
+ gchar *host;
+ gchar *auth;
+ struct upstream *up;
+ const gchar *url;
+ gsize max_size;
+ gint flags;
+ gint fd;
+ gint cbref;
+ struct thread_entry *thread;
+ ref_entry_t ref;
+};
+
+static const gdouble default_http_timeout = 5.0;
+
+static struct rspamd_dns_resolver *
+lua_http_global_resolver(struct ev_loop *ev_base)
+{
+ static struct rspamd_dns_resolver *global_resolver;
+
+ if (global_resolver == NULL) {
+ global_resolver = rspamd_dns_resolver_init(NULL, ev_base, NULL);
+ }
+
+ return global_resolver;
+}
+
+static void
+lua_http_fin(gpointer arg)
+{
+ struct lua_http_cbdata *cbd = (struct lua_http_cbdata *) arg;
+
+ if (cbd->cbref != -1) {
+ luaL_unref(cbd->cfg->lua_state, LUA_REGISTRYINDEX, cbd->cbref);
+ }
+
+ if (cbd->conn) {
+ /* Here we already have a connection, so we need to unref it */
+ rspamd_http_connection_unref(cbd->conn);
+ }
+ else if (cbd->msg != NULL) {
+ /* We need to free message */
+ rspamd_http_message_unref(cbd->msg);
+ }
+
+ if (cbd->fd != -1) {
+ close(cbd->fd);
+ }
+
+ if (cbd->addr) {
+ rspamd_inet_address_free(cbd->addr);
+ }
+
+ if (cbd->up) {
+ rspamd_upstream_unref(cbd->up);
+ }
+
+ if (cbd->mime_type) {
+ g_free(cbd->mime_type);
+ }
+
+ if (cbd->auth) {
+ g_free(cbd->auth);
+ }
+
+ if (cbd->host) {
+ g_free(cbd->host);
+ }
+
+ if (cbd->local_kp) {
+ rspamd_keypair_unref(cbd->local_kp);
+ }
+
+ if (cbd->peer_pk) {
+ rspamd_pubkey_unref(cbd->peer_pk);
+ }
+
+ g_free(cbd);
+}
+
+static void
+lua_http_cbd_dtor(struct lua_http_cbdata *cbd)
+{
+ if (cbd->session) {
+
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_RESOLVED) {
+ /* Event is added merely for resolved events */
+ if (cbd->item) {
+ rspamd_symcache_item_async_dec_check(cbd->task, cbd->item, M);
+ }
+
+ rspamd_session_remove_event(cbd->session, lua_http_fin, cbd);
+ }
+ }
+ else {
+ lua_http_fin(cbd);
+ }
+}
+
+static void
+lua_http_push_error(struct lua_http_cbdata *cbd, const char *err)
+{
+ struct lua_callback_state lcbd;
+ lua_State *L;
+
+ lua_thread_pool_prepare_callback(cbd->cfg->lua_thread_pool, &lcbd);
+
+ L = lcbd.L;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->cbref);
+ lua_pushstring(L, err);
+
+
+ if (cbd->item) {
+ rspamd_symcache_set_cur_item(cbd->task, cbd->item);
+ }
+
+ if (lua_pcall(L, 1, 0, 0) != 0) {
+ msg_info("callback call failed: %s", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ }
+
+ lua_thread_pool_restore_callback(&lcbd);
+}
+
+static void lua_http_resume_handler(struct rspamd_http_connection *conn,
+ struct rspamd_http_message *msg, const char *err);
+
+static void
+lua_http_error_handler(struct rspamd_http_connection *conn, GError *err)
+{
+ struct lua_http_cbdata *cbd = (struct lua_http_cbdata *) conn->ud;
+
+ if (cbd->up) {
+ rspamd_upstream_fail(cbd->up, false, err ? err->message : "unknown error");
+ }
+
+ if (cbd->cbref == -1) {
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_YIELDED) {
+ cbd->flags &= ~RSPAMD_LUA_HTTP_FLAG_YIELDED;
+ lua_http_resume_handler(conn, NULL, err->message);
+ }
+ else {
+ /* TODO: kill me please */
+ msg_info("lost HTTP error from %s in coroutines mess: %s",
+ rspamd_inet_address_to_string_pretty(cbd->addr),
+ err->message);
+ }
+ }
+ else {
+ lua_http_push_error(cbd, err->message);
+ }
+
+ REF_RELEASE(cbd);
+}
+
+static int
+lua_http_finish_handler(struct rspamd_http_connection *conn,
+ struct rspamd_http_message *msg)
+{
+ struct lua_http_cbdata *cbd = (struct lua_http_cbdata *) conn->ud;
+ struct rspamd_http_header *h;
+ const gchar *body;
+ gsize body_len;
+
+ struct lua_callback_state lcbd;
+ lua_State *L;
+
+ if (cbd->cbref == -1) {
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_YIELDED) {
+ cbd->flags &= ~RSPAMD_LUA_HTTP_FLAG_YIELDED;
+ lua_http_resume_handler(conn, msg, NULL);
+ }
+ else {
+ /* TODO: kill me please */
+ msg_err("lost HTTP data from %s in coroutines mess",
+ rspamd_inet_address_to_string_pretty(cbd->addr));
+ }
+
+ REF_RELEASE(cbd);
+
+ return 0;
+ }
+ lua_thread_pool_prepare_callback(cbd->cfg->lua_thread_pool, &lcbd);
+
+ if (cbd->up) {
+ rspamd_upstream_ok(cbd->up);
+ }
+
+ L = lcbd.L;
+
+ lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->cbref);
+ /* Error */
+ lua_pushnil(L);
+ /* Reply code */
+ lua_pushinteger(L, msg->code);
+ /* Body */
+ body = rspamd_http_message_get_body(msg, &body_len);
+
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_TEXT) {
+ struct rspamd_lua_text *t;
+
+ t = lua_newuserdata(L, sizeof(*t));
+ rspamd_lua_setclass(L, "rspamd{text}", -1);
+ t->start = body;
+ t->len = body_len;
+ t->flags = 0;
+ }
+ else {
+ if (body_len > 0) {
+ lua_pushlstring(L, body, body_len);
+ }
+ else {
+ lua_pushnil(L);
+ }
+ }
+ /* Headers */
+ lua_newtable(L);
+
+ kh_foreach_value(msg->headers, h, {
+ /*
+ * Lowercase header name, as Lua cannot search in caseless matter
+ */
+ rspamd_str_lc(h->combined->str, h->name.len);
+ lua_pushlstring(L, h->name.begin, h->name.len);
+ lua_pushlstring(L, h->value.begin, h->value.len);
+ lua_settable(L, -3);
+ });
+
+ if (cbd->item) {
+ /* Replace watcher to deal with nested calls */
+ rspamd_symcache_set_cur_item(cbd->task, cbd->item);
+ }
+
+ if (lua_pcall(L, 4, 0, 0) != 0) {
+ msg_info("callback call failed: %s", lua_tostring(L, -1));
+ lua_pop(L, 1);
+ }
+
+ REF_RELEASE(cbd);
+
+ lua_thread_pool_restore_callback(&lcbd);
+
+ return 0;
+}
+
+/*
+ * resumes yielded thread
+ */
+static void
+lua_http_resume_handler(struct rspamd_http_connection *conn,
+ struct rspamd_http_message *msg, const char *err)
+{
+ struct lua_http_cbdata *cbd = (struct lua_http_cbdata *) conn->ud;
+ lua_State *L = cbd->thread->lua_state;
+ const gchar *body;
+ gsize body_len;
+ struct rspamd_http_header *h;
+
+ if (err) {
+ lua_pushstring(L, err);
+ lua_pushnil(L);
+ }
+ else {
+ /*
+ * 1 - nil (error)
+ * 2 - table:
+ * code (int)
+ * content (string)
+ * headers (table: header -> value)
+ */
+ lua_pushnil(L);// error code
+
+ lua_createtable(L, 0, 3);
+
+ /* code */
+ lua_pushliteral(L, "code");
+ lua_pushinteger(L, msg->code);
+ lua_settable(L, -3);
+
+ /* content */
+ lua_pushliteral(L, "content");
+
+ body = rspamd_http_message_get_body(msg, &body_len);
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_TEXT) {
+ struct rspamd_lua_text *t;
+
+ t = lua_newuserdata(L, sizeof(*t));
+ rspamd_lua_setclass(L, "rspamd{text}", -1);
+ t->start = body;
+ t->len = body_len;
+ t->flags = 0;
+ }
+ else {
+ if (body_len > 0) {
+ lua_pushlstring(L, body, body_len);
+ }
+ else {
+ lua_pushnil(L);
+ }
+ }
+ lua_settable(L, -3);
+
+ /* headers */
+ lua_pushliteral(L, "headers");
+ lua_newtable(L);
+
+ kh_foreach_value(msg->headers, h, {
+ /*
+ * Lowercase header name, as Lua cannot search in caseless matter
+ */
+ rspamd_str_lc(h->combined->str, h->name.len);
+ lua_pushlstring(L, h->name.begin, h->name.len);
+ lua_pushlstring(L, h->value.begin, h->value.len);
+ lua_settable(L, -3);
+ });
+
+ lua_settable(L, -3);
+ }
+
+ if (cbd->item) {
+ /* Replace watcher to deal with nested calls */
+ rspamd_symcache_set_cur_item(cbd->task, cbd->item);
+ }
+
+ lua_thread_resume(cbd->thread, 2);
+}
+
+static gboolean
+lua_http_make_connection(struct lua_http_cbdata *cbd)
+{
+ rspamd_inet_address_set_port(cbd->addr, cbd->msg->port);
+ unsigned http_opts = RSPAMD_HTTP_CLIENT_SIMPLE;
+
+ if (cbd->msg->flags & RSPAMD_HTTP_FLAG_WANT_SSL) {
+ http_opts |= RSPAMD_HTTP_CLIENT_SSL;
+ }
+
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_KEEP_ALIVE) {
+ cbd->fd = -1; /* FD is owned by keepalive connection */
+ cbd->conn = rspamd_http_connection_new_client_keepalive(
+ NULL, /* Default context */
+ NULL,
+ lua_http_error_handler,
+ lua_http_finish_handler,
+ http_opts,
+ cbd->addr,
+ cbd->host);
+ }
+ else {
+ cbd->fd = -1;
+ cbd->conn = rspamd_http_connection_new_client(
+ NULL, /* Default context */
+ NULL,
+ lua_http_error_handler,
+ lua_http_finish_handler,
+ http_opts,
+ cbd->addr);
+ }
+
+ if (cbd->conn) {
+ if (cbd->local_kp) {
+ rspamd_http_connection_set_key(cbd->conn, cbd->local_kp);
+ }
+
+ if (cbd->peer_pk) {
+ rspamd_http_message_set_peer_key(cbd->msg, cbd->peer_pk);
+ }
+
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_NOVERIFY) {
+ cbd->msg->flags |= RSPAMD_HTTP_FLAG_SSL_NOVERIFY;
+ }
+
+ if (cbd->max_size) {
+ rspamd_http_connection_set_max_size(cbd->conn, cbd->max_size);
+ }
+
+ if (cbd->auth) {
+ rspamd_http_message_add_header(cbd->msg, "Authorization",
+ cbd->auth);
+ }
+
+ if (cbd->session) {
+ if (cbd->item) {
+ rspamd_session_add_event_full(cbd->session,
+ (event_finalizer_t) lua_http_fin, cbd,
+ M,
+ rspamd_symcache_dyn_item_name(cbd->task, cbd->item));
+ }
+ else {
+ rspamd_session_add_event(cbd->session,
+ (event_finalizer_t) lua_http_fin, cbd,
+ M);
+ }
+ cbd->flags |= RSPAMD_LUA_HTTP_FLAG_RESOLVED;
+ }
+
+ if (cbd->task) {
+ cbd->conn->log_tag = cbd->task->task_pool->tag.uid;
+
+ if (cbd->item) {
+ rspamd_symcache_item_async_inc(cbd->task, cbd->item, M);
+ }
+ }
+ else if (cbd->cfg) {
+ cbd->conn->log_tag = cbd->cfg->cfg_pool->tag.uid;
+ }
+
+ struct rspamd_http_message *msg = cbd->msg;
+
+ /* Message is now owned by a connection object */
+ cbd->msg = NULL;
+
+ return rspamd_http_connection_write_message(cbd->conn, msg,
+ cbd->host, cbd->mime_type, cbd,
+ cbd->timeout);
+ }
+
+ return FALSE;
+}
+
+static void
+lua_http_dns_handler(struct rdns_reply *reply, gpointer ud)
+{
+ struct lua_http_cbdata *cbd = (struct lua_http_cbdata *) ud;
+ struct rspamd_symcache_dynamic_item *item;
+ struct rspamd_task *task;
+
+ task = cbd->task;
+ item = cbd->item;
+
+ if (reply->code != RDNS_RC_NOERROR) {
+ lua_http_push_error(cbd, "unable to resolve host");
+ REF_RELEASE(cbd);
+ }
+ else {
+ struct rdns_reply_entry *entry;
+
+ DL_FOREACH(reply->entries, entry)
+ {
+ if (entry->type == RDNS_REQUEST_A) {
+ cbd->addr = rspamd_inet_address_new(AF_INET,
+ &entry->content.a.addr);
+ break;
+ }
+ else if (entry->type == RDNS_REQUEST_AAAA) {
+ cbd->addr = rspamd_inet_address_new(AF_INET6,
+ &entry->content.aaa.addr);
+ break;
+ }
+ }
+
+ if (cbd->addr == NULL) {
+ lua_http_push_error(cbd, "unable to resolve host: no records with such name");
+ REF_RELEASE(cbd);
+ }
+ else {
+ REF_RETAIN(cbd);
+ if (!lua_http_make_connection(cbd)) {
+ lua_http_push_error(cbd, "unable to make connection to the host");
+
+ if (cbd->ref.refcount > 1) {
+ REF_RELEASE(cbd);
+ }
+
+ REF_RELEASE(cbd);
+
+ return;
+ }
+ REF_RELEASE(cbd);
+ }
+ }
+
+ if (item) {
+ rspamd_symcache_item_async_dec_check(task, item, M);
+ }
+}
+
+static void
+lua_http_push_headers(lua_State *L, struct rspamd_http_message *msg)
+{
+ const char *name, *value;
+ gint i, sz;
+
+ lua_pushnil(L);
+ while (lua_next(L, -2) != 0) {
+
+ lua_pushvalue(L, -2);
+ name = lua_tostring(L, -1);
+ sz = rspamd_lua_table_size(L, -2);
+ if (sz != 0 && name != NULL) {
+ for (i = 1; i <= sz; i++) {
+ lua_rawgeti(L, -2, i);
+ value = lua_tostring(L, -1);
+ if (value != NULL) {
+ rspamd_http_message_add_header(msg, name, value);
+ }
+ lua_pop(L, 1);
+ }
+ }
+ else {
+ value = lua_tostring(L, -2);
+ if (name != NULL && value != NULL) {
+ rspamd_http_message_add_header(msg, name, value);
+ }
+ }
+ lua_pop(L, 2);
+ }
+}
+
+/***
+ * @function rspamd_http.request({params...})
+ * This function creates HTTP request and accepts several parameters as a table using key=value syntax.
+ * Required params are:
+ *
+ * - `url`
+ * - `task`
+ *
+ * In taskless mode, instead of `task` required are:
+ *
+ * - `ev_base`
+ * - `config`
+ *
+ * @param {string} url specifies URL for a request in the standard URI form (e.g. 'http://example.com/path')
+ * @param {function} callback specifies callback function in format `function (err_message, code, body, headers)` that is called on HTTP request completion. if this parameter is missing, the function performs "pseudo-synchronous" call (see [Synchronous and Asynchronous API overview](/doc/lua/sync_async.html#API-example-http-module)
+ * @param {task} task if called from symbol handler it is generally a good idea to use the common task objects: event base, DNS resolver and events session
+ * @param {table} headers optional headers in form `[name='value', name='value']`
+ * @param {string} mime_type MIME type of the HTTP content (for example, `text/html`)
+ * @param {string/text} body full body content, can be opaque `rspamd{text}` to avoid data copying
+ * @param {number} timeout floating point request timeout value in seconds (default is 5.0 seconds)
+ * @param {resolver} resolver to perform DNS-requests. Usually got from either `task` or `config`
+ * @param {boolean} gzip if true, body of the requests will be compressed
+ * @param {boolean} no_ssl_verify disable SSL peer checks
+ * @param {boolean} keepalive enable keep-alive pool
+ * @param {string} user for HTTP authentication
+ * @param {string} password for HTTP authentication, only if "user" present
+ * @return {boolean} `true`, in **async** mode, if a request has been successfully scheduled. If this value is `false` then some error occurred, the callback thus will not be called.
+ * @return In **sync** mode `string|nil, nil|table` In sync mode error message if any and response as table: `int` _code_, `string` _content_ and `table` _headers_ (header -> value)
+ */
+static gint
+lua_http_request(lua_State *L)
+{
+ LUA_TRACE_POINT;
+ struct ev_loop *ev_base;
+ struct rspamd_http_message *msg;
+ struct lua_http_cbdata *cbd;
+ struct rspamd_dns_resolver *resolver;
+ struct rspamd_async_session *session = NULL;
+ struct rspamd_lua_text *t;
+ struct rspamd_task *task = NULL;
+ struct rspamd_config *cfg = NULL;
+ struct rspamd_cryptobox_pubkey *peer_key = NULL;
+ struct rspamd_cryptobox_keypair *local_kp = NULL;
+ struct upstream *up = NULL;
+ const gchar *url, *lua_body;
+ rspamd_fstring_t *body = NULL;
+ gint cbref = -1;
+ gsize bodylen;
+ gdouble timeout = default_http_timeout;
+ gint flags = 0;
+ gchar *mime_type = NULL;
+ gchar *auth = NULL;
+ gsize max_size = 0;
+ gboolean gzip = FALSE;
+
+ if (lua_gettop(L) >= 2) {
+ /* url, callback and event_base format */
+ url = luaL_checkstring(L, 1);
+
+ if (url == NULL || lua_type(L, 2) != LUA_TFUNCTION) {
+ msg_err("http request has bad params");
+ lua_pushboolean(L, FALSE);
+ return 1;
+ }
+
+ lua_pushvalue(L, 2);
+ cbref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ if (lua_gettop(L) >= 3 && rspamd_lua_check_udata_maybe(L, 3, "rspamd{ev_base}")) {
+ ev_base = *(struct ev_loop **) lua_touserdata(L, 3);
+ }
+ else {
+ ev_base = NULL;
+ }
+
+ if (lua_gettop(L) >= 4 && rspamd_lua_check_udata_maybe(L, 4, "rspamd{resolver}")) {
+ resolver = *(struct rspamd_dns_resolver **) lua_touserdata(L, 4);
+ }
+ else {
+ resolver = lua_http_global_resolver(ev_base);
+ }
+
+ if (lua_gettop(L) >= 5 && rspamd_lua_check_udata_maybe(L, 5, "rspamd{session}")) {
+ session = *(struct rspamd_async_session **) lua_touserdata(L, 5);
+ }
+ else {
+ session = NULL;
+ }
+
+ msg = rspamd_http_message_from_url(url);
+
+ if (msg == NULL) {
+ luaL_unref(L, LUA_REGISTRYINDEX, cbref);
+ msg_err("cannot create HTTP message from url %s", url);
+ lua_pushboolean(L, FALSE);
+ return 1;
+ }
+ }
+ else if (lua_type(L, 1) == LUA_TTABLE) {
+ lua_pushstring(L, "url");
+ lua_gettable(L, 1);
+ url = luaL_checkstring(L, -1);
+ lua_pop(L, 1);
+
+ if (url == NULL) {
+ msg_err("cannot create HTTP message without url");
+ lua_pushboolean(L, FALSE);
+ return 1;
+ }
+
+ lua_pushstring(L, "callback");
+ lua_gettable(L, 1);
+ if (url == NULL || lua_type(L, -1) != LUA_TFUNCTION) {
+ lua_pop(L, 1);
+ }
+ else {
+ cbref = luaL_ref(L, LUA_REGISTRYINDEX);
+ }
+
+ lua_pushstring(L, "task");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TUSERDATA) {
+ task = lua_check_task(L, -1);
+
+ if (task) {
+ ev_base = task->event_loop;
+ resolver = task->resolver;
+ session = task->s;
+ cfg = task->cfg;
+ }
+ }
+ lua_pop(L, 1);
+
+ if (task == NULL) {
+ lua_pushstring(L, "ev_base");
+ lua_gettable(L, 1);
+ if (rspamd_lua_check_udata_maybe(L, -1, "rspamd{ev_base}")) {
+ ev_base = *(struct ev_loop **) lua_touserdata(L, -1);
+ }
+ else {
+ ev_base = NULL;
+ }
+ lua_pop(L, 1);
+
+
+ lua_pushstring(L, "session");
+ lua_gettable(L, 1);
+ if (rspamd_lua_check_udata_maybe(L, -1, "rspamd{session}")) {
+ session = *(struct rspamd_async_session **) lua_touserdata(L, -1);
+ }
+ else {
+ session = NULL;
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "config");
+ lua_gettable(L, 1);
+ if (rspamd_lua_check_udata_maybe(L, -1, "rspamd{config}")) {
+ cfg = *(struct rspamd_config **) lua_touserdata(L, -1);
+ }
+ else {
+ cfg = NULL;
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "resolver");
+ lua_gettable(L, 1);
+
+ if (rspamd_lua_check_udata_maybe(L, -1, "rspamd{resolver}")) {
+ resolver = *(struct rspamd_dns_resolver **) lua_touserdata(L, -1);
+ }
+ else {
+ if (cfg && cfg->dns_resolver) {
+ resolver = cfg->dns_resolver;
+ }
+ else {
+ resolver = lua_http_global_resolver(ev_base);
+ }
+ }
+ lua_pop(L, 1);
+ }
+
+ msg = rspamd_http_message_from_url(url);
+ if (msg == NULL) {
+ msg_err_task_check("cannot create HTTP message from url %s", url);
+ lua_pushboolean(L, FALSE);
+ return 1;
+ }
+
+ lua_pushstring(L, "headers");
+ lua_gettable(L, 1);
+ if (lua_type(L, -1) == LUA_TTABLE) {
+ lua_http_push_headers(L, msg);
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "timeout");
+ lua_gettable(L, 1);
+ if (lua_type(L, -1) == LUA_TNUMBER) {
+ timeout = lua_tonumber(L, -1);
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "mime_type");
+ lua_gettable(L, 1);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ mime_type = g_strdup(lua_tostring(L, -1));
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "body");
+ lua_gettable(L, 1);
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ lua_body = lua_tolstring(L, -1, &bodylen);
+ body = rspamd_fstring_new_init(lua_body, bodylen);
+ }
+ else if (lua_type(L, -1) == LUA_TUSERDATA) {
+ t = lua_check_text(L, -1);
+ /* TODO: think about zero-copy possibilities */
+ if (t) {
+ body = rspamd_fstring_new_init(t->start, t->len);
+ }
+ else {
+ rspamd_http_message_unref(msg);
+ g_free(mime_type);
+
+ return luaL_error(L, "invalid body argument type: %s",
+ lua_typename(L, lua_type(L, -1)));
+ }
+ }
+ else if (lua_type(L, -1) == LUA_TTABLE) {
+ gsize total_len = 0, nelts = rspamd_lua_table_size(L, -1);
+
+ /* Calculate length and check types */
+ for (gsize i = 0; i < nelts; i++) {
+ lua_rawgeti(L, -1, i + 1);
+
+ if (lua_type(L, -1) == LUA_TSTRING) {
+#if LUA_VERSION_NUM >= 502
+ total_len += lua_rawlen(L, -1);
+#else
+ total_len += lua_objlen(L, -1);
+#endif
+ }
+ else if (lua_type(L, -1) == LUA_TUSERDATA) {
+ t = lua_check_text(L, -1);
+
+ if (t) {
+ total_len += t->len;
+ }
+ else {
+ rspamd_http_message_unref(msg);
+ if (mime_type) {
+ g_free(mime_type);
+ }
+
+ return luaL_error(L, "invalid body argument: %s",
+ lua_typename(L, lua_type(L, -1)));
+ }
+ }
+ else {
+ rspamd_http_message_unref(msg);
+ if (mime_type) {
+ g_free(mime_type);
+ }
+
+ return luaL_error(L, "invalid body argument type: %s",
+ lua_typename(L, lua_type(L, -1)));
+ }
+
+ lua_pop(L, 1);
+ }
+
+ /* Preallocate body */
+ if (total_len > 0) {
+ body = rspamd_fstring_sized_new(total_len);
+ }
+ else {
+ rspamd_http_message_unref(msg);
+ if (mime_type) {
+ g_free(mime_type);
+ }
+
+ return luaL_error(L, "empty body specified");
+ }
+
+ /* Fill elements */
+ for (gsize i = 0; i < nelts; i++) {
+ lua_rawgeti(L, -1, i + 1);
+
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ lua_body = lua_tolstring(L, -1, &bodylen);
+ body = rspamd_fstring_append(body, lua_body, bodylen);
+ }
+ else {
+ t = lua_check_text(L, -1);
+
+ if (t) {
+ body = rspamd_fstring_append(body, t->start, t->len);
+ }
+ }
+
+ lua_pop(L, 1);
+ }
+ }
+ else if (lua_type(L, -1) != LUA_TNONE && lua_type(L, -1) != LUA_TNIL) {
+ rspamd_http_message_unref(msg);
+ return luaL_error(L, "invalid body argument type: %s",
+ lua_typename(L, lua_type(L, -1)));
+ }
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "peer_key");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ const gchar *in;
+ gsize inlen;
+
+ in = lua_tolstring(L, -1, &inlen);
+ peer_key = rspamd_pubkey_from_base32(in, inlen,
+ RSPAMD_KEYPAIR_KEX, RSPAMD_CRYPTOBOX_MODE_25519);
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "keypair");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TTABLE) {
+ ucl_object_t *kp_ucl = ucl_object_lua_import(L, -1);
+
+ local_kp = rspamd_keypair_from_ucl(kp_ucl);
+ ucl_object_unref(kp_ucl);
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "opaque_body");
+ lua_gettable(L, 1);
+
+ if (!!lua_toboolean(L, -1)) {
+ flags |= RSPAMD_LUA_HTTP_FLAG_TEXT;
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "gzip");
+ lua_gettable(L, 1);
+
+ if (!!lua_toboolean(L, -1)) {
+ gzip = TRUE;
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "no_ssl_verify");
+ lua_gettable(L, 1);
+
+ if (!!lua_toboolean(L, -1)) {
+ flags |= RSPAMD_LUA_HTTP_FLAG_NOVERIFY;
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "keepalive");
+ lua_gettable(L, 1);
+
+ if (!!lua_toboolean(L, -1)) {
+ flags |= RSPAMD_LUA_HTTP_FLAG_KEEP_ALIVE;
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "max_size");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TNUMBER) {
+ max_size = lua_tointeger(L, -1);
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "method");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ rspamd_http_message_set_method(msg, lua_tostring(L, -1));
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "upstream");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TUSERDATA) {
+ struct rspamd_lua_upstream *lup = lua_check_upstream(L, -1);
+
+ if (lup) {
+ /* Preserve pointer in case if lup is destructed */
+ up = lup->up;
+ }
+ }
+
+ lua_pop(L, 1);
+
+ lua_pushstring(L, "user");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ const gchar *user = lua_tostring(L, -1);
+
+ lua_pushstring(L, "password");
+ lua_gettable(L, 1);
+
+ if (lua_type(L, -1) == LUA_TSTRING) {
+ const gchar *password = lua_tostring(L, -1);
+ gchar *tmpbuf;
+ gsize tlen;
+
+ tlen = strlen(user) + strlen(password) + 1;
+ tmpbuf = g_malloc(tlen + 1);
+ rspamd_snprintf(tmpbuf, tlen + 1, "%s:%s", user, password);
+ tlen *= 2;
+ tlen += sizeof("Basic ") - 1;
+ auth = g_malloc(tlen + 1);
+ rspamd_snprintf(auth, tlen + 1, "Basic %Bs", tmpbuf);
+ g_free(tmpbuf);
+ }
+ else {
+ msg_warn("HTTP user must have password, disabling auth");
+ }
+
+ lua_pop(L, 1); /* password */
+ }
+
+ lua_pop(L, 1); /* username */
+ }
+ else {
+ msg_err("http request has bad params");
+ lua_pushboolean(L, FALSE);
+
+ return 1;
+ }
+
+ if (session && rspamd_session_blocked(session)) {
+ lua_pushboolean(L, FALSE);
+
+ g_free(auth);
+ rspamd_http_message_unref(msg);
+ if (body) {
+ rspamd_fstring_free(body);
+ }
+ if (local_kp) {
+ rspamd_keypair_unref(local_kp);
+ }
+
+ return 1;
+ }
+ if (task == NULL && cfg == NULL) {
+ g_free(auth);
+ rspamd_http_message_unref(msg);
+ if (body) {
+ rspamd_fstring_free(body);
+ }
+ if (local_kp) {
+ rspamd_keypair_unref(local_kp);
+ }
+
+ return luaL_error(L,
+ "Bad params to rspamd_http:request(): either task or config should be set");
+ }
+
+ if (ev_base == NULL) {
+ g_free(auth);
+ rspamd_http_message_unref(msg);
+ if (body) {
+ rspamd_fstring_free(body);
+ }
+ if (local_kp) {
+ rspamd_keypair_unref(local_kp);
+ }
+
+ return luaL_error(L,
+ "Bad params to rspamd_http:request(): ev_base isn't passed");
+ }
+
+ cbd = g_malloc0(sizeof(*cbd));
+ cbd->cbref = cbref;
+ cbd->msg = msg;
+ cbd->event_loop = ev_base;
+ cbd->mime_type = mime_type;
+ cbd->timeout = timeout;
+ cbd->fd = -1;
+ cbd->cfg = cfg;
+ cbd->peer_pk = peer_key;
+ cbd->local_kp = local_kp;
+ cbd->flags = flags;
+ cbd->max_size = max_size;
+ cbd->url = url;
+ cbd->auth = auth;
+ cbd->task = task;
+
+ if (up) {
+ cbd->up = rspamd_upstream_ref(up);
+ }
+
+ if (cbd->cbref == -1) {
+ cbd->thread = lua_thread_pool_get_running_entry(cfg->lua_thread_pool);
+ }
+
+ REF_INIT_RETAIN(cbd, lua_http_cbd_dtor);
+
+ if (task) {
+ cbd->item = rspamd_symcache_get_cur_item(task);
+ }
+
+
+ if (body) {
+ if (gzip) {
+ if (rspamd_fstring_gzip(&body)) {
+ rspamd_http_message_add_header(msg, "Content-Encoding", "gzip");
+ }
+ }
+
+ rspamd_http_message_set_body_from_fstring_steal(msg, body);
+ }
+
+ if (session) {
+ cbd->session = session;
+ }
+
+ bool numeric_ip = false;
+
+ /* Check if we can skip resolving */
+
+ gsize hostlen = 0;
+ const gchar *host = rspamd_http_message_get_http_host(msg, &hostlen);
+
+ if (host) {
+ cbd->host = g_malloc(hostlen + 1);
+ rspamd_strlcpy(cbd->host, host, hostlen + 1);
+
+ /* Keep-alive entry is available */
+ if (cbd->flags & RSPAMD_LUA_HTTP_FLAG_KEEP_ALIVE) {
+ const rspamd_inet_addr_t *ka_addr = rspamd_http_context_has_keepalive(NULL,
+ cbd->host,
+ msg->port,
+ msg->flags & RSPAMD_HTTP_FLAG_WANT_SSL);
+
+ if (ka_addr) {
+ cbd->addr = rspamd_inet_address_copy(ka_addr, NULL);
+ numeric_ip = true;
+ }
+ }
+
+ /*
+ * No keep-alive stuff, check if we have upstream or if we can parse host as
+ * a numeric address
+ */
+ if (!cbd->addr) {
+ if (cbd->up) {
+ numeric_ip = true;
+ cbd->addr = rspamd_inet_address_copy(rspamd_upstream_addr_next(cbd->up), NULL);
+ }
+ else {
+ /* We use msg->host here, not cbd->host ! */
+ if (rspamd_parse_inet_address(&cbd->addr,
+ msg->host->str, msg->host->len,
+ RSPAMD_INET_ADDRESS_PARSE_DEFAULT)) {
+ numeric_ip = true;
+ }
+ }
+ }
+ }
+ else {
+ if (cbd->up) {
+ numeric_ip = true;
+ cbd->addr = rspamd_inet_address_copy(rspamd_upstream_addr_next(cbd->up), NULL);
+ }
+ cbd->host = NULL;
+ }
+
+ if (numeric_ip) {
+ /* Host is numeric IP, no need to resolve */
+ gboolean ret;
+
+ REF_RETAIN(cbd);
+ ret = lua_http_make_connection(cbd);
+
+ if (!ret) {
+ if (cbd->up) {
+ rspamd_upstream_fail(cbd->up, true, "HTTP connection failed");
+ }
+ if (cbd->ref.refcount > 1) {
+ /* Not released by make_connection */
+ REF_RELEASE(cbd);
+ }
+
+ REF_RELEASE(cbd);
+ lua_pushboolean(L, FALSE);
+
+ return 1;
+ }
+
+ REF_RELEASE(cbd);
+ }
+ else {
+ if (!cbd->host) {
+ REF_RELEASE(cbd);
+
+ return luaL_error(L, "no host has been specified");
+ }
+ if (task == NULL) {
+
+ REF_RETAIN(cbd);
+ if (!rspamd_dns_resolver_request(resolver, session, NULL, lua_http_dns_handler, cbd,
+ RDNS_REQUEST_A,
+ cbd->host)) {
+ if (cbd->ref.refcount > 1) {
+ /* Not released by make_connection */
+ REF_RELEASE(cbd);
+ }
+
+ REF_RELEASE(cbd);
+ lua_pushboolean(L, FALSE);
+
+ return 1;
+ }
+
+ REF_RELEASE(cbd);
+ }
+ else {
+ REF_RETAIN(cbd);
+
+ if (!rspamd_dns_resolver_request_task_forced(task, lua_http_dns_handler, cbd,
+ RDNS_REQUEST_A, cbd->host)) {
+ if (cbd->ref.refcount > 1) {
+ /* Not released by make_connection */
+ REF_RELEASE(cbd);
+ }
+
+ REF_RELEASE(cbd);
+ lua_pushboolean(L, FALSE);
+
+ return 1;
+ }
+ else if (cbd->item) {
+ rspamd_symcache_item_async_inc(cbd->task, cbd->item, M);
+ }
+
+ REF_RELEASE(cbd);
+ }
+ }
+
+ if (cbd->cbref == -1) {
+ cbd->thread = lua_thread_pool_get_running_entry(cfg->lua_thread_pool);
+ cbd->flags |= RSPAMD_LUA_HTTP_FLAG_YIELDED;
+
+ return lua_thread_yield(cbd->thread, 0);
+ }
+ else {
+ lua_pushboolean(L, TRUE);
+ }
+
+ return 1;
+}
+
+static gint
+lua_load_http(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_register(L, NULL, httplib_m);
+
+ return 1;
+}
+
+void luaopen_http(lua_State *L)
+{
+ rspamd_lua_add_preload(L, "rspamd_http", lua_load_http);
+}