mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-04-03 20:07:36 +03:00
315 lines
7.1 KiB
Go
315 lines
7.1 KiB
Go
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)
|
|
}
|