mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-05-30 07:54:23 +03:00
mitm: Refactor & Add url
This commit is contained in:
315
script/modules/url/url.go
Normal file
315
script/modules/url/url.go
Normal file
@@ -0,0 +1,315 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user