From 0fb0984f434d2784440df40f1c2b374dbd1d9c74 Mon Sep 17 00:00:00 2001 From: Sam Uong Date: Wed, 15 Apr 2026 10:59:56 +1000 Subject: [PATCH] Migrate from otto to goja --- go.mod | 6 +- go.sum | 14 ++++- pacrunner.go | 151 ++++++++++++++++++++++------------------------ pacrunner_test.go | 135 ++++++++++++++++++++++------------------- 4 files changed, 163 insertions(+), 143 deletions(-) diff --git a/go.mod b/go.mod index 09b11da..27456e8 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.22.3 toolchain go1.22.4 require ( + github.com/dop251/goja v0.0.0-20240610225006-393f6d42497b github.com/gobwas/glob v0.2.3 github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 - github.com/robertkrimen/otto v0.4.0 + github.com/robertkrimen/otto v0.5.1 github.com/samuong/go-ntlmssp v0.0.0-20240616070040-65a20607c744 github.com/stretchr/testify v1.9.0 github.com/zalando/go-keyring v0.2.5 @@ -18,7 +19,10 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.24.0 // indirect golang.org/x/sys v0.21.0 // indirect diff --git a/go.sum b/go.sum index 9e3d582..402e9ca 100644 --- a/go.sum +++ b/go.sum @@ -4,16 +4,24 @@ github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7 github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20240610225006-393f6d42497b h1:fMKDnOAKCGXSZBphY/ilLtu7cmwMnjqE+xJxUkfkpCY= +github.com/dop251/goja v0.0.0-20240610225006-393f6d42497b/go.mod h1:o31y53rb/qiIAONF7w3FHJZRqqP3fzHUr1HqanthByw= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robertkrimen/otto v0.4.0 h1:/c0GRrK1XDPcgIasAsnlpBT5DelIeB9U/Z/JCQsgr7E= -github.com/robertkrimen/otto v0.4.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw= +github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0= +github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8= github.com/samuong/go-ntlmssp v0.0.0-20240616070040-65a20607c744 h1:AD1UeK7fZRLY7TEeQQZNTuHX3RAspwLUC36mNi47Xcs= github.com/samuong/go-ntlmssp v0.0.0-20240616070040-65a20607c744/go.mod h1:ioghl8+axI3Mx5Cs1LU/LzW18JE71qbwXwpOv/F9lCc= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -34,5 +42,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pacrunner.go b/pacrunner.go index bf40db2..037057d 100644 --- a/pacrunner.go +++ b/pacrunner.go @@ -16,7 +16,7 @@ package main import ( "encoding/binary" - "errors" + "fmt" "net" "net/url" "os" @@ -24,21 +24,23 @@ import ( "sync" "time" + "github.com/dop251/goja" "github.com/gobwas/glob" - "github.com/robertkrimen/otto" ) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_(PAC)_file +var pacVM *goja.Runtime + type PACRunner struct { - vm *otto.Otto - sync.Mutex + vm *goja.Runtime + mutex sync.Mutex } func (pr *PACRunner) Update(pacjs []byte) error { - vm := otto.New() + vm := goja.New() var err error - set := func(name string, handler func(otto.FunctionCall) otto.Value) { + set := func(name string, handler func(goja.FunctionCall) goja.Value) { if err != nil { return } @@ -55,87 +57,76 @@ func (pr *PACRunner) Update(pacjs []byte) error { set("myIpAddressEx", myIpAddressEx) set("dnsDomainLevels", dnsDomainLevels) set("shExpMatch", shExpMatch) - set("weekdayRange", func(fc otto.FunctionCall) otto.Value { + set("weekdayRange", func(fc goja.FunctionCall) goja.Value { return weekdayRange(fc, time.Now()) }) - set("dateRange", func(fc otto.FunctionCall) otto.Value { + set("dateRange", func(fc goja.FunctionCall) goja.Value { return dateRange(fc, time.Now()) }) - set("timeRange", func(fc otto.FunctionCall) otto.Value { + set("timeRange", func(fc goja.FunctionCall) goja.Value { return timeRange(fc, time.Now()) }) if err != nil { return err } - _, err = vm.Run(pacjs) + _, err = vm.RunString(string(pacjs)) if err != nil { return err } + pr.mutex.Lock() pr.vm = vm + pacVM = vm + pr.mutex.Unlock() return nil } func (pr *PACRunner) FindProxyForURL(u url.URL) (string, error) { - pr.Lock() - defer pr.Unlock() + pr.mutex.Lock() + defer pr.mutex.Unlock() if u.Scheme == "" { - // When a net/http Server parses a CONNECT request, the URL will - // have no Scheme. In that case, assume the scheme is "https". u.Scheme = "https" } if u.Scheme == "https" || u.Scheme == "wss" { - // Strip the path and query components of https:// URLs. - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_(PAC)_file#Parameters - // Like Chrome, also strip the path and query for wss:// URLs (secure WebSockets). - // https://cs.chromium.org/chromium/src/net/proxy_resolution/proxy_resolution_service.cc?rcl=fba6691ffca770dd0c916418601b9c9c019a2929&l=383 - // It also seems like a good idea to strip the fragment, so do that too. u.Path = "/" u.RawPath = "/" u.RawQuery = "" u.Fragment = "" } - val, err := pr.vm.Call("FindProxyForURL", nil, u.String(), u.Hostname()) + val, err := pr.vm.RunString("FindProxyForURL(" + fmt.Sprintf("%q", u.String()) + ", " + fmt.Sprintf("%q", u.Hostname()) + ")") if err != nil { return "", err - } else if !val.IsString() { - return "", errors.New("FindProxyForURL didn't return a string") } - return val.String(), nil + return val.Export().(string), nil } -func toValue(unwrapped interface{}) otto.Value { - wrapped, err := otto.ToValue(unwrapped) - if err != nil { - return otto.UndefinedValue() - } else { - return wrapped - } +func toValue(unwrapped interface{}) goja.Value { + return pacVM.ToValue(unwrapped) } -func isPlainHostName(call otto.FunctionCall) otto.Value { +func isPlainHostName(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() return toValue(!strings.ContainsRune(host, '.')) } -func dnsDomainIs(call otto.FunctionCall) otto.Value { +func dnsDomainIs(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() domain := call.Argument(1).String() return toValue(strings.HasSuffix(host, domain)) } -func localHostOrDomainIs(call otto.FunctionCall) otto.Value { +func localHostOrDomainIs(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() hostdom := call.Argument(1).String() return toValue(host == hostdom || strings.HasPrefix(hostdom, host+".")) } -func isResolvable(call otto.FunctionCall) otto.Value { +func isResolvable(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() _, err := net.LookupHost(host) return toValue(err == nil) } -func isInNet(call otto.FunctionCall) otto.Value { +func isInNet(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() pattern := call.Argument(1).String() mask := call.Argument(2).String() @@ -150,7 +141,7 @@ func isInNet(call otto.FunctionCall) otto.Value { return toValue(maskedIP.Equal(maskedPattern)) } -func dnsResolve(call otto.FunctionCall) otto.Value { +func dnsResolve(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() return toValue(resolve(host).String()) } @@ -174,7 +165,7 @@ func resolve(host string) net.IP { return nil } -func convertAddr(call otto.FunctionCall) otto.Value { +func convertAddr(call goja.FunctionCall) goja.Value { ipaddr := call.Argument(0).String() ipv4 := net.ParseIP(ipaddr).To4() if ipv4 == nil { @@ -183,8 +174,7 @@ func convertAddr(call otto.FunctionCall) otto.Value { return toValue(binary.BigEndian.Uint32(ipv4)) } -func myIpAddress(_ otto.FunctionCall) otto.Value { - // https://chromium.googlesource.com/chromium/src/+/ee43fa5328856129f46566b2ea1be5811739681c/net/docs/proxy.md#Resolving-client_s-IP-address-within-a-PAC-script-using-myIpAddress +func myIpAddress(call goja.FunctionCall) goja.Value { if localAddr := probeRoute("8.8.8.8"); localAddr != "" { return toValue(localAddr) } @@ -200,8 +190,7 @@ func myIpAddress(_ otto.FunctionCall) otto.Value { return toValue("127.0.0.1") } -func myIpAddressEx(_ otto.FunctionCall) otto.Value { - // https://chromium.googlesource.com/chromium/src/+/ee43fa5328856129f46566b2ea1be5811739681c/net/docs/proxy.md#resolving-client_s-ip-address-within-a-pac-script-using-myipaddressex +func myIpAddressEx(call goja.FunctionCall) goja.Value { public := []string{"8.8.8.8", "2001:4860:4860::8888"} if ips := probeRoutes(public); ips != "" { return toValue(ips) @@ -281,23 +270,23 @@ func resolveHostname(ipv6 bool) []net.IP { return addrs } -func dnsDomainLevels(call otto.FunctionCall) otto.Value { +func dnsDomainLevels(call goja.FunctionCall) goja.Value { host := call.Argument(0).String() return toValue(strings.Count(host, ".")) } -func shExpMatch(call otto.FunctionCall) otto.Value { +func shExpMatch(call goja.FunctionCall) goja.Value { str := call.Argument(0).String() shexp := call.Argument(1).String() g, err := glob.Compile(shexp) if err != nil { - return otto.UndefinedValue() + return goja.Undefined() } return toValue(g.Match(str)) } -func weekdayRange(call otto.FunctionCall, now time.Time) otto.Value { - if call.Argument(len(call.ArgumentList)-1).String() == "GMT" { +func weekdayRange(call goja.FunctionCall, now time.Time) goja.Value { + if call.Argument(len(call.Arguments)-1).String() == "GMT" { now = now.In(time.UTC) } weekdays := map[string]time.Weekday{ @@ -306,7 +295,7 @@ func weekdayRange(call otto.FunctionCall, now time.Time) otto.Value { } wd1, ok := weekdays[call.Argument(0).String()] if !ok { - return otto.UndefinedValue() + return goja.Undefined() } wd2, ok := weekdays[call.Argument(1).String()] if !ok { @@ -318,8 +307,8 @@ func weekdayRange(call otto.FunctionCall, now time.Time) otto.Value { } } -func dateRange(call otto.FunctionCall, now time.Time) otto.Value { - argc := len(call.ArgumentList) +func dateRange(call goja.FunctionCall, now time.Time) goja.Value { + argc := len(call.Arguments) if call.Argument(argc-1).String() == "GMT" { now = now.In(time.UTC) argc-- @@ -337,36 +326,51 @@ func dateRange(call otto.FunctionCall, now time.Time) otto.Value { } for i := 0; i < argc; i++ { - if call.Argument(i).IsNumber() { - n, err := call.Argument(i).ToInteger() - if err != nil { - return otto.UndefinedValue() - } else if 1 <= n && n <= 31 { + arg := call.Argument(i) + exported := arg.Export() + switch v := exported.(type) { + case float64: + n := int64(v) + if 1 <= n && n <= 31 { days = append(days, int(n)) } else { years = append(years, int(n)) } - } else if month, ok := monthmap[call.Argument(i).String()]; ok { - months = append(months, month) - } else { - return otto.UndefinedValue() + case int64: + n := v + if 1 <= n && n <= 31 { + days = append(days, int(n)) + } else { + years = append(years, int(n)) + } + case int: + n := int64(v) + if 1 <= n && n <= 31 { + days = append(days, int(n)) + } else { + years = append(years, int(n)) + } + default: + if month, ok := monthmap[arg.String()]; ok { + months = append(months, month) + } else { + return goja.Undefined() + } } } switch max(len(days), len(months), len(years)) { case 1: - // One (possibly partial) date provided; match it against the current date. if len(days) == 1 && days[0] != now.Day() { - return otto.FalseValue() + return toValue(false) } else if len(months) == 1 && months[0] != now.Month() { - return otto.FalseValue() + return toValue(false) } else if len(years) == 1 && years[0] != now.Year() { - return otto.FalseValue() + return toValue(false) } else { - return otto.TrueValue() + return toValue(true) } case 2: - // Two dates provided; check that the current date is inside the range. y1, m1, d1 := now.Date() y2, m2, d2 := now.Date() if len(days) == 2 { @@ -384,8 +388,7 @@ func dateRange(call otto.FunctionCall, now time.Time) otto.Value { end := time.Date(y2, m2, d2, h, m, s, ns, loc) return toValue(!start.After(now) && !end.Before(now)) default: - // Zero, three or more dates provided. Something's wrong. - return otto.UndefinedValue() + return goja.Undefined() } } @@ -399,20 +402,15 @@ func max(a, b, c int) int { } } -func timeRange(call otto.FunctionCall, now time.Time) otto.Value { - argc := len(call.ArgumentList) +func timeRange(call goja.FunctionCall, now time.Time) goja.Value { + argc := len(call.Arguments) if call.Argument(argc-1).String() == "GMT" { now = now.In(time.UTC) argc-- } h1, m1, s1, h2, m2, s2 := 0, 0, 0, 0, 0, 0 - var err error toInt := func(idx int) int { - val, err2 := call.Argument(idx).ToInteger() - if err2 != nil { - err = err2 - } - return int(val) + return int(call.Argument(idx).ToInteger()) } switch argc { case 1: @@ -428,10 +426,7 @@ func timeRange(call otto.FunctionCall, now time.Time) otto.Value { h1, m1, s1 = toInt(0), toInt(1), toInt(2) h2, m2, s2 = toInt(3), toInt(4), toInt(5) default: - return otto.UndefinedValue() - } - if err != nil { - return otto.UndefinedValue() + return goja.Undefined() } start := time.Date(now.Year(), now.Month(), now.Day(), h1, m1, s1, 0, now.Location()) end := time.Date(now.Year(), now.Month(), now.Day(), h2, m2, s2, 0, now.Location()) diff --git a/pacrunner_test.go b/pacrunner_test.go index 4c56eae..3fe0a94 100644 --- a/pacrunner_test.go +++ b/pacrunner_test.go @@ -15,17 +15,32 @@ package main import ( + "encoding/json" + "fmt" "net" "net/url" "strings" "testing" "time" - "github.com/robertkrimen/otto" + "github.com/dop251/goja" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func toJSArg(arg interface{}) string { + switch v := arg.(type) { + case string: + b, _ := json.Marshal(v) + return string(b) + case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64: + return fmt.Sprintf("%v", v) + default: + b, _ := json.Marshal(v) + return string(b) + } +} + func TestDirect(t *testing.T) { var pr PACRunner pacjs := []byte(`function FindProxyForURL(url, host) { return "DIRECT" }`) @@ -68,12 +83,11 @@ func TestIsPlainHostName(t *testing.T) { } for _, test := range tests { t.Run(test.host, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("isPlainHostName", isPlainHostName)) - value, err := vm.Call("isPlainHostName", nil, test.host) - require.NoError(t, err) - actual, err := value.ToBoolean() + value, err := vm.RunString("isPlainHostName(" + fmt.Sprintf("%q", test.host) + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, test.expected, actual) }) } @@ -91,12 +105,11 @@ func TestDnsDomainIs(t *testing.T) { } for _, test := range tests { t.Run(test.host+" "+test.domain, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("dnsDomainIs", dnsDomainIs)) - value, err := vm.Call("dnsDomainIs", nil, test.host, test.domain) - require.NoError(t, err) - actual, err := value.ToBoolean() + value, err := vm.RunString("dnsDomainIs(" + fmt.Sprintf("%q", test.host) + ", " + fmt.Sprintf("%q", test.domain) + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, test.expected, actual) }) } @@ -116,12 +129,11 @@ func TestLocalHostOrDomainIs(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("localHostOrDomainIs", localHostOrDomainIs)) - value, err := vm.Call("localHostOrDomainIs", nil, test.host, test.hostdom) - require.NoError(t, err) - actual, err := value.ToBoolean() + value, err := vm.RunString("localHostOrDomainIs(" + fmt.Sprintf("%q", test.host) + ", " + fmt.Sprintf("%q", test.hostdom) + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, test.expected, actual) }) } @@ -137,12 +149,11 @@ func TestIsResolvable(t *testing.T) { } for _, test := range tests { t.Run(test.host, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("isResolvable", isResolvable)) - value, err := vm.Call("isResolvable", nil, test.host) - require.NoError(t, err) - actual, err := value.ToBoolean() + value, err := vm.RunString("isResolvable(" + fmt.Sprintf("%q", test.host) + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, test.expected, actual) }) } @@ -163,12 +174,11 @@ func TestIsInNet(t *testing.T) { } for _, test := range tests { t.Run(test.host, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("isInNet", isInNet)) - value, err := vm.Call("isInNet", nil, test.host, test.pattern, test.mask) - require.NoError(t, err) - actual, err := value.ToBoolean() + value, err := vm.RunString("isInNet(" + fmt.Sprintf("%q", test.host) + ", " + fmt.Sprintf("%q", test.pattern) + ", " + fmt.Sprintf("%q", test.mask) + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, test.expected, actual) }) } @@ -184,12 +194,11 @@ func TestDnsResolve(t *testing.T) { } for _, test := range tests { t.Run(test.host, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("dnsResolve", dnsResolve)) - value, err := vm.Call("dnsResolve", nil, test.host) - require.NoError(t, err) - actual, err := value.ToString() + value, err := vm.RunString("dnsResolve(" + fmt.Sprintf("%q", test.host) + ")") require.NoError(t, err) + actual := value.Export().(string) assert.Equal(t, test.expected, actual) }) } @@ -206,33 +215,28 @@ func TestConvertAddr(t *testing.T) { } for _, test := range tests { t.Run(test.ipaddr, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("convert_addr", convertAddr)) - value, err := vm.Call("convert_addr", nil, test.ipaddr) - require.NoError(t, err) - actual, err := value.ToInteger() + value, err := vm.RunString("convert_addr(" + fmt.Sprintf("%q", test.ipaddr) + ")") require.NoError(t, err) + actual := value.ToInteger() assert.Equal(t, test.expected, actual) }) } } func TestMyIpAddress(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("myIpAddress", myIpAddress)) - value, err := vm.Call("myIpAddress", nil) + value, err := vm.RunString("myIpAddress()") require.NoError(t, err) - output, err := value.ToString() - require.NoError(t, err) - // Check it's a valid IPv4 or IPv6 address. - assert.NotNil(t, net.ParseIP(output)) - // Check that it's our IP address. Technically there's a race condition here (since both - // myIpAddress and this function will call net.InterfaceAddrs() separately), but this is - // only going to cause flakiness if the network changes during the test, which is unlikely. + output := value.ToString() + actualOutput := output.Export().(string) + assert.NotNil(t, net.ParseIP(actualOutput)) addrs, err := net.InterfaceAddrs() require.NoError(t, err) for _, addr := range addrs { - if strings.HasPrefix(addr.String(), output) { + if strings.HasPrefix(addr.String(), actualOutput) { return } } @@ -250,12 +254,11 @@ func TestDnsDomainLevels(t *testing.T) { } for _, test := range tests { t.Run(test.host, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("dnsDomainLevels", dnsDomainLevels)) - value, err := vm.Call("dnsDomainLevels", nil, test.host) - require.NoError(t, err) - actual, err := value.ToInteger() + value, err := vm.RunString("dnsDomainLevels(" + fmt.Sprintf("%q", test.host) + ")") require.NoError(t, err) + actual := value.ToInteger() assert.Equal(t, test.expected, actual) }) } @@ -271,12 +274,11 @@ func TestShExpMatch(t *testing.T) { } for _, test := range tests { t.Run(test.str+" "+test.shexp, func(t *testing.T) { - vm := otto.New() + vm := goja.New() require.NoError(t, vm.Set("shExpMatch", shExpMatch)) - value, err := vm.Call("shExpMatch", nil, test.str, test.shexp) - require.NoError(t, err) - actual, err := value.ToBoolean() + value, err := vm.RunString("shExpMatch(" + fmt.Sprintf("%q", test.str) + ", " + fmt.Sprintf("%q", test.shexp) + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, test.expected, actual) }) } @@ -314,15 +316,18 @@ func TestWeekdayRange(t *testing.T) { for _, test := range tests { for i, weekday := range weekdays { t.Run(test.name+" "+weekday.name, func(t *testing.T) { - vm := otto.New() - f := func(fc otto.FunctionCall) otto.Value { + vm := goja.New() + f := func(fc goja.FunctionCall) goja.Value { return weekdayRange(fc, weekday.t) } require.NoError(t, vm.Set("weekdayRange", f)) - value, err := vm.Call("weekdayRange", nil, test.args...) - require.NoError(t, err) - actual, err := value.ToBoolean() + args := make([]string, len(test.args)) + for j, arg := range test.args { + args[j] = toJSArg(arg) + } + value, err := vm.RunString("weekdayRange(" + strings.Join(args, ", ") + ")") require.NoError(t, err) + actual := value.ToBoolean() expected := test.expectations[i] == 'Y' assert.Equal(t, expected, actual) }) @@ -444,15 +449,18 @@ func TestDateRange(t *testing.T) { } check := func(t *testing.T, args []interface{}, date string, expected bool) { - vm := otto.New() + vm := goja.New() now, err := time.Parse(time.RFC3339, date+"T05:00:00+10:00") require.NoError(t, err) - f := func(fc otto.FunctionCall) otto.Value { return dateRange(fc, now) } + f := func(fc goja.FunctionCall) goja.Value { return dateRange(fc, now) } require.NoError(t, vm.Set("dateRange", f)) - value, err := vm.Call("dateRange", nil, args...) - require.NoError(t, err) - actual, err := value.ToBoolean() + argsStr := make([]string, len(args)) + for i, arg := range args { + argsStr[i] = toJSArg(arg) + } + value, err := vm.RunString("dateRange(" + strings.Join(argsStr, ", ") + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, expected, actual) } @@ -537,15 +545,18 @@ func TestTimeRange(t *testing.T) { } check := func(t *testing.T, args []interface{}, mocktime string, expected bool) { - vm := otto.New() + vm := goja.New() now, err := time.Parse(time.RFC3339, "2019-07-01T"+mocktime+"+10:00") require.NoError(t, err) - f := func(fc otto.FunctionCall) otto.Value { return timeRange(fc, now) } + f := func(fc goja.FunctionCall) goja.Value { return timeRange(fc, now) } require.NoError(t, vm.Set("timeRange", f)) - value, err := vm.Call("timeRange", nil, args...) - require.NoError(t, err) - actual, err := value.ToBoolean() + argsStr := make([]string, len(args)) + for i, arg := range args { + argsStr[i] = toJSArg(arg) + } + value, err := vm.RunString("timeRange(" + strings.Join(argsStr, ", ") + ")") require.NoError(t, err) + actual := value.ToBoolean() assert.Equal(t, expected, actual) }