123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315 |
- package url
- import (
- "net"
- "net/url"
- "strings"
- "github.com/sagernet/sing-box/script/jsc"
- E "github.com/sagernet/sing/common/exceptions"
- "github.com/dop251/goja"
- "golang.org/x/net/idna"
- )
- type URL struct {
- class jsc.Class[*Module, *URL]
- url *url.URL
- params *URLSearchParams
- paramsValue goja.Value
- }
- func newURL(c jsc.Class[*Module, *URL], call goja.ConstructorCall) *URL {
- var (
- u, base *url.URL
- err error
- )
- switch argURL := call.Argument(0).Export().(type) {
- case *URL:
- u = argURL.url
- default:
- u, err = parseURL(call.Argument(0).String())
- if err != nil {
- panic(c.Runtime().NewGoError(E.Cause(err, "parse URL")))
- }
- }
- if len(call.Arguments) == 2 {
- switch argBaseURL := call.Argument(1).Export().(type) {
- case *URL:
- base = argBaseURL.url
- default:
- base, err = parseURL(call.Argument(1).String())
- if err != nil {
- panic(c.Runtime().NewGoError(E.Cause(err, "parse base URL")))
- }
- }
- }
- if base != nil {
- u = base.ResolveReference(u)
- }
- return &URL{class: c, url: u}
- }
- func createURL(module *Module) jsc.Class[*Module, *URL] {
- class := jsc.NewClass[*Module, *URL](module)
- class.DefineConstructor(newURL)
- class.DefineField("hash", (*URL).getHash, (*URL).setHash)
- class.DefineField("host", (*URL).getHost, (*URL).setHost)
- class.DefineField("hostname", (*URL).getHostName, (*URL).setHostName)
- class.DefineField("href", (*URL).getHref, (*URL).setHref)
- class.DefineField("origin", (*URL).getOrigin, nil)
- class.DefineField("password", (*URL).getPassword, (*URL).setPassword)
- class.DefineField("pathname", (*URL).getPathname, (*URL).setPathname)
- class.DefineField("port", (*URL).getPort, (*URL).setPort)
- class.DefineField("protocol", (*URL).getProtocol, (*URL).setProtocol)
- class.DefineField("search", (*URL).getSearch, (*URL).setSearch)
- class.DefineField("searchParams", (*URL).getSearchParams, (*URL).setSearchParams)
- class.DefineField("username", (*URL).getUsername, (*URL).setUsername)
- class.DefineMethod("toString", (*URL).toString)
- class.DefineMethod("toJSON", (*URL).toJSON)
- class.DefineStaticMethod("canParse", canParse)
- // class.DefineStaticMethod("createObjectURL", createObjectURL)
- class.DefineStaticMethod("parse", parse)
- // class.DefineStaticMethod("revokeObjectURL", revokeObjectURL)
- return class
- }
- func canParse(class jsc.Class[*Module, *URL], call goja.FunctionCall) any {
- switch call.Argument(0).Export().(type) {
- case *URL:
- default:
- _, err := parseURL(call.Argument(0).String())
- if err != nil {
- return false
- }
- }
- if len(call.Arguments) == 2 {
- switch call.Argument(1).Export().(type) {
- case *URL:
- default:
- _, err := parseURL(call.Argument(1).String())
- if err != nil {
- return false
- }
- }
- }
- return true
- }
- func parse(class jsc.Class[*Module, *URL], call goja.FunctionCall) any {
- var (
- u, base *url.URL
- err error
- )
- switch argURL := call.Argument(0).Export().(type) {
- case *URL:
- u = argURL.url
- default:
- u, err = parseURL(call.Argument(0).String())
- if err != nil {
- return goja.Null()
- }
- }
- if len(call.Arguments) == 2 {
- switch argBaseURL := call.Argument(1).Export().(type) {
- case *URL:
- base = argBaseURL.url
- default:
- base, err = parseURL(call.Argument(1).String())
- if err != nil {
- return goja.Null()
- }
- }
- }
- if base != nil {
- u = base.ResolveReference(u)
- }
- return &URL{class: class, url: u}
- }
- func (r *URL) getHash() any {
- if r.url.Fragment != "" {
- return "#" + r.url.EscapedFragment()
- }
- return ""
- }
- func (r *URL) setHash(value goja.Value) {
- r.url.RawFragment = strings.TrimPrefix(value.String(), "#")
- }
- func (r *URL) getHost() any {
- return r.url.Host
- }
- func (r *URL) setHost(value goja.Value) {
- r.url.Host = strings.TrimSuffix(value.String(), ":")
- }
- func (r *URL) getHostName() any {
- return r.url.Hostname()
- }
- func (r *URL) setHostName(value goja.Value) {
- r.url.Host = joinHostPort(value.String(), r.url.Port())
- }
- func (r *URL) getHref() any {
- return r.url.String()
- }
- func (r *URL) setHref(value goja.Value) {
- newURL, err := url.Parse(value.String())
- if err != nil {
- panic(r.class.Runtime().NewGoError(err))
- }
- r.url = newURL
- r.params = nil
- }
- func (r *URL) getOrigin() any {
- return r.url.Scheme + "://" + r.url.Host
- }
- func (r *URL) getPassword() any {
- if r.url.User != nil {
- password, _ := r.url.User.Password()
- return password
- }
- return ""
- }
- func (r *URL) setPassword(value goja.Value) {
- if r.url.User == nil {
- r.url.User = url.UserPassword("", value.String())
- } else {
- r.url.User = url.UserPassword(r.url.User.Username(), value.String())
- }
- }
- func (r *URL) getPathname() any {
- return r.url.EscapedPath()
- }
- func (r *URL) setPathname(value goja.Value) {
- r.url.RawPath = value.String()
- }
- func (r *URL) getPort() any {
- return r.url.Port()
- }
- func (r *URL) setPort(value goja.Value) {
- r.url.Host = joinHostPort(r.url.Hostname(), value.String())
- }
- func (r *URL) getProtocol() any {
- return r.url.Scheme + ":"
- }
- func (r *URL) setProtocol(value goja.Value) {
- r.url.Scheme = strings.TrimSuffix(value.String(), ":")
- }
- func (r *URL) getSearch() any {
- if r.params != nil {
- if len(r.params.params) > 0 {
- return "?" + generateQuery(r.params.params)
- }
- } else if r.url.RawQuery != "" {
- return "?" + r.url.RawQuery
- }
- return ""
- }
- func (r *URL) setSearch(value goja.Value) {
- params, err := parseQuery(value.String())
- if err == nil {
- if r.params != nil {
- r.params.params = params
- } else {
- r.url.RawQuery = generateQuery(params)
- }
- }
- }
- func (r *URL) getSearchParams() any {
- var params []searchParam
- if r.url.RawQuery != "" {
- params, _ = parseQuery(r.url.RawQuery)
- }
- if r.params == nil {
- r.params = &URLSearchParams{
- class: r.class.Module().classURLSearchParams,
- params: params,
- }
- r.paramsValue = r.class.Module().classURLSearchParams.New(r.params)
- }
- return r.paramsValue
- }
- func (r *URL) setSearchParams(value goja.Value) {
- if params, ok := value.Export().(*URLSearchParams); ok {
- r.params = params
- r.paramsValue = value
- }
- }
- func (r *URL) getUsername() any {
- if r.url.User != nil {
- return r.url.User.Username()
- }
- return ""
- }
- func (r *URL) setUsername(value goja.Value) {
- if r.url.User == nil {
- r.url.User = url.User(value.String())
- } else {
- password, _ := r.url.User.Password()
- r.url.User = url.UserPassword(value.String(), password)
- }
- }
- func (r *URL) toString(call goja.FunctionCall) any {
- if r.params != nil {
- r.url.RawQuery = generateQuery(r.params.params)
- }
- return r.url.String()
- }
- func (r *URL) toJSON(call goja.FunctionCall) any {
- return r.toString(call)
- }
- func parseURL(s string) (*url.URL, error) {
- u, err := url.Parse(s)
- if err != nil {
- return nil, E.Cause(err, "invalid URL")
- }
- switch u.Scheme {
- case "https", "http", "ftp", "wss", "ws":
- if u.Path == "" {
- u.Path = "/"
- }
- hostname := u.Hostname()
- asciiHostname, err := idna.Punycode.ToASCII(strings.ToLower(hostname))
- if err != nil {
- return nil, E.Cause(err, "invalid hostname")
- }
- if asciiHostname != hostname {
- u.Host = joinHostPort(asciiHostname, u.Port())
- }
- }
- if u.RawQuery != "" {
- u.RawQuery = escape(u.RawQuery, &tblEscapeURLQuery, false)
- }
- return u, nil
- }
- func joinHostPort(hostname, port string) string {
- if port == "" {
- return hostname
- }
- return net.JoinHostPort(hostname, port)
- }
|