| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 | 
							- 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]"
 
- }
 
 
  |