From 856f1d0bed80590aa6f7c5583fc0007253962176 Mon Sep 17 00:00:00 2001 From: Christian Roessner Date: Thu, 26 Sep 2024 13:10:54 +0200 Subject: [PATCH] Feat: Integrate gluahttp library for HTTP requests Replaced custom HTTP requests with gluahttp library across various Lua plugins for improved consistency and error handling. This change simplifies the code and ensures that all HTTP interactions follow a unified approach. Signed-off-by: Christian Roessner --- go.mod | 1 + go.sum | 2 + server/global/const.go | 3 + server/lua-plugins.d/actions/bruteforce.lua | 14 +- .../lua-plugins.d/actions/haveibeenpwnd.lua | 81 +++--- server/lua-plugins.d/actions/telegram.lua | 42 +-- server/lua-plugins.d/features/blocklist.lua | 46 ++-- server/lua-plugins.d/filters/geoip.lua | 49 ++-- server/lua-plugins.d/filters/monitoring.lua | 22 +- server/lualib/loader.go | 9 + .../github.com/cjoudrey/gluahttp/.gitignore | 24 ++ .../github.com/cjoudrey/gluahttp/.travis.yml | 15 ++ vendor/github.com/cjoudrey/gluahttp/LICENSE | 22 ++ vendor/github.com/cjoudrey/gluahttp/README.md | 250 +++++++++++++++++ .../github.com/cjoudrey/gluahttp/gluahttp.go | 251 ++++++++++++++++++ .../cjoudrey/gluahttp/httpresponsetype.go | 98 +++++++ vendor/modules.txt | 3 + 17 files changed, 806 insertions(+), 126 deletions(-) create mode 100644 vendor/github.com/cjoudrey/gluahttp/.gitignore create mode 100644 vendor/github.com/cjoudrey/gluahttp/.travis.yml create mode 100644 vendor/github.com/cjoudrey/gluahttp/LICENSE create mode 100644 vendor/github.com/cjoudrey/gluahttp/README.md create mode 100644 vendor/github.com/cjoudrey/gluahttp/gluahttp.go create mode 100644 vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go diff --git a/go.mod b/go.mod index e8e9c8d3..3d0d7fdd 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ toolchain go1.23.1 require ( github.com/biter777/countries v1.7.5 + github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 github.com/coreos/go-oidc/v3 v3.11.0 github.com/dspinhirne/netaddr-go v0.0.0-20211008142535-a4c5bccad224 github.com/emersion/go-imap v1.2.1 diff --git a/go.sum b/go.sum index 400e0620..d805c95c 100644 --- a/go.sum +++ b/go.sum @@ -51,6 +51,8 @@ github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 h1:rdWOzitWlNYeUsXmz+IQfa9NkGEq3gA/qQ3mOEqBU6o= +github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= diff --git a/server/global/const.go b/server/global/const.go index 709f102c..91be2b07 100644 --- a/server/global/const.go +++ b/server/global/const.go @@ -986,6 +986,9 @@ const ( // LuaModGLuaCrypto is a constant that represents the name of the GLuaCrypto module in Lua. LuaModGLuaCrypto = "nauthilus_gluacrypto" + // LuaModGLuaHTTP is a constant that represents the module name for Lua HTTP functionality. + LuaModGLuaHTTP = "nauthilus_gluahttp" + // LuaModGLLPlugin is a constant that represents the name of the GLL plugin module in Lua. LuaModGLLPlugin = "nauthilus_gll_plugin" diff --git a/server/lua-plugins.d/actions/bruteforce.lua b/server/lua-plugins.d/actions/bruteforce.lua index 49a3d459..bb647f4e 100644 --- a/server/lua-plugins.d/actions/bruteforce.lua +++ b/server/lua-plugins.d/actions/bruteforce.lua @@ -13,16 +13,16 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -local nauthilus_util = require("nauthilus_util") +function nauthilus_call_action(request) + if not request.repeating then + local nauthilus_util = require("nauthilus_util") -dynamic_loader("nauthilus_context") -local nauthilus_context = require("nauthilus_context") + dynamic_loader("nauthilus_context") + local nauthilus_context = require("nauthilus_context") -dynamic_loader("nauthilus_gll_tcp") -local tcp = require("tcp") + dynamic_loader("nauthilus_gll_tcp") + local tcp = require("tcp") -function nauthilus_call_action(request) - if not request.repeating then -- Send IP/Mask local conn, err = tcp.open(os.getenv('HAPROXY_STATS')) diff --git a/server/lua-plugins.d/actions/haveibeenpwnd.lua b/server/lua-plugins.d/actions/haveibeenpwnd.lua index f8baafee..a7376133 100644 --- a/server/lua-plugins.d/actions/haveibeenpwnd.lua +++ b/server/lua-plugins.d/actions/haveibeenpwnd.lua @@ -13,37 +13,6 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -local nauthilus_util = require("nauthilus_util") - -dynamic_loader("nauthilus_redis") -local nauthilus_redis = require("nauthilus_redis") - -dynamic_loader("nauthilus_mail") -local nauthilus_mail = require("nauthilus_mail") - -dynamic_loader("nauthilus_misc") -local nauthilus_misc = require("nauthilus_misc") - -dynamic_loader("nauthilus_context") -local nauthilus_context = require("nauthilus_context") - -dynamic_loader("nauthilus_gluacrypto") -local crypto = require('crypto') - -dynamic_loader("nauthilus_gll_http") -local http = require("http") - -dynamic_loader("nauthilus_gll_strings") -local strings = require("strings") - -dynamic_loader("nauthilus_gll_template") -local template = require("template") - -local client = http.client({ - timeout = 30, - user_agent = "Nauthilus", -}) - local smtp_message = [[ Hello, @@ -62,8 +31,36 @@ Regards Postmaster ]] +local N = "haveibeenpwnd" + function nauthilus_call_action(request) if not request.no_auth and request.authenticated then + local nauthilus_util = require("nauthilus_util") + + dynamic_loader("nauthilus_redis") + local nauthilus_redis = require("nauthilus_redis") + + dynamic_loader("nauthilus_mail") + local nauthilus_mail = require("nauthilus_mail") + + dynamic_loader("nauthilus_misc") + local nauthilus_misc = require("nauthilus_misc") + + dynamic_loader("nauthilus_context") + local nauthilus_context = require("nauthilus_context") + + dynamic_loader("nauthilus_gluacrypto") + local crypto = require('crypto') + + dynamic_loader("nauthilus_gluahttp") + local http = require("glua_http") + + dynamic_loader("nauthilus_gll_strings") + local strings = require("strings") + + dynamic_loader("nauthilus_gll_template") + local template = require("template") + nauthilus_misc.wait_random(500, 3000) local redis_key = "ntc:HAVEIBEENPWND:" .. crypto.md5(request.account) @@ -76,22 +73,26 @@ function nauthilus_call_action(request) if nauthilus_util.is_number(redis_hash_count) then if redis_hash_count > 0 then -- Required by telegram.lua - nauthilus_context.context_set("haveibeenpwnd_hash_info", hash:sub(1, 5) .. redis_hash_count) + nauthilus_context.context_set(N .. "_hash_info", hash:sub(1, 5) .. redis_hash_count) - nauthilus_builtin.custom_log_add("action_haveibeenpwnd", "leaked") + nauthilus_builtin.custom_log_add(N .. "_result", "leaked") end return nauthilus_builtin.ACTION_RESULT_OK end end - local http_request = http.request("GET", "https://api.pwnedpasswords.com/range/" .. hash:sub(1, 5), "") - - local result, err = client:do_request(http_request) + local result, err = http.get("https://api.pwnedpasswords.com/range/" .. hash:sub(1, 5), { + timeout = "10s", + headers = { + Accept = "*/*", + ["User-Agent"] = "Nauthilus", + }, + }) nauthilus_util.if_error_raise(err) - if result.code ~= 200 then - nauthilus_util.if_error_raise("haveibeenpwnd did not return status code 200") + if result.status_code ~= 200 then + nauthilus_util.if_error_raise(N .. "_status_code=" .. tostring(result.status_code)) end for line in result.body:gmatch("([^\n]*)\n?") do @@ -104,8 +105,8 @@ function nauthilus_call_action(request) nauthilus_util.if_error_raise(err_redis_expire) -- Required by telegram.lua - nauthilus_context.context_set("haveibeenpwnd_hash_info", hash:sub(1, 5) .. cmp_hash[2]) - nauthilus_builtin.custom_log_add("action_haveibeenpwnd", "leaked") + nauthilus_context.context_set(N .. "_hash_info", hash:sub(1, 5) .. cmp_hash[2]) + nauthilus_builtin.custom_log_add(N .. "_action", "leaked") local already_sent_mail, err_redis_hget2 = nauthilus_redis.redis_hget(redis_key, "send_mail") nauthilus_util.if_error_raise(err_redis_hget2) diff --git a/server/lua-plugins.d/actions/telegram.lua b/server/lua-plugins.d/actions/telegram.lua index 2170b630..f45ebc70 100644 --- a/server/lua-plugins.d/actions/telegram.lua +++ b/server/lua-plugins.d/actions/telegram.lua @@ -13,29 +13,18 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -local nauthilus_util = require("nauthilus_util") - -dynamic_loader("nauthilus_context") -local nauthilus_context = require("nauthilus_context") - -dynamic_loader("nauthilus_gll_http") -local http = require("http") - -dynamic_loader("nauthilus_gll_telegram") -local telegram = require("telegram") - -dynamic_loader("nauthilus_gll_json") -local json = require("json") +local N = "telegram" -dynamic_loader("nauthilus_gll_template") -local template = require("template") +function nauthilus_call_action(request) + if request.no_auth then + return nauthilus_builtin.ACTION_RESULT_OK + end -local client = http.client() -local bot = telegram.bot(os.getenv("TELEGRAM_PASSWORD"), client) + local nauthilus_util = require("nauthilus_util") -local N = "telegram" + dynamic_loader("nauthilus_context") + local nauthilus_context = require("nauthilus_context") -function nauthilus_call_action(request) local send_message = false local pwnd_info = "n/a" local headline = "Information" @@ -101,6 +90,21 @@ function nauthilus_call_action(request) end if send_message then + dynamic_loader("nauthilus_gll_http") + local http = require("http") + + dynamic_loader("nauthilus_gll_telegram") + local telegram = require("telegram") + + dynamic_loader("nauthilus_gll_template") + local template = require("template") + + dynamic_loader("nauthilus_gll_json") + local json = require("json") + + local client = http.client() + local bot = telegram.bot(os.getenv("TELEGRAM_PASSWORD"), client) + local result = request local proto = request.protocol diff --git a/server/lua-plugins.d/features/blocklist.lua b/server/lua-plugins.d/features/blocklist.lua index 20143d72..3768e6b1 100644 --- a/server/lua-plugins.d/features/blocklist.lua +++ b/server/lua-plugins.d/features/blocklist.lua @@ -13,22 +13,6 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -local nauthilus_util = require("nauthilus_util") - -dynamic_loader("nauthilus_context") -local nauthilus_context = require("nauthilus_context") - -dynamic_loader("nauthilus_gll_http") -local http = require("http") - -dynamic_loader("nauthilus_gll_json") -local json = require("json") - -local client = http.client({ - timeout = 30, - user_agent = "Nauthilus" -}) - local N = "feature_blocklist" function nauthilus_call_feature(request) @@ -36,11 +20,16 @@ function nauthilus_call_feature(request) return nauthilus_builtin.FEATURE_TRIGGER_NO, nauthilus_builtin.FEATURES_ABORT_NO, nauthilus_builtin.FEATURE_RESULT_YES end - if not request.client_ip then - nauthilus_builtin.custom_log_add(N, "no client IP found") + local nauthilus_util = require("nauthilus_util") - return nauthilus_builtin.FEATURE_TRIGGER_NO, nauthilus_builtin.FEATURES_ABORT_NO, nauthilus_builtin.FEATURE_RESULT_FAILURE - end + dynamic_loader("nauthilus_context") + local nauthilus_context = require("nauthilus_context") + + dynamic_loader("nauthilus_gluahttp") + local http = require("glua_http") + + dynamic_loader("nauthilus_gll_json") + local json = require("json") -- Get result table local rt = nauthilus_context.context_get("rt") @@ -55,14 +44,19 @@ function nauthilus_call_feature(request) local payload, json_encode_err = json.encode(t) nauthilus_util.if_error_raise(json_encode_err) - local blocklist_request = http.request("POST", os.getenv("BLOCKLIST_URL"), payload) - blocklist_request:header_set("Content-Type", "application/json") - - local result, request_err = client:do_request(blocklist_request) + local result, request_err = http.post(os.getenv("BLOCKLIST_URL"), { + timeout = "10s", + headers = { + Accept = "*/*", + ["User-Agent"] = "Nauthilus", + ["Content-Type"] = "application/json", + }, + body = payload, + }) nauthilus_util.if_error_raise(request_err) - if result.code ~= 200 then - nauthilus_util.if_error_raise(N .. "_status_code=" .. tostring(result.code)) + if result.status_code ~= 200 then + nauthilus_util.if_error_raise(N .. "_status_code=" .. tostring(result.status_code)) end local response, err_jdec = json.decode(result.body) diff --git a/server/lua-plugins.d/filters/geoip.lua b/server/lua-plugins.d/filters/geoip.lua index 84dcd231..f0aba2fc 100644 --- a/server/lua-plugins.d/filters/geoip.lua +++ b/server/lua-plugins.d/filters/geoip.lua @@ -12,26 +12,15 @@ -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . +local N = "geoippolicyd" -local nauthilus_util = require("nauthilus_util") - -dynamic_loader("nauthilus_context") -local nauthilus_context = require("nauthilus_context") - -dynamic_loader("nauthilus_gll_http") -local http = require("http") - -dynamic_loader("nauthilus_gll_json") -local json = require("json") - -local client = http.client({ - timeout = 30, - user_agent = "Nauthilus" -}) +function nauthilus_call_filter(request) + if request.no_auth then + return nauthilus_builtin.FILTER_ACCEPT, nauthilus_builtin.FILTER_RESULT_OK + end -local N = "filter_geoippolicyd" + local nauthilus_util = require("nauthilus_util") -function nauthilus_call_filter(request) local function add_custom_logs(object) for item, values in pairs(object) do if type(values) == "table" then @@ -65,7 +54,16 @@ function nauthilus_call_filter(request) ts = "unknown" end - if request.user_found and request.authenticated and not (request.no_auth or request.client_ip == "127.0.0.1") then + if request.authenticated and request.client_ip ~= "127.0.0.1" then + dynamic_loader("nauthilus_context") + local nauthilus_context = require("nauthilus_context") + + dynamic_loader("nauthilus_gluahttp") + local http = require("glua_http") + + dynamic_loader("nauthilus_gll_json") + local json = require("json") + local t = {} t.key = "client" @@ -77,13 +75,18 @@ function nauthilus_call_filter(request) local payload, json_encode_err = json.encode(t) nauthilus_util.if_error_raise(json_encode_err) - local geoip_request = http.request("POST", os.getenv("GEOIP_POLICY_URL"), payload) - geoip_request:header_set("Content-Type", "application/json") - - local result, request_err = client:do_request(geoip_request) + local result, request_err = http.post(os.getenv("GEOIP_POLICY_URL"), { + timeout = "10s", + headers = { + Accept = "*/*", + ["User-Agent"] = "Nauthilus", + ["Content-Type"] = "application/json", + }, + body = payload, + }) nauthilus_util.if_error_raise(request_err) - if result.code ~= 202 then + if result.status_code ~= 202 then nauthilus_util.if_error_raise(N .. "_status_code=" .. tostring(result.code)) end diff --git a/server/lua-plugins.d/filters/monitoring.lua b/server/lua-plugins.d/filters/monitoring.lua index e91144e6..10c73509 100644 --- a/server/lua-plugins.d/filters/monitoring.lua +++ b/server/lua-plugins.d/filters/monitoring.lua @@ -13,20 +13,9 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . -local nauthilus_util = require("nauthilus_util") - -dynamic_loader("nauthilus_redis") -local nauthilus_redis = require("nauthilus_redis") - dynamic_loader("nauthilus_backend") local nauthilus_backend = require("nauthilus_backend") -dynamic_loader("nauthilus_http_request") -local nauthilus_http_request = require("nauthilus_http_request") - -dynamic_loader("nauthilus_gluacrypto") -local crypto = require("crypto") - local N = "monitoring" local wanted_protocols = { @@ -61,6 +50,11 @@ function nauthilus_call_filter(request) return nauthilus_builtin.FILTER_ACCEPT, nauthilus_builtin.FILTER_RESULT_OK end + local nauthilus_util = require("nauthilus_util") + + dynamic_loader("nauthilus_http_request") + local nauthilus_http_request = require("nauthilus_http_request") + local function get_dovecot_session() local header = nauthilus_http_request.get_http_request_header("X-Dovecot-Session") if nauthilus_util.table_length(header) == 1 then @@ -70,6 +64,9 @@ function nauthilus_call_filter(request) return nil end + dynamic_loader("nauthilus_redis") + local nauthilus_redis = require("nauthilus_redis") + local function set_initial_expiry(redis_key) local length, err_redis_hlen = nauthilus_redis.redis_hlen(redis_key) if err_redis_hlen then @@ -84,6 +81,9 @@ function nauthilus_call_filter(request) end end + dynamic_loader("nauthilus_gluacrypto") + local crypto = require("crypto") + local function add_session(session, server) if session == nil then return diff --git a/server/lualib/loader.go b/server/lualib/loader.go index b5bca098..d1ef3e9f 100644 --- a/server/lualib/loader.go +++ b/server/lualib/loader.go @@ -16,6 +16,11 @@ package lualib import ( + "crypto/tls" + stdhttp "net/http" + + "github.com/cjoudrey/gluahttp" + "github.com/croessner/nauthilus/server/config" "github.com/croessner/nauthilus/server/global" "github.com/croessner/nauthilus/server/lualib/redislib" "github.com/tengattack/gluacrypto" @@ -139,6 +144,10 @@ func RegisterCommonLuaLibraries(L *lua.LState, modName string, registry map[stri zabbix.Preload(L) case global.LuaModGLuaCrypto: gluacrypto.Preload(L) + case global.LuaModGLuaHTTP: + L.PreloadModule("glua_http", gluahttp.NewHttpModule(&stdhttp.Client{ + Transport: &stdhttp.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: config.LoadableConfig.Server.TLS.HTTPClientSkipVerify}}, + }).Loader) case global.LuaModPassword: L.PreloadModule(modName, LoaderModPassword) case global.LuaModRedis: diff --git a/vendor/github.com/cjoudrey/gluahttp/.gitignore b/vendor/github.com/cjoudrey/gluahttp/.gitignore new file mode 100644 index 00000000..daf913b1 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/cjoudrey/gluahttp/.travis.yml b/vendor/github.com/cjoudrey/gluahttp/.travis.yml new file mode 100644 index 00000000..16687575 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - "1.8" + - "1.9" + - "1.10" + +install: + - go get github.com/yuin/gopher-lua + +script: + - go test -v + +notifications: + email: false diff --git a/vendor/github.com/cjoudrey/gluahttp/LICENSE b/vendor/github.com/cjoudrey/gluahttp/LICENSE new file mode 100644 index 00000000..56a399ad --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Christian Joudrey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/cjoudrey/gluahttp/README.md b/vendor/github.com/cjoudrey/gluahttp/README.md new file mode 100644 index 00000000..91ad632f --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/README.md @@ -0,0 +1,250 @@ +# gluahttp + +[![](https://travis-ci.org/cjoudrey/gluahttp.svg)](https://travis-ci.org/cjoudrey/gluahttp) + +gluahttp provides an easy way to make HTTP requests from within [GopherLua](https://github.com/yuin/gopher-lua). + +## Installation + +``` +go get github.com/cjoudrey/gluahttp +``` + +## Usage + +```go +import "github.com/yuin/gopher-lua" +import "github.com/cjoudrey/gluahttp" + +func main() { + L := lua.NewState() + defer L.Close() + + L.PreloadModule("http", NewHttpModule(&http.Client{}).Loader) + + if err := L.DoString(` + + local http = require("http") + + response, error_message = http.request("GET", "http://example.com", { + query="page=1", + timeout="30s", + headers={ + Accept="*/*" + } + }) + + `); err != nil { + panic(err) + } +} +``` + +## API + +- [`http.delete(url [, options])`](#httpdeleteurl--options) +- [`http.get(url [, options])`](#httpgeturl--options) +- [`http.head(url [, options])`](#httpheadurl--options) +- [`http.patch(url [, options])`](#httppatchurl--options) +- [`http.post(url [, options])`](#httpposturl--options) +- [`http.put(url [, options])`](#httpputurl--options) +- [`http.request(method, url [, options])`](#httprequestmethod-url--options) +- [`http.request_batch(requests)`](#httprequest_batchrequests) +- [`http.response`](#httpresponse) + +### http.delete(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.get(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.head(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.patch(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.post(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.put(url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.request(method, url [, options]) + +**Attributes** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| method | String | The HTTP request method | +| url | String | URL of the resource to load | +| options | Table | Additional options | + +**Options** + +| Name | Type | Description | +| ------- | ------ | ----------- | +| query | String | URL encoded query params | +| cookies | Table | Additional cookies to send with the request | +| body | String | Request body. | +| form | String | Deprecated. URL encoded request body. This will also set the `Content-Type` header to `application/x-www-form-urlencoded` | +| headers | Table | Additional headers to send with the request | +| timeout | Number/String | Request timeout. Number of seconds or String such as "1h" | +| auth | Table | Username and password for HTTP basic auth. Table keys are *user* for username, *pass* for passwod. `auth={user="user", pass="pass"}` | + +**Returns** + +[http.response](#httpresponse) or (nil, error message) + +### http.request_batch(requests) + +**Attributes** + +| Name | Type | Description | +| -------- | ----- | ----------- | +| requests | Table | A table of requests to send. Each request item is by itself a table containing [http.request](#httprequestmethod-url--options) parameters for the request | + +**Returns** + +[[http.response](#httpresponse)] or ([[http.response](#httpresponse)], [error message]) + +### http.response + +The `http.response` table contains information about a completed HTTP request. + +**Attributes** + +| Name | Type | Description | +| ----------- | ------ | ----------- | +| body | String | The HTTP response body | +| body_size | Number | The size of the HTTP reponse body in bytes | +| headers | Table | The HTTP response headers | +| cookies | Table | The cookies sent by the server in the HTTP response | +| status_code | Number | The HTTP response status code | +| url | String | The final URL the request ended pointing to after redirects | diff --git a/vendor/github.com/cjoudrey/gluahttp/gluahttp.go b/vendor/github.com/cjoudrey/gluahttp/gluahttp.go new file mode 100644 index 00000000..609b3215 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/gluahttp.go @@ -0,0 +1,251 @@ +package gluahttp + +import ( + "context" + "errors" + "fmt" + "github.com/yuin/gopher-lua" + "io/ioutil" + "net/http" + "strings" + "time" +) + +type httpModule struct { + do func(req *http.Request) (*http.Response, error) +} + +type empty struct{} + +func NewHttpModule(client *http.Client) *httpModule { + return NewHttpModuleWithDo(client.Do) +} + +func NewHttpModuleWithDo(do func(req *http.Request) (*http.Response, error)) *httpModule { + return &httpModule{ + do: do, + } +} + +func (h *httpModule) Loader(L *lua.LState) int { + mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{ + "get": h.get, + "delete": h.delete, + "head": h.head, + "patch": h.patch, + "post": h.post, + "put": h.put, + "request": h.request, + "request_batch": h.requestBatch, + }) + registerHttpResponseType(mod, L) + L.Push(mod) + return 1 +} + +func (h *httpModule) get(L *lua.LState) int { + return h.doRequestAndPush(L, "get", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) delete(L *lua.LState) int { + return h.doRequestAndPush(L, "delete", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) head(L *lua.LState) int { + return h.doRequestAndPush(L, "head", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) patch(L *lua.LState) int { + return h.doRequestAndPush(L, "patch", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) post(L *lua.LState) int { + return h.doRequestAndPush(L, "post", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) put(L *lua.LState) int { + return h.doRequestAndPush(L, "put", L.ToString(1), L.ToTable(2)) +} + +func (h *httpModule) request(L *lua.LState) int { + return h.doRequestAndPush(L, L.ToString(1), L.ToString(2), L.ToTable(3)) +} + +func (h *httpModule) requestBatch(L *lua.LState) int { + requests := L.ToTable(1) + amountRequests := requests.Len() + + errs := make([]error, amountRequests) + responses := make([]*lua.LUserData, amountRequests) + sem := make(chan empty, amountRequests) + + i := 0 + + requests.ForEach(func(_ lua.LValue, value lua.LValue) { + requestTable := toTable(value) + + if requestTable != nil { + method := requestTable.RawGet(lua.LNumber(1)).String() + url := requestTable.RawGet(lua.LNumber(2)).String() + options := toTable(requestTable.RawGet(lua.LNumber(3))) + + go func(i int, L *lua.LState, method string, url string, options *lua.LTable) { + response, err := h.doRequest(L, method, url, options) + + if err == nil { + errs[i] = nil + responses[i] = response + } else { + errs[i] = err + responses[i] = nil + } + + sem <- empty{} + }(i, L, method, url, options) + } else { + errs[i] = errors.New("Request must be a table") + responses[i] = nil + sem <- empty{} + } + + i = i + 1 + }) + + for i = 0; i < amountRequests; i++ { + <-sem + } + + hasErrors := false + errorsTable := L.NewTable() + responsesTable := L.NewTable() + for i = 0; i < amountRequests; i++ { + if errs[i] == nil { + responsesTable.Append(responses[i]) + errorsTable.Append(lua.LNil) + } else { + responsesTable.Append(lua.LNil) + errorsTable.Append(lua.LString(fmt.Sprintf("%s", errs[i]))) + hasErrors = true + } + } + + if hasErrors { + L.Push(responsesTable) + L.Push(errorsTable) + return 2 + } else { + L.Push(responsesTable) + return 1 + } +} + +func (h *httpModule) doRequest(L *lua.LState, method string, url string, options *lua.LTable) (*lua.LUserData, error) { + req, err := http.NewRequest(strings.ToUpper(method), url, nil) + if err != nil { + return nil, err + } + + if ctx := L.Context(); ctx != nil { + req = req.WithContext(ctx) + } + + if options != nil { + if reqCookies, ok := options.RawGet(lua.LString("cookies")).(*lua.LTable); ok { + reqCookies.ForEach(func(key lua.LValue, value lua.LValue) { + req.AddCookie(&http.Cookie{Name: key.String(), Value: value.String()}) + }) + } + + switch reqQuery := options.RawGet(lua.LString("query")).(type) { + case lua.LString: + req.URL.RawQuery = reqQuery.String() + } + + body := options.RawGet(lua.LString("body")) + if _, ok := body.(lua.LString); !ok { + // "form" is deprecated. + body = options.RawGet(lua.LString("form")) + // Only set the Content-Type to application/x-www-form-urlencoded + // when someone uses "form", not for "body". + if _, ok := body.(lua.LString); ok { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } + } + + switch reqBody := body.(type) { + case lua.LString: + body := reqBody.String() + req.ContentLength = int64(len(body)) + req.Body = ioutil.NopCloser(strings.NewReader(body)) + } + + reqTimeout := options.RawGet(lua.LString("timeout")) + if reqTimeout != lua.LNil { + duration := time.Duration(0) + switch reqTimeout.(type) { + case lua.LNumber: + duration = time.Second * time.Duration(int(reqTimeout.(lua.LNumber))) + case lua.LString: + duration, err = time.ParseDuration(string(reqTimeout.(lua.LString))) + if err != nil { + return nil, err + } + } + ctx, cancel := context.WithTimeout(req.Context(), duration) + req = req.WithContext(ctx) + defer cancel() + } + + // Basic auth + if reqAuth, ok := options.RawGet(lua.LString("auth")).(*lua.LTable); ok { + user := reqAuth.RawGetString("user") + pass := reqAuth.RawGetString("pass") + if !lua.LVIsFalse(user) && !lua.LVIsFalse(pass) { + req.SetBasicAuth(user.String(), pass.String()) + } else { + return nil, fmt.Errorf("auth table must contain no nil user and pass fields") + } + } + + // Set these last. That way the code above doesn't overwrite them. + if reqHeaders, ok := options.RawGet(lua.LString("headers")).(*lua.LTable); ok { + reqHeaders.ForEach(func(key lua.LValue, value lua.LValue) { + req.Header.Set(key.String(), value.String()) + }) + } + } + + res, err := h.do(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + + if err != nil { + return nil, err + } + + return newHttpResponse(res, &body, len(body), L), nil +} + +func (h *httpModule) doRequestAndPush(L *lua.LState, method string, url string, options *lua.LTable) int { + response, err := h.doRequest(L, method, url, options) + + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(fmt.Sprintf("%s", err))) + return 2 + } + + L.Push(response) + return 1 +} + +func toTable(v lua.LValue) *lua.LTable { + if lv, ok := v.(*lua.LTable); ok { + return lv + } + return nil +} diff --git a/vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go b/vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go new file mode 100644 index 00000000..744a5877 --- /dev/null +++ b/vendor/github.com/cjoudrey/gluahttp/httpresponsetype.go @@ -0,0 +1,98 @@ +package gluahttp + +import "github.com/yuin/gopher-lua" +import "net/http" + +const luaHttpResponseTypeName = "http.response" + +type luaHttpResponse struct { + res *http.Response + body lua.LString + bodySize int +} + +func registerHttpResponseType(module *lua.LTable, L *lua.LState) { + mt := L.NewTypeMetatable(luaHttpResponseTypeName) + L.SetField(mt, "__index", L.NewFunction(httpResponseIndex)) + + L.SetField(module, "response", mt) +} + +func newHttpResponse(res *http.Response, body *[]byte, bodySize int, L *lua.LState) *lua.LUserData { + ud := L.NewUserData() + ud.Value = &luaHttpResponse{ + res: res, + body: lua.LString(*body), + bodySize: bodySize, + } + L.SetMetatable(ud, L.GetTypeMetatable(luaHttpResponseTypeName)) + return ud +} + +func checkHttpResponse(L *lua.LState) *luaHttpResponse { + ud := L.CheckUserData(1) + if v, ok := ud.Value.(*luaHttpResponse); ok { + return v + } + L.ArgError(1, "http.response expected") + return nil +} + +func httpResponseIndex(L *lua.LState) int { + res := checkHttpResponse(L) + + switch L.CheckString(2) { + case "headers": + return httpResponseHeaders(res, L) + case "cookies": + return httpResponseCookies(res, L) + case "status_code": + return httpResponseStatusCode(res, L) + case "url": + return httpResponseUrl(res, L) + case "body": + return httpResponseBody(res, L) + case "body_size": + return httpResponseBodySize(res, L) + } + + return 0 +} + +func httpResponseHeaders(res *luaHttpResponse, L *lua.LState) int { + headers := L.NewTable() + for key, _ := range res.res.Header { + headers.RawSetString(key, lua.LString(res.res.Header.Get(key))) + } + L.Push(headers) + return 1 +} + +func httpResponseCookies(res *luaHttpResponse, L *lua.LState) int { + cookies := L.NewTable() + for _, cookie := range res.res.Cookies() { + cookies.RawSetString(cookie.Name, lua.LString(cookie.Value)) + } + L.Push(cookies) + return 1 +} + +func httpResponseStatusCode(res *luaHttpResponse, L *lua.LState) int { + L.Push(lua.LNumber(res.res.StatusCode)) + return 1 +} + +func httpResponseUrl(res *luaHttpResponse, L *lua.LState) int { + L.Push(lua.LString(res.res.Request.URL.String())) + return 1 +} + +func httpResponseBody(res *luaHttpResponse, L *lua.LState) int { + L.Push(res.body) + return 1 +} + +func httpResponseBodySize(res *luaHttpResponse, L *lua.LState) int { + L.Push(lua.LNumber(res.bodySize)) + return 1 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 5e9d90c5..148a339d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -119,6 +119,9 @@ github.com/cespare/xxhash/v2 ## explicit; go 1.17 github.com/cheggaaa/pb/v3 github.com/cheggaaa/pb/v3/termutil +# github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 +## explicit +github.com/cjoudrey/gluahttp # github.com/cloudwego/base64x v0.1.4 ## explicit; go 1.16 github.com/cloudwego/base64x