script_surge_http_response.go 4.8 KB

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