script_surge_http_request.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package script
  2. import (
  3. "context"
  4. "net/http"
  5. "regexp"
  6. "time"
  7. "unsafe"
  8. "github.com/sagernet/sing-box/adapter"
  9. C "github.com/sagernet/sing-box/constant"
  10. "github.com/sagernet/sing-box/option"
  11. "github.com/sagernet/sing-box/script/jsc"
  12. "github.com/sagernet/sing/common"
  13. E "github.com/sagernet/sing/common/exceptions"
  14. F "github.com/sagernet/sing/common/format"
  15. "github.com/sagernet/sing/common/logger"
  16. "github.com/dop251/goja"
  17. )
  18. var _ adapter.HTTPRequestScript = (*SurgeHTTPRequestScript)(nil)
  19. type SurgeHTTPRequestScript struct {
  20. GenericScript
  21. pattern *regexp.Regexp
  22. requiresBody bool
  23. maxSize int64
  24. binaryBodyMode bool
  25. }
  26. func NewSurgeHTTPRequestScript(ctx context.Context, logger logger.ContextLogger, options option.Script) (*SurgeHTTPRequestScript, error) {
  27. source, err := NewSource(ctx, logger, options)
  28. if err != nil {
  29. return nil, err
  30. }
  31. pattern, err := regexp.Compile(options.HTTPOptions.Pattern)
  32. if err != nil {
  33. return nil, E.Cause(err, "parse pattern")
  34. }
  35. return &SurgeHTTPRequestScript{
  36. GenericScript: GenericScript{
  37. logger: logger,
  38. tag: options.Tag,
  39. timeout: time.Duration(options.Timeout),
  40. arguments: options.Arguments,
  41. source: source,
  42. },
  43. pattern: pattern,
  44. requiresBody: options.HTTPOptions.RequiresBody,
  45. maxSize: options.HTTPOptions.MaxSize,
  46. binaryBodyMode: options.HTTPOptions.BinaryBodyMode,
  47. }, nil
  48. }
  49. func (s *SurgeHTTPRequestScript) Type() string {
  50. return C.ScriptTypeSurgeHTTPRequest
  51. }
  52. func (s *SurgeHTTPRequestScript) Tag() string {
  53. return s.tag
  54. }
  55. func (s *SurgeHTTPRequestScript) StartContext(ctx context.Context, startContext *adapter.HTTPStartContext) error {
  56. return s.source.StartContext(ctx, startContext)
  57. }
  58. func (s *SurgeHTTPRequestScript) PostStart() error {
  59. return s.source.PostStart()
  60. }
  61. func (s *SurgeHTTPRequestScript) Close() error {
  62. return s.source.Close()
  63. }
  64. func (s *SurgeHTTPRequestScript) Match(requestURL string) bool {
  65. return s.pattern.MatchString(requestURL)
  66. }
  67. func (s *SurgeHTTPRequestScript) RequiresBody() bool {
  68. return s.requiresBody
  69. }
  70. func (s *SurgeHTTPRequestScript) MaxSize() int64 {
  71. return s.maxSize
  72. }
  73. func (s *SurgeHTTPRequestScript) Run(ctx context.Context, request *http.Request, body []byte) (*adapter.HTTPRequestScriptResult, error) {
  74. program := s.source.Program()
  75. if program == nil {
  76. return nil, E.New("invalid script")
  77. }
  78. ctx, cancel := context.WithCancelCause(ctx)
  79. defer cancel(nil)
  80. vm := NewRuntime(ctx, s.logger, cancel)
  81. err := SetSurgeModules(vm, ctx, s.logger, cancel, s.Tag(), s.Type(), s.arguments)
  82. if err != nil {
  83. return nil, err
  84. }
  85. return ExecuteSurgeHTTPRequest(vm, program, ctx, s.timeout, request, body, s.binaryBodyMode)
  86. }
  87. func ExecuteSurgeHTTPRequest(vm *goja.Runtime, program *goja.Program, ctx context.Context, timeout time.Duration, request *http.Request, body []byte, binaryBody bool) (*adapter.HTTPRequestScriptResult, error) {
  88. if timeout == 0 {
  89. timeout = defaultScriptTimeout
  90. }
  91. ctx, cancel := context.WithTimeout(ctx, timeout)
  92. defer cancel()
  93. vm.ClearInterrupt()
  94. requestObject := vm.NewObject()
  95. requestObject.Set("url", request.URL.String())
  96. requestObject.Set("method", request.Method)
  97. requestObject.Set("headers", jsc.HeadersToValue(vm, request.Header))
  98. if !binaryBody {
  99. requestObject.Set("body", string(body))
  100. } else {
  101. requestObject.Set("body", jsc.NewUint8Array(vm, body))
  102. }
  103. requestObject.Set("id", F.ToString(uintptr(unsafe.Pointer(request))))
  104. vm.Set("request", requestObject)
  105. done := make(chan struct{})
  106. doneFunc := common.OnceFunc(func() {
  107. close(done)
  108. })
  109. var result adapter.HTTPRequestScriptResult
  110. vm.Set("done", func(call goja.FunctionCall) goja.Value {
  111. defer doneFunc()
  112. resultObject := jsc.AssertObject(vm, call.Argument(0), "done() argument", true)
  113. if resultObject == nil {
  114. panic(vm.NewGoError(E.New("request rejected by script")))
  115. }
  116. result.URL = jsc.AssertString(vm, resultObject.Get("url"), "url", true)
  117. result.Headers = jsc.AssertHTTPHeader(vm, resultObject.Get("headers"), "headers")
  118. result.Body = jsc.AssertStringBinary(vm, resultObject.Get("body"), "body", true)
  119. responseObject := jsc.AssertObject(vm, resultObject.Get("response"), "response", true)
  120. if responseObject != nil {
  121. result.Response = &adapter.HTTPRequestScriptResponse{
  122. Status: int(jsc.AssertInt(vm, responseObject.Get("status"), "status", true)),
  123. Headers: jsc.AssertHTTPHeader(vm, responseObject.Get("headers"), "headers"),
  124. Body: jsc.AssertStringBinary(vm, responseObject.Get("body"), "body", true),
  125. }
  126. }
  127. return goja.Undefined()
  128. })
  129. var err error
  130. go func() {
  131. _, err = vm.RunProgram(program)
  132. if err != nil {
  133. doneFunc()
  134. }
  135. }()
  136. select {
  137. case <-ctx.Done():
  138. vm.Interrupt(ctx.Err())
  139. return nil, ctx.Err()
  140. case <-done:
  141. if err != nil {
  142. vm.Interrupt(err)
  143. } else {
  144. vm.Interrupt("script done")
  145. }
  146. }
  147. return &result, err
  148. }