quickjs 0.13.0.pre → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +94 -69
- data/Rakefile +7 -0
- data/ext/quickjsrb/extconf.rb +7 -0
- data/ext/quickjsrb/quickjsrb.c +177 -43
- data/ext/quickjsrb/quickjsrb.h +6 -4
- data/ext/quickjsrb/quickjsrb_crypto.c +71 -0
- data/ext/quickjsrb/quickjsrb_crypto.h +6 -0
- data/ext/quickjsrb/quickjsrb_crypto_subtle.c +1001 -0
- data/ext/quickjsrb/quickjsrb_crypto_subtle.h +6 -0
- data/ext/quickjsrb/vendor/polyfill-intl-en.min.js +3 -11
- data/ext/quickjsrb/vendor/polyfill-url.min.js +1 -0
- data/lib/quickjs/crypto_key.rb +15 -0
- data/lib/quickjs/subtle_crypto.rb +493 -0
- data/lib/quickjs/version.rb +1 -1
- data/lib/quickjs.rb +3 -0
- data/polyfills/check-licenses.mjs +72 -0
- data/polyfills/package-lock.json +183 -154
- data/polyfills/package.json +9 -8
- data/polyfills/rolldown.config.mjs +8 -0
- data/polyfills/src/url.js +1089 -0
- data/sig/quickjs.rbs +6 -1
- metadata +11 -2
|
@@ -0,0 +1,1089 @@
|
|
|
1
|
+
// WHATWG URL and URLSearchParams polyfill for QuickJS
|
|
2
|
+
// Spec: https://url.spec.whatwg.org/
|
|
3
|
+
|
|
4
|
+
const SPECIAL_SCHEMES = {
|
|
5
|
+
ftp: 21,
|
|
6
|
+
file: null,
|
|
7
|
+
http: 80,
|
|
8
|
+
https: 443,
|
|
9
|
+
ws: 80,
|
|
10
|
+
wss: 443,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function isSpecial(scheme) {
|
|
14
|
+
return Object.prototype.hasOwnProperty.call(SPECIAL_SCHEMES, scheme);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function defaultPort(scheme) {
|
|
18
|
+
return SPECIAL_SCHEMES[scheme] ?? null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Percent-encoding helpers
|
|
22
|
+
const C0_PERCENT_ENCODE = /[\x00-\x1f\x7f-\uffff]/;
|
|
23
|
+
const FRAGMENT_PERCENT_ENCODE = /[\x00-\x1f \x22\x3c\x3e\x60\x7f-\uffff]/;
|
|
24
|
+
const QUERY_PERCENT_ENCODE = /[\x00-\x1f \x22\x23\x3c\x3e\x7f-\uffff]/;
|
|
25
|
+
const SPECIAL_QUERY_PERCENT_ENCODE = /[\x00-\x1f \x22\x23\x27\x3c\x3e\x7f-\uffff]/;
|
|
26
|
+
const PATH_PERCENT_ENCODE = /[\x00-\x1f \x22\x23\x3c\x3e\x3f\x60\x7b-\x7d\x7f-\uffff]/;
|
|
27
|
+
const USERINFO_PERCENT_ENCODE = /[\x00-\x1f \x22\x23\x2f\x3a\x3b\x3d\x40\x5b-\x5e\x60\x7b-\x7d\x7f-\uffff]/;
|
|
28
|
+
|
|
29
|
+
function utf8PercentEncode(str, encodeSet) {
|
|
30
|
+
let result = "";
|
|
31
|
+
for (let i = 0; i < str.length; i++) {
|
|
32
|
+
const c = str[i];
|
|
33
|
+
const code = str.charCodeAt(i);
|
|
34
|
+
if (code >= 0xd800 && code <= 0xdbff && i + 1 < str.length) {
|
|
35
|
+
const next = str.charCodeAt(i + 1);
|
|
36
|
+
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
37
|
+
const cp = (code - 0xd800) * 0x400 + (next - 0xdc00) + 0x10000;
|
|
38
|
+
const bytes = encodeCodePoint(cp);
|
|
39
|
+
for (const b of bytes) result += "%" + b.toString(16).toUpperCase().padStart(2, "0");
|
|
40
|
+
i++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (encodeSet.test(c)) {
|
|
45
|
+
const bytes = encodeCodePoint(code);
|
|
46
|
+
for (const b of bytes) result += "%" + b.toString(16).toUpperCase().padStart(2, "0");
|
|
47
|
+
} else {
|
|
48
|
+
result += c;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function encodeCodePoint(cp) {
|
|
55
|
+
if (cp <= 0x7f) return [cp];
|
|
56
|
+
if (cp <= 0x7ff) return [0xc0 | (cp >> 6), 0x80 | (cp & 0x3f)];
|
|
57
|
+
if (cp <= 0xffff) return [0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)];
|
|
58
|
+
return [0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f)];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function percentDecode(str) {
|
|
62
|
+
return str.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isAsciiDigit(c) { return c >= "0" && c <= "9"; }
|
|
66
|
+
function isAsciiHexDigit(c) { return (c >= "0" && c <= "9") || (c >= "a" && c <= "f") || (c >= "A" && c <= "F"); }
|
|
67
|
+
function isAsciiAlpha(c) { return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z"); }
|
|
68
|
+
|
|
69
|
+
function isWindowsDriveLetter(s, normalized) {
|
|
70
|
+
if (s.length < 2) return false;
|
|
71
|
+
if (!isAsciiAlpha(s[0])) return false;
|
|
72
|
+
const second = s[1];
|
|
73
|
+
if (normalized) return second === ":";
|
|
74
|
+
return second === ":" || second === "|";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function startsWithWindowsDriveLetter(s) {
|
|
78
|
+
if (s.length < 2) return false;
|
|
79
|
+
if (!isAsciiAlpha(s[0])) return false;
|
|
80
|
+
if (s[1] !== ":" && s[1] !== "|") return false;
|
|
81
|
+
if (s.length === 2) return true;
|
|
82
|
+
return s[2] === "/" || s[2] === "\\" || s[2] === "?" || s[2] === "#";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function shortenPath(path, scheme) {
|
|
86
|
+
if (scheme === "file" && path.length === 1 && isWindowsDriveLetter(path[0], true)) return;
|
|
87
|
+
if (path.length > 0) path.pop();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// URL record
|
|
91
|
+
function createURL() {
|
|
92
|
+
return {
|
|
93
|
+
scheme: "",
|
|
94
|
+
username: "",
|
|
95
|
+
password: "",
|
|
96
|
+
host: null,
|
|
97
|
+
port: null,
|
|
98
|
+
path: [],
|
|
99
|
+
query: null,
|
|
100
|
+
fragment: null,
|
|
101
|
+
cannotBeABaseURL: false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function serializeURL(url, excludeFragment) {
|
|
106
|
+
let output = url.scheme + ":";
|
|
107
|
+
if (url.host !== null) {
|
|
108
|
+
output += "//";
|
|
109
|
+
if (url.username !== "" || url.password !== "") {
|
|
110
|
+
output += url.username;
|
|
111
|
+
if (url.password !== "") output += ":" + url.password;
|
|
112
|
+
output += "@";
|
|
113
|
+
}
|
|
114
|
+
output += serializeHost(url.host);
|
|
115
|
+
if (url.port !== null) output += ":" + url.port;
|
|
116
|
+
} else if (url.scheme === "file") {
|
|
117
|
+
output += "//";
|
|
118
|
+
}
|
|
119
|
+
if (url.cannotBeABaseURL) {
|
|
120
|
+
output += url.path[0] ?? "";
|
|
121
|
+
} else {
|
|
122
|
+
for (const segment of url.path) {
|
|
123
|
+
output += "/" + segment;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (url.query !== null) output += "?" + url.query;
|
|
127
|
+
if (!excludeFragment && url.fragment !== null) output += "#" + url.fragment;
|
|
128
|
+
return output;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function serializeHost(host) {
|
|
132
|
+
if (typeof host === "number") return serializeIPv4(host);
|
|
133
|
+
if (Array.isArray(host)) return "[" + serializeIPv6(host) + "]";
|
|
134
|
+
return host;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function serializeIPv4(addr) {
|
|
138
|
+
let n = addr;
|
|
139
|
+
const parts = [];
|
|
140
|
+
for (let i = 0; i < 4; i++) {
|
|
141
|
+
parts.unshift(n % 256);
|
|
142
|
+
n = Math.floor(n / 256);
|
|
143
|
+
}
|
|
144
|
+
return parts.join(".");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function serializeIPv6(addr) {
|
|
148
|
+
let output = "";
|
|
149
|
+
const compress = longestRunOfZeroes(addr);
|
|
150
|
+
let ignore = false;
|
|
151
|
+
for (let i = 0; i < 8; i++) {
|
|
152
|
+
if (ignore) {
|
|
153
|
+
if (addr[i] !== 0) ignore = false;
|
|
154
|
+
else continue;
|
|
155
|
+
}
|
|
156
|
+
if (i === compress) {
|
|
157
|
+
output += i === 0 ? "::" : ":";
|
|
158
|
+
ignore = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
output += addr[i].toString(16);
|
|
162
|
+
if (i !== 7) output += ":";
|
|
163
|
+
}
|
|
164
|
+
return output;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function longestRunOfZeroes(addr) {
|
|
168
|
+
let longestStart = -1, longestLen = 1;
|
|
169
|
+
let start = -1, len = 0;
|
|
170
|
+
for (let i = 0; i < 8; i++) {
|
|
171
|
+
if (addr[i] === 0) {
|
|
172
|
+
if (start === -1) { start = i; len = 0; }
|
|
173
|
+
len++;
|
|
174
|
+
if (len > longestLen) { longestLen = len; longestStart = start; }
|
|
175
|
+
} else {
|
|
176
|
+
start = -1;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return longestStart;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function serializeOrigin(url) {
|
|
183
|
+
if (isSpecial(url.scheme) && url.scheme !== "file") {
|
|
184
|
+
return url.scheme + "://" + serializeHost(url.host) + (url.port !== null ? ":" + url.port : "");
|
|
185
|
+
}
|
|
186
|
+
return "null";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Parsing helpers
|
|
190
|
+
function parseIPv4(input) {
|
|
191
|
+
const parts = input.split(".");
|
|
192
|
+
if (parts[parts.length - 1] === "") parts.pop();
|
|
193
|
+
if (parts.length > 4) return null;
|
|
194
|
+
const numbers = [];
|
|
195
|
+
for (const p of parts) {
|
|
196
|
+
if (p === "") return null;
|
|
197
|
+
let n;
|
|
198
|
+
if (/^0[xX]/.test(p)) {
|
|
199
|
+
n = parseInt(p, 16);
|
|
200
|
+
} else if (p.startsWith("0") && p.length > 1) {
|
|
201
|
+
n = parseInt(p, 8);
|
|
202
|
+
} else {
|
|
203
|
+
n = parseInt(p, 10);
|
|
204
|
+
}
|
|
205
|
+
if (isNaN(n) || n < 0) return null;
|
|
206
|
+
numbers.push(n);
|
|
207
|
+
}
|
|
208
|
+
if (numbers.some((n, i) => i < numbers.length - 1 && n > 255)) return null;
|
|
209
|
+
const last = numbers[numbers.length - 1];
|
|
210
|
+
if (last >= 256 ** (5 - numbers.length)) return null;
|
|
211
|
+
let ipv4 = last;
|
|
212
|
+
for (let i = 0; i < numbers.length - 1; i++) {
|
|
213
|
+
if (numbers[i] > 255) return null;
|
|
214
|
+
ipv4 += numbers[i] * 256 ** (3 - i);
|
|
215
|
+
}
|
|
216
|
+
return ipv4;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseIPv6(input) {
|
|
220
|
+
const addr = [0, 0, 0, 0, 0, 0, 0, 0];
|
|
221
|
+
let pieceIndex = 0;
|
|
222
|
+
let compress = null;
|
|
223
|
+
let pointer = 0;
|
|
224
|
+
|
|
225
|
+
if (input[pointer] === ":" && input[pointer + 1] === ":") {
|
|
226
|
+
pointer += 2;
|
|
227
|
+
pieceIndex++;
|
|
228
|
+
compress = pieceIndex;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
while (pointer < input.length) {
|
|
232
|
+
if (pieceIndex === 8) return null;
|
|
233
|
+
if (input[pointer] === ":") {
|
|
234
|
+
if (compress !== null) return null;
|
|
235
|
+
pointer++;
|
|
236
|
+
pieceIndex++;
|
|
237
|
+
compress = pieceIndex;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
let value = 0;
|
|
241
|
+
let length = 0;
|
|
242
|
+
while (length < 4 && pointer < input.length && isAsciiHexDigit(input[pointer])) {
|
|
243
|
+
value = value * 16 + parseInt(input[pointer], 16);
|
|
244
|
+
pointer++;
|
|
245
|
+
length++;
|
|
246
|
+
}
|
|
247
|
+
if (pointer < input.length && input[pointer] === ".") {
|
|
248
|
+
if (length === 0) return null;
|
|
249
|
+
pointer -= length;
|
|
250
|
+
if (pieceIndex > 6) return null;
|
|
251
|
+
let numbersSeen = 0;
|
|
252
|
+
while (pointer < input.length) {
|
|
253
|
+
let ipv4Piece = null;
|
|
254
|
+
if (numbersSeen > 0) {
|
|
255
|
+
if (input[pointer] === "." && numbersSeen < 4) pointer++;
|
|
256
|
+
else return null;
|
|
257
|
+
}
|
|
258
|
+
if (!isAsciiDigit(input[pointer])) return null;
|
|
259
|
+
while (pointer < input.length && isAsciiDigit(input[pointer])) {
|
|
260
|
+
const n = parseInt(input[pointer], 10);
|
|
261
|
+
ipv4Piece = ipv4Piece === null ? n : ipv4Piece * 10 + n;
|
|
262
|
+
if (ipv4Piece > 255) return null;
|
|
263
|
+
pointer++;
|
|
264
|
+
}
|
|
265
|
+
addr[pieceIndex] = addr[pieceIndex] * 256 + ipv4Piece;
|
|
266
|
+
numbersSeen++;
|
|
267
|
+
if (numbersSeen === 2 || numbersSeen === 4) pieceIndex++;
|
|
268
|
+
}
|
|
269
|
+
if (numbersSeen !== 4) return null;
|
|
270
|
+
break;
|
|
271
|
+
} else if (pointer < input.length && input[pointer] === ":") {
|
|
272
|
+
pointer++;
|
|
273
|
+
if (pointer >= input.length) return null;
|
|
274
|
+
} else if (pointer < input.length) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
addr[pieceIndex] = value;
|
|
278
|
+
pieceIndex++;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (compress !== null) {
|
|
282
|
+
let swaps = pieceIndex - compress;
|
|
283
|
+
pieceIndex = 7;
|
|
284
|
+
while (pieceIndex !== 0 && swaps > 0) {
|
|
285
|
+
[addr[pieceIndex], addr[compress + swaps - 1]] = [addr[compress + swaps - 1], addr[pieceIndex]];
|
|
286
|
+
pieceIndex--;
|
|
287
|
+
swaps--;
|
|
288
|
+
}
|
|
289
|
+
} else if (compress === null && pieceIndex !== 8) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
return addr;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function parseOpaqueHost(input) {
|
|
296
|
+
for (const c of input) {
|
|
297
|
+
if (" #%/:?@[\\]".includes(c)) return null;
|
|
298
|
+
if (c.charCodeAt(0) > 0x7e) return null;
|
|
299
|
+
}
|
|
300
|
+
return utf8PercentEncode(input, C0_PERCENT_ENCODE);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseHost(input, isNotSpecial) {
|
|
304
|
+
if (input.startsWith("[")) {
|
|
305
|
+
if (!input.endsWith("]")) return null;
|
|
306
|
+
return parseIPv6(input.slice(1, -1));
|
|
307
|
+
}
|
|
308
|
+
if (isNotSpecial) return parseOpaqueHost(input);
|
|
309
|
+
|
|
310
|
+
const domain = percentDecode(input).toLowerCase();
|
|
311
|
+
|
|
312
|
+
if (!domain) return null;
|
|
313
|
+
|
|
314
|
+
// Simple IPv4 check
|
|
315
|
+
const ipv4 = parseIPv4(domain);
|
|
316
|
+
if (ipv4 !== null) return ipv4;
|
|
317
|
+
|
|
318
|
+
// Validate domain - basic checks
|
|
319
|
+
if (domain.includes("..")) return null;
|
|
320
|
+
|
|
321
|
+
return domain;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Basic URL parser
|
|
325
|
+
function basicURLParse(input, base, url, stateOverride) {
|
|
326
|
+
if (!url) url = createURL();
|
|
327
|
+
|
|
328
|
+
input = input.trim().replace(/[\t\n\r]/g, "");
|
|
329
|
+
|
|
330
|
+
let state = stateOverride || "scheme start";
|
|
331
|
+
let buffer = "";
|
|
332
|
+
let atFlag = false;
|
|
333
|
+
let bracketFlag = false;
|
|
334
|
+
let passwordTokenSeen = false;
|
|
335
|
+
let pointer = 0;
|
|
336
|
+
|
|
337
|
+
while (pointer <= input.length) {
|
|
338
|
+
const c = input[pointer];
|
|
339
|
+
|
|
340
|
+
switch (state) {
|
|
341
|
+
case "scheme start":
|
|
342
|
+
if (c !== undefined && isAsciiAlpha(c)) {
|
|
343
|
+
buffer += c.toLowerCase();
|
|
344
|
+
state = "scheme";
|
|
345
|
+
} else if (!stateOverride) {
|
|
346
|
+
state = "no scheme";
|
|
347
|
+
continue;
|
|
348
|
+
} else {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
|
+
|
|
353
|
+
case "scheme":
|
|
354
|
+
if (c !== undefined && (isAsciiAlpha(c) || isAsciiDigit(c) || "+-." .includes(c))) {
|
|
355
|
+
buffer += c.toLowerCase();
|
|
356
|
+
} else if (c === ":") {
|
|
357
|
+
if (stateOverride) {
|
|
358
|
+
const wasSpecial = isSpecial(url.scheme);
|
|
359
|
+
const willBeSpecial = isSpecial(buffer);
|
|
360
|
+
if (wasSpecial !== willBeSpecial) return url;
|
|
361
|
+
if ((buffer === "http" || buffer === "https") && url.scheme === "file") return url;
|
|
362
|
+
if (url.scheme === "file" && (url.host === "" || url.host === null)) return url;
|
|
363
|
+
}
|
|
364
|
+
url.scheme = buffer;
|
|
365
|
+
if (stateOverride) {
|
|
366
|
+
if (url.port === defaultPort(url.scheme)) url.port = null;
|
|
367
|
+
return url;
|
|
368
|
+
}
|
|
369
|
+
buffer = "";
|
|
370
|
+
if (url.scheme === "file") {
|
|
371
|
+
state = "file";
|
|
372
|
+
} else if (isSpecial(url.scheme) && base && base.scheme === url.scheme) {
|
|
373
|
+
state = "special relative or authority";
|
|
374
|
+
} else if (isSpecial(url.scheme)) {
|
|
375
|
+
state = "special authority slashes";
|
|
376
|
+
} else if (input[pointer + 1] === "/") {
|
|
377
|
+
state = "path or authority";
|
|
378
|
+
pointer++;
|
|
379
|
+
} else {
|
|
380
|
+
url.cannotBeABaseURL = true;
|
|
381
|
+
url.path.push("");
|
|
382
|
+
state = "cannot-be-a-base-URL path";
|
|
383
|
+
}
|
|
384
|
+
} else if (!stateOverride) {
|
|
385
|
+
buffer = "";
|
|
386
|
+
state = "no scheme";
|
|
387
|
+
pointer = -1;
|
|
388
|
+
} else {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
break;
|
|
392
|
+
|
|
393
|
+
case "no scheme":
|
|
394
|
+
if (!base || (base.cannotBeABaseURL && c !== "#")) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
if (base.cannotBeABaseURL && c === "#") {
|
|
398
|
+
url.scheme = base.scheme;
|
|
399
|
+
url.path = base.path.slice();
|
|
400
|
+
url.query = base.query;
|
|
401
|
+
url.fragment = "";
|
|
402
|
+
url.cannotBeABaseURL = true;
|
|
403
|
+
state = "fragment";
|
|
404
|
+
break;
|
|
405
|
+
}
|
|
406
|
+
if (base.scheme !== "file") {
|
|
407
|
+
state = "relative";
|
|
408
|
+
} else {
|
|
409
|
+
state = "file";
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
|
|
413
|
+
case "special relative or authority":
|
|
414
|
+
if (c === "/" && input[pointer + 1] === "/") {
|
|
415
|
+
state = "special authority ignore slashes";
|
|
416
|
+
pointer++;
|
|
417
|
+
} else {
|
|
418
|
+
state = "relative";
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
break;
|
|
422
|
+
|
|
423
|
+
case "path or authority":
|
|
424
|
+
if (c === "/") {
|
|
425
|
+
state = "authority";
|
|
426
|
+
} else {
|
|
427
|
+
state = "path";
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
|
|
432
|
+
case "relative":
|
|
433
|
+
url.scheme = base.scheme;
|
|
434
|
+
if (c === undefined) {
|
|
435
|
+
url.username = base.username;
|
|
436
|
+
url.password = base.password;
|
|
437
|
+
url.host = base.host;
|
|
438
|
+
url.port = base.port;
|
|
439
|
+
url.path = base.path.slice();
|
|
440
|
+
url.query = base.query;
|
|
441
|
+
} else if (c === "/") {
|
|
442
|
+
state = "relative slash";
|
|
443
|
+
} else if (c === "?") {
|
|
444
|
+
url.username = base.username;
|
|
445
|
+
url.password = base.password;
|
|
446
|
+
url.host = base.host;
|
|
447
|
+
url.port = base.port;
|
|
448
|
+
url.path = base.path.slice();
|
|
449
|
+
url.query = "";
|
|
450
|
+
state = "query";
|
|
451
|
+
} else if (c === "#") {
|
|
452
|
+
url.username = base.username;
|
|
453
|
+
url.password = base.password;
|
|
454
|
+
url.host = base.host;
|
|
455
|
+
url.port = base.port;
|
|
456
|
+
url.path = base.path.slice();
|
|
457
|
+
url.query = base.query;
|
|
458
|
+
url.fragment = "";
|
|
459
|
+
state = "fragment";
|
|
460
|
+
} else {
|
|
461
|
+
if (isSpecial(url.scheme) && c === "\\") {
|
|
462
|
+
state = "relative slash";
|
|
463
|
+
} else {
|
|
464
|
+
url.username = base.username;
|
|
465
|
+
url.password = base.password;
|
|
466
|
+
url.host = base.host;
|
|
467
|
+
url.port = base.port;
|
|
468
|
+
url.path = base.path.slice();
|
|
469
|
+
if (url.path.length > 0) url.path.pop();
|
|
470
|
+
state = "path";
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
|
|
476
|
+
case "relative slash":
|
|
477
|
+
if (isSpecial(url.scheme) && (c === "/" || c === "\\")) {
|
|
478
|
+
state = "special authority ignore slashes";
|
|
479
|
+
} else if (c === "/") {
|
|
480
|
+
state = "authority";
|
|
481
|
+
} else {
|
|
482
|
+
url.username = base.username;
|
|
483
|
+
url.password = base.password;
|
|
484
|
+
url.host = base.host;
|
|
485
|
+
url.port = base.port;
|
|
486
|
+
state = "path";
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
|
|
491
|
+
case "special authority slashes":
|
|
492
|
+
if (c === "/" && input[pointer + 1] === "/") {
|
|
493
|
+
state = "special authority ignore slashes";
|
|
494
|
+
pointer++;
|
|
495
|
+
} else {
|
|
496
|
+
state = "special authority ignore slashes";
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
break;
|
|
500
|
+
|
|
501
|
+
case "special authority ignore slashes":
|
|
502
|
+
if (c !== "/" && c !== "\\") {
|
|
503
|
+
state = "authority";
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
|
|
508
|
+
case "authority":
|
|
509
|
+
if (c === "@") {
|
|
510
|
+
if (atFlag) buffer = "%40" + buffer;
|
|
511
|
+
atFlag = true;
|
|
512
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
513
|
+
const bc = buffer[i];
|
|
514
|
+
if (bc === ":" && !passwordTokenSeen) {
|
|
515
|
+
passwordTokenSeen = true;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const encodedCodePoints = utf8PercentEncode(bc, USERINFO_PERCENT_ENCODE);
|
|
519
|
+
if (passwordTokenSeen) url.password += encodedCodePoints;
|
|
520
|
+
else url.username += encodedCodePoints;
|
|
521
|
+
}
|
|
522
|
+
buffer = "";
|
|
523
|
+
} else if (c === undefined || c === "/" || c === "?" || c === "#" || (isSpecial(url.scheme) && c === "\\")) {
|
|
524
|
+
if (atFlag && buffer === "") return null;
|
|
525
|
+
pointer -= buffer.length + 1;
|
|
526
|
+
buffer = "";
|
|
527
|
+
state = "host";
|
|
528
|
+
} else {
|
|
529
|
+
buffer += c;
|
|
530
|
+
}
|
|
531
|
+
break;
|
|
532
|
+
|
|
533
|
+
case "host":
|
|
534
|
+
case "hostname":
|
|
535
|
+
if (stateOverride && url.scheme === "file") {
|
|
536
|
+
state = "file host";
|
|
537
|
+
continue;
|
|
538
|
+
} else if (c === ":" && !bracketFlag) {
|
|
539
|
+
if (buffer === "") return null;
|
|
540
|
+
const host = parseHost(buffer, !isSpecial(url.scheme));
|
|
541
|
+
if (host === null) return null;
|
|
542
|
+
url.host = host;
|
|
543
|
+
buffer = "";
|
|
544
|
+
state = "port";
|
|
545
|
+
if (stateOverride === "hostname") return url;
|
|
546
|
+
} else if (c === undefined || c === "/" || c === "?" || c === "#" || (isSpecial(url.scheme) && c === "\\")) {
|
|
547
|
+
if (isSpecial(url.scheme) && buffer === "") return null;
|
|
548
|
+
if (stateOverride && buffer === "" && (url.username !== "" || url.password !== "" || url.port !== null)) return url;
|
|
549
|
+
const host = parseHost(buffer, !isSpecial(url.scheme));
|
|
550
|
+
if (host === null) return null;
|
|
551
|
+
url.host = host;
|
|
552
|
+
buffer = "";
|
|
553
|
+
state = "path start";
|
|
554
|
+
if (stateOverride) return url;
|
|
555
|
+
continue;
|
|
556
|
+
} else {
|
|
557
|
+
if (c === "[") bracketFlag = true;
|
|
558
|
+
if (c === "]") bracketFlag = false;
|
|
559
|
+
buffer += c;
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
|
|
563
|
+
case "port":
|
|
564
|
+
if (isAsciiDigit(c)) {
|
|
565
|
+
buffer += c;
|
|
566
|
+
} else if (c === undefined || c === "/" || c === "?" || c === "#" || (isSpecial(url.scheme) && c === "\\") || stateOverride) {
|
|
567
|
+
if (buffer !== "") {
|
|
568
|
+
const port = parseInt(buffer, 10);
|
|
569
|
+
if (port > 65535) return null;
|
|
570
|
+
url.port = port === defaultPort(url.scheme) ? null : port;
|
|
571
|
+
buffer = "";
|
|
572
|
+
}
|
|
573
|
+
if (stateOverride) return url;
|
|
574
|
+
state = "path start";
|
|
575
|
+
continue;
|
|
576
|
+
} else {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
break;
|
|
580
|
+
|
|
581
|
+
case "file":
|
|
582
|
+
url.scheme = "file";
|
|
583
|
+
url.host = "";
|
|
584
|
+
if (c === "/" || c === "\\") {
|
|
585
|
+
state = "file slash";
|
|
586
|
+
} else if (base && base.scheme === "file") {
|
|
587
|
+
url.host = base.host;
|
|
588
|
+
url.path = base.path.slice();
|
|
589
|
+
url.query = base.query;
|
|
590
|
+
if (c === "?") {
|
|
591
|
+
url.query = "";
|
|
592
|
+
state = "query";
|
|
593
|
+
} else if (c === "#") {
|
|
594
|
+
url.fragment = "";
|
|
595
|
+
state = "fragment";
|
|
596
|
+
} else if (c !== undefined) {
|
|
597
|
+
url.query = null;
|
|
598
|
+
if (!startsWithWindowsDriveLetter(input.slice(pointer))) {
|
|
599
|
+
shortenPath(url.path, url.scheme);
|
|
600
|
+
} else {
|
|
601
|
+
url.path = [];
|
|
602
|
+
}
|
|
603
|
+
state = "path";
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
state = "path";
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
break;
|
|
611
|
+
|
|
612
|
+
case "file slash":
|
|
613
|
+
if (c === "/" || c === "\\") {
|
|
614
|
+
state = "file host";
|
|
615
|
+
} else {
|
|
616
|
+
if (base && base.scheme === "file") {
|
|
617
|
+
url.host = base.host;
|
|
618
|
+
if (!startsWithWindowsDriveLetter(input.slice(pointer)) && base.path.length > 0 && isWindowsDriveLetter(base.path[0], true)) {
|
|
619
|
+
url.path.push(base.path[0]);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
state = "path";
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
break;
|
|
626
|
+
|
|
627
|
+
case "file host":
|
|
628
|
+
if (c === undefined || c === "/" || c === "\\" || c === "?" || c === "#") {
|
|
629
|
+
if (!stateOverride && isWindowsDriveLetter(buffer, false)) {
|
|
630
|
+
state = "path";
|
|
631
|
+
} else if (buffer === "") {
|
|
632
|
+
url.host = "";
|
|
633
|
+
if (stateOverride) return url;
|
|
634
|
+
state = "path start";
|
|
635
|
+
} else {
|
|
636
|
+
const host = parseHost(buffer, false);
|
|
637
|
+
if (host === null) return null;
|
|
638
|
+
url.host = host === "localhost" ? "" : host;
|
|
639
|
+
if (stateOverride) return url;
|
|
640
|
+
buffer = "";
|
|
641
|
+
state = "path start";
|
|
642
|
+
}
|
|
643
|
+
continue;
|
|
644
|
+
} else {
|
|
645
|
+
buffer += c;
|
|
646
|
+
}
|
|
647
|
+
break;
|
|
648
|
+
|
|
649
|
+
case "path start":
|
|
650
|
+
if (isSpecial(url.scheme)) {
|
|
651
|
+
state = "path";
|
|
652
|
+
if (c !== "/" && c !== "\\") continue;
|
|
653
|
+
} else if (!stateOverride && c === "?") {
|
|
654
|
+
url.query = "";
|
|
655
|
+
state = "query";
|
|
656
|
+
} else if (!stateOverride && c === "#") {
|
|
657
|
+
url.fragment = "";
|
|
658
|
+
state = "fragment";
|
|
659
|
+
} else if (c !== undefined) {
|
|
660
|
+
state = "path";
|
|
661
|
+
if (c !== "/") continue;
|
|
662
|
+
} else if (stateOverride && url.host === null) {
|
|
663
|
+
url.path.push("");
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
|
|
667
|
+
case "path":
|
|
668
|
+
if (c === undefined || c === "/" || (isSpecial(url.scheme) && c === "\\") || (!stateOverride && (c === "?" || c === "#"))) {
|
|
669
|
+
const decoded = buffer.toLowerCase();
|
|
670
|
+
if (decoded === "%2e" || decoded === ".") {
|
|
671
|
+
buffer = ".";
|
|
672
|
+
} else if (decoded === "%2e%2e" || decoded === ".%2e" || decoded === "%2e." || decoded === "..") {
|
|
673
|
+
buffer = "..";
|
|
674
|
+
}
|
|
675
|
+
if (buffer === "..") {
|
|
676
|
+
shortenPath(url.path, url.scheme);
|
|
677
|
+
if (c !== "/" && !(isSpecial(url.scheme) && c === "\\")) {
|
|
678
|
+
url.path.push("");
|
|
679
|
+
}
|
|
680
|
+
} else if (buffer === ".") {
|
|
681
|
+
if (c !== "/" && !(isSpecial(url.scheme) && c === "\\")) {
|
|
682
|
+
url.path.push("");
|
|
683
|
+
}
|
|
684
|
+
} else {
|
|
685
|
+
if (url.scheme === "file" && url.path.length === 0 && isWindowsDriveLetter(buffer, false)) {
|
|
686
|
+
buffer = buffer[0] + ":";
|
|
687
|
+
}
|
|
688
|
+
url.path.push(buffer);
|
|
689
|
+
}
|
|
690
|
+
buffer = "";
|
|
691
|
+
if (c === "?") {
|
|
692
|
+
url.query = "";
|
|
693
|
+
state = "query";
|
|
694
|
+
} else if (c === "#") {
|
|
695
|
+
url.fragment = "";
|
|
696
|
+
state = "fragment";
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
buffer += utf8PercentEncode(c, PATH_PERCENT_ENCODE);
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
|
|
703
|
+
case "cannot-be-a-base-URL path":
|
|
704
|
+
if (c === "?") {
|
|
705
|
+
url.query = "";
|
|
706
|
+
state = "query";
|
|
707
|
+
} else if (c === "#") {
|
|
708
|
+
url.fragment = "";
|
|
709
|
+
state = "fragment";
|
|
710
|
+
} else if (c !== undefined) {
|
|
711
|
+
url.path[0] += utf8PercentEncode(c, C0_PERCENT_ENCODE);
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
|
|
715
|
+
case "query":
|
|
716
|
+
if (c === undefined || (!stateOverride && c === "#")) {
|
|
717
|
+
const encodeSet = isSpecial(url.scheme) ? SPECIAL_QUERY_PERCENT_ENCODE : QUERY_PERCENT_ENCODE;
|
|
718
|
+
url.query += utf8PercentEncode(buffer, encodeSet);
|
|
719
|
+
buffer = "";
|
|
720
|
+
if (c === "#") {
|
|
721
|
+
url.fragment = "";
|
|
722
|
+
state = "fragment";
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
buffer += c;
|
|
726
|
+
}
|
|
727
|
+
break;
|
|
728
|
+
|
|
729
|
+
case "fragment":
|
|
730
|
+
if (c !== undefined) {
|
|
731
|
+
url.fragment += utf8PercentEncode(c, FRAGMENT_PERCENT_ENCODE);
|
|
732
|
+
}
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
pointer++;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return url;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// URLSearchParams
|
|
743
|
+
class URLSearchParams {
|
|
744
|
+
#list;
|
|
745
|
+
#url;
|
|
746
|
+
|
|
747
|
+
constructor(init = "") {
|
|
748
|
+
this.#list = [];
|
|
749
|
+
this.#url = null;
|
|
750
|
+
|
|
751
|
+
if (typeof init === "string") {
|
|
752
|
+
if (init.startsWith("?")) init = init.slice(1);
|
|
753
|
+
this.#list = parseQueryString(init);
|
|
754
|
+
} else if (Array.isArray(init)) {
|
|
755
|
+
for (const pair of init) {
|
|
756
|
+
if (!Array.isArray(pair) || pair.length !== 2) {
|
|
757
|
+
throw new TypeError("Each pair must be an iterable [name, value] tuple");
|
|
758
|
+
}
|
|
759
|
+
this.#list.push([String(pair[0]), String(pair[1])]);
|
|
760
|
+
}
|
|
761
|
+
} else if (init !== null && typeof init === "object") {
|
|
762
|
+
for (const key of Object.keys(init)) {
|
|
763
|
+
this.#list.push([key, String(init[key])]);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
_setURL(url) {
|
|
769
|
+
this.#url = url;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
_update() {
|
|
773
|
+
if (this.#url) {
|
|
774
|
+
const serialized = this.toString();
|
|
775
|
+
this.#url._urlRecord.query = serialized === "" ? null : serialized;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
append(name, value) {
|
|
780
|
+
this.#list.push([String(name), String(value)]);
|
|
781
|
+
this._update();
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
delete(name, value) {
|
|
785
|
+
name = String(name);
|
|
786
|
+
if (value !== undefined) {
|
|
787
|
+
value = String(value);
|
|
788
|
+
this.#list = this.#list.filter(([n, v]) => !(n === name && v === value));
|
|
789
|
+
} else {
|
|
790
|
+
this.#list = this.#list.filter(([n]) => n !== name);
|
|
791
|
+
}
|
|
792
|
+
this._update();
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
get(name) {
|
|
796
|
+
name = String(name);
|
|
797
|
+
const entry = this.#list.find(([n]) => n === name);
|
|
798
|
+
return entry ? entry[1] : null;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
getAll(name) {
|
|
802
|
+
name = String(name);
|
|
803
|
+
return this.#list.filter(([n]) => n === name).map(([, v]) => v);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
has(name, value) {
|
|
807
|
+
name = String(name);
|
|
808
|
+
if (value !== undefined) {
|
|
809
|
+
value = String(value);
|
|
810
|
+
return this.#list.some(([n, v]) => n === name && v === value);
|
|
811
|
+
}
|
|
812
|
+
return this.#list.some(([n]) => n === name);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
set(name, value) {
|
|
816
|
+
name = String(name);
|
|
817
|
+
value = String(value);
|
|
818
|
+
let found = false;
|
|
819
|
+
this.#list = this.#list.filter(([n]) => {
|
|
820
|
+
if (n === name) {
|
|
821
|
+
if (!found) { found = true; return true; }
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
return true;
|
|
825
|
+
});
|
|
826
|
+
if (!found) this.#list.push([name, value]);
|
|
827
|
+
else {
|
|
828
|
+
const idx = this.#list.findIndex(([n]) => n === name);
|
|
829
|
+
this.#list[idx][1] = value;
|
|
830
|
+
}
|
|
831
|
+
this._update();
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
sort() {
|
|
835
|
+
this.#list.sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
836
|
+
this._update();
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
keys() {
|
|
840
|
+
return this.#list.map(([k]) => k)[Symbol.iterator]();
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
values() {
|
|
844
|
+
return this.#list.map(([, v]) => v)[Symbol.iterator]();
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
entries() {
|
|
848
|
+
return this.#list.slice()[Symbol.iterator]();
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
forEach(callback, thisArg) {
|
|
852
|
+
for (const [name, value] of this.#list) {
|
|
853
|
+
callback.call(thisArg, value, name, this);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
[Symbol.iterator]() {
|
|
858
|
+
return this.entries();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
get size() {
|
|
862
|
+
return this.#list.length;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
toString() {
|
|
866
|
+
return this.#list.map(([k, v]) => encodeFormComponent(k) + "=" + encodeFormComponent(v)).join("&");
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
get [Symbol.toStringTag]() {
|
|
870
|
+
return "URLSearchParams";
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function parseQueryString(query) {
|
|
875
|
+
if (query === "") return [];
|
|
876
|
+
return query.split("&").map(pair => {
|
|
877
|
+
const eqIdx = pair.indexOf("=");
|
|
878
|
+
let name, value;
|
|
879
|
+
if (eqIdx === -1) {
|
|
880
|
+
name = pair;
|
|
881
|
+
value = "";
|
|
882
|
+
} else {
|
|
883
|
+
name = pair.slice(0, eqIdx);
|
|
884
|
+
value = pair.slice(eqIdx + 1);
|
|
885
|
+
}
|
|
886
|
+
return [decodeFormComponent(name), decodeFormComponent(value)];
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function encodeFormComponent(str) {
|
|
891
|
+
return str.replace(/[^*\-._A-Za-z0-9]/g, c => {
|
|
892
|
+
if (c === " ") return "+";
|
|
893
|
+
const bytes = encodeCodePoint(c.charCodeAt(0));
|
|
894
|
+
return bytes.map(b => "%" + b.toString(16).toUpperCase().padStart(2, "0")).join("");
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function decodeFormComponent(str) {
|
|
899
|
+
return decodeURIComponent(str.replace(/\+/g, " "));
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// URL class
|
|
903
|
+
class URL {
|
|
904
|
+
#urlRecord;
|
|
905
|
+
#searchParams;
|
|
906
|
+
|
|
907
|
+
constructor(url, base) {
|
|
908
|
+
let parsedBase = null;
|
|
909
|
+
if (base !== undefined) {
|
|
910
|
+
parsedBase = basicURLParse(String(base));
|
|
911
|
+
if (parsedBase === null) {
|
|
912
|
+
throw new TypeError("Failed to construct 'URL': Invalid base URL");
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const parsed = basicURLParse(String(url), parsedBase);
|
|
916
|
+
if (parsed === null) {
|
|
917
|
+
throw new TypeError("Failed to construct 'URL': Invalid URL");
|
|
918
|
+
}
|
|
919
|
+
this.#urlRecord = parsed;
|
|
920
|
+
this.#searchParams = new URLSearchParams(parsed.query ?? "");
|
|
921
|
+
this.#searchParams._setURL(this);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
get _urlRecord() { return this.#urlRecord; }
|
|
925
|
+
|
|
926
|
+
get href() {
|
|
927
|
+
return serializeURL(this.#urlRecord, false);
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
set href(value) {
|
|
931
|
+
const parsed = basicURLParse(String(value));
|
|
932
|
+
if (parsed === null) throw new TypeError("Invalid URL");
|
|
933
|
+
this.#urlRecord = parsed;
|
|
934
|
+
this.#searchParams = new URLSearchParams(parsed.query ?? "");
|
|
935
|
+
this.#searchParams._setURL(this);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
get origin() {
|
|
939
|
+
return serializeOrigin(this.#urlRecord);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
get protocol() {
|
|
943
|
+
return this.#urlRecord.scheme + ":";
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
set protocol(value) {
|
|
947
|
+
basicURLParse(String(value) + ":", null, this.#urlRecord, "scheme start");
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
get username() {
|
|
951
|
+
return this.#urlRecord.username;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
set username(value) {
|
|
955
|
+
if (this.#urlRecord.host === null || this.#urlRecord.host === "" || this.#urlRecord.cannotBeABaseURL || this.#urlRecord.scheme === "file") return;
|
|
956
|
+
this.#urlRecord.username = utf8PercentEncode(String(value), USERINFO_PERCENT_ENCODE);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
get password() {
|
|
960
|
+
return this.#urlRecord.password;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
set password(value) {
|
|
964
|
+
if (this.#urlRecord.host === null || this.#urlRecord.host === "" || this.#urlRecord.cannotBeABaseURL || this.#urlRecord.scheme === "file") return;
|
|
965
|
+
this.#urlRecord.password = utf8PercentEncode(String(value), USERINFO_PERCENT_ENCODE);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
get host() {
|
|
969
|
+
const url = this.#urlRecord;
|
|
970
|
+
if (url.host === null) return "";
|
|
971
|
+
if (url.port === null) return serializeHost(url.host);
|
|
972
|
+
return serializeHost(url.host) + ":" + url.port;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
set host(value) {
|
|
976
|
+
if (this.#urlRecord.cannotBeABaseURL) return;
|
|
977
|
+
basicURLParse(String(value), null, this.#urlRecord, "host");
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
get hostname() {
|
|
981
|
+
if (this.#urlRecord.host === null) return "";
|
|
982
|
+
return serializeHost(this.#urlRecord.host);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
set hostname(value) {
|
|
986
|
+
if (this.#urlRecord.cannotBeABaseURL) return;
|
|
987
|
+
basicURLParse(String(value), null, this.#urlRecord, "hostname");
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
get port() {
|
|
991
|
+
if (this.#urlRecord.port === null) return "";
|
|
992
|
+
return String(this.#urlRecord.port);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
set port(value) {
|
|
996
|
+
if (this.#urlRecord.host === null || this.#urlRecord.host === "" || this.#urlRecord.cannotBeABaseURL || this.#urlRecord.scheme === "file") return;
|
|
997
|
+
value = String(value);
|
|
998
|
+
if (value === "") {
|
|
999
|
+
this.#urlRecord.port = null;
|
|
1000
|
+
} else {
|
|
1001
|
+
basicURLParse(value, null, this.#urlRecord, "port");
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
get pathname() {
|
|
1006
|
+
const url = this.#urlRecord;
|
|
1007
|
+
if (url.cannotBeABaseURL) return url.path[0] ?? "";
|
|
1008
|
+
if (url.path.length === 0) return "";
|
|
1009
|
+
return "/" + url.path.join("/");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
set pathname(value) {
|
|
1013
|
+
if (this.#urlRecord.cannotBeABaseURL) return;
|
|
1014
|
+
this.#urlRecord.path = [];
|
|
1015
|
+
basicURLParse(String(value), null, this.#urlRecord, "path start");
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
get search() {
|
|
1019
|
+
if (this.#urlRecord.query === null || this.#urlRecord.query === "") return "";
|
|
1020
|
+
return "?" + this.#urlRecord.query;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
set search(value) {
|
|
1024
|
+
value = String(value);
|
|
1025
|
+
if (value === "") {
|
|
1026
|
+
this.#urlRecord.query = null;
|
|
1027
|
+
this.#searchParams = new URLSearchParams("");
|
|
1028
|
+
this.#searchParams._setURL(this);
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
if (value.startsWith("?")) value = value.slice(1);
|
|
1032
|
+
this.#urlRecord.query = "";
|
|
1033
|
+
basicURLParse(value, null, this.#urlRecord, "query");
|
|
1034
|
+
this.#searchParams = new URLSearchParams(this.#urlRecord.query ?? "");
|
|
1035
|
+
this.#searchParams._setURL(this);
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
get searchParams() {
|
|
1039
|
+
return this.#searchParams;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
get hash() {
|
|
1043
|
+
if (this.#urlRecord.fragment === null || this.#urlRecord.fragment === "") return "";
|
|
1044
|
+
return "#" + this.#urlRecord.fragment;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
set hash(value) {
|
|
1048
|
+
value = String(value);
|
|
1049
|
+
if (value === "") {
|
|
1050
|
+
this.#urlRecord.fragment = null;
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (value.startsWith("#")) value = value.slice(1);
|
|
1054
|
+
this.#urlRecord.fragment = "";
|
|
1055
|
+
basicURLParse(value, null, this.#urlRecord, "fragment");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
toString() {
|
|
1059
|
+
return this.href;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
toJSON() {
|
|
1063
|
+
return this.href;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
get [Symbol.toStringTag]() {
|
|
1067
|
+
return "URL";
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
static canParse(url, base) {
|
|
1071
|
+
try {
|
|
1072
|
+
new URL(url, base);
|
|
1073
|
+
return true;
|
|
1074
|
+
} catch {
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
static parse(url, base) {
|
|
1080
|
+
try {
|
|
1081
|
+
return new URL(url, base);
|
|
1082
|
+
} catch {
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
globalThis.URL = URL;
|
|
1089
|
+
globalThis.URLSearchParams = URLSearchParams;
|