Skip to content

Commit 76fdc05

Browse files
URLSearchParams Implementation (dop251#54)
1 parent 94e5081 commit 76fdc05

10 files changed

+1095
-357
lines changed

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/dop251/goja_nodejs
33
go 1.16
44

55
require (
6-
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7
6+
github.com/dop251/goja v0.0.0-20230626124041-ba8a63e79201
77
golang.org/x/net v0.10.0
88
golang.org/x/text v0.9.0
99
)

go.sum

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnm
88
github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
99
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7 h1:cVGkvrdHgyBkYeB6kMCaF5j2d9Bg4trgbIpcUrKrvk4=
1010
github.com/dop251/goja v0.0.0-20230531210528-d7324b2d74f7/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
11+
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3 h1:+3HCtB74++ClLy8GgjUQYeC8R4ILzVcIe8+5edAJJnE=
12+
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
13+
github.com/dop251/goja v0.0.0-20230626124041-ba8a63e79201 h1:+9NRIliCUhliHMCixEO0mcXmrv3HYwxs9oxM1Z+qnYM=
14+
github.com/dop251/goja v0.0.0-20230626124041-ba8a63e79201/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
1115
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
1216
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
1317
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=

url/module.go

+5-321
Original file line numberDiff line numberDiff line change
@@ -1,113 +1,22 @@
11
package url
22

33
import (
4-
"math"
5-
"net/url"
6-
"reflect"
7-
"strconv"
8-
"strings"
9-
10-
"golang.org/x/net/idna"
11-
124
"github.com/dop251/goja"
135
"github.com/dop251/goja_nodejs/require"
146
)
157

168
const ModuleName = "url"
179

18-
var (
19-
reflectTypeURL = reflect.TypeOf((*url.URL)(nil))
20-
reflectTypeInt = reflect.TypeOf(0)
21-
)
22-
23-
func isDefaultURLPort(protocol string, port int) bool {
24-
switch port {
25-
case 21:
26-
if protocol == "ftp" {
27-
return true
28-
}
29-
case 80:
30-
if protocol == "http" || protocol == "ws" {
31-
return true
32-
}
33-
case 443:
34-
if protocol == "https" || protocol == "wss" {
35-
return true
36-
}
37-
}
38-
return false
39-
}
40-
41-
func isSpecialProtocol(protocol string) bool {
42-
switch protocol {
43-
case "ftp", "file", "http", "https", "ws", "wss":
44-
return true
45-
}
46-
return false
47-
}
48-
49-
func clearURLPort(u *url.URL) {
50-
u.Host = u.Hostname()
51-
}
52-
53-
func valueToURLPort(v goja.Value) (portNum int, empty bool) {
54-
portNum = -1
55-
if et := v.ExportType(); et == reflectTypeInt {
56-
if num := v.ToInteger(); num >= 0 && num <= math.MaxUint16 {
57-
portNum = int(num)
58-
}
59-
} else {
60-
s := v.String()
61-
if s == "" {
62-
return 0, true
63-
}
64-
for i := 0; i < len(s); i++ {
65-
if c := s[i]; c >= '0' && c <= '9' {
66-
if portNum == -1 {
67-
portNum = 0
68-
}
69-
portNum = portNum*10 + int(c-'0')
70-
if portNum > math.MaxUint16 {
71-
portNum = -1
72-
break
73-
}
74-
} else {
75-
break
76-
}
77-
}
78-
}
79-
return
80-
}
81-
82-
func setURLPort(u *url.URL, v goja.Value) {
83-
if u.Scheme == "file" {
84-
return
85-
}
86-
portNum, empty := valueToURLPort(v)
87-
if empty {
88-
clearURLPort(u)
89-
return
90-
}
91-
if portNum == -1 {
92-
return
93-
}
94-
if isDefaultURLPort(u.Scheme, portNum) {
95-
clearURLPort(u)
96-
} else {
97-
u.Host = u.Hostname() + ":" + strconv.Itoa(portNum)
98-
}
99-
}
100-
101-
func toURL(r *goja.Runtime, v goja.Value) *url.URL {
10+
func toURL(r *goja.Runtime, v goja.Value) *nodeURL {
10211
if v.ExportType() == reflectTypeURL {
103-
if u := v.Export().(*url.URL); u != nil {
12+
if u := v.Export().(*nodeURL); u != nil {
10413
return u
10514
}
10615
}
10716
panic(r.NewTypeError("Expected URL"))
10817
}
10918

110-
func defineURLAccessorProp(r *goja.Runtime, p *goja.Object, name string, getter func(*url.URL) interface{}, setter func(*url.URL, goja.Value)) {
19+
func defineURLAccessorProp(r *goja.Runtime, p *goja.Object, name string, getter func(*nodeURL) interface{}, setter func(*nodeURL, goja.Value)) {
11120
var getterVal, setterVal goja.Value
11221
if getter != nil {
11322
getterVal = r.ToValue(func(call goja.FunctionCall) goja.Value {
@@ -123,240 +32,15 @@ func defineURLAccessorProp(r *goja.Runtime, p *goja.Object, name string, getter
12332
p.DefineAccessorProperty(name, getterVal, setterVal, goja.FLAG_FALSE, goja.FLAG_TRUE)
12433
}
12534

126-
func createURLPrototype(r *goja.Runtime) *goja.Object {
127-
p := r.NewObject()
128-
129-
// host
130-
defineURLAccessorProp(r, p, "host", func(u *url.URL) interface{} {
131-
return u.Host
132-
}, func(u *url.URL, arg goja.Value) {
133-
host := arg.String()
134-
if _, err := url.ParseRequestURI(u.Scheme + "://" + host); err == nil {
135-
u.Host = host
136-
fixURL(r, u)
137-
}
138-
})
139-
140-
// hash
141-
defineURLAccessorProp(r, p, "hash", func(u *url.URL) interface{} {
142-
if u.Fragment != "" {
143-
return "#" + u.EscapedFragment()
144-
}
145-
return ""
146-
}, func(u *url.URL, arg goja.Value) {
147-
h := arg.String()
148-
if len(h) > 0 && h[0] == '#' {
149-
h = h[1:]
150-
}
151-
u.Fragment = h
152-
})
153-
154-
// hostname
155-
defineURLAccessorProp(r, p, "hostname", func(u *url.URL) interface{} {
156-
return strings.Split(u.Host, ":")[0]
157-
}, func(u *url.URL, arg goja.Value) {
158-
h := arg.String()
159-
if strings.IndexByte(h, ':') >= 0 {
160-
return
161-
}
162-
if _, err := url.ParseRequestURI(u.Scheme + "://" + h); err == nil {
163-
if port := u.Port(); port != "" {
164-
u.Host = h + ":" + port
165-
} else {
166-
u.Host = h
167-
}
168-
fixURL(r, u)
169-
}
170-
})
171-
172-
// href
173-
defineURLAccessorProp(r, p, "href", func(u *url.URL) interface{} {
174-
return u.String()
175-
}, func(u *url.URL, arg goja.Value) {
176-
url := parseURL(r, arg.String(), true)
177-
*u = *url
178-
})
179-
180-
// pathname
181-
defineURLAccessorProp(r, p, "pathname", func(u *url.URL) interface{} {
182-
return u.EscapedPath()
183-
}, func(u *url.URL, arg goja.Value) {
184-
p := arg.String()
185-
if _, err := url.Parse(p); err == nil {
186-
switch u.Scheme {
187-
case "https", "http", "ftp", "ws", "wss":
188-
if !strings.HasPrefix(p, "/") {
189-
p = "/" + p
190-
}
191-
}
192-
u.Path = p
193-
}
194-
})
195-
196-
// origin
197-
defineURLAccessorProp(r, p, "origin", func(u *url.URL) interface{} {
198-
return u.Scheme + "://" + u.Hostname()
199-
}, nil)
200-
201-
// password
202-
defineURLAccessorProp(r, p, "password", func(u *url.URL) interface{} {
203-
p, _ := u.User.Password()
204-
return p
205-
}, func(u *url.URL, arg goja.Value) {
206-
user := u.User
207-
u.User = url.UserPassword(user.Username(), arg.String())
208-
})
209-
210-
// username
211-
defineURLAccessorProp(r, p, "username", func(u *url.URL) interface{} {
212-
return u.User.Username()
213-
}, func(u *url.URL, arg goja.Value) {
214-
p, has := u.User.Password()
215-
if !has {
216-
u.User = url.User(arg.String())
217-
} else {
218-
u.User = url.UserPassword(arg.String(), p)
219-
}
220-
})
221-
222-
// port
223-
defineURLAccessorProp(r, p, "port", func(u *url.URL) interface{} {
224-
return u.Port()
225-
}, func(u *url.URL, arg goja.Value) {
226-
setURLPort(u, arg)
227-
})
228-
229-
// protocol
230-
defineURLAccessorProp(r, p, "protocol", func(u *url.URL) interface{} {
231-
return u.Scheme + ":"
232-
}, func(u *url.URL, arg goja.Value) {
233-
s := arg.String()
234-
pos := strings.IndexByte(s, ':')
235-
if pos >= 0 {
236-
s = s[:pos]
237-
}
238-
s = strings.ToLower(s)
239-
if isSpecialProtocol(u.Scheme) == isSpecialProtocol(s) {
240-
if _, err := url.ParseRequestURI(s + "://" + u.Host); err == nil {
241-
u.Scheme = s
242-
}
243-
}
244-
})
245-
246-
// Search
247-
defineURLAccessorProp(r, p, "search", func(u *url.URL) interface{} {
248-
if u.RawQuery != "" {
249-
return "?" + u.RawQuery
250-
}
251-
return ""
252-
}, func(u *url.URL, arg goja.Value) {
253-
u.RawQuery = arg.String()
254-
fixRawQuery(u)
255-
})
256-
257-
p.Set("toString", r.ToValue(func(call goja.FunctionCall) goja.Value {
258-
return r.ToValue(toURL(r, call.This).String())
259-
}))
260-
261-
p.Set("toJSON", r.ToValue(func(call goja.FunctionCall) goja.Value {
262-
return r.ToValue(toURL(r, call.This).String())
263-
}))
264-
265-
return p
266-
}
267-
268-
const (
269-
URLNotAbsolute = "URL is not absolute"
270-
InvalidURL = "Invalid URL"
271-
InvalidBaseURL = "Invalid base URL"
272-
InvalidHostname = "Invalid hostname"
273-
)
274-
275-
func newInvalidURLError(r *goja.Runtime, msg, input string) *goja.Object {
276-
// when node's error module is added this should return a NodeError
277-
o := r.NewTypeError(msg)
278-
o.Set("input", r.ToValue(input))
279-
return o
280-
}
281-
282-
func fixRawQuery(u *url.URL) {
283-
if u.RawQuery != "" {
284-
var u1 url.URL
285-
u1.Fragment = u.RawQuery
286-
u.RawQuery = u1.EscapedFragment()
287-
}
288-
}
289-
290-
func fixURL(r *goja.Runtime, u *url.URL) {
291-
switch u.Scheme {
292-
case "https", "http", "ftp", "wss", "ws":
293-
if u.Path == "" {
294-
u.Path = "/"
295-
}
296-
hostname := u.Hostname()
297-
lh := strings.ToLower(hostname)
298-
ch, err := idna.Punycode.ToASCII(lh)
299-
if err != nil {
300-
panic(newInvalidURLError(r, InvalidHostname, lh))
301-
}
302-
if ch != hostname {
303-
if port := u.Port(); port != "" {
304-
u.Host = ch + ":" + port
305-
} else {
306-
u.Host = ch
307-
}
308-
}
309-
fixRawQuery(u)
310-
}
311-
}
312-
313-
func parseURL(r *goja.Runtime, s string, isBase bool) *url.URL {
314-
u, err := url.Parse(s)
315-
if err != nil {
316-
if isBase {
317-
panic(newInvalidURLError(r, InvalidBaseURL, s))
318-
} else {
319-
panic(newInvalidURLError(r, InvalidURL, s))
320-
}
321-
}
322-
if isBase && !u.IsAbs() {
323-
panic(newInvalidURLError(r, URLNotAbsolute, s))
324-
}
325-
if portStr := u.Port(); portStr != "" {
326-
if port, err := strconv.Atoi(portStr); err != nil || isDefaultURLPort(u.Scheme, port) {
327-
clearURLPort(u)
328-
}
329-
}
330-
fixURL(r, u)
331-
return u
332-
}
333-
334-
func createURLConstructor(r *goja.Runtime) goja.Value {
335-
f := r.ToValue(func(call goja.ConstructorCall) *goja.Object {
336-
var u *url.URL
337-
if baseArg := call.Argument(1); !goja.IsUndefined(baseArg) {
338-
base := parseURL(r, baseArg.String(), true)
339-
ref := parseURL(r, call.Arguments[0].String(), false)
340-
u = base.ResolveReference(ref)
341-
} else {
342-
u = parseURL(r, call.Argument(0).String(), true)
343-
}
344-
res := r.ToValue(u).(*goja.Object)
345-
res.SetPrototype(call.This.Prototype())
346-
return res
347-
}).(*goja.Object)
348-
349-
f.Set("prototype", createURLPrototype(r))
350-
return f
351-
}
352-
35335
func Require(runtime *goja.Runtime, module *goja.Object) {
35436
exports := module.Get("exports").(*goja.Object)
35537
exports.Set("URL", createURLConstructor(runtime))
38+
exports.Set("URLSearchParams", createURLSearchParamsConstructor(runtime))
35639
}
35740

35841
func Enable(runtime *goja.Runtime) {
35942
runtime.Set("URL", require.Require(runtime, ModuleName).ToObject(runtime).Get("URL"))
43+
runtime.Set("URLSearchParams", require.Require(runtime, ModuleName).ToObject(runtime).Get("URLSearchParams"))
36044
}
36145

36246
func init() {

0 commit comments

Comments
 (0)