chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
package luaplugin
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
// ssrfGuard rejects connections to non-public addresses. It runs as the
|
||||
// dialer's Control hook, so it sees the resolved IP for every connection
|
||||
// attempt, including ones reached via HTTP redirects or DNS rebinding.
|
||||
func ssrfGuard(network, address string, _ syscall.RawConn) error {
|
||||
host, _, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("cannot resolve dial address %q", address)
|
||||
}
|
||||
if ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() ||
|
||||
ip.IsLinkLocalMulticast() || ip.IsMulticast() || ip.IsUnspecified() {
|
||||
return fmt.Errorf("blocked request to non-public address %s", ip)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var httpClient = &http.Client{
|
||||
Timeout: 5 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 5 * time.Second,
|
||||
Control: ssrfGuard,
|
||||
}).DialContext,
|
||||
},
|
||||
}
|
||||
|
||||
// registerHTTPAPI adds cliamp.http.{get,post} to the cliamp table.
|
||||
func registerHTTPAPI(L *lua.LState, cliamp *lua.LTable) {
|
||||
tbl := L.NewTable()
|
||||
|
||||
// cliamp.http.get(url, opts?) -> body, status
|
||||
L.SetField(tbl, "get", L.NewFunction(func(L *lua.LState) int {
|
||||
return doHTTP(L, "GET")
|
||||
}))
|
||||
|
||||
// cliamp.http.post(url, opts?) -> body, status
|
||||
L.SetField(tbl, "post", L.NewFunction(func(L *lua.LState) int {
|
||||
return doHTTP(L, "POST")
|
||||
}))
|
||||
|
||||
L.SetField(cliamp, "http", tbl)
|
||||
}
|
||||
|
||||
const maxResponseBody = 1 << 20 // 1MB
|
||||
|
||||
func doHTTP(L *lua.LState, method string) int {
|
||||
rawURL := L.CheckString(1)
|
||||
if u, err := url.Parse(rawURL); err != nil || (u.Scheme != "http" && u.Scheme != "https") {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString("only http and https URLs are allowed"))
|
||||
return 2
|
||||
}
|
||||
opts := L.OptTable(2, nil)
|
||||
|
||||
var bodyReader io.Reader
|
||||
if opts != nil {
|
||||
// JSON body: cliamp.http.post(url, {json = {...}})
|
||||
if jsonVal := opts.RawGetString("json"); jsonVal != lua.LNil {
|
||||
goVal := luaToGo(jsonVal)
|
||||
data, err := json.Marshal(goVal)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
bodyReader = strings.NewReader(string(data))
|
||||
}
|
||||
|
||||
// Raw body: cliamp.http.post(url, {body = "..."})
|
||||
if body := opts.RawGetString("body"); body != lua.LNil {
|
||||
bodyReader = strings.NewReader(body.String())
|
||||
}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, rawURL, bodyReader)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
// Apply headers from opts.
|
||||
if opts != nil {
|
||||
if hdrs := opts.RawGetString("headers"); hdrs != lua.LNil {
|
||||
if tbl, ok := hdrs.(*lua.LTable); ok {
|
||||
tbl.ForEach(func(k, v lua.LValue) {
|
||||
req.Header.Set(k.String(), v.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
// Auto-set Content-Type for JSON if not explicitly set.
|
||||
if opts.RawGetString("json") != lua.LNil && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseBody))
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
L.Push(lua.LString(err.Error()))
|
||||
return 2
|
||||
}
|
||||
|
||||
L.Push(lua.LString(string(body)))
|
||||
L.Push(lua.LNumber(resp.StatusCode))
|
||||
return 2
|
||||
}
|
||||
Reference in New Issue
Block a user