url.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. package url
  2. import (
  3. "net"
  4. "net/url"
  5. "strings"
  6. "github.com/sagernet/sing-box/script/jsc"
  7. E "github.com/sagernet/sing/common/exceptions"
  8. "github.com/dop251/goja"
  9. "golang.org/x/net/idna"
  10. )
  11. type URL struct {
  12. class jsc.Class[*Module, *URL]
  13. url *url.URL
  14. params *URLSearchParams
  15. paramsValue goja.Value
  16. }
  17. func newURL(c jsc.Class[*Module, *URL], call goja.ConstructorCall) *URL {
  18. var (
  19. u, base *url.URL
  20. err error
  21. )
  22. switch argURL := call.Argument(0).Export().(type) {
  23. case *URL:
  24. u = argURL.url
  25. default:
  26. u, err = parseURL(call.Argument(0).String())
  27. if err != nil {
  28. panic(c.Runtime().NewGoError(E.Cause(err, "parse URL")))
  29. }
  30. }
  31. if len(call.Arguments) == 2 {
  32. switch argBaseURL := call.Argument(1).Export().(type) {
  33. case *URL:
  34. base = argBaseURL.url
  35. default:
  36. base, err = parseURL(call.Argument(1).String())
  37. if err != nil {
  38. panic(c.Runtime().NewGoError(E.Cause(err, "parse base URL")))
  39. }
  40. }
  41. }
  42. if base != nil {
  43. u = base.ResolveReference(u)
  44. }
  45. return &URL{class: c, url: u}
  46. }
  47. func createURL(module *Module) jsc.Class[*Module, *URL] {
  48. class := jsc.NewClass[*Module, *URL](module)
  49. class.DefineConstructor(newURL)
  50. class.DefineField("hash", (*URL).getHash, (*URL).setHash)
  51. class.DefineField("host", (*URL).getHost, (*URL).setHost)
  52. class.DefineField("hostname", (*URL).getHostName, (*URL).setHostName)
  53. class.DefineField("href", (*URL).getHref, (*URL).setHref)
  54. class.DefineField("origin", (*URL).getOrigin, nil)
  55. class.DefineField("password", (*URL).getPassword, (*URL).setPassword)
  56. class.DefineField("pathname", (*URL).getPathname, (*URL).setPathname)
  57. class.DefineField("port", (*URL).getPort, (*URL).setPort)
  58. class.DefineField("protocol", (*URL).getProtocol, (*URL).setProtocol)
  59. class.DefineField("search", (*URL).getSearch, (*URL).setSearch)
  60. class.DefineField("searchParams", (*URL).getSearchParams, (*URL).setSearchParams)
  61. class.DefineField("username", (*URL).getUsername, (*URL).setUsername)
  62. class.DefineMethod("toString", (*URL).toString)
  63. class.DefineMethod("toJSON", (*URL).toJSON)
  64. class.DefineStaticMethod("canParse", canParse)
  65. // class.DefineStaticMethod("createObjectURL", createObjectURL)
  66. class.DefineStaticMethod("parse", parse)
  67. // class.DefineStaticMethod("revokeObjectURL", revokeObjectURL)
  68. return class
  69. }
  70. func canParse(class jsc.Class[*Module, *URL], call goja.FunctionCall) any {
  71. switch call.Argument(0).Export().(type) {
  72. case *URL:
  73. default:
  74. _, err := parseURL(call.Argument(0).String())
  75. if err != nil {
  76. return false
  77. }
  78. }
  79. if len(call.Arguments) == 2 {
  80. switch call.Argument(1).Export().(type) {
  81. case *URL:
  82. default:
  83. _, err := parseURL(call.Argument(1).String())
  84. if err != nil {
  85. return false
  86. }
  87. }
  88. }
  89. return true
  90. }
  91. func parse(class jsc.Class[*Module, *URL], call goja.FunctionCall) any {
  92. var (
  93. u, base *url.URL
  94. err error
  95. )
  96. switch argURL := call.Argument(0).Export().(type) {
  97. case *URL:
  98. u = argURL.url
  99. default:
  100. u, err = parseURL(call.Argument(0).String())
  101. if err != nil {
  102. return goja.Null()
  103. }
  104. }
  105. if len(call.Arguments) == 2 {
  106. switch argBaseURL := call.Argument(1).Export().(type) {
  107. case *URL:
  108. base = argBaseURL.url
  109. default:
  110. base, err = parseURL(call.Argument(1).String())
  111. if err != nil {
  112. return goja.Null()
  113. }
  114. }
  115. }
  116. if base != nil {
  117. u = base.ResolveReference(u)
  118. }
  119. return &URL{class: class, url: u}
  120. }
  121. func (r *URL) getHash() any {
  122. if r.url.Fragment != "" {
  123. return "#" + r.url.EscapedFragment()
  124. }
  125. return ""
  126. }
  127. func (r *URL) setHash(value goja.Value) {
  128. r.url.RawFragment = strings.TrimPrefix(value.String(), "#")
  129. }
  130. func (r *URL) getHost() any {
  131. return r.url.Host
  132. }
  133. func (r *URL) setHost(value goja.Value) {
  134. r.url.Host = strings.TrimSuffix(value.String(), ":")
  135. }
  136. func (r *URL) getHostName() any {
  137. return r.url.Hostname()
  138. }
  139. func (r *URL) setHostName(value goja.Value) {
  140. r.url.Host = joinHostPort(value.String(), r.url.Port())
  141. }
  142. func (r *URL) getHref() any {
  143. return r.url.String()
  144. }
  145. func (r *URL) setHref(value goja.Value) {
  146. newURL, err := url.Parse(value.String())
  147. if err != nil {
  148. panic(r.class.Runtime().NewGoError(err))
  149. }
  150. r.url = newURL
  151. r.params = nil
  152. }
  153. func (r *URL) getOrigin() any {
  154. return r.url.Scheme + "://" + r.url.Host
  155. }
  156. func (r *URL) getPassword() any {
  157. if r.url.User != nil {
  158. password, _ := r.url.User.Password()
  159. return password
  160. }
  161. return ""
  162. }
  163. func (r *URL) setPassword(value goja.Value) {
  164. if r.url.User == nil {
  165. r.url.User = url.UserPassword("", value.String())
  166. } else {
  167. r.url.User = url.UserPassword(r.url.User.Username(), value.String())
  168. }
  169. }
  170. func (r *URL) getPathname() any {
  171. return r.url.EscapedPath()
  172. }
  173. func (r *URL) setPathname(value goja.Value) {
  174. r.url.RawPath = value.String()
  175. }
  176. func (r *URL) getPort() any {
  177. return r.url.Port()
  178. }
  179. func (r *URL) setPort(value goja.Value) {
  180. r.url.Host = joinHostPort(r.url.Hostname(), value.String())
  181. }
  182. func (r *URL) getProtocol() any {
  183. return r.url.Scheme + ":"
  184. }
  185. func (r *URL) setProtocol(value goja.Value) {
  186. r.url.Scheme = strings.TrimSuffix(value.String(), ":")
  187. }
  188. func (r *URL) getSearch() any {
  189. if r.params != nil {
  190. if len(r.params.params) > 0 {
  191. return "?" + generateQuery(r.params.params)
  192. }
  193. } else if r.url.RawQuery != "" {
  194. return "?" + r.url.RawQuery
  195. }
  196. return ""
  197. }
  198. func (r *URL) setSearch(value goja.Value) {
  199. params, err := parseQuery(value.String())
  200. if err == nil {
  201. if r.params != nil {
  202. r.params.params = params
  203. } else {
  204. r.url.RawQuery = generateQuery(params)
  205. }
  206. }
  207. }
  208. func (r *URL) getSearchParams() any {
  209. var params []searchParam
  210. if r.url.RawQuery != "" {
  211. params, _ = parseQuery(r.url.RawQuery)
  212. }
  213. if r.params == nil {
  214. r.params = &URLSearchParams{
  215. class: r.class.Module().classURLSearchParams,
  216. params: params,
  217. }
  218. r.paramsValue = r.class.Module().classURLSearchParams.New(r.params)
  219. }
  220. return r.paramsValue
  221. }
  222. func (r *URL) setSearchParams(value goja.Value) {
  223. if params, ok := value.Export().(*URLSearchParams); ok {
  224. r.params = params
  225. r.paramsValue = value
  226. }
  227. }
  228. func (r *URL) getUsername() any {
  229. if r.url.User != nil {
  230. return r.url.User.Username()
  231. }
  232. return ""
  233. }
  234. func (r *URL) setUsername(value goja.Value) {
  235. if r.url.User == nil {
  236. r.url.User = url.User(value.String())
  237. } else {
  238. password, _ := r.url.User.Password()
  239. r.url.User = url.UserPassword(value.String(), password)
  240. }
  241. }
  242. func (r *URL) toString(call goja.FunctionCall) any {
  243. if r.params != nil {
  244. r.url.RawQuery = generateQuery(r.params.params)
  245. }
  246. return r.url.String()
  247. }
  248. func (r *URL) toJSON(call goja.FunctionCall) any {
  249. return r.toString(call)
  250. }
  251. func parseURL(s string) (*url.URL, error) {
  252. u, err := url.Parse(s)
  253. if err != nil {
  254. return nil, E.Cause(err, "invalid URL")
  255. }
  256. switch u.Scheme {
  257. case "https", "http", "ftp", "wss", "ws":
  258. if u.Path == "" {
  259. u.Path = "/"
  260. }
  261. hostname := u.Hostname()
  262. asciiHostname, err := idna.Punycode.ToASCII(strings.ToLower(hostname))
  263. if err != nil {
  264. return nil, E.Cause(err, "invalid hostname")
  265. }
  266. if asciiHostname != hostname {
  267. u.Host = joinHostPort(asciiHostname, u.Port())
  268. }
  269. }
  270. if u.RawQuery != "" {
  271. u.RawQuery = escape(u.RawQuery, &tblEscapeURLQuery, false)
  272. }
  273. return u, nil
  274. }
  275. func joinHostPort(hostname, port string) string {
  276. if port == "" {
  277. return hostname
  278. }
  279. return net.JoinHostPort(hostname, port)
  280. }