mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-28 23:15:59 +03:00
mitm: Refactor & Add url
This commit is contained in:
150
script/modules/surge/http.go
Normal file
150
script/modules/surge/http.go
Normal file
@@ -0,0 +1,150 @@
|
||||
package surge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/script/jsc"
|
||||
"github.com/sagernet/sing-box/script/modules/boxctx"
|
||||
"github.com/sagernet/sing/common"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
type HTTP struct {
|
||||
class jsc.Class[*Module, *HTTP]
|
||||
cookieJar *cookiejar.Jar
|
||||
httpTransport *http.Transport
|
||||
}
|
||||
|
||||
func createHTTP(module *Module) jsc.Class[*Module, *HTTP] {
|
||||
class := jsc.NewClass[*Module, *HTTP](module)
|
||||
class.DefineConstructor(newHTTP)
|
||||
class.DefineMethod("get", httpRequest(http.MethodGet))
|
||||
class.DefineMethod("post", httpRequest(http.MethodPost))
|
||||
class.DefineMethod("put", httpRequest(http.MethodPut))
|
||||
class.DefineMethod("delete", httpRequest(http.MethodDelete))
|
||||
class.DefineMethod("head", httpRequest(http.MethodHead))
|
||||
class.DefineMethod("options", httpRequest(http.MethodOptions))
|
||||
class.DefineMethod("patch", httpRequest(http.MethodPatch))
|
||||
class.DefineMethod("trace", httpRequest(http.MethodTrace))
|
||||
class.DefineMethod("toString", (*HTTP).toString)
|
||||
return class
|
||||
}
|
||||
|
||||
func newHTTP(class jsc.Class[*Module, *HTTP], call goja.ConstructorCall) *HTTP {
|
||||
return &HTTP{
|
||||
class: class,
|
||||
cookieJar: common.Must1(cookiejar.New(&cookiejar.Options{
|
||||
PublicSuffixList: publicsuffix.List,
|
||||
})),
|
||||
httpTransport: &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
TLSClientConfig: &tls.Config{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func httpRequest(method string) func(s *HTTP, call goja.FunctionCall) any {
|
||||
return func(s *HTTP, call goja.FunctionCall) any {
|
||||
if len(call.Arguments) != 2 {
|
||||
panic(s.class.Runtime().NewTypeError("invalid arguments"))
|
||||
}
|
||||
context := boxctx.MustFromRuntime(s.class.Runtime())
|
||||
var (
|
||||
url string
|
||||
headers http.Header
|
||||
body []byte
|
||||
timeout = 5 * time.Second
|
||||
insecure bool
|
||||
autoCookie bool = true
|
||||
autoRedirect bool
|
||||
// policy string
|
||||
binaryMode bool
|
||||
)
|
||||
switch optionsValue := call.Argument(0).(type) {
|
||||
case goja.String:
|
||||
url = optionsValue.String()
|
||||
case *goja.Object:
|
||||
url = jsc.AssertString(s.class.Runtime(), optionsValue.Get("url"), "options.url", false)
|
||||
headers = jsc.AssertHTTPHeader(s.class.Runtime(), optionsValue.Get("headers"), "option.headers")
|
||||
body = jsc.AssertStringBinary(s.class.Runtime(), optionsValue.Get("body"), "options.body", true)
|
||||
timeoutInt := jsc.AssertInt(s.class.Runtime(), optionsValue.Get("timeout"), "options.timeout", true)
|
||||
if timeoutInt > 0 {
|
||||
timeout = time.Duration(timeoutInt) * time.Second
|
||||
}
|
||||
insecure = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("insecure"), "options.insecure", true)
|
||||
autoCookie = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("auto-cookie"), "options.auto-cookie", true)
|
||||
autoRedirect = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("auto-redirect"), "options.auto-redirect", true)
|
||||
// policy = jsc.AssertString(s.class.Runtime(), optionsValue.Get("policy"), "options.policy", true)
|
||||
binaryMode = jsc.AssertBool(s.class.Runtime(), optionsValue.Get("binary-mode"), "options.binary-mode", true)
|
||||
default:
|
||||
panic(s.class.Runtime().NewTypeError(F.ToString("invalid argument: options: expected string or object, but got ", optionsValue)))
|
||||
}
|
||||
callback := jsc.AssertFunction(s.class.Runtime(), call.Argument(1), "callback")
|
||||
s.httpTransport.TLSClientConfig.InsecureSkipVerify = insecure
|
||||
httpClient := &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: s.httpTransport,
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
if autoRedirect {
|
||||
return nil
|
||||
}
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
if autoCookie {
|
||||
httpClient.Jar = s.cookieJar
|
||||
}
|
||||
request, err := http.NewRequestWithContext(context.Context, method, url, bytes.NewReader(body))
|
||||
if host := headers.Get("Host"); host != "" {
|
||||
request.Host = host
|
||||
headers.Del("Host")
|
||||
}
|
||||
request.Header = headers
|
||||
if err != nil {
|
||||
panic(s.class.Runtime().NewGoError(err))
|
||||
}
|
||||
go func() {
|
||||
defer s.httpTransport.CloseIdleConnections()
|
||||
response, executeErr := httpClient.Do(request)
|
||||
if err != nil {
|
||||
_, err = callback(nil, s.class.Runtime().NewGoError(executeErr), nil, nil)
|
||||
if err != nil {
|
||||
context.ErrorHandler(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer response.Body.Close()
|
||||
var content []byte
|
||||
content, err = io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
_, err = callback(nil, s.class.Runtime().NewGoError(err), nil, nil)
|
||||
if err != nil {
|
||||
context.ErrorHandler(err)
|
||||
}
|
||||
}
|
||||
responseObject := s.class.Runtime().NewObject()
|
||||
responseObject.Set("status", response.StatusCode)
|
||||
responseObject.Set("headers", jsc.HeadersToValue(s.class.Runtime(), response.Header))
|
||||
var bodyValue goja.Value
|
||||
if binaryMode {
|
||||
bodyValue = jsc.NewUint8Array(s.class.Runtime(), content)
|
||||
} else {
|
||||
bodyValue = s.class.Runtime().ToValue(string(content))
|
||||
}
|
||||
_, err = callback(nil, nil, responseObject, bodyValue)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HTTP) toString(call goja.FunctionCall) any {
|
||||
return "[sing-box Surge HTTP]"
|
||||
}
|
||||
Reference in New Issue
Block a user