From 021090f52cf6e78e0102884bbec18b82cf354de1 Mon Sep 17 00:00:00 2001 From: Hadi <112569860+anotherhadi@users.noreply.github.com> Date: Thu, 21 May 2026 10:21:37 +0200 Subject: [PATCH] init send_request func for plugins Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com> --- docs/plugins.md | 20 +++++++++++ internal/plugins/lua.go | 76 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/docs/plugins.md b/docs/plugins.md index 81fe7f2..bc35f63 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -123,6 +123,26 @@ else log("output: " .. out) end +-- Send an HTTP request directly (bypasses the proxy pipeline and other plugins). +-- method: HTTP method ("GET", "POST", etc.) +-- url: full URL to request +-- headers: table of request headers (optional, pass {} if unused) +-- body: request body string (optional, pass "" if unused) +-- options: optional table of options: +-- insecure = true skip TLS certificate verification +-- Returns: response table, error string (nil on success). +local res, err = send_request("POST", "https://example.com/api", { + ["Authorization"] = "Bearer " .. token, + ["Content-Type"] = "application/json", +}, '{"key":"value"}', { insecure = true }) +if err then + log("request failed: " .. err) +else + log(tostring(res.status_code)) + log(res.body) + log(res.headers["Content-Type"] or "") +end + -- Return the plugin's config section as a Lua table (parsed from YAML). -- Returns an empty table if no config is set. local cfg = get_config() diff --git a/internal/plugins/lua.go b/internal/plugins/lua.go index 3795ec0..df86363 100644 --- a/internal/plugins/lua.go +++ b/internal/plugins/lua.go @@ -3,12 +3,16 @@ package plugins import ( "bytes" "context" + "crypto/tls" + "io" "log" + "net/http" "net/url" "os/exec" "strings" "time" + "github.com/anotherhadi/spilltea/internal/config" "github.com/anotherhadi/spilltea/internal/db" goproxy "github.com/lqqyt2423/go-mitmproxy/proxy" lua "github.com/yuin/gopher-lua" @@ -198,6 +202,78 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) { return 1 })) + L.SetGlobal("send_request", L.NewFunction(func(L *lua.LState) int { + method := L.CheckString(1) + rawURL := L.CheckString(2) + hdrs := L.OptTable(3, L.NewTable()) + body := L.OptString(4, "") + opts := L.OptTable(5, L.NewTable()) + + insecure := false + if v, ok := L.GetField(opts, "insecure").(lua.LBool); ok { + insecure = bool(v) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, + } + if up := config.Global.App.UpstreamProxy; up != "" { + if proxyURL, err := url.Parse(up); err == nil { + transport.Proxy = http.ProxyURL(proxyURL) + } + } + client := &http.Client{ + Transport: transport, + Timeout: 30 * time.Second, + } + + var bodyReader io.Reader + if body != "" { + bodyReader = strings.NewReader(body) + } + req, err := http.NewRequest(method, rawURL, bodyReader) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + hdrs.ForEach(func(k, v lua.LValue) { + ks, kok := k.(lua.LString) + vs, vok := v.(lua.LString) + if kok && vok { + req.Header.Set(string(ks), string(vs)) + } + }) + + resp, err := client.Do(req) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + defer resp.Body.Close() + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + L.Push(lua.LNil) + L.Push(lua.LString(err.Error())) + return 2 + } + + result := L.NewTable() + L.SetField(result, "status_code", lua.LNumber(resp.StatusCode)) + L.SetField(result, "body", lua.LString(string(respBody))) + respHeaders := L.NewTable() + for k, vals := range resp.Header { + L.SetField(respHeaders, k, lua.LString(strings.Join(vals, ", "))) + } + L.SetField(result, "headers", respHeaders) + + L.Push(result) + L.Push(lua.LNil) + return 2 + })) + L.SetGlobal("shell_pipe", L.NewFunction(func(L *lua.LState) int { cmd := L.CheckString(1) input := L.OptString(2, "")