mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-03 20:07:36 +03:00
244 lines
6.6 KiB
Go
244 lines
6.6 KiB
Go
package url
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/sagernet/sing-box/script/jsc"
|
|
F "github.com/sagernet/sing/common/format"
|
|
|
|
"github.com/dop251/goja"
|
|
)
|
|
|
|
type URLSearchParams struct {
|
|
class jsc.Class[*Module, *URLSearchParams]
|
|
params []searchParam
|
|
}
|
|
|
|
func createURLSearchParams(module *Module) jsc.Class[*Module, *URLSearchParams] {
|
|
class := jsc.NewClass[*Module, *URLSearchParams](module)
|
|
class.DefineConstructor(newURLSearchParams)
|
|
class.DefineField("size", (*URLSearchParams).getSize, nil)
|
|
class.DefineMethod("append", (*URLSearchParams).append)
|
|
class.DefineMethod("delete", (*URLSearchParams).delete)
|
|
class.DefineMethod("entries", (*URLSearchParams).entries)
|
|
class.DefineMethod("forEach", (*URLSearchParams).forEach)
|
|
class.DefineMethod("get", (*URLSearchParams).get)
|
|
class.DefineMethod("getAll", (*URLSearchParams).getAll)
|
|
class.DefineMethod("has", (*URLSearchParams).has)
|
|
class.DefineMethod("keys", (*URLSearchParams).keys)
|
|
class.DefineMethod("set", (*URLSearchParams).set)
|
|
class.DefineMethod("sort", (*URLSearchParams).sort)
|
|
class.DefineMethod("toString", (*URLSearchParams).toString)
|
|
class.DefineMethod("values", (*URLSearchParams).values)
|
|
return class
|
|
}
|
|
|
|
func newURLSearchParams(class jsc.Class[*Module, *URLSearchParams], call goja.ConstructorCall) *URLSearchParams {
|
|
var (
|
|
params []searchParam
|
|
err error
|
|
)
|
|
switch argInit := call.Argument(0).Export().(type) {
|
|
case *URLSearchParams:
|
|
params = argInit.params
|
|
case string:
|
|
params, err = parseQuery(argInit)
|
|
if err != nil {
|
|
panic(class.Runtime().NewGoError(err))
|
|
}
|
|
case [][]string:
|
|
for _, pair := range argInit {
|
|
if len(pair) != 2 {
|
|
panic(class.Runtime().NewTypeError("Each query pair must be an iterable [name, value] tuple"))
|
|
}
|
|
params = append(params, searchParam{pair[0], pair[1]})
|
|
}
|
|
case map[string]any:
|
|
for name, value := range argInit {
|
|
stringValue, isString := value.(string)
|
|
if !isString {
|
|
panic(class.Runtime().NewTypeError("Invalid query value"))
|
|
}
|
|
params = append(params, searchParam{name, stringValue})
|
|
}
|
|
}
|
|
return &URLSearchParams{class, params}
|
|
}
|
|
|
|
func (s *URLSearchParams) getSize() any {
|
|
return len(s.params)
|
|
}
|
|
|
|
func (s *URLSearchParams) append(call goja.FunctionCall) any {
|
|
name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
|
|
value := call.Argument(1).String()
|
|
s.params = append(s.params, searchParam{name, value})
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (s *URLSearchParams) delete(call goja.FunctionCall) any {
|
|
name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
|
|
argValue := call.Argument(1)
|
|
if !jsc.IsNil(argValue) {
|
|
value := argValue.String()
|
|
for i, param := range s.params {
|
|
if param.Key == name && param.Value == value {
|
|
s.params = append(s.params[:i], s.params[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
for i, param := range s.params {
|
|
if param.Key == name {
|
|
s.params = append(s.params[:i], s.params[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (s *URLSearchParams) entries(call goja.FunctionCall) any {
|
|
return jsc.NewIterator[*Module, searchParam](s.class.Module().classURLSearchParamsIterator, s.params, func(this searchParam) any {
|
|
return s.class.Runtime().NewArray(this.Key, this.Value)
|
|
})
|
|
}
|
|
|
|
func (s *URLSearchParams) forEach(call goja.FunctionCall) any {
|
|
callback := jsc.AssertFunction(s.class.Runtime(), call.Argument(0), "callbackFn")
|
|
thisValue := call.Argument(1)
|
|
for _, param := range s.params {
|
|
for _, value := range param.Value {
|
|
_, err := callback(thisValue, s.class.Runtime().ToValue(value), s.class.Runtime().ToValue(param.Key), call.This)
|
|
if err != nil {
|
|
panic(s.class.Runtime().NewGoError(err))
|
|
}
|
|
}
|
|
}
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (s *URLSearchParams) get(call goja.FunctionCall) any {
|
|
name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
|
|
for _, param := range s.params {
|
|
if param.Key == name {
|
|
return param.Value
|
|
}
|
|
}
|
|
return goja.Null()
|
|
}
|
|
|
|
func (s *URLSearchParams) getAll(call goja.FunctionCall) any {
|
|
name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
|
|
var values []any
|
|
for _, param := range s.params {
|
|
if param.Key == name {
|
|
values = append(values, param.Value)
|
|
}
|
|
}
|
|
return s.class.Runtime().NewArray(values...)
|
|
}
|
|
|
|
func (s *URLSearchParams) has(call goja.FunctionCall) any {
|
|
name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
|
|
argValue := call.Argument(1)
|
|
if !jsc.IsNil(argValue) {
|
|
value := argValue.String()
|
|
for _, param := range s.params {
|
|
if param.Key == name && param.Value == value {
|
|
return true
|
|
}
|
|
}
|
|
} else {
|
|
for _, param := range s.params {
|
|
if param.Key == name {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *URLSearchParams) keys(call goja.FunctionCall) any {
|
|
return jsc.NewIterator[*Module, searchParam](s.class.Module().classURLSearchParamsIterator, s.params, func(this searchParam) any {
|
|
return this.Key
|
|
})
|
|
}
|
|
|
|
func (s *URLSearchParams) set(call goja.FunctionCall) any {
|
|
name := jsc.AssertString(s.class.Runtime(), call.Argument(0), "name", false)
|
|
value := call.Argument(1).String()
|
|
for i, param := range s.params {
|
|
if param.Key == name {
|
|
s.params[i].Value = value
|
|
return goja.Undefined()
|
|
}
|
|
}
|
|
s.params = append(s.params, searchParam{name, value})
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (s *URLSearchParams) sort(call goja.FunctionCall) any {
|
|
sort.SliceStable(s.params, func(i, j int) bool {
|
|
return s.params[i].Key < s.params[j].Key
|
|
})
|
|
return goja.Undefined()
|
|
}
|
|
|
|
func (s *URLSearchParams) toString(call goja.FunctionCall) any {
|
|
return generateQuery(s.params)
|
|
}
|
|
|
|
func (s *URLSearchParams) values(call goja.FunctionCall) any {
|
|
return jsc.NewIterator[*Module, searchParam](s.class.Module().classURLSearchParamsIterator, s.params, func(this searchParam) any {
|
|
return this.Value
|
|
})
|
|
}
|
|
|
|
type searchParam struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
func parseQuery(query string) (params []searchParam, err error) {
|
|
query = strings.TrimPrefix(query, "?")
|
|
for query != "" {
|
|
var key string
|
|
key, query, _ = strings.Cut(query, "&")
|
|
if strings.Contains(key, ";") {
|
|
err = fmt.Errorf("invalid semicolon separator in query")
|
|
continue
|
|
}
|
|
if key == "" {
|
|
continue
|
|
}
|
|
key, value, _ := strings.Cut(key, "=")
|
|
key, err1 := url.QueryUnescape(key)
|
|
if err1 != nil {
|
|
if err == nil {
|
|
err = err1
|
|
}
|
|
continue
|
|
}
|
|
value, err1 = url.QueryUnescape(value)
|
|
if err1 != nil {
|
|
if err == nil {
|
|
err = err1
|
|
}
|
|
continue
|
|
}
|
|
params = append(params, searchParam{key, value})
|
|
}
|
|
return
|
|
}
|
|
|
|
func generateQuery(params []searchParam) string {
|
|
var parts []string
|
|
for _, param := range params {
|
|
parts = append(parts, F.ToString(param.Key, "=", url.QueryEscape(param.Value)))
|
|
}
|
|
return strings.Join(parts, "&")
|
|
}
|