url_search_params.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. package url
  2. import (
  3. "fmt"
  4. "net/url"
  5. "sort"
  6. "strings"
  7. "github.com/sagernet/sing-box/script/jsc"
  8. F "github.com/sagernet/sing/common/format"
  9. "github.com/dop251/goja"
  10. )
  11. type URLSearchParams struct {
  12. class jsc.Class[*Module, *URLSearchParams]
  13. params []searchParam
  14. }
  15. func createURLSearchParams(module *Module) jsc.Class[*Module, *URLSearchParams] {
  16. class := jsc.NewClass[*Module, *URLSearchParams](module)
  17. class.DefineConstructor(newURLSearchParams)
  18. class.DefineField("size", (*URLSearchParams).getSize, nil)
  19. class.DefineMethod("append", (*URLSearchParams).append)
  20. class.DefineMethod("delete", (*URLSearchParams).delete)
  21. class.DefineMethod("entries", (*URLSearchParams).entries)
  22. class.DefineMethod("forEach", (*URLSearchParams).forEach)
  23. class.DefineMethod("get", (*URLSearchParams).get)
  24. class.DefineMethod("getAll", (*URLSearchParams).getAll)
  25. class.DefineMethod("has", (*URLSearchParams).has)
  26. class.DefineMethod("keys", (*URLSearchParams).keys)
  27. class.DefineMethod("set", (*URLSearchParams).set)
  28. class.DefineMethod("sort", (*URLSearchParams).sort)
  29. class.DefineMethod("toString", (*URLSearchParams).toString)
  30. class.DefineMethod("values", (*URLSearchParams).values)
  31. return class
  32. }
  33. func newURLSearchParams(class jsc.Class[*Module, *URLSearchParams], call goja.ConstructorCall) *URLSearchParams {
  34. var (
  35. params []searchParam
  36. err error
  37. )
  38. switch argInit := call.Argument(0).Export().(type) {
  39. case *URLSearchParams:
  40. params = argInit.params
  41. case string:
  42. params, err = parseQuery(argInit)
  43. if err != nil {
  44. panic(class.Runtime().NewGoError(err))
  45. }
  46. case [][]string:
  47. for _, pair := range argInit {
  48. if len(pair) != 2 {
  49. panic(class.Runtime().NewTypeError("Each query pair must be an iterable [name, value] tuple"))
  50. }
  51. params = append(params, searchParam{pair[0], pair[1]})
  52. }
  53. case map[string]any:
  54. for name, value := range argInit {
  55. stringValue, isString := value.(string)
  56. if !isString {
  57. panic(class.Runtime().NewTypeError("Invalid query value"))
  58. }
  59. params = append(params, searchParam{name, stringValue})
  60. }
  61. }
  62. return &URLSearchParams{class, params}
  63. }
  64. func (s *URLSearchParams) getSize() any {
  65. return len(s.params)
  66. }
  67. func (s *URLSearchParams) append(call goja.FunctionCall) any {
  68. name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
  69. value := call.Argument(1).String()
  70. s.params = append(s.params, searchParam{name, value})
  71. return goja.Undefined()
  72. }
  73. func (s *URLSearchParams) delete(call goja.FunctionCall) any {
  74. name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
  75. argValue := call.Argument(1)
  76. if !jsc.IsNil(argValue) {
  77. value := argValue.String()
  78. for i, param := range s.params {
  79. if param.Key == name && param.Value == value {
  80. s.params = append(s.params[:i], s.params[i+1:]...)
  81. break
  82. }
  83. }
  84. } else {
  85. for i, param := range s.params {
  86. if param.Key == name {
  87. s.params = append(s.params[:i], s.params[i+1:]...)
  88. break
  89. }
  90. }
  91. }
  92. return goja.Undefined()
  93. }
  94. func (s *URLSearchParams) entries(call goja.FunctionCall) any {
  95. return jsc.NewIterator[*Module, searchParam](s.class.Module().classURLSearchParamsIterator, s.params, func(this searchParam) any {
  96. return s.class.Runtime().NewArray(this.Key, this.Value)
  97. })
  98. }
  99. func (s *URLSearchParams) forEach(call goja.FunctionCall) any {
  100. callback := jsc.AssertFunction(s.class.Runtime(), call.Argument(0), "callbackFn")
  101. thisValue := call.Argument(1)
  102. for _, param := range s.params {
  103. for _, value := range param.Value {
  104. _, err := callback(thisValue, s.class.Runtime().ToValue(value), s.class.Runtime().ToValue(param.Key), call.This)
  105. if err != nil {
  106. panic(s.class.Runtime().NewGoError(err))
  107. }
  108. }
  109. }
  110. return goja.Undefined()
  111. }
  112. func (s *URLSearchParams) get(call goja.FunctionCall) any {
  113. name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
  114. for _, param := range s.params {
  115. if param.Key == name {
  116. return param.Value
  117. }
  118. }
  119. return goja.Null()
  120. }
  121. func (s *URLSearchParams) getAll(call goja.FunctionCall) any {
  122. name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
  123. var values []any
  124. for _, param := range s.params {
  125. if param.Key == name {
  126. values = append(values, param.Value)
  127. }
  128. }
  129. return s.class.Runtime().NewArray(values...)
  130. }
  131. func (s *URLSearchParams) has(call goja.FunctionCall) any {
  132. name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
  133. argValue := call.Argument(1)
  134. if !jsc.IsNil(argValue) {
  135. value := argValue.String()
  136. for _, param := range s.params {
  137. if param.Key == name && param.Value == value {
  138. return true
  139. }
  140. }
  141. } else {
  142. for _, param := range s.params {
  143. if param.Key == name {
  144. return true
  145. }
  146. }
  147. }
  148. return false
  149. }
  150. func (s *URLSearchParams) keys(call goja.FunctionCall) any {
  151. return jsc.NewIterator[*Module, searchParam](s.class.Module().classURLSearchParamsIterator, s.params, func(this searchParam) any {
  152. return this.Key
  153. })
  154. }
  155. func (s *URLSearchParams) set(call goja.FunctionCall) any {
  156. name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
  157. value := call.Argument(1).String()
  158. for i, param := range s.params {
  159. if param.Key == name {
  160. s.params[i].Value = value
  161. return goja.Undefined()
  162. }
  163. }
  164. s.params = append(s.params, searchParam{name, value})
  165. return goja.Undefined()
  166. }
  167. func (s *URLSearchParams) sort(call goja.FunctionCall) any {
  168. sort.SliceStable(s.params, func(i, j int) bool {
  169. return s.params[i].Key < s.params[j].Key
  170. })
  171. return goja.Undefined()
  172. }
  173. func (s *URLSearchParams) toString(call goja.FunctionCall) any {
  174. return generateQuery(s.params)
  175. }
  176. func (s *URLSearchParams) values(call goja.FunctionCall) any {
  177. return jsc.NewIterator[*Module, searchParam](s.class.Module().classURLSearchParamsIterator, s.params, func(this searchParam) any {
  178. return this.Value
  179. })
  180. }
  181. type searchParam struct {
  182. Key string
  183. Value string
  184. }
  185. func parseQuery(query string) (params []searchParam, err error) {
  186. query = strings.TrimPrefix(query, "?")
  187. for query != "" {
  188. var key string
  189. key, query, _ = strings.Cut(query, "&")
  190. if strings.Contains(key, ";") {
  191. err = fmt.Errorf("invalid semicolon separator in query")
  192. continue
  193. }
  194. if key == "" {
  195. continue
  196. }
  197. key, value, _ := strings.Cut(key, "=")
  198. key, err1 := url.QueryUnescape(key)
  199. if err1 != nil {
  200. if err == nil {
  201. err = err1
  202. }
  203. continue
  204. }
  205. value, err1 = url.QueryUnescape(value)
  206. if err1 != nil {
  207. if err == nil {
  208. err = err1
  209. }
  210. continue
  211. }
  212. params = append(params, searchParam{key, value})
  213. }
  214. return
  215. }
  216. func generateQuery(params []searchParam) string {
  217. var parts []string
  218. for _, param := range params {
  219. parts = append(parts, F.ToString(param.Key, "=", url.QueryEscape(param.Value)))
  220. }
  221. return strings.Join(parts, "&")
  222. }