bare-url 2.5.0 → 2.5.2
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.
- package/binding.c +107 -38
- package/index.js +105 -55
- package/lib/url-search-params.js +103 -28
- package/package.json +2 -2
- package/prebuilds/android-arm/bare-url.bare +0 -0
- package/prebuilds/android-arm64/bare-url.bare +0 -0
- package/prebuilds/android-ia32/bare-url.bare +0 -0
- package/prebuilds/android-x64/bare-url.bare +0 -0
- package/prebuilds/darwin-arm64/bare-url.bare +0 -0
- package/prebuilds/darwin-x64/bare-url.bare +0 -0
- package/prebuilds/ios-arm64/bare-url.bare +0 -0
- package/prebuilds/ios-arm64-simulator/bare-url.bare +0 -0
- package/prebuilds/ios-x64-simulator/bare-url.bare +0 -0
- package/prebuilds/linux-arm64/bare-url.bare +0 -0
- package/prebuilds/linux-x64/bare-url.bare +0 -0
- package/prebuilds/win32-arm64/bare-url.bare +0 -0
- package/prebuilds/win32-x64/bare-url.bare +0 -0
package/binding.c
CHANGED
|
@@ -8,8 +8,91 @@
|
|
|
8
8
|
#include <utf.h>
|
|
9
9
|
#include <utf/string.h>
|
|
10
10
|
|
|
11
|
+
// Maximum length, in bytes, of a UTF-8 string that is read into a stack buffer.
|
|
12
|
+
// Longer strings fall back to a heap allocation. This covers the vast majority
|
|
13
|
+
// of URLs without touching the heap.
|
|
14
|
+
#define BARE_URL_STACK_STRING_MAX 1024
|
|
15
|
+
|
|
16
|
+
// The UTF-8 encoding of a JavaScript string, together with whatever backs it.
|
|
17
|
+
typedef struct {
|
|
18
|
+
const utf8_t *data;
|
|
19
|
+
size_t len;
|
|
20
|
+
|
|
21
|
+
js_string_view_t *view;
|
|
22
|
+
utf8_t *heap;
|
|
23
|
+
} bare_url_string_t;
|
|
24
|
+
|
|
25
|
+
// Exposes `value` as UTF-8. Strings that are stored as ASCII, which is nearly
|
|
26
|
+
// all of them, are borrowed directly from the engine without being copied.
|
|
27
|
+
// Anything else is transcoded into `stack`, or into a freshly allocated heap
|
|
28
|
+
// buffer when it does not fit. The result must be released with
|
|
29
|
+
// bare_url__free_string().
|
|
30
|
+
static inline void
|
|
31
|
+
bare_url__read_string(js_env_t *env, js_value_t *value, utf8_t *stack, size_t stack_len, bare_url_string_t *result) {
|
|
32
|
+
int err;
|
|
33
|
+
|
|
34
|
+
js_string_encoding_t encoding;
|
|
35
|
+
const void *data;
|
|
36
|
+
size_t len;
|
|
37
|
+
|
|
38
|
+
err = js_get_string_view(env, value, &encoding, &data, &len, &result->view);
|
|
39
|
+
assert(err == 0);
|
|
40
|
+
|
|
41
|
+
result->heap = NULL;
|
|
42
|
+
|
|
43
|
+
size_t utf8_len;
|
|
44
|
+
|
|
45
|
+
if (encoding == js_utf8) {
|
|
46
|
+
result->data = (const utf8_t *) data;
|
|
47
|
+
result->len = len;
|
|
48
|
+
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (encoding == js_latin1) {
|
|
53
|
+
utf8_len = utf8_length_from_latin1((const latin1_t *) data, len);
|
|
54
|
+
|
|
55
|
+
// A Latin-1 string that is no longer as UTF-8 contains only ASCII, whose
|
|
56
|
+
// Latin-1 and UTF-8 encodings are identical.
|
|
57
|
+
if (utf8_len == len) {
|
|
58
|
+
result->data = (const utf8_t *) data;
|
|
59
|
+
result->len = len;
|
|
60
|
+
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
assert(encoding == js_utf16le);
|
|
65
|
+
|
|
66
|
+
utf8_len = utf8_length_from_utf16le((const utf16_t *) data, len);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
utf8_t *buffer = utf8_len <= stack_len ? stack : (result->heap = malloc(utf8_len));
|
|
70
|
+
|
|
71
|
+
if (encoding == js_latin1) {
|
|
72
|
+
latin1_convert_to_utf8((const latin1_t *) data, len, buffer);
|
|
73
|
+
} else {
|
|
74
|
+
utf16le_convert_to_utf8((const utf16_t *) data, len, buffer);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
result->data = buffer;
|
|
78
|
+
result->len = utf8_len;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Releases a string read with bare_url__read_string(), freeing its buffer only
|
|
82
|
+
// when it was heap allocated rather than borrowed or written to the caller's
|
|
83
|
+
// stack buffer.
|
|
84
|
+
static inline void
|
|
85
|
+
bare_url__free_string(js_env_t *env, bare_url_string_t *string) {
|
|
86
|
+
int err;
|
|
87
|
+
|
|
88
|
+
free(string->heap);
|
|
89
|
+
|
|
90
|
+
err = js_release_string_view(env, string->view);
|
|
91
|
+
assert(err == 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
11
94
|
static js_value_t *
|
|
12
|
-
bare_url_parse
|
|
95
|
+
bare_url_parse(js_env_t *env, js_callback_info_t *info) {
|
|
13
96
|
int err;
|
|
14
97
|
|
|
15
98
|
size_t argc = 4;
|
|
@@ -32,17 +115,14 @@ bare_url_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
32
115
|
url_init(&base);
|
|
33
116
|
|
|
34
117
|
if (has_base) {
|
|
35
|
-
|
|
36
|
-
err = js_get_value_string_utf8(env, argv[1], NULL, 0, &len);
|
|
37
|
-
assert(err == 0);
|
|
118
|
+
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
38
119
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
assert(err == 0);
|
|
120
|
+
bare_url_string_t input;
|
|
121
|
+
bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
|
|
42
122
|
|
|
43
|
-
err = url_parse(&base, input, len, NULL);
|
|
123
|
+
err = url_parse(&base, input.data, input.len, NULL);
|
|
44
124
|
|
|
45
|
-
|
|
125
|
+
bare_url__free_string(env, &input);
|
|
46
126
|
|
|
47
127
|
if (err < 0) {
|
|
48
128
|
url_destroy(&base);
|
|
@@ -53,22 +133,17 @@ bare_url_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
53
133
|
}
|
|
54
134
|
}
|
|
55
135
|
|
|
56
|
-
|
|
57
|
-
err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
|
|
58
|
-
assert(err == 0);
|
|
136
|
+
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
59
137
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
assert(err == 0);
|
|
63
|
-
|
|
64
|
-
js_value_t *handle;
|
|
138
|
+
bare_url_string_t input;
|
|
139
|
+
bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
|
|
65
140
|
|
|
66
141
|
url_t url;
|
|
67
142
|
url_init(&url);
|
|
68
143
|
|
|
69
|
-
err = url_parse(&url, input, len, has_base ? &base : NULL);
|
|
144
|
+
err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
|
|
70
145
|
|
|
71
|
-
|
|
146
|
+
bare_url__free_string(env, &input);
|
|
72
147
|
|
|
73
148
|
if (err < 0) {
|
|
74
149
|
url_destroy(&base);
|
|
@@ -80,7 +155,7 @@ bare_url_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
80
155
|
}
|
|
81
156
|
|
|
82
157
|
js_value_t *href;
|
|
83
|
-
err =
|
|
158
|
+
err = js_create_string_latin1(env, (const latin1_t *) url.href.data, url.href.len, &href);
|
|
84
159
|
assert(err == 0);
|
|
85
160
|
|
|
86
161
|
uint32_t *components;
|
|
@@ -96,7 +171,7 @@ bare_url_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
96
171
|
}
|
|
97
172
|
|
|
98
173
|
static js_value_t *
|
|
99
|
-
bare_url_can_parse
|
|
174
|
+
bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
|
|
100
175
|
int err;
|
|
101
176
|
|
|
102
177
|
size_t argc = 2;
|
|
@@ -115,17 +190,14 @@ bare_url_can_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
115
190
|
url_init(&base);
|
|
116
191
|
|
|
117
192
|
if (has_base) {
|
|
118
|
-
|
|
119
|
-
err = js_get_value_string_utf8(env, argv[1], NULL, 0, &len);
|
|
120
|
-
assert(err == 0);
|
|
193
|
+
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
121
194
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
assert(err == 0);
|
|
195
|
+
bare_url_string_t input;
|
|
196
|
+
bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
|
|
125
197
|
|
|
126
|
-
err = url_parse(&base, input, len, NULL);
|
|
198
|
+
err = url_parse(&base, input.data, input.len, NULL);
|
|
127
199
|
|
|
128
|
-
|
|
200
|
+
bare_url__free_string(env, &input);
|
|
129
201
|
|
|
130
202
|
if (err < 0) {
|
|
131
203
|
url_destroy(&base);
|
|
@@ -138,20 +210,17 @@ bare_url_can_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
138
210
|
}
|
|
139
211
|
}
|
|
140
212
|
|
|
141
|
-
|
|
142
|
-
err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
|
|
143
|
-
assert(err == 0);
|
|
213
|
+
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
144
214
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
assert(err == 0);
|
|
215
|
+
bare_url_string_t input;
|
|
216
|
+
bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
|
|
148
217
|
|
|
149
218
|
url_t url;
|
|
150
219
|
url_init(&url);
|
|
151
220
|
|
|
152
|
-
err = url_parse(&url, input, len, has_base ? &base : NULL);
|
|
221
|
+
err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
|
|
153
222
|
|
|
154
|
-
|
|
223
|
+
bare_url__free_string(env, &input);
|
|
155
224
|
|
|
156
225
|
url_destroy(&base);
|
|
157
226
|
url_destroy(&url);
|
|
@@ -164,7 +233,7 @@ bare_url_can_parse (js_env_t *env, js_callback_info_t *info) {
|
|
|
164
233
|
}
|
|
165
234
|
|
|
166
235
|
static js_value_t *
|
|
167
|
-
bare_url_exports
|
|
236
|
+
bare_url_exports(js_env_t *env, js_value_t *exports) {
|
|
168
237
|
int err;
|
|
169
238
|
|
|
170
239
|
#define V(name, fn) \
|
package/index.js
CHANGED
|
@@ -7,6 +7,20 @@ const kind = Symbol.for('bare.url.kind')
|
|
|
7
7
|
|
|
8
8
|
const isWindows = Bare.platform === 'win32'
|
|
9
9
|
|
|
10
|
+
// Scratch buffer that the binding writes the parsed component offsets into. A
|
|
11
|
+
// single shared buffer is reused across every parse.
|
|
12
|
+
//
|
|
13
|
+
// The offsets are copied out into fields on the URL immediately after a
|
|
14
|
+
// successful parse, so nothing observes the buffer across calls.
|
|
15
|
+
const components = new Uint32Array(8)
|
|
16
|
+
|
|
17
|
+
// The value used for a component that is not present in the URL.
|
|
18
|
+
const unset = 0xffffffff
|
|
19
|
+
|
|
20
|
+
// The characters that pathToFileURL() has to percent-encode itself. A backslash
|
|
21
|
+
// is a path separator on Windows and so is left alone there.
|
|
22
|
+
const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/
|
|
23
|
+
|
|
10
24
|
class URL {
|
|
11
25
|
static get [kind]() {
|
|
12
26
|
return 0 // Compatibility version
|
|
@@ -19,11 +33,17 @@ class URL {
|
|
|
19
33
|
|
|
20
34
|
if (base !== undefined) base = String(base)
|
|
21
35
|
|
|
22
|
-
this.
|
|
36
|
+
this._href = undefined
|
|
37
|
+
this._schemeEnd = 0
|
|
38
|
+
this._usernameEnd = 0
|
|
39
|
+
this._hostStart = 0
|
|
40
|
+
this._hostEnd = 0
|
|
41
|
+
this._pathStart = 0
|
|
42
|
+
this._queryStart = 0
|
|
43
|
+
this._fragmentStart = 0
|
|
44
|
+
this._params = null
|
|
23
45
|
|
|
24
46
|
this._parse(input, base, opts.throw !== false)
|
|
25
|
-
|
|
26
|
-
if (this._href) this._params = new URLSearchParams(this.search, this)
|
|
27
47
|
}
|
|
28
48
|
|
|
29
49
|
get [kind]() {
|
|
@@ -39,23 +59,23 @@ class URL {
|
|
|
39
59
|
set href(value) {
|
|
40
60
|
this._update(value)
|
|
41
61
|
|
|
42
|
-
this._params._parse(this.search)
|
|
62
|
+
if (this._params) this._params._parse(this.search)
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
// https://url.spec.whatwg.org/#dom-url-protocol
|
|
46
66
|
|
|
47
67
|
get protocol() {
|
|
48
|
-
return this._slice(0, this.
|
|
68
|
+
return this._slice(0, this._schemeEnd) + ':'
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
set protocol(value) {
|
|
52
|
-
this._update(this._replace(value.replace(/:+$/, ''), 0, this.
|
|
72
|
+
this._update(this._replace(value.replace(/:+$/, ''), 0, this._schemeEnd))
|
|
53
73
|
}
|
|
54
74
|
|
|
55
75
|
// https://url.spec.whatwg.org/#dom-url-username
|
|
56
76
|
|
|
57
77
|
get username() {
|
|
58
|
-
return this._slice(this.
|
|
78
|
+
return this._slice(this._schemeEnd + 3 /* :// */, this._usernameEnd)
|
|
59
79
|
}
|
|
60
80
|
|
|
61
81
|
set username(value) {
|
|
@@ -65,13 +85,13 @@ class URL {
|
|
|
65
85
|
|
|
66
86
|
if (this.username === '') value += '@'
|
|
67
87
|
|
|
68
|
-
this._update(this._replace(value, this.
|
|
88
|
+
this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
|
|
69
89
|
}
|
|
70
90
|
|
|
71
91
|
// https://url.spec.whatwg.org/#dom-url-password
|
|
72
92
|
|
|
73
93
|
get password() {
|
|
74
|
-
return this._href.slice(this.
|
|
94
|
+
return this._href.slice(this._usernameEnd + 1 /* : */, this._hostStart - 1 /* @ */)
|
|
75
95
|
}
|
|
76
96
|
|
|
77
97
|
set password(value) {
|
|
@@ -79,8 +99,8 @@ class URL {
|
|
|
79
99
|
return
|
|
80
100
|
}
|
|
81
101
|
|
|
82
|
-
let start = this.
|
|
83
|
-
let end = this.
|
|
102
|
+
let start = this._usernameEnd + 1 /* : */
|
|
103
|
+
let end = this._hostStart - 1 /* @ */
|
|
84
104
|
|
|
85
105
|
if (this.password === '') {
|
|
86
106
|
value = ':' + value
|
|
@@ -98,7 +118,7 @@ class URL {
|
|
|
98
118
|
// https://url.spec.whatwg.org/#dom-url-host
|
|
99
119
|
|
|
100
120
|
get host() {
|
|
101
|
-
return this._slice(this.
|
|
121
|
+
return this._slice(this._hostStart, this._pathStart)
|
|
102
122
|
}
|
|
103
123
|
|
|
104
124
|
set host(value) {
|
|
@@ -107,14 +127,14 @@ class URL {
|
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
this._update(
|
|
110
|
-
this._replace(value, this.
|
|
130
|
+
this._replace(value, this._hostStart, value.includes(':') ? this._pathStart : this._hostEnd)
|
|
111
131
|
)
|
|
112
132
|
}
|
|
113
133
|
|
|
114
134
|
// https://url.spec.whatwg.org/#dom-url-hostname
|
|
115
135
|
|
|
116
136
|
get hostname() {
|
|
117
|
-
return this._slice(this.
|
|
137
|
+
return this._slice(this._hostStart, this._hostEnd)
|
|
118
138
|
}
|
|
119
139
|
|
|
120
140
|
set hostname(value) {
|
|
@@ -122,13 +142,13 @@ class URL {
|
|
|
122
142
|
return
|
|
123
143
|
}
|
|
124
144
|
|
|
125
|
-
this._update(this._replace(value, this.
|
|
145
|
+
this._update(this._replace(value, this._hostStart, this._hostEnd))
|
|
126
146
|
}
|
|
127
147
|
|
|
128
148
|
// https://url.spec.whatwg.org/#dom-url-port
|
|
129
149
|
|
|
130
150
|
get port() {
|
|
131
|
-
return this._slice(this.
|
|
151
|
+
return this._slice(this._hostEnd + 1 /* : */, this._pathStart)
|
|
132
152
|
}
|
|
133
153
|
|
|
134
154
|
set port(value) {
|
|
@@ -136,20 +156,20 @@ class URL {
|
|
|
136
156
|
return
|
|
137
157
|
}
|
|
138
158
|
|
|
139
|
-
let start = this.
|
|
159
|
+
let start = this._hostEnd + 1 /* : */
|
|
140
160
|
|
|
141
161
|
if (this.port === '') {
|
|
142
162
|
value = ':' + value
|
|
143
163
|
start--
|
|
144
164
|
}
|
|
145
165
|
|
|
146
|
-
this._update(this._replace(value, start, this.
|
|
166
|
+
this._update(this._replace(value, start, this._pathStart))
|
|
147
167
|
}
|
|
148
168
|
|
|
149
169
|
// https://url.spec.whatwg.org/#dom-url-pathname
|
|
150
170
|
|
|
151
171
|
get pathname() {
|
|
152
|
-
return this._slice(this.
|
|
172
|
+
return this._slice(this._pathStart, this._queryStart - 1 /* ? */)
|
|
153
173
|
}
|
|
154
174
|
|
|
155
175
|
set pathname(value) {
|
|
@@ -161,41 +181,45 @@ class URL {
|
|
|
161
181
|
value = '/' + value
|
|
162
182
|
}
|
|
163
183
|
|
|
164
|
-
this._update(this._replace(value, this.
|
|
184
|
+
this._update(this._replace(value, this._pathStart, this._queryStart - 1 /* ? */))
|
|
165
185
|
}
|
|
166
186
|
|
|
167
187
|
// https://url.spec.whatwg.org/#dom-url-search
|
|
168
188
|
|
|
169
189
|
get search() {
|
|
170
|
-
return this._slice(this.
|
|
190
|
+
return this._slice(this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
|
|
171
191
|
}
|
|
172
192
|
|
|
173
193
|
set search(value) {
|
|
174
194
|
if (value && value[0] !== '?') value = '?' + value
|
|
175
195
|
|
|
176
196
|
this._update(
|
|
177
|
-
this._replace(value, this.
|
|
197
|
+
this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
|
|
178
198
|
)
|
|
179
199
|
|
|
180
|
-
this._params._parse(this.search)
|
|
200
|
+
if (this._params) this._params._parse(this.search)
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
// https://url.spec.whatwg.org/#dom-url-searchparams
|
|
184
204
|
|
|
185
205
|
get searchParams() {
|
|
206
|
+
if (this._params === null) {
|
|
207
|
+
this._params = new URLSearchParams(this.search, this)
|
|
208
|
+
}
|
|
209
|
+
|
|
186
210
|
return this._params
|
|
187
211
|
}
|
|
188
212
|
|
|
189
213
|
// https://url.spec.whatwg.org/#dom-url-hash
|
|
190
214
|
|
|
191
215
|
get hash() {
|
|
192
|
-
return this._slice(this.
|
|
216
|
+
return this._slice(this._fragmentStart - 1 /* # */)
|
|
193
217
|
}
|
|
194
218
|
|
|
195
219
|
set hash(value) {
|
|
196
220
|
if (value && value[0] !== '#') value = '#' + value
|
|
197
221
|
|
|
198
|
-
this._update(this._replace(value, this.
|
|
222
|
+
this._update(this._replace(value, this._fragmentStart - 1 /* # */))
|
|
199
223
|
}
|
|
200
224
|
|
|
201
225
|
toString() {
|
|
@@ -233,18 +257,32 @@ class URL {
|
|
|
233
257
|
}
|
|
234
258
|
|
|
235
259
|
_parse(input, base, shouldThrow) {
|
|
260
|
+
let href
|
|
261
|
+
|
|
236
262
|
try {
|
|
237
|
-
|
|
238
|
-
String(input),
|
|
239
|
-
base ? String(base) : null,
|
|
240
|
-
this._components,
|
|
241
|
-
shouldThrow
|
|
242
|
-
)
|
|
263
|
+
href = binding.parse(input, base || null, components, shouldThrow)
|
|
243
264
|
} catch (err) {
|
|
244
265
|
if (err instanceof TypeError) throw err
|
|
245
266
|
|
|
246
267
|
throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
|
|
247
268
|
}
|
|
269
|
+
|
|
270
|
+
if (href === undefined) return
|
|
271
|
+
|
|
272
|
+
this._href = href
|
|
273
|
+
this._schemeEnd = components[0]
|
|
274
|
+
this._usernameEnd = components[1]
|
|
275
|
+
this._hostStart = components[2]
|
|
276
|
+
this._hostEnd = components[3]
|
|
277
|
+
this._pathStart = components[5]
|
|
278
|
+
|
|
279
|
+
const queryStart = components[6]
|
|
280
|
+
const fragmentStart = components[7]
|
|
281
|
+
|
|
282
|
+
const end = href.length + 1
|
|
283
|
+
|
|
284
|
+
this._queryStart = queryStart === unset ? end : queryStart
|
|
285
|
+
this._fragmentStart = fragmentStart === unset ? end : fragmentStart
|
|
248
286
|
}
|
|
249
287
|
|
|
250
288
|
_update(input) {
|
|
@@ -301,27 +339,35 @@ exports.fileURLToPath = function fileURLToPath(url) {
|
|
|
301
339
|
throw errors.INVALID_URL_SCHEME('The URL must use the file: protocol')
|
|
302
340
|
}
|
|
303
341
|
|
|
304
|
-
if (isWindows) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
} else {
|
|
311
|
-
if (url.hostname) {
|
|
312
|
-
throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
|
|
313
|
-
}
|
|
342
|
+
if (!isWindows && url.hostname) {
|
|
343
|
+
throw errors.INVALID_FILE_URL_HOST("The file: URL host must be 'localhost' or empty")
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const encoded = url.pathname
|
|
314
347
|
|
|
315
|
-
|
|
348
|
+
// Every check below looks for a percent encoded sequence, as does the decoding
|
|
349
|
+
// that follows, so a path without any can skip all of them.
|
|
350
|
+
const hasEncoded = encoded.includes('%')
|
|
351
|
+
|
|
352
|
+
if (hasEncoded) {
|
|
353
|
+
if (isWindows) {
|
|
354
|
+
if (/%2f|%5c/i.test(encoded)) {
|
|
355
|
+
throw errors.INVALID_FILE_URL_PATH(
|
|
356
|
+
'The file: URL path must not include encoded \\ or / characters'
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
} else if (/%2f/i.test(encoded)) {
|
|
316
360
|
throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded / characters')
|
|
317
361
|
}
|
|
318
|
-
}
|
|
319
362
|
|
|
320
|
-
|
|
321
|
-
|
|
363
|
+
if (/%00/i.test(encoded)) {
|
|
364
|
+
throw errors.INVALID_FILE_URL_PATH(
|
|
365
|
+
'The file: URL path must not include encoded NUL characters'
|
|
366
|
+
)
|
|
367
|
+
}
|
|
322
368
|
}
|
|
323
369
|
|
|
324
|
-
const pathname = path.normalize(decodeURIComponent(
|
|
370
|
+
const pathname = path.normalize(hasEncoded ? decodeURIComponent(encoded) : encoded)
|
|
325
371
|
|
|
326
372
|
if (isWindows) {
|
|
327
373
|
if (url.hostname) return '\\\\' + url.hostname + pathname
|
|
@@ -347,16 +393,20 @@ exports.pathToFileURL = function pathToFileURL(pathname) {
|
|
|
347
393
|
resolved += '\\'
|
|
348
394
|
}
|
|
349
395
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
396
|
+
// Paths hardly ever contain any of these, so one pass to rule them out is
|
|
397
|
+
// cheaper than the six or seven replacements it stands in for.
|
|
398
|
+
if (reserved.test(resolved)) {
|
|
399
|
+
resolved = resolved
|
|
400
|
+
.replaceAll('%', '%25') // Must be first
|
|
401
|
+
.replaceAll('#', '%23')
|
|
402
|
+
.replaceAll('?', '%3f')
|
|
403
|
+
.replaceAll('\n', '%0a')
|
|
404
|
+
.replaceAll('\r', '%0d')
|
|
405
|
+
.replaceAll('\t', '%09')
|
|
357
406
|
|
|
358
|
-
|
|
359
|
-
|
|
407
|
+
if (!isWindows) {
|
|
408
|
+
resolved = resolved.replaceAll('\\', '%5c')
|
|
409
|
+
}
|
|
360
410
|
}
|
|
361
411
|
|
|
362
412
|
return new URL('file:' + resolved)
|
package/lib/url-search-params.js
CHANGED
|
@@ -9,17 +9,21 @@ class URLSearchParams {
|
|
|
9
9
|
|
|
10
10
|
// https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
|
|
11
11
|
constructor(init, url = null) {
|
|
12
|
-
this._params =
|
|
12
|
+
this._params = null
|
|
13
13
|
|
|
14
14
|
if (url) URLSearchParams._urls.set(this, url)
|
|
15
15
|
|
|
16
16
|
if (typeof init === 'string') {
|
|
17
17
|
this._parse(init)
|
|
18
|
-
} else
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
} else {
|
|
19
|
+
this._params = new Map()
|
|
20
|
+
|
|
21
|
+
if (init) {
|
|
22
|
+
for (const [name, value] of typeof init[Symbol.iterator] === 'function'
|
|
23
|
+
? init
|
|
24
|
+
: Object.entries(init)) {
|
|
25
|
+
this.append(name, value)
|
|
26
|
+
}
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
29
|
}
|
|
@@ -30,7 +34,11 @@ class URLSearchParams {
|
|
|
30
34
|
|
|
31
35
|
// https://url.spec.whatwg.org/#dom-urlsearchparams-size
|
|
32
36
|
get size() {
|
|
33
|
-
|
|
37
|
+
let size = 0
|
|
38
|
+
|
|
39
|
+
for (const values of this._params.values()) size += values.length
|
|
40
|
+
|
|
41
|
+
return size
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
// https://url.spec.whatwg.org/#dom-urlsearchparams-append
|
|
@@ -141,47 +149,51 @@ class URLSearchParams {
|
|
|
141
149
|
|
|
142
150
|
// https://url.spec.whatwg.org/#concept-urlencoded-parser
|
|
143
151
|
_parse(input) {
|
|
144
|
-
|
|
152
|
+
const params = new Map()
|
|
145
153
|
|
|
146
|
-
this._params =
|
|
154
|
+
this._params = params
|
|
147
155
|
|
|
148
|
-
|
|
149
|
-
if (sequence.length === 0) continue
|
|
156
|
+
const len = input.length
|
|
150
157
|
|
|
151
|
-
|
|
158
|
+
let start = input.charCodeAt(0) === 0x3f /* ? */ ? 1 : 0
|
|
152
159
|
|
|
153
|
-
|
|
154
|
-
|
|
160
|
+
while (start < len) {
|
|
161
|
+
let end = input.indexOf('&', start)
|
|
162
|
+
if (end === -1) end = len
|
|
155
163
|
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
if (end !== start) {
|
|
165
|
+
let split = input.indexOf('=', start)
|
|
166
|
+
if (split === -1 || split > end) split = end
|
|
158
167
|
|
|
159
|
-
|
|
168
|
+
const name = decode(input.slice(start, split))
|
|
169
|
+
const value = split === end ? '' : decode(input.slice(split + 1, end))
|
|
160
170
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
171
|
+
const list = params.get(name)
|
|
172
|
+
|
|
173
|
+
if (list === undefined) params.set(name, [value])
|
|
174
|
+
else list.push(value)
|
|
164
175
|
}
|
|
165
176
|
|
|
166
|
-
|
|
177
|
+
start = end + 1
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
// https://url.spec.whatwg.org/#concept-urlencoded-serializer
|
|
171
182
|
_serialize() {
|
|
172
|
-
let
|
|
183
|
+
let result = ''
|
|
184
|
+
let separator = ''
|
|
173
185
|
|
|
174
|
-
for (
|
|
175
|
-
name
|
|
186
|
+
for (const [name, values] of this._params) {
|
|
187
|
+
// The name is shared by all of its values, so it is only encoded once.
|
|
188
|
+
const encoded = encode(String(name))
|
|
176
189
|
|
|
177
190
|
for (const value of values) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
output += name + '=' + encodeURIComponent(value)
|
|
191
|
+
result += separator + encoded + '=' + encode(String(value))
|
|
192
|
+
separator = '&'
|
|
181
193
|
}
|
|
182
194
|
}
|
|
183
195
|
|
|
184
|
-
return
|
|
196
|
+
return result
|
|
185
197
|
}
|
|
186
198
|
}
|
|
187
199
|
|
|
@@ -192,3 +204,66 @@ exports.isURLSearchParams = function isURLSearchParams(value) {
|
|
|
192
204
|
|
|
193
205
|
return typeof value === 'object' && value !== null && value[kind] === URLSearchParams[kind]
|
|
194
206
|
}
|
|
207
|
+
|
|
208
|
+
// https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
|
|
209
|
+
//
|
|
210
|
+
// Everything except ASCII alphanumerics and `*-._` is percent-encoded, and a
|
|
211
|
+
// space becomes `+` rather than `%20`. Names and values usually consist entirely
|
|
212
|
+
// of characters that are left alone, so a component is tested as a whole before
|
|
213
|
+
// any of it is rewritten.
|
|
214
|
+
const unencoded = /^[\w*.-]*$/
|
|
215
|
+
|
|
216
|
+
// As above, but allowing spaces, which need nothing more than turning into `+`.
|
|
217
|
+
// Spaces are by far the most common reason for a component to need rewriting at
|
|
218
|
+
// all, so recognizing them here spares those components a trip through
|
|
219
|
+
// encodeURIComponent().
|
|
220
|
+
const spaced = /^[\w*. -]*$/
|
|
221
|
+
|
|
222
|
+
// The characters that encodeURIComponent() leaves alone but the urlencoded byte
|
|
223
|
+
// serializer does not. Two patterns are needed because a global regular
|
|
224
|
+
// expression cannot be shared between test() and replace(), as test() advances
|
|
225
|
+
// its lastIndex.
|
|
226
|
+
const extra = /[!'()~]/
|
|
227
|
+
const extraAll = /[!'()~]/g
|
|
228
|
+
|
|
229
|
+
const escapes = {
|
|
230
|
+
'!': '%21',
|
|
231
|
+
"'": '%27',
|
|
232
|
+
'(': '%28',
|
|
233
|
+
')': '%29',
|
|
234
|
+
'~': '%7E'
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// A surrogate that is not part of a pair, and so does not encode a scalar value.
|
|
238
|
+
const lone = /[\ud800-\udbff](?![\udc00-\udfff])|(?<![\ud800-\udbff])[\udc00-\udfff]/g
|
|
239
|
+
|
|
240
|
+
function encode(component) {
|
|
241
|
+
if (unencoded.test(component)) return component
|
|
242
|
+
|
|
243
|
+
if (spaced.test(component)) return component.replaceAll(' ', '+')
|
|
244
|
+
|
|
245
|
+
let encoded
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
encoded = encodeURIComponent(component)
|
|
249
|
+
} catch {
|
|
250
|
+
// encodeURIComponent() rejects lone surrogates, whereas the UTF-8 encoder the
|
|
251
|
+
// serializer is defined in terms of replaces them with U+FFFD.
|
|
252
|
+
encoded = encodeURIComponent(component.replace(lone, '\ufffd'))
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (encoded.includes('%20')) encoded = encoded.replaceAll('%20', '+')
|
|
256
|
+
|
|
257
|
+
if (extra.test(encoded)) encoded = encoded.replace(extraAll, (match) => escapes[match])
|
|
258
|
+
|
|
259
|
+
return encoded
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// https://url.spec.whatwg.org/#percent-decode-after-encoding
|
|
263
|
+
function decode(component) {
|
|
264
|
+
if (component.indexOf('+') !== -1) component = component.replaceAll('+', ' ')
|
|
265
|
+
|
|
266
|
+
if (component.indexOf('%') === -1) return component
|
|
267
|
+
|
|
268
|
+
return decodeURIComponent(component)
|
|
269
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-url",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.2",
|
|
4
4
|
"description": "WHATWG URL implementation for JavaScript",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./package": "./package.json",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"brittle": "^3.3.2",
|
|
48
|
-
"cmake-bare": "^1.1
|
|
48
|
+
"cmake-bare": "^1.8.1",
|
|
49
49
|
"cmake-fetch": "^1.0.0",
|
|
50
50
|
"lunte": "^1.6.0",
|
|
51
51
|
"prettier": "^3.3.3",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|