Adding upstream version 0.0~git20250409.f7acab6.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
b9b5d88025
commit
21b930d007
51 changed files with 11229 additions and 0 deletions
134
url/escape.go
Normal file
134
url/escape.go
Normal 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
36
url/module.go
Normal 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
148
url/nodeurl.go
Normal 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
387
url/testdata/url_search_params.js
vendored
Normal 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
262
url/testdata/url_test.js
vendored
Normal 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
10
url/types/README.md
Normal 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
19
url/types/package.json
Normal 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
16
url/types/tsconfig.json
Normal 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
543
url/types/url.d.ts
vendored
Normal 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 (`&` 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&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&a=c
|
||||
*
|
||||
* // newSearchParams.toString() is implicitly called
|
||||
* myURL.search = newSearchParams;
|
||||
* console.log(myURL.href);
|
||||
* // Prints https://example.org/?a=b&a=c
|
||||
* newSearchParams.delete('a');
|
||||
* console.log(myURL.href);
|
||||
* // Prints https://example.org/?a=b&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&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&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&foo=baz&abc=def
|
||||
*
|
||||
* params.set('foo', 'def');
|
||||
* params.set('xyz', 'opq');
|
||||
* console.log(params.toString());
|
||||
* // Prints foo=def&abc=def&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&type=search&query[]=123');
|
||||
* params.sort();
|
||||
* console.log(params.toString());
|
||||
* // Prints query%5B%5D=abc&query%5B%5D=123&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
417
url/url.go
Normal 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
122
url/url_test.go
Normal 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
408
url/urlsearchparams.go
Normal 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
|
||||
}
|
53
url/urlsearchparams_test.go
Normal file
53
url/urlsearchparams_test.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue