-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathURL.mjs
More file actions
272 lines (272 loc) · 9.06 KB
/
Copy pathURL.mjs
File metadata and controls
272 lines (272 loc) · 9.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
import { URLSearchParams } from "./URLSearchParams.mjs";
/**
* 为未提供 Web URL API 的 JavaScriptCore 环境提供 URL polyfill。
*
* A URL polyfill for JavaScriptCore environments that do not provide the Web
* URL API.
*/
export class URL {
/**
* 使用绝对 URL,或相对 URL 与基础 URL 创建 URL 实例。
*
* Creates a URL from an absolute URL string or a relative URL with a base.
*
* @param url - 要解析的绝对或相对 URL。<br />
* The absolute or relative URL to parse.
* @param base - 用于解析相对 URL 的绝对基础 URL。<br />
* The absolute base URL used to resolve a relative URL.
*/
constructor(url, base) {
switch (typeof url) {
case "string": {
const urlIsValid = /^(blob:|file:)?[a-zA-z]+:\/\/.*/.test(url);
const baseIsValid = base ? /^(blob:|file:)?[a-zA-z]+:\/\/.*/.test(base) : false;
// If a string is passed for url instead of location or link, then set the properties of the URL instance.
if (urlIsValid)
this.href = url;
// If the url isn't valid, but the base is, then prepend the base to the url.
else if (baseIsValid)
this.href = base + url;
// If no valid url or base is given, then throw a type error.
else
throw new TypeError('URL string is not valid. If using a relative url, a second argument needs to be passed representing the base URL. Example: new URL("relative/path", "http://www.example.com");');
break;
}
case "object":
break;
default:
throw new TypeError("Invalid argument type.");
}
}
#url = {
hash: "",
host: "",
hostname: "",
href: "",
password: "",
pathname: "",
port: Number.NaN,
protocol: "",
search: "",
searchParams: new URLSearchParams(""),
username: "",
};
// refer: http://www.ietf.org/rfc/rfc3986.txt
static #URLRegExp = /^(?<scheme>([^:\/?#]+):)?(?:\/\/(?<authority>[^\/?#]*))?(?<path>[^?#]*)(?<query>\?([^#]*))?(?<hash>#(.*))?$/;
static #AuthorityRegExp = /^(?<authentication>(?<username>[^:]*)(:(?<password>[^@]*))?@)?(?<hostname>[^:]+)(:(?<port>\d+))?$/;
/**
* URL 片段;存在时包含开头的 `#`。<br />
* The URL fragment, including the leading `#` when present.
*/
get hash() {
return this.#url.hash;
}
set hash(value) {
if (value.length !== 0) {
if (value.startsWith("#"))
value = value.slice(1);
this.#url.hash = `#${encodeURIComponent(value)}`;
}
}
/**
* URL 的主机名与端口。<br />
* The hostname and port of the URL.
*/
get host() {
return this.port.length > 0 ? `${this.hostname}:${this.port}` : this.hostname;
}
set host(value) {
[this.hostname, this.port] = value.split(":", 2);
}
/**
* URL 编码后的主机名。<br />
* The encoded hostname of the URL.
*/
get hostname() {
return encodeURIComponent(this.#url.hostname);
}
set hostname(value) {
this.#url.hostname = value ?? "";
}
/**
* 完整序列化后的 URL。<br />
* The complete serialized URL.
*/
get href() {
let authority = "";
if (this.username.length > 0) {
authority += this.username;
if (this.password.length > 0)
authority += `:${this.password}`;
authority += "@";
}
return `${this.protocol}//${authority}${this.host}${this.pathname}${this.search}${this.hash}`;
}
set href(value) {
if (value.startsWith("blob:") || value.startsWith("file:"))
value = value.slice(5);
const urlMatch = value.match(URL.#URLRegExp);
if (!urlMatch)
throw new TypeError("Invalid URL format.");
this.protocol = urlMatch.groups.scheme ?? "";
const authorityMatch = urlMatch.groups.authority.match(URL.#AuthorityRegExp);
this.username = authorityMatch.groups.username ?? "";
this.password = authorityMatch.groups.password ?? "";
this.hostname = authorityMatch.groups.hostname ?? "";
this.port = authorityMatch.groups.port ?? "";
this.pathname = urlMatch.groups.path ?? "";
this.search = urlMatch.groups.query ?? "";
this.hash = urlMatch.groups.hash ?? "";
}
/**
* 由协议与主机组成的序列化源。<br />
* The serialized origin, consisting of the protocol and host.
*/
get origin() {
return `${this.protocol}//${this.host}`;
}
/**
* 主机名前指定的编码后密码。<br />
* The encoded password specified before the host.
*/
get password() {
return encodeURIComponent(this.#url.password);
}
set password(value) {
if (this.username.length > 0)
this.#url.password = value ?? "";
}
/**
* URL 路径,包含开头的 `/`。<br />
* The URL path, including the leading `/`.
*/
get pathname() {
return `/${this.#url.pathname}`;
}
set pathname(value) {
value = `${value}`;
if (value.startsWith("/"))
value = value.slice(1);
this.#url.pathname = value;
}
/**
* 显式端口;使用协议默认端口时为空字符串。<br />
* The explicit port, or an empty string for the protocol's default port.
*/
get port() {
if (Number.isNaN(this.#url.port))
return "";
const port = this.#url.port.toString();
if (this.protocol === "ftp:" && port === "21")
return "";
if (this.protocol === "http:" && port === "80")
return "";
if (this.protocol === "https:" && port === "443")
return "";
return port;
}
set port(value) {
switch (value) {
case "":
this.#url.port = Number.NaN;
break;
default: {
const port = Number.parseInt(value, 10);
if (port >= 0 && port < 65535)
this.#url.port = port;
}
}
}
/**
* URL 协议,包含结尾的 `:`。<br />
* The URL scheme, including the trailing `:`.
*/
get protocol() {
return `${this.#url.protocol}:`;
}
set protocol(value) {
if (value.endsWith(":"))
value = value.slice(0, -1);
this.#url.protocol = value;
}
/**
* 序列化后的查询字符串;存在时包含开头的 `?`。<br />
* The serialized query string, including the leading `?` when present.
*/
get search() {
if (this.#url.search.length > 0)
return `?${this.#url.search}`;
else
return "";
}
set search(value) {
value = `${value}`;
if (value.startsWith("?"))
value = value.slice(1);
this.#url.search = value;
this.#url.searchParams = new URLSearchParams(this.#url.search, search => {
this.#url.search = search;
});
}
/**
* URL 查询参数的可变视图。<br />
* A mutable view of the URL query parameters.
*/
get searchParams() {
return this.#url.searchParams;
}
/**
* 主机名前指定的编码后用户名。<br />
* The encoded username specified before the host.
*/
get username() {
return encodeURIComponent(this.#url.username);
}
set username(value) {
this.#url.username = value ?? "";
}
/**
* 使用与构造函数相同的输入解析 URL。
*
* Parses a URL using the same inputs accepted by the constructor.
*
* @param url - 要解析的绝对或相对 URL。<br />
* The absolute or relative URL to parse.
* @param base - 用于解析相对 URL 的绝对基础 URL。<br />
* The absolute base URL used to resolve a relative URL.
* @returns 解析得到的 URL 实例。<br />
* A parsed URL instance.
*/
static parse = (url, base) => new URL(url, base);
/**
* 返回 URL 的字符串表示。
*
* Returns the string representation of the URL.
*
* @returns 完整序列化后的 URL。<br />
* The complete serialized URL.
*/
toString = () => this.href;
/**
* 将 URL 对象的公开属性转换为 JSON 字符串。
*
* Converts the URL object properties to a JSON string.
*
* @returns 包含 URL 公开属性的 JSON 字符串。<br />
* A JSON string containing the public URL properties.
*/
toJSON = () => JSON.stringify({
hash: this.hash,
host: this.host,
hostname: this.hostname,
href: this.href,
origin: this.origin,
password: this.password,
pathname: this.pathname,
port: this.port,
protocol: this.protocol,
search: this.search,
searchParams: this.searchParams,
username: this.username,
});
}