1
0
Fork 0

Adding upstream version 0.0~git20250409.f7acab6.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-22 11:36:18 +02:00
parent b9b5d88025
commit 21b930d007
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
51 changed files with 11229 additions and 0 deletions

134
url/escape.go Normal file
View file

@ -0,0 +1,134 @@
package url
import "strings"
var tblEscapeURLQuery = [128]byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
}
var tblEscapeURLQueryParam = [128]byte{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
}
// The code below is mostly borrowed from the standard Go url package
const upperhex = "0123456789ABCDEF"
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}
func unhex(c byte) byte {
switch {
case '0' <= c && c <= '9':
return c - '0'
case 'a' <= c && c <= 'f':
return c - 'a' + 10
case 'A' <= c && c <= 'F':
return c - 'A' + 10
}
return 0
}
func escape(s string, table *[128]byte, spaceToPlus bool) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if c > 127 || table[c] == 0 {
if c == ' ' && spaceToPlus {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
var sb strings.Builder
hexBuf := [3]byte{'%', 0, 0}
sb.Grow(len(s) + 2*hexCount)
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && spaceToPlus:
sb.WriteByte('+')
case c > 127 || table[c] == 0:
hexBuf[1] = upperhex[c>>4]
hexBuf[2] = upperhex[c&15]
sb.Write(hexBuf[:])
default:
sb.WriteByte(c)
}
}
return sb.String()
}
func unescapeSearchParam(s string) string {
n := 0
hasPlus := false
for i := 0; i < len(s); {
switch s[i] {
case '%':
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
i++
continue
}
n++
i += 3
case '+':
hasPlus = true
i++
default:
i++
}
}
if n == 0 && !hasPlus {
return s
}
var t strings.Builder
t.Grow(len(s) - 2*n)
for i := 0; i < len(s); i++ {
switch s[i] {
case '%':
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
t.WriteByte('%')
} else {
t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2]))
i += 2
}
case '+':
t.WriteByte(' ')
default:
t.WriteByte(s[i])
}
}
return t.String()
}

36
url/module.go Normal file
View file

@ -0,0 +1,36 @@
package url
import (
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/require"
)
const ModuleName = "url"
type urlModule struct {
r *goja.Runtime
URLSearchParamsPrototype *goja.Object
URLSearchParamsIteratorPrototype *goja.Object
}
func Require(runtime *goja.Runtime, module *goja.Object) {
exports := module.Get("exports").(*goja.Object)
m := &urlModule{
r: runtime,
}
exports.Set("URL", m.createURLConstructor())
exports.Set("URLSearchParams", m.createURLSearchParamsConstructor())
exports.Set("domainToASCII", m.domainToASCII)
exports.Set("domainToUnicode", m.domainToUnicode)
}
func Enable(runtime *goja.Runtime) {
m := require.Require(runtime, ModuleName).ToObject(runtime)
runtime.Set("URL", m.Get("URL"))
runtime.Set("URLSearchParams", m.Get("URLSearchParams"))
}
func init() {
require.RegisterCoreModule(ModuleName, Require)
}

148
url/nodeurl.go Normal file
View file

@ -0,0 +1,148 @@
package url
import (
"net/url"
"strings"
)
type searchParam struct {
name string
value string
}
func (sp *searchParam) Encode() string {
return sp.string(true)
}
func escapeSearchParam(s string) string {
return escape(s, &tblEscapeURLQueryParam, true)
}
func (sp *searchParam) string(encode bool) string {
if encode {
return escapeSearchParam(sp.name) + "=" + escapeSearchParam(sp.value)
} else {
return sp.name + "=" + sp.value
}
}
type searchParams []searchParam
func (s searchParams) Len() int {
return len(s)
}
func (s searchParams) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s searchParams) Less(i, j int) bool {
return strings.Compare(s[i].name, s[j].name) < 0
}
func (s searchParams) Encode() string {
var sb strings.Builder
for i, v := range s {
if i > 0 {
sb.WriteByte('&')
}
sb.WriteString(v.Encode())
}
return sb.String()
}
func (s searchParams) String() string {
var sb strings.Builder
for i, v := range s {
if i > 0 {
sb.WriteByte('&')
}
sb.WriteString(v.string(false))
}
return sb.String()
}
type nodeURL struct {
url *url.URL
searchParams searchParams
}
type urlSearchParams nodeURL
// This methods ensures that the url.URL has the proper RawQuery based on the searchParam
// structs. If a change is made to the searchParams we need to keep them in sync.
func (nu *nodeURL) syncSearchParams() {
if nu.rawQueryUpdateNeeded() {
nu.url.RawQuery = nu.searchParams.Encode()
}
}
func (nu *nodeURL) rawQueryUpdateNeeded() bool {
return len(nu.searchParams) > 0 && nu.url.RawQuery == ""
}
func (nu *nodeURL) String() string {
return nu.url.String()
}
func (sp *urlSearchParams) hasName(name string) bool {
for _, v := range sp.searchParams {
if v.name == name {
return true
}
}
return false
}
func (sp *urlSearchParams) hasValue(name, value string) bool {
for _, v := range sp.searchParams {
if v.name == name && v.value == value {
return true
}
}
return false
}
func (sp *urlSearchParams) getValues(name string) []string {
vals := make([]string, 0, len(sp.searchParams))
for _, v := range sp.searchParams {
if v.name == name {
vals = append(vals, v.value)
}
}
return vals
}
func (sp *urlSearchParams) getFirstValue(name string) (string, bool) {
for _, v := range sp.searchParams {
if v.name == name {
return v.value, true
}
}
return "", false
}
func parseSearchQuery(query string) (ret searchParams) {
if query == "" {
return
}
query = strings.TrimPrefix(query, "?")
for _, v := range strings.Split(query, "&") {
if v == "" {
continue
}
pair := strings.SplitN(v, "=", 2)
l := len(pair)
if l == 1 {
ret = append(ret, searchParam{name: unescapeSearchParam(pair[0]), value: ""})
} else if l == 2 {
ret = append(ret, searchParam{name: unescapeSearchParam(pair[0]), value: unescapeSearchParam(pair[1])})
}
}
return
}

387
url/testdata/url_search_params.js vendored Normal file
View file

@ -0,0 +1,387 @@
"use strict";
const assert = require("../../assert.js");
let params;
function testCtor(value, expected) {
assert.sameValue(new URLSearchParams(value).toString(), expected);
}
testCtor("user=abc&query=xyz", "user=abc&query=xyz");
testCtor("?user=abc&query=xyz", "user=abc&query=xyz");
testCtor(
{
num: 1,
user: "abc",
query: ["first", "second"],
obj: { prop: "value" },
b: true,
},
"num=1&user=abc&query=first%2Csecond&obj=%5Bobject+Object%5D&b=true"
);
const map = new Map();
map.set("user", "abc");
map.set("query", "xyz");
testCtor(map, "user=abc&query=xyz");
testCtor(
[
["user", "abc"],
["query", "first"],
["query", "second"],
],
"user=abc&query=first&query=second"
);
// Each key-value pair must have exactly two elements
assert.throwsNodeError(() => new URLSearchParams([["single_value"]]), TypeError, "ERR_INVALID_TUPLE");
assert.throwsNodeError(() => new URLSearchParams([["too", "many", "values"]]), TypeError, "ERR_INVALID_TUPLE");
params = new URLSearchParams("a=b&cc=d");
params.forEach((value, name, searchParams) => {
if (name === "a") {
assert.sameValue(value, "b");
}
if (name === "cc") {
assert.sameValue(value, "d");
}
assert.sameValue(searchParams, params);
});
params.forEach((value, name, searchParams) => {
if (name === "a") {
assert.sameValue(value, "b");
searchParams.set("cc", "d1");
}
if (name === "cc") {
assert.sameValue(value, "d1");
}
assert.sameValue(searchParams, params);
});
assert.throwsNodeError(() => params.forEach(123), TypeError, "ERR_INVALID_ARG_TYPE");
assert.throwsNodeError(() => params.forEach.call(1, 2), TypeError, "ERR_INVALID_THIS");
params = new URLSearchParams("a=1=2&b=3");
assert.sameValue(params.size, 2);
assert.sameValue(params.get("a"), "1=2");
assert.sameValue(params.get("b"), "3");
params = new URLSearchParams("&");
assert.sameValue(params.size, 0);
params = new URLSearchParams("& ");
assert.sameValue(params.size, 1);
assert.sameValue(params.get(" "), "");
params = new URLSearchParams(" &");
assert.sameValue(params.size, 1);
assert.sameValue(params.get(" "), "");
params = new URLSearchParams("=");
assert.sameValue(params.size, 1);
assert.sameValue(params.get(""), "");
params = new URLSearchParams("&=2");
assert.sameValue(params.size, 1);
assert.sameValue(params.get(""), "2");
params = new URLSearchParams("?user=abc");
assert.throwsNodeError(() => params.append(), TypeError, "ERR_MISSING_ARGS");
params.append("query", "first");
assert.sameValue(params.toString(), "user=abc&query=first");
params = new URLSearchParams("first=one&second=two&third=three");
assert.throwsNodeError(() => params.delete(), TypeError, "ERR_MISSING_ARGS");
params.delete("second", "fake-value");
assert.sameValue(params.toString(), "first=one&second=two&third=three");
params.delete("third", "three");
assert.sameValue(params.toString(), "first=one&second=two");
params.delete("second");
assert.sameValue(params.toString(), "first=one");
params = new URLSearchParams("user=abc&query=xyz");
assert.throwsNodeError(() => params.get(), TypeError, "ERR_MISSING_ARGS");
assert.sameValue(params.get("user"), "abc");
assert.sameValue(params.get("non-existant"), null);
params = new URLSearchParams("query=first&query=second");
assert.throwsNodeError(() => params.getAll(), TypeError, "ERR_MISSING_ARGS");
const all = params.getAll("query");
assert.sameValue(all.includes("first"), true);
assert.sameValue(all.includes("second"), true);
assert.sameValue(all.length, 2);
const getAllUndefined = params.getAll(undefined);
assert.sameValue(getAllUndefined.length, 0);
const getAllNonExistant = params.getAll("does_not_exists");
assert.sameValue(getAllNonExistant.length, 0);
params = new URLSearchParams("user=abc&query=xyz");
assert.throwsNodeError(() => params.has(), TypeError, "ERR_MISSING_ARGS");
assert.sameValue(params.has(undefined), false);
assert.sameValue(params.has("user"), true);
assert.sameValue(params.has("user", "abc"), true);
assert.sameValue(params.has("user", "abc", "extra-param"), true);
assert.sameValue(params.has("user", "efg"), false);
assert.sameValue(params.has("user", undefined), true);
params = new URLSearchParams();
params.append("foo", "bar");
params.append("foo", "baz");
params.append("abc", "def");
assert.sameValue(params.toString(), "foo=bar&foo=baz&abc=def");
params.set("foo", "def");
params.set("xyz", "opq");
assert.sameValue(params.toString(), "foo=def&abc=def&xyz=opq");
params = new URLSearchParams("query=first&query=second&user=abc&double=first,second");
const URLSearchIteratorPrototype = params.entries().__proto__;
assert.sameValue(typeof URLSearchIteratorPrototype, "object");
assert.sameValue(URLSearchIteratorPrototype.__proto__, [][Symbol.iterator]().__proto__.__proto__);
assert.sameValue(params[Symbol.iterator], params.entries);
{
const entries = params.entries();
assert.sameValue(entries.toString(), "[object URLSearchParams Iterator]");
assert.sameValue(entries.__proto__, URLSearchIteratorPrototype);
let item = entries.next();
assert.sameValue(item.value.toString(), ["query", "first"].toString());
assert.sameValue(item.done, false);
item = entries.next();
assert.sameValue(item.value.toString(), ["query", "second"].toString());
assert.sameValue(item.done, false);
item = entries.next();
assert.sameValue(item.value.toString(), ["user", "abc"].toString());
assert.sameValue(item.done, false);
item = entries.next();
assert.sameValue(item.value.toString(), ["double", "first,second"].toString());
assert.sameValue(item.done, false);
item = entries.next();
assert.sameValue(item.value, undefined);
assert.sameValue(item.done, true);
}
params = new URLSearchParams("query=first&query=second&user=abc");
{
const keys = params.keys();
assert.sameValue(keys.__proto__, URLSearchIteratorPrototype);
let item = keys.next();
assert.sameValue(item.value, "query");
assert.sameValue(item.done, false);
item = keys.next();
assert.sameValue(item.value, "query");
assert.sameValue(item.done, false);
item = keys.next();
assert.sameValue(item.value, "user");
assert.sameValue(item.done, false);
item = keys.next();
assert.sameValue(item.value, undefined);
assert.sameValue(item.done, true);
assert.sameValue(Array.from(params.keys()).length, 3);
}
params = new URLSearchParams("query=first&query=second&user=abc");
{
const values = params.values();
assert.sameValue(values.__proto__, URLSearchIteratorPrototype);
let item = values.next();
assert.sameValue(item.value, "first");
assert.sameValue(item.done, false);
item = values.next();
assert.sameValue(item.value, "second");
assert.sameValue(item.done, false);
item = values.next();
assert.sameValue(item.value, "abc");
assert.sameValue(item.done, false);
item = values.next();
assert.sameValue(item.value, undefined);
assert.sameValue(item.done, true);
}
params = new URLSearchParams("query[]=abc&type=search&query[]=123");
params.sort();
assert.sameValue(params.toString(), "query%5B%5D=abc&query%5B%5D=123&type=search");
params = new URLSearchParams("query=first&query=second&user=abc");
assert.sameValue(params.size, 3);
params = new URLSearchParams("%");
assert.sameValue(params.has("%"), true);
assert.sameValue(params.toString(), "%25=");
{
const params = new URLSearchParams("");
assert.sameValue(params.size, 0);
assert.sameValue(params.toString(), "");
assert.sameValue(params.get(undefined), null);
params.set(undefined, true);
assert.sameValue(params.has(undefined), true);
assert.sameValue(params.has("undefined"), true);
assert.sameValue(params.get("undefined"), "true");
assert.sameValue(params.get(undefined), "true");
assert.sameValue(params.getAll(undefined).toString(), ["true"].toString());
params.delete(undefined);
assert.sameValue(params.has(undefined), false);
assert.sameValue(params.has("undefined"), false);
assert.sameValue(params.has(null), false);
params.set(null, "nullval");
assert.sameValue(params.has(null), true);
assert.sameValue(params.has("null"), true);
assert.sameValue(params.get(null), "nullval");
assert.sameValue(params.get("null"), "nullval");
params.delete(null);
assert.sameValue(params.has(null), false);
assert.sameValue(params.has("null"), false);
}
function* functionGeneratorExample() {
yield ["user", "abc"];
yield ["query", "first"];
yield ["query", "second"];
}
params = new URLSearchParams(functionGeneratorExample());
assert.sameValue(params.toString(), "user=abc&query=first&query=second");
assert.sameValue(params.__proto__.constructor, URLSearchParams);
assert.sameValue(params instanceof URLSearchParams, true);
{
const params = new URLSearchParams("1=2&1=3");
assert.sameValue(params.get(1), "2");
assert.sameValue(params.getAll(1).toString(), ["2", "3"].toString());
assert.sameValue(params.getAll("x").toString(), [].toString());
}
// Sync
{
const url = new URL("https://test.com/");
const params = url.searchParams;
assert.sameValue(params.size, 0);
url.search = "a=1";
assert.sameValue(params.size, 1);
assert.sameValue(params.get("a"), "1");
}
{
const url = new URL("https://test.com/?a=1");
const params = url.searchParams;
assert.sameValue(params.size, 1);
url.search = "";
assert.sameValue(params.size, 0);
url.search = "b=2";
assert.sameValue(params.size, 1);
}
{
const url = new URL("https://test.com/");
const params = url.searchParams;
params.append("a", "1");
assert.sameValue(url.toString(), "https://test.com/?a=1");
}
{
const url = new URL("https://test.com/");
url.searchParams.append("a", "1");
url.searchParams.append("b", "1");
assert.sameValue(url.toString(), "https://test.com/?a=1&b=1");
}
{
const url = new URL("https://test.com/");
url.searchParams.append("a", "1");
assert.sameValue(url.search, "?a=1");
}
{
const url = new URL("https://test.com/?a=1");
const params = url.searchParams;
params.append("a", "2");
assert.sameValue(url.search, "?a=1&a=2");
}
{
const url = new URL("https://test.com/");
const params = url.searchParams;
params.set("a", "1");
assert.sameValue(url.search, "?a=1");
}
{
const url = new URL("https://test.com/");
url.searchParams.set("a", "1");
url.searchParams.set("b", "1");
assert.sameValue(url.toString(), "https://test.com/?a=1&b=1");
}
{
const url = new URL("https://test.com/?a=1&b=2");
const params = url.searchParams;
params.delete("a");
assert.sameValue(url.search, "?b=2");
}
{
const url = new URL("https://test.com/?b=2&a=1");
const params = url.searchParams;
params.sort();
assert.sameValue(url.search, "?a=1&b=2");
}
{
const url = new URL("https://test.com/?a=1");
const params = url.searchParams;
params.delete("a");
assert.sameValue(url.search, "");
params.set("a", 2);
assert.sameValue(url.search, "?a=2");
}
// FAILING: no custom properties on wrapped Go structs
/*
{
const params = new URLSearchParams("");
assert.sameValue(Object.isExtensible(params), true);
assert.sameValue(Reflect.defineProperty(params, "customField", {value: 42, configurable: true}), true);
assert.sameValue(params.customField, 42);
const desc = Reflect.getOwnPropertyDescriptor(params, "customField");
assert.sameValue(desc.value, 42);
assert.sameValue(desc.writable, false);
assert.sameValue(desc.enumerable, false);
assert.sameValue(desc.configurable, true);
}
*/
// Escape
{
const myURL = new URL('https://example.org/abc?fo~o=~ba r%z');
assert.sameValue(myURL.search, "?fo~o=~ba%20r%z");
// Modify the URL via searchParams...
myURL.searchParams.sort();
assert.sameValue(myURL.search, "?fo%7Eo=%7Eba+r%25z");
}

262
url/testdata/url_test.js vendored Normal file
View file

@ -0,0 +1,262 @@
"use strict";
const assert = require("../../assert.js");
function testURLCtor(str, expected) {
assert.sameValue(new URL(str).toString(), expected);
}
function testURLCtorBase(ref, base, expected, message) {
assert.sameValue(new URL(ref, base).toString(), expected, message);
}
testURLCtorBase("https://example.org/", undefined, "https://example.org/");
testURLCtorBase("/foo", "https://example.org/", "https://example.org/foo");
testURLCtorBase("http://Example.com/", "https://example.org/", "http://example.com/");
testURLCtorBase("https://Example.com/", "https://example.org/", "https://example.com/");
testURLCtorBase("foo://Example.com/", "https://example.org/", "foo://Example.com/");
testURLCtorBase("foo:Example.com/", "https://example.org/", "foo:Example.com/");
testURLCtorBase("#hash", "https://example.org/", "https://example.org/#hash");
testURLCtor("HTTP://test.com", "http://test.com/");
testURLCtor("HTTPS://á.com", "https://xn--1ca.com/");
testURLCtor("HTTPS://á.com:123", "https://xn--1ca.com:123/");
testURLCtor("https://test.com#asdfá", "https://test.com/#asdf%C3%A1");
testURLCtor("HTTPS://á.com:123/á", "https://xn--1ca.com:123/%C3%A1");
testURLCtor("fish://á.com", "fish://%C3%A1.com");
testURLCtor("https://test.com/?a=1 /2", "https://test.com/?a=1%20/2");
testURLCtor("https://test.com/á=1?á=1&ü=2#é", "https://test.com/%C3%A1=1?%C3%A1=1&%C3%BC=2#%C3%A9");
assert.throws(() => new URL("test"), TypeError);
assert.throws(() => new URL("ssh://EEE:ddd"), TypeError);
{
let u = new URL("https://example.org/");
assert.sameValue(u.__proto__.constructor, URL);
assert.sameValue(u instanceof URL, true);
}
{
let u = new URL("https://example.org/");
assert.sameValue(u.searchParams, u.searchParams);
}
let myURL;
// Hash
myURL = new URL("https://example.org/foo#bar");
myURL.hash = "baz";
assert.sameValue(myURL.href, "https://example.org/foo#baz");
myURL.hash = "#baz";
assert.sameValue(myURL.href, "https://example.org/foo#baz");
myURL.hash = "#á=1 2";
assert.sameValue(myURL.href, "https://example.org/foo#%C3%A1=1%202");
myURL.hash = "#a/#b";
// FAILING: the second # gets escaped
//assert.sameValue(myURL.href, "https://example.org/foo#a/#b");
assert.sameValue(myURL.search, "");
// FAILING: the second # gets escaped
//assert.sameValue(myURL.hash, "#a/#b");
// Host
myURL = new URL("https://example.org:81/foo");
myURL.host = "example.com:82";
assert.sameValue(myURL.href, "https://example.com:82/foo");
// Hostname
myURL = new URL("https://example.org:81/foo");
myURL.hostname = "example.com:82";
assert.sameValue(myURL.href, "https://example.org:81/foo");
myURL.hostname = "á.com";
assert.sameValue(myURL.href, "https://xn--1ca.com:81/foo");
// href
myURL = new URL("https://example.org/foo");
myURL.href = "https://example.com/bar";
assert.sameValue(myURL.href, "https://example.com/bar");
// Password
myURL = new URL("https://abc:xyz@example.com");
myURL.password = "123";
assert.sameValue(myURL.href, "https://abc:123@example.com/");
// pathname
myURL = new URL("https://example.org/abc/xyz?123");
myURL.pathname = "/abcdef";
assert.sameValue(myURL.href, "https://example.org/abcdef?123");
assert.sameValue(myURL.toString(), "https://example.org/abcdef?123");
myURL.pathname = "";
assert.sameValue(myURL.href, "https://example.org/?123");
myURL.pathname = "á";
assert.sameValue(myURL.pathname, "/%C3%A1");
assert.sameValue(myURL.href, "https://example.org/%C3%A1?123");
myURL = new URL("file:///./abc");
assert.sameValue(myURL.pathname, "/abc");
myURL = new URL("fish://host/.");
assert.sameValue(myURL.pathname, "/");
myURL.pathname = ".";
assert.sameValue(myURL.pathname, "/");
myURL = new URL("fish://host/a/../b");
assert.sameValue(myURL.pathname, '/b');
myURL.pathname = 'a/../c';
assert.sameValue(myURL.pathname, '/c');
myURL = new URL("file://");
assert.sameValue(myURL.pathname, "/");
assert.sameValue(myURL.href, "file:///");
assert.throwsNodeError(() => {
new URL("http://");
}, TypeError, "ERR_INVALID_URL");
// myURL = new URL("fish://");
// assert.sameValue(myURL.pathname, "");
// Currently returns "fish:"
// assert.sameValue(myURL.href, "fish://");
// port
myURL = new URL("https://example.org:8888");
assert.sameValue(myURL.port, "8888");
function testSetPort(port, expected) {
const url = new URL("https://example.org:8888");
url.port = port;
assert.sameValue(url.port, expected);
}
testSetPort(0, "0");
testSetPort(-0, "0");
// Default ports are automatically transformed to the empty string
// (HTTPS protocol's default port is 443)
testSetPort("443", "");
testSetPort(443, "");
// Empty string is the same as default port
testSetPort("", "");
// Completely invalid port strings are ignored
testSetPort("abcd", "8888");
testSetPort("-123", "");
testSetPort(-123, "");
testSetPort(-123.45, "");
testSetPort(undefined, "8888");
testSetPort(null, "8888");
testSetPort(+Infinity, "8888");
testSetPort(-Infinity, "8888");
testSetPort(NaN, "8888");
// Leading numbers are treated as a port number
testSetPort("5678abcd", "5678");
testSetPort("a5678abcd", "");
// Non-integers are truncated
testSetPort(1234.5678, "1234");
// Out-of-range numbers which are not represented in scientific notation
// will be ignored.
testSetPort(1e10, "8888");
testSetPort("123456", "8888");
testSetPort(123456, "8888");
testSetPort(4.567e21, "4");
// toString() takes precedence over valueOf(), even if it returns a valid integer
testSetPort(
{
toString() {
return "2";
},
valueOf() {
return 1;
},
},
"2"
);
// Protocol
function testSetProtocol(url, protocol, expected) {
url.protocol = protocol;
assert.sameValue(url.protocol, expected);
}
testSetProtocol(new URL("https://example.org"), "ftp", "ftp:");
testSetProtocol(new URL("https://example.org"), "ftp:", "ftp:");
testSetProtocol(new URL("https://example.org"), "FTP:", "ftp:");
testSetProtocol(new URL("https://example.org"), "ftp: blah", "ftp:");
// special to non-special
testSetProtocol(new URL("https://example.org"), "foo", "https:");
// non-special to special
testSetProtocol(new URL("fish://example.org"), "https", "fish:");
// Search
myURL = new URL("https://example.org/abc?123");
myURL.search = "abc=xyz";
assert.sameValue(myURL.href, "https://example.org/abc?abc=xyz");
myURL.search = "a=1 2";
assert.sameValue(myURL.href, "https://example.org/abc?a=1%202");
myURL.search = "á=ú";
assert.sameValue(myURL.search, "?%C3%A1=%C3%BA");
assert.sameValue(myURL.href, "https://example.org/abc?%C3%A1=%C3%BA");
myURL.hash = "hash";
myURL.search = "a=#b";
assert.sameValue(myURL.href, "https://example.org/abc?a=%23b#hash");
assert.sameValue(myURL.search, "?a=%23b");
assert.sameValue(myURL.hash, "#hash");
// Username
myURL = new URL("https://abc:xyz@example.com/");
myURL.username = "123";
assert.sameValue(myURL.href, "https://123:xyz@example.com/");
// Origin, read-only
assert.throws(() => {
myURL.origin = "abc";
}, TypeError);
// href
myURL = new URL("https://example.org");
myURL.href = "https://example.com";
assert.sameValue(myURL.href, "https://example.com/");
assert.throws(() => {
myURL.href = "test";
}, TypeError);
// Search Params
myURL = new URL("https://example.com/");
myURL.searchParams.append("user", "abc");
assert.sameValue(myURL.toString(), "https://example.com/?user=abc");
myURL.searchParams.append("first", "one");
assert.sameValue(myURL.toString(), "https://example.com/?user=abc&first=one");
myURL.searchParams.delete("user");
assert.sameValue(myURL.toString(), "https://example.com/?first=one");
{
const url = require("url");
assert.sameValue(url.domainToASCII('español.com'), "xn--espaol-zwa.com");
assert.sameValue(url.domainToASCII('中文.com'), "xn--fiq228c.com");
assert.sameValue(url.domainToASCII('xn--iñvalid.com'), "");
assert.sameValue(url.domainToUnicode('xn--espaol-zwa.com'), "español.com");
assert.sameValue(url.domainToUnicode('xn--fiq228c.com'), "中文.com");
assert.sameValue(url.domainToUnicode('xn--iñvalid.com'), "");
}
{
const url = new URL("otpauth://totp");
url.pathname = 'domain.com Domain:user@domain.com';
assert.sameValue(url.toString(), 'otpauth://totp/domain.com%20Domain:user@domain.com');
}

10
url/types/README.md Normal file
View file

@ -0,0 +1,10 @@
## Type definitions for the goja_nodejs url module.
This package contains type definitions which only include features
currently implemented by the goja_nodejs url module.
### Install
```shell
npm install --save-dev @dop251/types-goja_nodejs-url
```

19
url/types/package.json Normal file
View file

@ -0,0 +1,19 @@
{
"name": "@dop251/types-goja_nodejs-url",
"version": "0.0.1-rc2",
"types": "url.d.ts",
"scripts": {
"test": "tsc --noEmit"
},
"repository": {
"type": "git",
"url": "git+https://github.com/dop251/goja_nodejs.git"
},
"dependencies": {
"@dop251/types-goja_nodejs-global": "0.0.1-rc2"
},
"devDependencies": {
"typescript": "next"
},
"private": false
}

16
url/types/tsconfig.json Normal file
View file

@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"lib": [
"es6",
"dom"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noEmit": true,
"forceConsistentCasingInFileNames": true
}
}

543
url/types/url.d.ts vendored Normal file
View file

@ -0,0 +1,543 @@
/// <reference types="@dop251/types-goja_nodejs-global" />
declare module 'url' {
/**
* Returns the [Punycode](https://tools.ietf.org/html/rfc5891#section-4.4) ASCII serialization of the `domain`. If `domain` is an
* invalid domain, the empty string is returned.
*
* It performs the inverse operation to {@link domainToUnicode}.
*
* ```js
* import url from 'node:url';
*
* console.log(url.domainToASCII('español.com'));
* // Prints xn--espaol-zwa.com
* console.log(url.domainToASCII('中文.com'));
* // Prints xn--fiq228c.com
* console.log(url.domainToASCII('xn--iñvalid.com'));
* // Prints an empty string
* ```
* @since v7.4.0, v6.13.0
*/
function domainToASCII(domain: string): string;
/**
* Returns the Unicode serialization of the `domain`. If `domain` is an invalid
* domain, the empty string is returned.
*
* It performs the inverse operation to {@link domainToASCII}.
*
* ```js
* import url from 'node:url';
*
* console.log(url.domainToUnicode('xn--espaol-zwa.com'));
* // Prints español.com
* console.log(url.domainToUnicode('xn--fiq228c.com'));
* // Prints 中文.com
* console.log(url.domainToUnicode('xn--iñvalid.com'));
* // Prints an empty string
* ```
* @since v7.4.0, v6.13.0
*/
function domainToUnicode(domain: string): string;
/**
* Browser-compatible `URL` class, implemented by following the WHATWG URL
* Standard. [Examples of parsed URLs](https://url.spec.whatwg.org/#example-url-parsing) may be found in the Standard itself.
* The `URL` class is also available on the global object.
*
* In accordance with browser conventions, all properties of `URL` objects
* are implemented as getters and setters on the class prototype, rather than as
* data properties on the object itself. Thus, unlike `legacy urlObject`s,
* using the `delete` keyword on any properties of `URL` objects (e.g. `delete myURL.protocol`, `delete myURL.pathname`, etc) has no effect but will still
* return `true`.
* @since v7.0.0, v6.13.0
*/
class URL {
constructor(input: string | { toString: () => string }, base?: string | URL);
/**
* Gets and sets the fragment portion of the URL.
*
* ```js
* const myURL = new URL('https://example.org/foo#bar');
* console.log(myURL.hash);
* // Prints #bar
*
* myURL.hash = 'baz';
* console.log(myURL.href);
* // Prints https://example.org/foo#baz
* ```
*
* Invalid URL characters included in the value assigned to the `hash` property
* are `percent-encoded`. The selection of which characters to
* percent-encode may vary somewhat from what the {@link parse} and {@link format} methods would produce.
*/
hash: string;
/**
* Gets and sets the host portion of the URL.
*
* ```js
* const myURL = new URL('https://example.org:81/foo');
* console.log(myURL.host);
* // Prints example.org:81
*
* myURL.host = 'example.com:82';
* console.log(myURL.href);
* // Prints https://example.com:82/foo
* ```
*
* Invalid host values assigned to the `host` property are ignored.
*/
host: string;
/**
* Gets and sets the host name portion of the URL. The key difference between`url.host` and `url.hostname` is that `url.hostname` does _not_ include the
* port.
*
* ```js
* const myURL = new URL('https://example.org:81/foo');
* console.log(myURL.hostname);
* // Prints example.org
*
* // Setting the hostname does not change the port
* myURL.hostname = 'example.com';
* console.log(myURL.href);
* // Prints https://example.com:81/foo
*
* // Use myURL.host to change the hostname and port
* myURL.host = 'example.org:82';
* console.log(myURL.href);
* // Prints https://example.org:82/foo
* ```
*
* Invalid host name values assigned to the `hostname` property are ignored.
*/
hostname: string;
/**
* Gets and sets the serialized URL.
*
* ```js
* const myURL = new URL('https://example.org/foo');
* console.log(myURL.href);
* // Prints https://example.org/foo
*
* myURL.href = 'https://example.com/bar';
* console.log(myURL.href);
* // Prints https://example.com/bar
* ```
*
* Getting the value of the `href` property is equivalent to calling {@link toString}.
*
* Setting the value of this property to a new value is equivalent to creating a
* new `URL` object using `new URL(value)`. Each of the `URL` object's properties will be modified.
*
* If the value assigned to the `href` property is not a valid URL, a `TypeError` will be thrown.
*/
href: string;
/**
* Gets the read-only serialization of the URL's origin.
*
* ```js
* const myURL = new URL('https://example.org/foo/bar?baz');
* console.log(myURL.origin);
* // Prints https://example.org
* ```
*
* ```js
* const idnURL = new URL('https://測試');
* console.log(idnURL.origin);
* // Prints https://xn--g6w251d
*
* console.log(idnURL.hostname);
* // Prints xn--g6w251d
* ```
*/
readonly origin: string;
/**
* Gets and sets the password portion of the URL.
*
* ```js
* const myURL = new URL('https://abc:xyz@example.com');
* console.log(myURL.password);
* // Prints xyz
*
* myURL.password = '123';
* console.log(myURL.href);
* // Prints https://abc:123@example.com/
* ```
*
* Invalid URL characters included in the value assigned to the `password` property
* are `percent-encoded`. The selection of which characters to
* percent-encode may vary somewhat from what the {@link parse} and {@link format} methods would produce.
*/
password: string;
/**
* Gets and sets the path portion of the URL.
*
* ```js
* const myURL = new URL('https://example.org/abc/xyz?123');
* console.log(myURL.pathname);
* // Prints /abc/xyz
*
* myURL.pathname = '/abcdef';
* console.log(myURL.href);
* // Prints https://example.org/abcdef?123
* ```
*
* Invalid URL characters included in the value assigned to the `pathname` property are `percent-encoded`. The selection of which characters
* to percent-encode may vary somewhat from what the {@link parse} and {@link format} methods would produce.
*/
pathname: string;
/**
* Gets and sets the port portion of the URL.
*
* The port value may be a number or a string containing a number in the range `0` to `65535` (inclusive). Setting the value to the default port of the `URL` objects given `protocol` will
* result in the `port` value becoming
* the empty string (`''`).
*
* The port value can be an empty string in which case the port depends on
* the protocol/scheme:
*
* <omitted>
*
* Upon assigning a value to the port, the value will first be converted to a
* string using `.toString()`.
*
* If that string is invalid but it begins with a number, the leading number is
* assigned to `port`.
* If the number lies outside the range denoted above, it is ignored.
*
* ```js
* const myURL = new URL('https://example.org:8888');
* console.log(myURL.port);
* // Prints 8888
*
* // Default ports are automatically transformed to the empty string
* // (HTTPS protocol's default port is 443)
* myURL.port = '443';
* console.log(myURL.port);
* // Prints the empty string
* console.log(myURL.href);
* // Prints https://example.org/
*
* myURL.port = 1234;
* console.log(myURL.port);
* // Prints 1234
* console.log(myURL.href);
* // Prints https://example.org:1234/
*
* // Completely invalid port strings are ignored
* myURL.port = 'abcd';
* console.log(myURL.port);
* // Prints 1234
*
* // Leading numbers are treated as a port number
* myURL.port = '5678abcd';
* console.log(myURL.port);
* // Prints 5678
*
* // Non-integers are truncated
* myURL.port = 1234.5678;
* console.log(myURL.port);
* // Prints 1234
*
* // Out-of-range numbers which are not represented in scientific notation
* // will be ignored.
* myURL.port = 1e10; // 10000000000, will be range-checked as described below
* console.log(myURL.port);
* // Prints 1234
* ```
*
* Numbers which contain a decimal point,
* such as floating-point numbers or numbers in scientific notation,
* are not an exception to this rule.
* Leading numbers up to the decimal point will be set as the URL's port,
* assuming they are valid:
*
* ```js
* myURL.port = 4.567e21;
* console.log(myURL.port);
* // Prints 4 (because it is the leading number in the string '4.567e21')
* ```
*/
port: string;
/**
* Gets and sets the protocol portion of the URL.
*
* ```js
* const myURL = new URL('https://example.org');
* console.log(myURL.protocol);
* // Prints https:
*
* myURL.protocol = 'ftp';
* console.log(myURL.href);
* // Prints ftp://example.org/
* ```
*
* Invalid URL protocol values assigned to the `protocol` property are ignored.
*/
protocol: string;
/**
* Gets and sets the serialized query portion of the URL.
*
* ```js
* const myURL = new URL('https://example.org/abc?123');
* console.log(myURL.search);
* // Prints ?123
*
* myURL.search = 'abc=xyz';
* console.log(myURL.href);
* // Prints https://example.org/abc?abc=xyz
* ```
*
* Any invalid URL characters appearing in the value assigned the `search` property will be `percent-encoded`. The selection of which
* characters to percent-encode may vary somewhat from what the {@link parse} and {@link format} methods would produce.
*/
search: string;
/**
* Gets the `URLSearchParams` object representing the query parameters of the
* URL. This property is read-only but the `URLSearchParams` object it provides
* can be used to mutate the URL instance; to replace the entirety of query
* parameters of the URL, use the {@link search} setter. See `URLSearchParams` documentation for details.
*
* Use care when using `.searchParams` to modify the `URL` because,
* per the WHATWG specification, the `URLSearchParams` object uses
* different rules to determine which characters to percent-encode. For
* instance, the `URL` object will not percent encode the ASCII tilde (`~`)
* character, while `URLSearchParams` will always encode it:
*
* ```js
* const myURL = new URL('https://example.org/abc?foo=~bar');
*
* console.log(myURL.search); // prints ?foo=~bar
*
* // Modify the URL via searchParams...
* myURL.searchParams.sort();
*
* console.log(myURL.search); // prints ?foo=%7Ebar
* ```
*/
readonly searchParams: URLSearchParams;
/**
* Gets and sets the username portion of the URL.
*
* ```js
* const myURL = new URL('https://abc:xyz@example.com');
* console.log(myURL.username);
* // Prints abc
*
* myURL.username = '123';
* console.log(myURL.href);
* // Prints https://123:xyz@example.com/
* ```
*
* Any invalid URL characters appearing in the value assigned the `username` property will be `percent-encoded`. The selection of which
* characters to percent-encode may vary somewhat from what the {@link parse} and {@link format} methods would produce.
*/
username: string;
/**
* The `toString()` method on the `URL` object returns the serialized URL. The
* value returned is equivalent to that of {@link href} and {@link toJSON}.
*/
toString(): string;
/**
* The `toJSON()` method on the `URL` object returns the serialized URL. The
* value returned is equivalent to that of {@link href} and {@link toString}.
*
* This method is automatically called when an `URL` object is serialized
* with [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify).
*
* ```js
* const myURLs = [
* new URL('https://www.example.com'),
* new URL('https://test.example.org'),
* ];
* console.log(JSON.stringify(myURLs));
* // Prints ["https://www.example.com/","https://test.example.org/"]
* ```
*/
toJSON(): string;
}
interface URLSearchParamsIterator<T> extends GojaNodeJS.Iterator<T, GojaNodeJS.BuiltinIteratorReturn, unknown> {
[Symbol.iterator](): URLSearchParamsIterator<T>;
}
/**
* The `URLSearchParams` API provides read and write access to the query of a `URL`. The `URLSearchParams` class can also be used standalone with one of the
* four following constructors.
* The `URLSearchParams` class is also available on the global object.
*
* The WHATWG `URLSearchParams` interface and the `querystring` module have
* similar purpose, but the purpose of the `querystring` module is more
* general, as it allows the customization of delimiter characters (`&#x26;` and `=`).
* On the other hand, this API is designed purely for URL query strings.
*
* ```js
* const myURL = new URL('https://example.org/?abc=123');
* console.log(myURL.searchParams.get('abc'));
* // Prints 123
*
* myURL.searchParams.append('abc', 'xyz');
* console.log(myURL.href);
* // Prints https://example.org/?abc=123&#x26;abc=xyz
*
* myURL.searchParams.delete('abc');
* myURL.searchParams.set('a', 'b');
* console.log(myURL.href);
* // Prints https://example.org/?a=b
*
* const newSearchParams = new URLSearchParams(myURL.searchParams);
* // The above is equivalent to
* // const newSearchParams = new URLSearchParams(myURL.search);
*
* newSearchParams.append('a', 'c');
* console.log(myURL.href);
* // Prints https://example.org/?a=b
* console.log(newSearchParams.toString());
* // Prints a=b&#x26;a=c
*
* // newSearchParams.toString() is implicitly called
* myURL.search = newSearchParams;
* console.log(myURL.href);
* // Prints https://example.org/?a=b&#x26;a=c
* newSearchParams.delete('a');
* console.log(myURL.href);
* // Prints https://example.org/?a=b&#x26;a=c
* ```
* @since v7.5.0, v6.13.0
*/
class URLSearchParams implements Iterable<[string, string]> {
constructor(
init?:
| URLSearchParams
| string
| Record<string, string | readonly string[]>
| Iterable<[string, string]>
| ReadonlyArray<[string, string]>,
);
/**
* Append a new name-value pair to the query string.
*/
append(name: string, value: string): void;
/**
* If `value` is provided, removes all name-value pairs
* where name is `name` and value is `value`.
*
* If `value` is not provided, removes all name-value pairs whose name is `name`.
*/
delete(name: string, value?: string): void;
/**
* Returns an ES6 `Iterator` over each of the name-value pairs in the query.
* Each item of the iterator is a JavaScript `Array`. The first item of the `Array` is the `name`, the second item of the `Array` is the `value`.
*
* Alias for `urlSearchParams[@@iterator]()`.
*/
entries(): URLSearchParamsIterator<[string, string]>;
/**
* Iterates over each name-value pair in the query and invokes the given function.
*
* ```js
* const myURL = new URL('https://example.org/?a=b&#x26;c=d');
* myURL.searchParams.forEach((value, name, searchParams) => {
* console.log(name, value, myURL.searchParams === searchParams);
* });
* // Prints:
* // a b true
* // c d true
* ```
* @param fn Invoked for each name-value pair in the query
* @param thisArg To be used as `this` value for when `fn` is called
*/
forEach<TThis = this>(
fn: (this: TThis, value: string, name: string, searchParams: URLSearchParams) => void,
thisArg?: TThis,
): void;
/**
* Returns the value of the first name-value pair whose name is `name`. If there
* are no such pairs, `null` is returned.
* @return or `null` if there is no name-value pair with the given `name`.
*/
get(name: string): string | null;
/**
* Returns the values of all name-value pairs whose name is `name`. If there are
* no such pairs, an empty array is returned.
*/
getAll(name: string): string[];
/**
* Checks if the `URLSearchParams` object contains key-value pair(s) based on `name` and an optional `value` argument.
*
* If `value` is provided, returns `true` when name-value pair with
* same `name` and `value` exists.
*
* If `value` is not provided, returns `true` if there is at least one name-value
* pair whose name is `name`.
*/
has(name: string, value?: string): boolean;
/**
* Returns an ES6 `Iterator` over the names of each name-value pair.
*
* ```js
* const params = new URLSearchParams('foo=bar&#x26;foo=baz');
* for (const name of params.keys()) {
* console.log(name);
* }
* // Prints:
* // foo
* // foo
* ```
*/
keys(): URLSearchParamsIterator<string>;
/**
* Sets the value in the `URLSearchParams` object associated with `name` to `value`. If there are any pre-existing name-value pairs whose names are `name`,
* set the first such pair's value to `value` and remove all others. If not,
* append the name-value pair to the query string.
*
* ```js
* const params = new URLSearchParams();
* params.append('foo', 'bar');
* params.append('foo', 'baz');
* params.append('abc', 'def');
* console.log(params.toString());
* // Prints foo=bar&#x26;foo=baz&#x26;abc=def
*
* params.set('foo', 'def');
* params.set('xyz', 'opq');
* console.log(params.toString());
* // Prints foo=def&#x26;abc=def&#x26;xyz=opq
* ```
*/
set(name: string, value: string): void;
/**
* The total number of parameter entries.
* @since v19.8.0
*/
readonly size: number;
/**
* Sort all existing name-value pairs in-place by their names. Sorting is done
* with a [stable sorting algorithm](https://en.wikipedia.org/wiki/Sorting_algorithm#Stability), so relative order between name-value pairs
* with the same name is preserved.
*
* This method can be used, in particular, to increase cache hits.
*
* ```js
* const params = new URLSearchParams('query[]=abc&#x26;type=search&#x26;query[]=123');
* params.sort();
* console.log(params.toString());
* // Prints query%5B%5D=abc&#x26;query%5B%5D=123&#x26;type=search
* ```
* @since v7.7.0, v6.13.0
*/
sort(): void;
/**
* Returns the search parameters serialized as a string, with characters
* percent-encoded where necessary.
*/
toString(): string;
/**
* Returns an ES6 `Iterator` over the values of each name-value pair.
*/
values(): URLSearchParamsIterator<string>;
[Symbol.iterator](): URLSearchParamsIterator<[string, string]>;
}
}
declare module "node:url" {
export * from "url";
}

417
url/url.go Normal file
View file

@ -0,0 +1,417 @@
package url
import (
"math"
"net/url"
"path"
"reflect"
"strconv"
"strings"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/errors"
"golang.org/x/net/idna"
)
const (
URLNotAbsolute = "URL is not absolute"
InvalidURL = "Invalid URL"
InvalidBaseURL = "Invalid base URL"
InvalidHostname = "Invalid hostname"
)
var (
reflectTypeURL = reflect.TypeOf((*nodeURL)(nil))
reflectTypeInt = reflect.TypeOf(int64(0))
)
func toURL(r *goja.Runtime, v goja.Value) *nodeURL {
if v.ExportType() == reflectTypeURL {
if u := v.Export().(*nodeURL); u != nil {
return u
}
}
panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URL`))
}
func (m *urlModule) newInvalidURLError(msg, input string) *goja.Object {
o := errors.NewTypeError(m.r, "ERR_INVALID_URL", msg)
o.Set("input", m.r.ToValue(input))
return o
}
func (m *urlModule) defineURLAccessorProp(p *goja.Object, name string, getter func(*nodeURL) interface{}, setter func(*nodeURL, goja.Value)) {
var getterVal, setterVal goja.Value
if getter != nil {
getterVal = m.r.ToValue(func(call goja.FunctionCall) goja.Value {
return m.r.ToValue(getter(toURL(m.r, call.This)))
})
}
if setter != nil {
setterVal = m.r.ToValue(func(call goja.FunctionCall) goja.Value {
setter(toURL(m.r, call.This), call.Argument(0))
return goja.Undefined()
})
}
p.DefineAccessorProperty(name, getterVal, setterVal, goja.FLAG_FALSE, goja.FLAG_TRUE)
}
func valueToURLPort(v goja.Value) (portNum int, empty bool) {
portNum = -1
if et := v.ExportType(); et == reflectTypeInt {
num := v.ToInteger()
if num < 0 {
empty = true
} else if num <= math.MaxUint16 {
portNum = int(num)
}
} else {
s := v.String()
if s == "" {
return 0, true
}
firstDigitIdx := -1
for i := 0; i < len(s); i++ {
if c := s[i]; c >= '0' && c <= '9' {
firstDigitIdx = i
break
}
}
if firstDigitIdx == -1 {
return -1, false
}
if firstDigitIdx > 0 {
return 0, true
}
for i := 0; i < len(s); i++ {
if c := s[i]; c >= '0' && c <= '9' {
if portNum == -1 {
portNum = 0
}
portNum = portNum*10 + int(c-'0')
if portNum > math.MaxUint16 {
portNum = -1
break
}
} else {
break
}
}
}
return
}
func isDefaultURLPort(protocol string, port int) bool {
switch port {
case 21:
if protocol == "ftp" {
return true
}
case 80:
if protocol == "http" || protocol == "ws" {
return true
}
case 443:
if protocol == "https" || protocol == "wss" {
return true
}
}
return false
}
func isSpecialProtocol(protocol string) bool {
switch protocol {
case "ftp", "file", "http", "https", "ws", "wss":
return true
}
return false
}
func isSpecialNetProtocol(protocol string) bool {
switch protocol {
case "https", "http", "ftp", "wss", "ws":
return true
}
return false
}
func clearURLPort(u *url.URL) {
u.Host = u.Hostname()
}
func setURLPort(nu *nodeURL, v goja.Value) {
u := nu.url
if u.Scheme == "file" {
return
}
portNum, empty := valueToURLPort(v)
if empty {
clearURLPort(u)
return
}
if portNum == -1 {
return
}
if isDefaultURLPort(u.Scheme, portNum) {
clearURLPort(u)
} else {
u.Host = u.Hostname() + ":" + strconv.Itoa(portNum)
}
}
func (m *urlModule) parseURL(s string, isBase bool) *url.URL {
u, err := url.Parse(s)
if err != nil {
if isBase {
panic(m.newInvalidURLError(InvalidBaseURL, s))
} else {
panic(m.newInvalidURLError(InvalidURL, s))
}
}
if isBase && !u.IsAbs() {
panic(m.newInvalidURLError(URLNotAbsolute, s))
}
if isSpecialNetProtocol(u.Scheme) && u.Host == "" && u.Path == "" {
panic(m.newInvalidURLError(InvalidURL, s))
}
if portStr := u.Port(); portStr != "" {
if port, err := strconv.Atoi(portStr); err != nil || isDefaultURLPort(u.Scheme, port) {
u.Host = u.Hostname() // Clear port
}
}
m.fixURL(u)
return u
}
func fixRawQuery(u *url.URL) {
if u.RawQuery != "" {
u.RawQuery = escape(u.RawQuery, &tblEscapeURLQuery, false)
}
}
func cleanPath(p, proto string) string {
if !strings.HasPrefix(p, "/") && (isSpecialProtocol(proto) || p != "") {
p = "/" + p
}
if p != "" {
return path.Clean(p)
}
return ""
}
func (m *urlModule) fixURL(u *url.URL) {
u.Path = cleanPath(u.Path, u.Scheme)
if isSpecialNetProtocol(u.Scheme) {
hostname := u.Hostname()
lh := strings.ToLower(hostname)
ch, err := idna.Punycode.ToASCII(lh)
if err != nil {
panic(m.newInvalidURLError(InvalidHostname, lh))
}
if ch != hostname {
if port := u.Port(); port != "" {
u.Host = ch + ":" + port
} else {
u.Host = ch
}
}
}
fixRawQuery(u)
}
func (m *urlModule) createURLPrototype() *goja.Object {
p := m.r.NewObject()
// host
m.defineURLAccessorProp(p, "host", func(u *nodeURL) interface{} {
return u.url.Host
}, func(u *nodeURL, arg goja.Value) {
host := arg.String()
if _, err := url.ParseRequestURI(u.url.Scheme + "://" + host); err == nil {
u.url.Host = host
m.fixURL(u.url)
}
})
// hash
m.defineURLAccessorProp(p, "hash", func(u *nodeURL) interface{} {
if u.url.Fragment != "" {
return "#" + u.url.EscapedFragment()
}
return ""
}, func(u *nodeURL, arg goja.Value) {
h := arg.String()
if len(h) > 0 && h[0] == '#' {
h = h[1:]
}
u.url.Fragment = h
})
// hostname
m.defineURLAccessorProp(p, "hostname", func(u *nodeURL) interface{} {
return strings.Split(u.url.Host, ":")[0]
}, func(u *nodeURL, arg goja.Value) {
h := arg.String()
if strings.IndexByte(h, ':') >= 0 {
return
}
if _, err := url.ParseRequestURI(u.url.Scheme + "://" + h); err == nil {
if port := u.url.Port(); port != "" {
u.url.Host = h + ":" + port
} else {
u.url.Host = h
}
m.fixURL(u.url)
}
})
// href
m.defineURLAccessorProp(p, "href", func(u *nodeURL) interface{} {
return u.String()
}, func(u *nodeURL, arg goja.Value) {
u.url = m.parseURL(arg.String(), true)
})
// pathname
m.defineURLAccessorProp(p, "pathname", func(u *nodeURL) interface{} {
return u.url.EscapedPath()
}, func(u *nodeURL, arg goja.Value) {
u.url.Path = cleanPath(arg.String(), u.url.Scheme)
})
// origin
m.defineURLAccessorProp(p, "origin", func(u *nodeURL) interface{} {
return u.url.Scheme + "://" + u.url.Hostname()
}, nil)
// password
m.defineURLAccessorProp(p, "password", func(u *nodeURL) interface{} {
p, _ := u.url.User.Password()
return p
}, func(u *nodeURL, arg goja.Value) {
user := u.url.User
u.url.User = url.UserPassword(user.Username(), arg.String())
})
// username
m.defineURLAccessorProp(p, "username", func(u *nodeURL) interface{} {
return u.url.User.Username()
}, func(u *nodeURL, arg goja.Value) {
p, has := u.url.User.Password()
if !has {
u.url.User = url.User(arg.String())
} else {
u.url.User = url.UserPassword(arg.String(), p)
}
})
// port
m.defineURLAccessorProp(p, "port", func(u *nodeURL) interface{} {
return u.url.Port()
}, func(u *nodeURL, arg goja.Value) {
setURLPort(u, arg)
})
// protocol
m.defineURLAccessorProp(p, "protocol", func(u *nodeURL) interface{} {
return u.url.Scheme + ":"
}, func(u *nodeURL, arg goja.Value) {
s := arg.String()
pos := strings.IndexByte(s, ':')
if pos >= 0 {
s = s[:pos]
}
s = strings.ToLower(s)
if isSpecialProtocol(u.url.Scheme) == isSpecialProtocol(s) {
if _, err := url.ParseRequestURI(s + "://" + u.url.Host); err == nil {
u.url.Scheme = s
}
}
})
// Search
m.defineURLAccessorProp(p, "search", func(u *nodeURL) interface{} {
u.syncSearchParams()
if u.url.RawQuery != "" {
return "?" + u.url.RawQuery
}
return ""
}, func(u *nodeURL, arg goja.Value) {
u.url.RawQuery = arg.String()
fixRawQuery(u.url)
if u.searchParams != nil {
u.searchParams = parseSearchQuery(u.url.RawQuery)
if u.searchParams == nil {
u.searchParams = make(searchParams, 0)
}
}
})
// search Params
m.defineURLAccessorProp(p, "searchParams", func(u *nodeURL) interface{} {
if u.searchParams == nil {
sp := parseSearchQuery(u.url.RawQuery)
if sp == nil {
sp = make(searchParams, 0)
}
u.searchParams = sp
}
return m.newURLSearchParams((*urlSearchParams)(u))
}, nil)
p.Set("toString", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toURL(m.r, call.This)
u.syncSearchParams()
return m.r.ToValue(u.url.String())
}))
p.Set("toJSON", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toURL(m.r, call.This)
u.syncSearchParams()
return m.r.ToValue(u.url.String())
}))
return p
}
func (m *urlModule) createURLConstructor() goja.Value {
f := m.r.ToValue(func(call goja.ConstructorCall) *goja.Object {
var u *url.URL
if baseArg := call.Argument(1); !goja.IsUndefined(baseArg) {
base := m.parseURL(baseArg.String(), true)
ref := m.parseURL(call.Argument(0).String(), false)
u = base.ResolveReference(ref)
} else {
u = m.parseURL(call.Argument(0).String(), true)
}
res := m.r.ToValue(&nodeURL{url: u}).(*goja.Object)
res.SetPrototype(call.This.Prototype())
return res
}).(*goja.Object)
proto := m.createURLPrototype()
f.Set("prototype", proto)
proto.DefineDataProperty("constructor", f, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
return f
}
func (m *urlModule) domainToASCII(domUnicode string) string {
res, err := idna.ToASCII(domUnicode)
if err != nil {
return ""
}
return res
}
func (m *urlModule) domainToUnicode(domASCII string) string {
res, err := idna.ToUnicode(domASCII)
if err != nil {
return ""
}
return res
}

122
url/url_test.go Normal file
View file

@ -0,0 +1,122 @@
package url
import (
_ "embed"
"testing"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/require"
)
func TestURL(t *testing.T) {
vm := goja.New()
new(require.Registry).Enable(vm)
Enable(vm)
if c := vm.Get("URL"); c == nil {
t.Fatal("URL not found")
}
script := `const url = new URL("https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash");`
if _, err := vm.RunString(script); err != nil {
t.Fatal("Failed to process url script.", err)
}
}
func TestGetters(t *testing.T) {
vm := goja.New()
new(require.Registry).Enable(vm)
Enable(vm)
if c := vm.Get("URL"); c == nil {
t.Fatal("URL not found")
}
script := `
new URL("https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hashed");
`
v, err := vm.RunString(script)
if err != nil {
t.Fatal("Failed to process url script.", err)
}
url := v.ToObject(vm)
tests := []struct {
prop string
expected string
}{
{
prop: "hash",
expected: "#hashed",
},
{
prop: "host",
expected: "sub.example.com:8080",
},
{
prop: "hostname",
expected: "sub.example.com",
},
{
prop: "href",
expected: "https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hashed",
},
{
prop: "origin",
expected: "https://sub.example.com",
},
{
prop: "password",
expected: "pass",
},
{
prop: "username",
expected: "user",
},
{
prop: "port",
expected: "8080",
},
{
prop: "protocol",
expected: "https:",
},
{
prop: "search",
expected: "?query=string",
},
}
for _, test := range tests {
v := url.Get(test.prop).String()
if v != test.expected {
t.Fatal("failed to match " + test.prop + " property. got: " + v + ", expected: " + test.expected)
}
}
}
//go:embed testdata/url_test.js
var urlTest string
func TestJs(t *testing.T) {
vm := goja.New()
new(require.Registry).Enable(vm)
Enable(vm)
if c := vm.Get("URL"); c == nil {
t.Fatal("URL not found")
}
// Script will throw an error on failed validation
_, err := vm.RunScript("testdata/url_test.js", urlTest)
if err != nil {
if ex, ok := err.(*goja.Exception); ok {
t.Fatal(ex.String())
}
t.Fatal("Failed to process url script.", err)
}
}

408
url/urlsearchparams.go Normal file
View file

@ -0,0 +1,408 @@
package url
import (
"reflect"
"sort"
"github.com/dop251/goja_nodejs/errors"
"github.com/dop251/goja"
)
var (
reflectTypeURLSearchParams = reflect.TypeOf((*urlSearchParams)(nil))
reflectTypeURLSearchParamsIterator = reflect.TypeOf((*urlSearchParamsIterator)(nil))
)
func newInvalidTupleError(r *goja.Runtime) *goja.Object {
return errors.NewTypeError(r, "ERR_INVALID_TUPLE", "Each query pair must be an iterable [name, value] tuple")
}
func newMissingArgsError(r *goja.Runtime, msg string) *goja.Object {
return errors.NewTypeError(r, errors.ErrCodeMissingArgs, msg)
}
func newInvalidCallbackTypeError(r *goja.Runtime) *goja.Object {
return errors.NewNotCorrectTypeError(r, "callback", "function")
}
func toUrlSearchParams(r *goja.Runtime, v goja.Value) *urlSearchParams {
if v.ExportType() == reflectTypeURLSearchParams {
if u := v.Export().(*urlSearchParams); u != nil {
return u
}
}
panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParams`))
}
func (m *urlModule) newURLSearchParams(sp *urlSearchParams) *goja.Object {
v := m.r.ToValue(sp).(*goja.Object)
v.SetPrototype(m.URLSearchParamsPrototype)
return v
}
func (m *urlModule) createURLSearchParamsConstructor() goja.Value {
f := m.r.ToValue(func(call goja.ConstructorCall) *goja.Object {
var sp searchParams
v := call.Argument(0)
if o, ok := v.(*goja.Object); ok {
sp = m.buildParamsFromObject(o)
} else if !goja.IsUndefined(v) {
sp = parseSearchQuery(v.String())
}
return m.newURLSearchParams(&urlSearchParams{searchParams: sp})
}).(*goja.Object)
m.URLSearchParamsPrototype = m.createURLSearchParamsPrototype()
f.Set("prototype", m.URLSearchParamsPrototype)
m.URLSearchParamsPrototype.DefineDataProperty("constructor", f, goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_FALSE)
return f
}
func (m *urlModule) buildParamsFromObject(o *goja.Object) searchParams {
var query searchParams
if o.GetSymbol(goja.SymIterator) != nil {
return m.buildParamsFromIterable(o)
}
for _, k := range o.Keys() {
val := o.Get(k).String()
query = append(query, searchParam{name: k, value: val})
}
return query
}
func (m *urlModule) buildParamsFromIterable(o *goja.Object) searchParams {
var query searchParams
m.r.ForOf(o, func(val goja.Value) bool {
obj := val.ToObject(m.r)
var name, value string
i := 0
// Use ForOf to determine if the object is iterable
m.r.ForOf(obj, func(val goja.Value) bool {
if i == 0 {
name = val.String()
i++
return true
}
if i == 1 {
value = val.String()
i++
return true
}
// Array isn't a tuple
panic(newInvalidTupleError(m.r))
})
// Ensure we have two values
if i <= 1 {
panic(newInvalidTupleError(m.r))
}
query = append(query, searchParam{
name: name,
value: value,
})
return true
})
return query
}
func (m *urlModule) createURLSearchParamsPrototype() *goja.Object {
p := m.r.NewObject()
p.Set("append", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
if len(call.Arguments) < 2 {
panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`))
}
u := toUrlSearchParams(m.r, call.This)
u.searchParams = append(u.searchParams, searchParam{
name: call.Argument(0).String(),
value: call.Argument(1).String(),
})
u.markUpdated()
return goja.Undefined()
}))
p.Set("delete", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
if len(call.Arguments) < 1 {
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
}
name := call.Argument(0).String()
isValid := func(v searchParam) bool {
if len(call.Arguments) == 1 {
return v.name != name
} else if v.name == name {
arg := call.Argument(1)
if !goja.IsUndefined(arg) && v.value == arg.String() {
return false
}
}
return true
}
j := 0
for i, v := range u.searchParams {
if isValid(v) {
if i != j {
u.searchParams[j] = v
}
j++
}
}
u.searchParams = u.searchParams[:j]
u.markUpdated()
return goja.Undefined()
}))
entries := m.r.ToValue(func(call goja.FunctionCall) goja.Value {
return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorEntries)
})
p.Set("entries", entries)
p.DefineDataPropertySymbol(goja.SymIterator, entries, goja.FLAG_TRUE, goja.FLAG_FALSE, goja.FLAG_TRUE)
p.Set("forEach", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
if fn, ok := goja.AssertFunction(call.Argument(0)); ok {
for _, pair := range u.searchParams {
// name, value, searchParams
_, err := fn(
nil,
m.r.ToValue(pair.name),
m.r.ToValue(pair.value),
call.This,
)
if err != nil {
panic(err)
}
}
} else {
panic(newInvalidCallbackTypeError(m.r))
}
return goja.Undefined()
}))
p.Set("get", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
if len(call.Arguments) == 0 {
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
}
if val, exists := u.getFirstValue(call.Argument(0).String()); exists {
return m.r.ToValue(val)
}
return goja.Null()
}))
p.Set("getAll", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
if len(call.Arguments) == 0 {
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
}
vals := u.getValues(call.Argument(0).String())
return m.r.ToValue(vals)
}))
p.Set("has", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
if len(call.Arguments) == 0 {
panic(newMissingArgsError(m.r, `The "name" argument must be specified`))
}
name := call.Argument(0).String()
value := call.Argument(1)
var res bool
if goja.IsUndefined(value) {
res = u.hasName(name)
} else {
res = u.hasValue(name, value.String())
}
return m.r.ToValue(res)
}))
p.Set("keys", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorKeys)
}))
p.Set("set", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
if len(call.Arguments) < 2 {
panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`))
}
name := call.Argument(0).String()
found := false
j := 0
for i, sp := range u.searchParams {
if sp.name == name {
if found {
continue // Remove all values
}
u.searchParams[i].value = call.Argument(1).String()
found = true
}
if i != j {
u.searchParams[j] = sp
}
j++
}
if !found {
u.searchParams = append(u.searchParams, searchParam{
name: name,
value: call.Argument(1).String(),
})
} else {
u.searchParams = u.searchParams[:j]
}
u.markUpdated()
return goja.Undefined()
}))
p.Set("sort", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
sort.Stable(u.searchParams)
u.markUpdated()
return goja.Undefined()
}))
p.DefineAccessorProperty("size", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
return m.r.ToValue(len(u.searchParams))
}), nil, goja.FLAG_FALSE, goja.FLAG_TRUE)
p.Set("toString", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
u := toUrlSearchParams(m.r, call.This)
str := u.searchParams.Encode()
return m.r.ToValue(str)
}))
p.Set("values", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorValues)
}))
return p
}
func (sp *urlSearchParams) markUpdated() {
if sp.url != nil && sp.url.RawQuery != "" {
sp.url.RawQuery = ""
}
}
type urlSearchParamsIteratorType int
const (
urlSearchParamsIteratorKeys urlSearchParamsIteratorType = iota
urlSearchParamsIteratorValues
urlSearchParamsIteratorEntries
)
type urlSearchParamsIterator struct {
typ urlSearchParamsIteratorType
sp *urlSearchParams
idx int
}
func toURLSearchParamsIterator(r *goja.Runtime, v goja.Value) *urlSearchParamsIterator {
if v.ExportType() == reflectTypeURLSearchParamsIterator {
if u := v.Export().(*urlSearchParamsIterator); u != nil {
return u
}
}
panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParamIterator`))
}
func getIteratorPrototype(r *goja.Runtime) (iteratorProto *goja.Object) {
ar := r.NewArray()
if fn, ok := goja.AssertFunction(ar.GetSymbol(goja.SymIterator)); ok {
iter, err := fn(ar)
if err != nil {
panic(err)
}
iteratorProto = iter.ToObject(r).Prototype()
if iteratorProto == nil {
panic(r.NewTypeError("[][Symbol.iterator().__proto__ is null"))
}
iteratorProto = iteratorProto.Prototype()
if iteratorProto == nil {
panic(r.NewTypeError("[][Symbol.iterator().__proto__.__proto__ is null"))
}
} else {
panic(r.NewTypeError("[][Symbol.iterator is not a function"))
}
return
}
func (m *urlModule) getURLSearchParamsIteratorPrototype() *goja.Object {
if m.URLSearchParamsIteratorPrototype != nil {
return m.URLSearchParamsIteratorPrototype
}
p := m.r.NewObject()
p.SetPrototype(getIteratorPrototype(m.r))
p.Set("next", m.r.ToValue(func(call goja.FunctionCall) goja.Value {
it := toURLSearchParamsIterator(m.r, call.This)
res := m.r.NewObject()
if it.idx < len(it.sp.searchParams) {
param := it.sp.searchParams[it.idx]
switch it.typ {
case urlSearchParamsIteratorKeys:
res.Set("value", param.name)
case urlSearchParamsIteratorValues:
res.Set("value", param.value)
default:
res.Set("value", m.r.NewArray(param.name, param.value))
}
res.Set("done", false)
it.idx++
} else {
res.Set("value", goja.Undefined())
res.Set("done", true)
}
return res
}))
p.DefineDataPropertySymbol(goja.SymToStringTag, m.r.ToValue("URLSearchParams Iterator"), goja.FLAG_FALSE, goja.FLAG_FALSE, goja.FLAG_TRUE)
m.URLSearchParamsIteratorPrototype = p
return p
}
func (m *urlModule) newURLSearchParamsIterator(sp *urlSearchParams, typ urlSearchParamsIteratorType) goja.Value {
it := m.r.ToValue(&urlSearchParamsIterator{
typ: typ,
sp: sp,
}).(*goja.Object)
it.SetPrototype(m.getURLSearchParamsIteratorPrototype())
return it
}

View file

@ -0,0 +1,53 @@
package url
import (
_ "embed"
"testing"
"github.com/dop251/goja"
"github.com/dop251/goja_nodejs/console"
"github.com/dop251/goja_nodejs/require"
)
func createVM() *goja.Runtime {
vm := goja.New()
new(require.Registry).Enable(vm)
console.Enable(vm)
Enable(vm)
return vm
}
func TestURLSearchParams(t *testing.T) {
vm := createVM()
if c := vm.Get("URLSearchParams"); c == nil {
t.Fatal("URLSearchParams not found")
}
script := `const params = new URLSearchParams();`
if _, err := vm.RunString(script); err != nil {
t.Fatal("Failed to process url script.", err)
}
}
//go:embed testdata/url_search_params.js
var url_search_params string
func TestURLSearchParameters(t *testing.T) {
vm := createVM()
if c := vm.Get("URLSearchParams"); c == nil {
t.Fatal("URLSearchParams not found")
}
// Script will throw an error on failed validation
_, err := vm.RunScript("testdata/url_search_params.js", url_search_params)
if err != nil {
if ex, ok := err.(*goja.Exception); ok {
t.Fatal(ex.String())
}
t.Fatal("Failed to process url script.", err)
}
}