bare-url 2.5.2 → 2.5.3
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/CMakeLists.txt +1 -1
- package/README.md +1 -140
- package/binding.c +194 -32
- package/global.d.ts +12 -0
- package/index.d.ts +55 -0
- package/index.js +241 -26
- package/lib/url-search-params.d.ts +43 -0
- package/lib/url-search-params.js +14 -7
- package/package.json +1 -1
- 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/CMakeLists.txt
CHANGED
|
@@ -9,7 +9,7 @@ fetch_package("github:holepunchto/libutf#a1ceca8")
|
|
|
9
9
|
fetch_package("github:holepunchto/libpunycode#e91ee34")
|
|
10
10
|
fetch_package("github:holepunchto/libnormalize#0e81f65")
|
|
11
11
|
fetch_package("github:holepunchto/libidna#1471406")
|
|
12
|
-
fetch_package("github:holepunchto/liburl#
|
|
12
|
+
fetch_package("github:holepunchto/liburl#2efedc5")
|
|
13
13
|
|
|
14
14
|
add_bare_module(bare_url)
|
|
15
15
|
|
package/README.md
CHANGED
|
@@ -26,146 +26,7 @@ require('bare-url/global')
|
|
|
26
26
|
|
|
27
27
|
## API
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
Parse `input` as a URL. If `base` is provided, `input` is resolved relative to `base`. Throws if `input` is not a valid URL.
|
|
32
|
-
|
|
33
|
-
#### `url.href`
|
|
34
|
-
|
|
35
|
-
The full serialized URL string. Setting this property reparses the URL.
|
|
36
|
-
|
|
37
|
-
#### `url.protocol`
|
|
38
|
-
|
|
39
|
-
The URL scheme followed by `':'`, e.g. `'https:'`.
|
|
40
|
-
|
|
41
|
-
#### `url.username`
|
|
42
|
-
|
|
43
|
-
The username portion of the URL, or an empty string.
|
|
44
|
-
|
|
45
|
-
#### `url.password`
|
|
46
|
-
|
|
47
|
-
The password portion of the URL, or an empty string.
|
|
48
|
-
|
|
49
|
-
#### `url.host`
|
|
50
|
-
|
|
51
|
-
The hostname and port, e.g. `'example.com:8080'`.
|
|
52
|
-
|
|
53
|
-
#### `url.hostname`
|
|
54
|
-
|
|
55
|
-
The hostname without the port.
|
|
56
|
-
|
|
57
|
-
#### `url.port`
|
|
58
|
-
|
|
59
|
-
The port as a string, or an empty string if not present.
|
|
60
|
-
|
|
61
|
-
#### `url.pathname`
|
|
62
|
-
|
|
63
|
-
The path portion of the URL.
|
|
64
|
-
|
|
65
|
-
#### `url.search`
|
|
66
|
-
|
|
67
|
-
The query string including the leading `'?'`, or an empty string.
|
|
68
|
-
|
|
69
|
-
#### `url.searchParams`
|
|
70
|
-
|
|
71
|
-
A `URLSearchParams` object for the query string. Mutations to the params are reflected in the URL.
|
|
72
|
-
|
|
73
|
-
#### `url.hash`
|
|
74
|
-
|
|
75
|
-
The fragment including the leading `'#'`, or an empty string.
|
|
76
|
-
|
|
77
|
-
#### `url.toString()`
|
|
78
|
-
|
|
79
|
-
Returns the serialized URL string. Equivalent to `url.href`.
|
|
80
|
-
|
|
81
|
-
#### `url.toJSON()`
|
|
82
|
-
|
|
83
|
-
Returns the serialized URL string. Suitable for JSON serialization.
|
|
84
|
-
|
|
85
|
-
#### `const params = new URLSearchParams([init])`
|
|
86
|
-
|
|
87
|
-
Create a new `URLSearchParams` instance. `init` may be a query string, an iterable of `[name, value]` pairs, or an object of key-value pairs.
|
|
88
|
-
|
|
89
|
-
#### `params.size`
|
|
90
|
-
|
|
91
|
-
The total number of search parameters.
|
|
92
|
-
|
|
93
|
-
#### `params.append(name, value)`
|
|
94
|
-
|
|
95
|
-
Append a new `name`/`value` pair.
|
|
96
|
-
|
|
97
|
-
#### `params.delete(name[, value])`
|
|
98
|
-
|
|
99
|
-
Remove all pairs with `name`. If `value` is provided, only pairs with both the matching `name` and `value` are removed.
|
|
100
|
-
|
|
101
|
-
#### `params.get(name)`
|
|
102
|
-
|
|
103
|
-
Return the first value for `name`, or `null` if not present.
|
|
104
|
-
|
|
105
|
-
#### `params.getAll(name)`
|
|
106
|
-
|
|
107
|
-
Return all values for `name` as an array.
|
|
108
|
-
|
|
109
|
-
#### `params.has(name[, value])`
|
|
110
|
-
|
|
111
|
-
Return `true` if a pair with `name` exists. If `value` is provided, the pair must also match `value`.
|
|
112
|
-
|
|
113
|
-
#### `params.set(name, value)`
|
|
114
|
-
|
|
115
|
-
Set the value for `name`, replacing any existing pairs with that name.
|
|
116
|
-
|
|
117
|
-
#### `params.toString()`
|
|
118
|
-
|
|
119
|
-
Return the serialized query string without the leading `'?'`.
|
|
120
|
-
|
|
121
|
-
#### `params.toJSON()`
|
|
122
|
-
|
|
123
|
-
Return the parameters as an array of `[name, value]` pairs.
|
|
124
|
-
|
|
125
|
-
#### `URL.isURL(value)`
|
|
126
|
-
|
|
127
|
-
Return `true` if `value` is a `URL` instance.
|
|
128
|
-
|
|
129
|
-
#### `URLSearchParams.isURLSearchParams(value)`
|
|
130
|
-
|
|
131
|
-
Return `true` if `value` is a `URLSearchParams` instance.
|
|
132
|
-
|
|
133
|
-
#### `const url = URL.parse(input[, base])`
|
|
134
|
-
|
|
135
|
-
Parse `input` as a URL without throwing. Returns a `URL` instance on success, or `null` on failure.
|
|
136
|
-
|
|
137
|
-
#### `const valid = URL.canParse(input[, base])`
|
|
138
|
-
|
|
139
|
-
Return `true` if `input` can be parsed as a valid URL, optionally relative to `base`.
|
|
140
|
-
|
|
141
|
-
#### `const pathname = URL.fileURLToPath(url)`
|
|
142
|
-
|
|
143
|
-
Convert a `file:` URL to a platform-specific file path. `url` may be a `URL` instance or a string. Throws if the URL does not use the `file:` protocol or contains invalid path characters.
|
|
144
|
-
|
|
145
|
-
#### `const url = URL.pathToFileURL(pathname)`
|
|
146
|
-
|
|
147
|
-
Convert a platform-specific file path to a `file:` URL.
|
|
148
|
-
|
|
149
|
-
#### `const href = URL.format(parts)`
|
|
150
|
-
|
|
151
|
-
Format a URL from individual `parts`:
|
|
152
|
-
|
|
153
|
-
```js
|
|
154
|
-
parts = {
|
|
155
|
-
protocol,
|
|
156
|
-
auth,
|
|
157
|
-
host,
|
|
158
|
-
hostname,
|
|
159
|
-
port,
|
|
160
|
-
pathname,
|
|
161
|
-
search,
|
|
162
|
-
query,
|
|
163
|
-
hash,
|
|
164
|
-
slashes
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
All properties are optional. If `host` is provided, `hostname` and `port` are ignored. If `search` is provided, `query` is ignored. Set `slashes` to `true` to include `'//'` after the protocol.
|
|
29
|
+
See the [`bare-url` reference](https://docs.pears.com/reference/bare/modules/bare-url).
|
|
169
30
|
|
|
170
31
|
## License
|
|
171
32
|
|
package/binding.c
CHANGED
|
@@ -1,18 +1,134 @@
|
|
|
1
1
|
#include <assert.h>
|
|
2
2
|
#include <bare.h>
|
|
3
3
|
#include <js.h>
|
|
4
|
+
#include <stdbool.h>
|
|
4
5
|
#include <stddef.h>
|
|
5
6
|
#include <stdlib.h>
|
|
6
7
|
#include <string.h>
|
|
7
8
|
#include <url.h>
|
|
8
9
|
#include <utf.h>
|
|
9
10
|
#include <utf/string.h>
|
|
11
|
+
#include <uv.h>
|
|
10
12
|
|
|
11
13
|
// Maximum length, in bytes, of a UTF-8 string that is read into a stack buffer.
|
|
12
14
|
// Longer strings fall back to a heap allocation. This covers the vast majority
|
|
13
15
|
// of URLs without touching the heap.
|
|
14
16
|
#define BARE_URL_STACK_STRING_MAX 1024
|
|
15
17
|
|
|
18
|
+
// The number of component offsets the parser writes out.
|
|
19
|
+
#define BARE_URL_COMPONENTS_LEN (sizeof(((url_t *) 0)->components) / sizeof(url_component_t))
|
|
20
|
+
|
|
21
|
+
static bool
|
|
22
|
+
bare_url__check_argc(js_env_t *env, size_t argc, size_t expected) {
|
|
23
|
+
int err;
|
|
24
|
+
|
|
25
|
+
if (argc < expected) {
|
|
26
|
+
err = js_throw_type_errorf(env, NULL, "Expected %zu arguments, got %zu", expected, argc);
|
|
27
|
+
assert(err == 0);
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static bool
|
|
36
|
+
bare_url__check_string(js_env_t *env, js_value_t *value, const char *message) {
|
|
37
|
+
int err;
|
|
38
|
+
|
|
39
|
+
bool is_string;
|
|
40
|
+
err = js_is_string(env, value, &is_string);
|
|
41
|
+
assert(err == 0);
|
|
42
|
+
|
|
43
|
+
if (!is_string) {
|
|
44
|
+
err = js_throw_type_error(env, NULL, message);
|
|
45
|
+
assert(err == 0);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return is_string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static bool
|
|
52
|
+
bare_url__check_base(js_env_t *env, js_value_t *value, bool *has_base) {
|
|
53
|
+
int err;
|
|
54
|
+
|
|
55
|
+
js_value_type_t type;
|
|
56
|
+
err = js_typeof(env, value, &type);
|
|
57
|
+
assert(err == 0);
|
|
58
|
+
|
|
59
|
+
*has_base = type == js_string;
|
|
60
|
+
|
|
61
|
+
if (type == js_string || type == js_null || type == js_undefined) return true;
|
|
62
|
+
|
|
63
|
+
err = js_throw_type_error(env, NULL, "Base must be a string, null, or undefined");
|
|
64
|
+
assert(err == 0);
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static bool
|
|
70
|
+
bare_url__check_boolean(js_env_t *env, js_value_t *value, const char *message) {
|
|
71
|
+
int err;
|
|
72
|
+
|
|
73
|
+
bool is_boolean;
|
|
74
|
+
err = js_is_boolean(env, value, &is_boolean);
|
|
75
|
+
assert(err == 0);
|
|
76
|
+
|
|
77
|
+
if (!is_boolean) {
|
|
78
|
+
err = js_throw_type_error(env, NULL, message);
|
|
79
|
+
assert(err == 0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return is_boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static bool
|
|
86
|
+
bare_url__check_components(js_env_t *env, js_value_t *value, uint32_t **result) {
|
|
87
|
+
int err;
|
|
88
|
+
|
|
89
|
+
bool is_typedarray;
|
|
90
|
+
err = js_is_typedarray(env, value, &is_typedarray);
|
|
91
|
+
assert(err == 0);
|
|
92
|
+
|
|
93
|
+
js_typedarray_type_t type;
|
|
94
|
+
size_t len;
|
|
95
|
+
js_value_t *arraybuffer;
|
|
96
|
+
|
|
97
|
+
if (is_typedarray) {
|
|
98
|
+
err = js_get_typedarray_info(env, value, &type, (void **) result, &len, &arraybuffer, NULL);
|
|
99
|
+
if (err < 0) return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!is_typedarray || type != js_uint32array) {
|
|
103
|
+
err = js_throw_type_error(env, NULL, "Components must be a Uint32Array");
|
|
104
|
+
assert(err == 0);
|
|
105
|
+
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// A detached typed array reports no length of its own, but its data pointer
|
|
110
|
+
// is stale rather than null and so must not be written to.
|
|
111
|
+
bool is_detached;
|
|
112
|
+
err = js_is_detached_arraybuffer(env, arraybuffer, &is_detached);
|
|
113
|
+
assert(err == 0);
|
|
114
|
+
|
|
115
|
+
if (is_detached) {
|
|
116
|
+
err = js_throw_type_error(env, NULL, "Components must not be detached");
|
|
117
|
+
assert(err == 0);
|
|
118
|
+
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (len < BARE_URL_COMPONENTS_LEN) {
|
|
123
|
+
err = js_throw_range_errorf(env, NULL, "Components must have at least %zu elements, got %zu", (size_t) BARE_URL_COMPONENTS_LEN, len);
|
|
124
|
+
assert(err == 0);
|
|
125
|
+
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
|
|
16
132
|
// The UTF-8 encoding of a JavaScript string, together with whatever backs it.
|
|
17
133
|
typedef struct {
|
|
18
134
|
const utf8_t *data;
|
|
@@ -25,20 +141,23 @@ typedef struct {
|
|
|
25
141
|
// Exposes `value` as UTF-8. Strings that are stored as ASCII, which is nearly
|
|
26
142
|
// all of them, are borrowed directly from the engine without being copied.
|
|
27
143
|
// Anything else is transcoded into `stack`, or into a freshly allocated heap
|
|
28
|
-
// buffer when it does not fit.
|
|
29
|
-
// bare_url__free_string().
|
|
30
|
-
static inline
|
|
144
|
+
// buffer when it does not fit. A non-zero return leaves an exception pending.
|
|
145
|
+
// The result must be released with bare_url__free_string() either way.
|
|
146
|
+
static inline int
|
|
31
147
|
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
148
|
int err;
|
|
33
149
|
|
|
150
|
+
result->data = NULL;
|
|
151
|
+
result->len = 0;
|
|
152
|
+
result->view = NULL;
|
|
153
|
+
result->heap = NULL;
|
|
154
|
+
|
|
34
155
|
js_string_encoding_t encoding;
|
|
35
156
|
const void *data;
|
|
36
157
|
size_t len;
|
|
37
158
|
|
|
38
159
|
err = js_get_string_view(env, value, &encoding, &data, &len, &result->view);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
result->heap = NULL;
|
|
160
|
+
if (err < 0) return err;
|
|
42
161
|
|
|
43
162
|
size_t utf8_len;
|
|
44
163
|
|
|
@@ -46,7 +165,7 @@ bare_url__read_string(js_env_t *env, js_value_t *value, utf8_t *stack, size_t st
|
|
|
46
165
|
result->data = (const utf8_t *) data;
|
|
47
166
|
result->len = len;
|
|
48
167
|
|
|
49
|
-
return;
|
|
168
|
+
return 0;
|
|
50
169
|
}
|
|
51
170
|
|
|
52
171
|
if (encoding == js_latin1) {
|
|
@@ -58,7 +177,7 @@ bare_url__read_string(js_env_t *env, js_value_t *value, utf8_t *stack, size_t st
|
|
|
58
177
|
result->data = (const utf8_t *) data;
|
|
59
178
|
result->len = len;
|
|
60
179
|
|
|
61
|
-
return;
|
|
180
|
+
return 0;
|
|
62
181
|
}
|
|
63
182
|
} else {
|
|
64
183
|
assert(encoding == js_utf16le);
|
|
@@ -66,7 +185,20 @@ bare_url__read_string(js_env_t *env, js_value_t *value, utf8_t *stack, size_t st
|
|
|
66
185
|
utf8_len = utf8_length_from_utf16le((const utf16_t *) data, len);
|
|
67
186
|
}
|
|
68
187
|
|
|
69
|
-
utf8_t *buffer
|
|
188
|
+
utf8_t *buffer;
|
|
189
|
+
|
|
190
|
+
if (utf8_len <= stack_len) {
|
|
191
|
+
buffer = stack;
|
|
192
|
+
} else {
|
|
193
|
+
buffer = result->heap = malloc(utf8_len);
|
|
194
|
+
|
|
195
|
+
if (buffer == NULL) {
|
|
196
|
+
err = js_throw_error(env, uv_err_name(UV_ENOMEM), uv_strerror(UV_ENOMEM));
|
|
197
|
+
assert(err == 0);
|
|
198
|
+
|
|
199
|
+
return -1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
70
202
|
|
|
71
203
|
if (encoding == js_latin1) {
|
|
72
204
|
latin1_convert_to_utf8((const latin1_t *) data, len, buffer);
|
|
@@ -76,6 +208,8 @@ bare_url__read_string(js_env_t *env, js_value_t *value, utf8_t *stack, size_t st
|
|
|
76
208
|
|
|
77
209
|
result->data = buffer;
|
|
78
210
|
result->len = utf8_len;
|
|
211
|
+
|
|
212
|
+
return 0;
|
|
79
213
|
}
|
|
80
214
|
|
|
81
215
|
// Releases a string read with bare_url__read_string(), freeing its buffer only
|
|
@@ -87,6 +221,8 @@ bare_url__free_string(js_env_t *env, bare_url_string_t *string) {
|
|
|
87
221
|
|
|
88
222
|
free(string->heap);
|
|
89
223
|
|
|
224
|
+
if (string->view == NULL) return;
|
|
225
|
+
|
|
90
226
|
err = js_release_string_view(env, string->view);
|
|
91
227
|
assert(err == 0);
|
|
92
228
|
}
|
|
@@ -101,15 +237,25 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
101
237
|
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
|
|
102
238
|
assert(err == 0);
|
|
103
239
|
|
|
104
|
-
|
|
240
|
+
if (!bare_url__check_argc(env, argc, 4)) return NULL;
|
|
241
|
+
|
|
242
|
+
if (!bare_url__check_string(env, argv[0], "Input must be a string")) return NULL;
|
|
243
|
+
|
|
244
|
+
bool has_base;
|
|
245
|
+
if (!bare_url__check_base(env, argv[1], &has_base)) return NULL;
|
|
246
|
+
|
|
247
|
+
uint32_t *components;
|
|
248
|
+
if (!bare_url__check_components(env, argv[2], &components)) return NULL;
|
|
249
|
+
|
|
250
|
+
if (!bare_url__check_boolean(env, argv[3], "Throw must be a boolean")) return NULL;
|
|
105
251
|
|
|
106
252
|
bool should_throw;
|
|
107
253
|
err = js_get_value_bool(env, argv[3], &should_throw);
|
|
108
254
|
assert(err == 0);
|
|
109
255
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
256
|
+
// Set when the input could not be read, which leaves an exception of its own
|
|
257
|
+
// pending and so must not be reported as a parse failure on top.
|
|
258
|
+
bool threw = false;
|
|
113
259
|
|
|
114
260
|
url_t base;
|
|
115
261
|
url_init(&base);
|
|
@@ -118,16 +264,20 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
118
264
|
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
119
265
|
|
|
120
266
|
bare_url_string_t input;
|
|
121
|
-
bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
|
|
267
|
+
err = bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
|
|
122
268
|
|
|
123
|
-
err = url_parse(&base, input.data, input.len, NULL);
|
|
269
|
+
if (err == 0) err = url_parse(&base, input.data, input.len, NULL);
|
|
270
|
+
else threw = true;
|
|
124
271
|
|
|
125
272
|
bare_url__free_string(env, &input);
|
|
126
273
|
|
|
127
274
|
if (err < 0) {
|
|
128
275
|
url_destroy(&base);
|
|
129
276
|
|
|
130
|
-
if (should_throw
|
|
277
|
+
if (should_throw && !threw) {
|
|
278
|
+
err = js_throw_error(env, NULL, "Invalid base URL");
|
|
279
|
+
assert(err == 0);
|
|
280
|
+
}
|
|
131
281
|
|
|
132
282
|
return NULL;
|
|
133
283
|
}
|
|
@@ -136,12 +286,13 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
136
286
|
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
137
287
|
|
|
138
288
|
bare_url_string_t input;
|
|
139
|
-
bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
|
|
289
|
+
err = bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
|
|
140
290
|
|
|
141
291
|
url_t url;
|
|
142
292
|
url_init(&url);
|
|
143
293
|
|
|
144
|
-
err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
|
|
294
|
+
if (err == 0) err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
|
|
295
|
+
else threw = true;
|
|
145
296
|
|
|
146
297
|
bare_url__free_string(env, &input);
|
|
147
298
|
|
|
@@ -149,24 +300,26 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
149
300
|
url_destroy(&base);
|
|
150
301
|
url_destroy(&url);
|
|
151
302
|
|
|
152
|
-
if (should_throw
|
|
303
|
+
if (should_throw && !threw) {
|
|
304
|
+
err = js_throw_error(env, NULL, "Invalid URL");
|
|
305
|
+
assert(err == 0);
|
|
306
|
+
}
|
|
153
307
|
|
|
154
308
|
return NULL;
|
|
155
309
|
}
|
|
156
310
|
|
|
157
311
|
js_value_t *href;
|
|
158
312
|
err = js_create_string_latin1(env, (const latin1_t *) url.href.data, url.href.len, &href);
|
|
159
|
-
assert(err == 0);
|
|
160
|
-
|
|
161
|
-
uint32_t *components;
|
|
162
|
-
err = js_get_typedarray_info(env, argv[2], NULL, (void **) &components, NULL, NULL, NULL);
|
|
163
|
-
assert(err == 0);
|
|
164
313
|
|
|
165
|
-
|
|
314
|
+
// The offsets are only handed over once the href they refer to is, so a
|
|
315
|
+
// failure here leaves the caller's buffer as it was.
|
|
316
|
+
if (err == 0) memcpy(components, &url.components, sizeof(url.components));
|
|
166
317
|
|
|
167
318
|
url_destroy(&base);
|
|
168
319
|
url_destroy(&url);
|
|
169
320
|
|
|
321
|
+
if (err < 0) return NULL;
|
|
322
|
+
|
|
170
323
|
return href;
|
|
171
324
|
}
|
|
172
325
|
|
|
@@ -180,11 +333,14 @@ bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
180
333
|
err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
|
|
181
334
|
assert(err == 0);
|
|
182
335
|
|
|
183
|
-
|
|
336
|
+
if (!bare_url__check_argc(env, argc, 2)) return NULL;
|
|
337
|
+
|
|
338
|
+
if (!bare_url__check_string(env, argv[0], "Input must be a string")) return NULL;
|
|
184
339
|
|
|
185
340
|
bool has_base;
|
|
186
|
-
|
|
187
|
-
|
|
341
|
+
if (!bare_url__check_base(env, argv[1], &has_base)) return NULL;
|
|
342
|
+
|
|
343
|
+
bool threw = false;
|
|
188
344
|
|
|
189
345
|
url_t base;
|
|
190
346
|
url_init(&base);
|
|
@@ -193,15 +349,18 @@ bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
193
349
|
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
194
350
|
|
|
195
351
|
bare_url_string_t input;
|
|
196
|
-
bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
|
|
352
|
+
err = bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
|
|
197
353
|
|
|
198
|
-
err = url_parse(&base, input.data, input.len, NULL);
|
|
354
|
+
if (err == 0) err = url_parse(&base, input.data, input.len, NULL);
|
|
355
|
+
else threw = true;
|
|
199
356
|
|
|
200
357
|
bare_url__free_string(env, &input);
|
|
201
358
|
|
|
202
359
|
if (err < 0) {
|
|
203
360
|
url_destroy(&base);
|
|
204
361
|
|
|
362
|
+
if (threw) return NULL;
|
|
363
|
+
|
|
205
364
|
js_value_t *result;
|
|
206
365
|
err = js_get_boolean(env, false, &result);
|
|
207
366
|
assert(err == 0);
|
|
@@ -213,18 +372,21 @@ bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
|
|
|
213
372
|
utf8_t stack[BARE_URL_STACK_STRING_MAX];
|
|
214
373
|
|
|
215
374
|
bare_url_string_t input;
|
|
216
|
-
bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
|
|
375
|
+
err = bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
|
|
217
376
|
|
|
218
377
|
url_t url;
|
|
219
378
|
url_init(&url);
|
|
220
379
|
|
|
221
|
-
err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
|
|
380
|
+
if (err == 0) err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
|
|
381
|
+
else threw = true;
|
|
222
382
|
|
|
223
383
|
bare_url__free_string(env, &input);
|
|
224
384
|
|
|
225
385
|
url_destroy(&base);
|
|
226
386
|
url_destroy(&url);
|
|
227
387
|
|
|
388
|
+
if (threw) return NULL;
|
|
389
|
+
|
|
228
390
|
js_value_t *result;
|
|
229
391
|
err = js_get_boolean(env, err == 0, &result);
|
|
230
392
|
assert(err == 0);
|
package/global.d.ts
CHANGED
|
@@ -4,7 +4,19 @@ type URLConstructor = typeof url.URL
|
|
|
4
4
|
type URLSearchParamsConstructor = typeof url.URLSearchParams
|
|
5
5
|
|
|
6
6
|
declare global {
|
|
7
|
+
/**
|
|
8
|
+
* Parse `input` as a URL. If `base` is provided, `input` is resolved relative to `base`.
|
|
9
|
+
* @param input - The URL string to parse.
|
|
10
|
+
* @param base - A base URL that `input` is resolved relative to, if provided.
|
|
11
|
+
* @throws {INVALID_URL} `input` is not a valid URL.
|
|
12
|
+
*/
|
|
7
13
|
type URL = url.URL
|
|
14
|
+
/**
|
|
15
|
+
* Create a new `URLSearchParams` instance. `init` may be a query string, an iterable of `[name,
|
|
16
|
+
* value]` pairs, or an object of key-value pairs.
|
|
17
|
+
* @param init - A query string, an iterable of `[name, value]` pairs, or an object of key-value
|
|
18
|
+
* pairs to initialize the params from.
|
|
19
|
+
*/
|
|
8
20
|
type URLSearchParams = url.URLSearchParams
|
|
9
21
|
|
|
10
22
|
const URL: URLConstructor
|
package/index.d.ts
CHANGED
|
@@ -2,37 +2,92 @@ import URLError from './lib/errors'
|
|
|
2
2
|
import URLSearchParams from './lib/url-search-params'
|
|
3
3
|
|
|
4
4
|
interface URL {
|
|
5
|
+
/** The full serialized URL string. Setting this property reparses the URL. */
|
|
5
6
|
href: string
|
|
7
|
+
/** The URL scheme followed by `':'`, for example `'https:'`. */
|
|
6
8
|
protocol: string
|
|
9
|
+
/** The username portion of the URL, or an empty string. */
|
|
7
10
|
username: string
|
|
11
|
+
/** The password portion of the URL, or an empty string. */
|
|
8
12
|
password: string
|
|
13
|
+
/** The hostname and port, for example `'example.com:8080'`. */
|
|
9
14
|
host: string
|
|
15
|
+
/** The hostname without the port. */
|
|
10
16
|
hostname: string
|
|
17
|
+
/** The port as a string, or an empty string if not present. */
|
|
11
18
|
port: string
|
|
19
|
+
/** The path portion of the URL. */
|
|
12
20
|
pathname: string
|
|
21
|
+
/** The query string including the leading `'?'`, or an empty string. */
|
|
13
22
|
search: string
|
|
23
|
+
/**
|
|
24
|
+
* A `URLSearchParams` object for the query string. Mutations to the params are reflected in the
|
|
25
|
+
* URL.
|
|
26
|
+
*/
|
|
14
27
|
searchParams: URLSearchParams
|
|
28
|
+
/** The fragment including the leading `'#'`, or an empty string. */
|
|
15
29
|
hash: string
|
|
16
30
|
|
|
31
|
+
/** Returns the serialized string form. */
|
|
17
32
|
toString(): string
|
|
33
|
+
/** Returns the serialized string form. Suitable for JSON serialization. */
|
|
18
34
|
toJSON(): string
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
declare class URL {
|
|
38
|
+
/**
|
|
39
|
+
* Parse `input` as a URL. If `base` is provided, `input` is resolved relative to `base`.
|
|
40
|
+
* @param input - The URL string to parse.
|
|
41
|
+
* @param base - A base URL that `input` is resolved relative to, if provided.
|
|
42
|
+
* @throws {INVALID_URL} `input` is not a valid URL.
|
|
43
|
+
*/
|
|
22
44
|
constructor(input: string | URL, base?: string | URL)
|
|
23
45
|
}
|
|
24
46
|
|
|
25
47
|
declare namespace URL {
|
|
48
|
+
/**
|
|
49
|
+
* Return `true` if `value` is a `URL` instance.
|
|
50
|
+
* @param value - The value to test.
|
|
51
|
+
*/
|
|
26
52
|
export function isURL(value: unknown): value is URL
|
|
27
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Return `true` if `value` is a `URLSearchParams` instance.
|
|
56
|
+
* @param value - The value to test.
|
|
57
|
+
*/
|
|
28
58
|
export function isURLSearchParams(value: unknown): value is URLSearchParams
|
|
29
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Parse `input` as a URL without throwing.
|
|
62
|
+
* @param input - The URL string to parse.
|
|
63
|
+
* @param base - A base URL that `input` is resolved relative to, if provided.
|
|
64
|
+
* @returns A `URL` instance if `input` parses successfully, or `null` on failure.
|
|
65
|
+
*/
|
|
30
66
|
export function parse(input: string, base?: string | URL): URL | null
|
|
31
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Return `true` if `input` can be parsed as a valid URL, optionally relative to `base`.
|
|
70
|
+
* @param input - The URL string to test.
|
|
71
|
+
* @param base - A base URL that `input` is resolved relative to, if provided.
|
|
72
|
+
*/
|
|
32
73
|
export function canParse(input: string, base?: string | URL): boolean
|
|
33
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Convert a `file:` URL to a platform-specific file path. `url` may be a `URL` instance or a
|
|
77
|
+
* string.
|
|
78
|
+
* @param url - The `file:` URL to convert, as a `URL` instance or a string.
|
|
79
|
+
* @throws {INVALID_URL_SCHEME} the URL does not use the `file:` protocol.
|
|
80
|
+
* @throws {INVALID_FILE_URL_HOST} (non-Windows) the URL has a host other than empty or
|
|
81
|
+
* `'localhost'`.
|
|
82
|
+
* @throws {INVALID_FILE_URL_PATH} the URL path contains an encoded path-separator or NUL
|
|
83
|
+
* character, or, on Windows, is not an absolute drive path.
|
|
84
|
+
*/
|
|
34
85
|
export function fileURLToPath(url: URL | string): string
|
|
35
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Convert a platform-specific file path to a `file:` URL.
|
|
89
|
+
* @param pathname - The platform-specific file path to convert.
|
|
90
|
+
*/
|
|
36
91
|
export function pathToFileURL(pathname: string): URL
|
|
37
92
|
|
|
38
93
|
export { URL, type URLError, URLError as errors, URLSearchParams }
|
package/index.js
CHANGED
|
@@ -17,6 +17,53 @@ const components = new Uint32Array(8)
|
|
|
17
17
|
// The value used for a component that is not present in the URL.
|
|
18
18
|
const unset = 0xffffffff
|
|
19
19
|
|
|
20
|
+
// The schemes the parser treats specially. A URL cannot be switched between a
|
|
21
|
+
// special and a non-special scheme, and a backslash only terminates a host for
|
|
22
|
+
// the former.
|
|
23
|
+
const special = new Set(['ftp', 'file', 'http', 'https', 'ws', 'wss'])
|
|
24
|
+
|
|
25
|
+
// https://url.spec.whatwg.org/#scheme-start-state
|
|
26
|
+
const scheme = /^[a-z][a-z0-9+\-.]*$/
|
|
27
|
+
|
|
28
|
+
// ASCII tab and newline are removed from input before it is parsed rather than
|
|
29
|
+
// percent-encoded like the other C0 controls.
|
|
30
|
+
const whitespaceAll = /[\t\n\r]/g
|
|
31
|
+
|
|
32
|
+
// The characters that terminate a host, and so bound the value the host and
|
|
33
|
+
// hostname setters accept.
|
|
34
|
+
const hostEnd = /[/\\?#]/
|
|
35
|
+
const hostEndOpaque = /[/?#]/
|
|
36
|
+
|
|
37
|
+
// The delimiters that would let a setter's value escape the component it is
|
|
38
|
+
// spliced into. Everything else is left to the reparse, which applies the full
|
|
39
|
+
// percent-encode set for the component. As elsewhere in this package, each set
|
|
40
|
+
// needs two patterns because test() advances a global pattern's lastIndex.
|
|
41
|
+
//
|
|
42
|
+
// Credentials are not run through the parser and so keep their tabs and
|
|
43
|
+
// newlines, percent-encoded, rather than having them stripped.
|
|
44
|
+
const userinfoDelimiter = /[\t\n\r/\\?#@:]/
|
|
45
|
+
const userinfoDelimiterAll = /[\t\n\r/\\?#@:]/g
|
|
46
|
+
|
|
47
|
+
const pathDelimiter = /[?#]/
|
|
48
|
+
const pathDelimiterAll = /[?#]/g
|
|
49
|
+
|
|
50
|
+
// A leading or trailing run of C0 control or space in a value that ends up at
|
|
51
|
+
// either end of the href. The parser strips those, but only when parsing a URL
|
|
52
|
+
// as a whole, so a setter has to encode its own.
|
|
53
|
+
const edges = /^[\u0000-\u0020]+|[\u0000-\u0020]+$/g
|
|
54
|
+
|
|
55
|
+
const escapes = {
|
|
56
|
+
'\t': '%09',
|
|
57
|
+
'\n': '%0A',
|
|
58
|
+
'\r': '%0D',
|
|
59
|
+
'/': '%2F',
|
|
60
|
+
'\\': '%5C',
|
|
61
|
+
':': '%3A',
|
|
62
|
+
'?': '%3F',
|
|
63
|
+
'@': '%40',
|
|
64
|
+
'#': '%23'
|
|
65
|
+
}
|
|
66
|
+
|
|
20
67
|
// The characters that pathToFileURL() has to percent-encode itself. A backslash
|
|
21
68
|
// is a path separator on Windows and so is left alone there.
|
|
22
69
|
const reserved = isWindows ? /[%#?\n\r\t]/ : /[%#?\n\r\t\\]/
|
|
@@ -57,7 +104,9 @@ class URL {
|
|
|
57
104
|
}
|
|
58
105
|
|
|
59
106
|
set href(value) {
|
|
60
|
-
|
|
107
|
+
// Unlike every other setter, the href setter reports a parse failure rather
|
|
108
|
+
// than leaving the URL untouched.
|
|
109
|
+
this._parse(String(value), null, true)
|
|
61
110
|
|
|
62
111
|
if (this._params) this._params._parse(this.search)
|
|
63
112
|
}
|
|
@@ -69,7 +118,27 @@ class URL {
|
|
|
69
118
|
}
|
|
70
119
|
|
|
71
120
|
set protocol(value) {
|
|
72
|
-
|
|
121
|
+
value = strip(String(value))
|
|
122
|
+
|
|
123
|
+
const end = value.indexOf(':')
|
|
124
|
+
|
|
125
|
+
if (end !== -1) value = value.slice(0, end)
|
|
126
|
+
|
|
127
|
+
value = value.toLowerCase()
|
|
128
|
+
|
|
129
|
+
if (!scheme.test(value)) return
|
|
130
|
+
|
|
131
|
+
const current = this._slice(0, this._schemeEnd)
|
|
132
|
+
|
|
133
|
+
if (special.has(current) !== special.has(value)) return
|
|
134
|
+
|
|
135
|
+
if (value === 'file' && (this.username || this.password || this.port)) {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (current === 'file' && this._hostStart === this._hostEnd) return
|
|
140
|
+
|
|
141
|
+
this._update(this._replace(value, 0, this._schemeEnd))
|
|
73
142
|
}
|
|
74
143
|
|
|
75
144
|
// https://url.spec.whatwg.org/#dom-url-username
|
|
@@ -83,7 +152,9 @@ class URL {
|
|
|
83
152
|
return
|
|
84
153
|
}
|
|
85
154
|
|
|
86
|
-
|
|
155
|
+
value = encodeUserinfo(String(value))
|
|
156
|
+
|
|
157
|
+
if (!hasCredentials(this)) value += '@'
|
|
87
158
|
|
|
88
159
|
this._update(this._replace(value, this._schemeEnd + 3 /* :// */, this._usernameEnd))
|
|
89
160
|
}
|
|
@@ -99,20 +170,16 @@ class URL {
|
|
|
99
170
|
return
|
|
100
171
|
}
|
|
101
172
|
|
|
102
|
-
|
|
103
|
-
let end = this._hostStart - 1 /* @ */
|
|
173
|
+
value = ':' + encodeUserinfo(String(value))
|
|
104
174
|
|
|
105
|
-
|
|
106
|
-
value = ':' + value
|
|
107
|
-
start--
|
|
108
|
-
}
|
|
175
|
+
let end = this._hostStart - 1 /* @ */
|
|
109
176
|
|
|
110
|
-
if (this
|
|
177
|
+
if (!hasCredentials(this)) {
|
|
111
178
|
value += '@'
|
|
112
|
-
end
|
|
179
|
+
end = this._usernameEnd
|
|
113
180
|
}
|
|
114
181
|
|
|
115
|
-
this._update(this._replace(value,
|
|
182
|
+
this._update(this._replace(value, this._usernameEnd, end))
|
|
116
183
|
}
|
|
117
184
|
|
|
118
185
|
// https://url.spec.whatwg.org/#dom-url-host
|
|
@@ -126,9 +193,38 @@ class URL {
|
|
|
126
193
|
return
|
|
127
194
|
}
|
|
128
195
|
|
|
129
|
-
this.
|
|
130
|
-
|
|
131
|
-
)
|
|
196
|
+
const protocol = this._slice(0, this._schemeEnd)
|
|
197
|
+
|
|
198
|
+
value = truncateHost(protocol, String(value))
|
|
199
|
+
|
|
200
|
+
// An `@` would make the reparse read the value as credentials rather than
|
|
201
|
+
// as a host, so it is rejected outright.
|
|
202
|
+
if (value.includes('@')) return
|
|
203
|
+
|
|
204
|
+
const separator = portSeparator(value)
|
|
205
|
+
|
|
206
|
+
let end = this._hostEnd
|
|
207
|
+
|
|
208
|
+
// A port in the value is parsed separately so that an invalid one leaves
|
|
209
|
+
// the existing port in place rather than rejecting the host along with it.
|
|
210
|
+
if (separator !== -1) {
|
|
211
|
+
// A file URL cannot have a port, so a value carrying one is rejected
|
|
212
|
+
// rather than split.
|
|
213
|
+
if (protocol === 'file') return
|
|
214
|
+
|
|
215
|
+
const port = parsePort(value.slice(separator + 1))
|
|
216
|
+
|
|
217
|
+
value = value.slice(0, separator)
|
|
218
|
+
|
|
219
|
+
if (port !== null) {
|
|
220
|
+
value += port
|
|
221
|
+
end = this._pathStart
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (value === '' && cannotHaveEmptyHost(protocol)) return
|
|
226
|
+
|
|
227
|
+
this._update(this._replace(value, this._hostStart, end))
|
|
132
228
|
}
|
|
133
229
|
|
|
134
230
|
// https://url.spec.whatwg.org/#dom-url-hostname
|
|
@@ -142,6 +238,17 @@ class URL {
|
|
|
142
238
|
return
|
|
143
239
|
}
|
|
144
240
|
|
|
241
|
+
const protocol = this._slice(0, this._schemeEnd)
|
|
242
|
+
|
|
243
|
+
value = truncateHost(protocol, String(value))
|
|
244
|
+
|
|
245
|
+
// A port cannot be set through this setter, and a value that carries one is
|
|
246
|
+
// rejected outright rather than truncated. An `@` would make the reparse
|
|
247
|
+
// read the value as credentials rather than as a host.
|
|
248
|
+
if (value.includes('@') || portSeparator(value) !== -1) return
|
|
249
|
+
|
|
250
|
+
if (value === '' && cannotHaveEmptyHost(protocol)) return
|
|
251
|
+
|
|
145
252
|
this._update(this._replace(value, this._hostStart, this._hostEnd))
|
|
146
253
|
}
|
|
147
254
|
|
|
@@ -156,14 +263,15 @@ class URL {
|
|
|
156
263
|
return
|
|
157
264
|
}
|
|
158
265
|
|
|
159
|
-
|
|
266
|
+
value = strip(String(value))
|
|
160
267
|
|
|
161
|
-
if (
|
|
162
|
-
value =
|
|
163
|
-
|
|
268
|
+
if (value !== '') {
|
|
269
|
+
value = parsePort(value)
|
|
270
|
+
|
|
271
|
+
if (value === null) return
|
|
164
272
|
}
|
|
165
273
|
|
|
166
|
-
this._update(this._replace(value,
|
|
274
|
+
this._update(this._replace(value, this._hostEnd, this._pathStart))
|
|
167
275
|
}
|
|
168
276
|
|
|
169
277
|
// https://url.spec.whatwg.org/#dom-url-pathname
|
|
@@ -177,7 +285,11 @@ class URL {
|
|
|
177
285
|
return
|
|
178
286
|
}
|
|
179
287
|
|
|
180
|
-
|
|
288
|
+
value = encodePath(encodeEdges(String(value)))
|
|
289
|
+
|
|
290
|
+
// An empty path is left alone, as only a special scheme is required to have
|
|
291
|
+
// one and the reparse inserts it there.
|
|
292
|
+
if (value !== '' && value[0] !== '/' && value[0] !== '\\') {
|
|
181
293
|
value = '/' + value
|
|
182
294
|
}
|
|
183
295
|
|
|
@@ -191,13 +303,17 @@ class URL {
|
|
|
191
303
|
}
|
|
192
304
|
|
|
193
305
|
set search(value) {
|
|
194
|
-
|
|
306
|
+
value = String(value)
|
|
307
|
+
|
|
308
|
+
if (value !== '') {
|
|
309
|
+
if (value[0] === '?') value = value.slice(1)
|
|
310
|
+
|
|
311
|
+
value = '?' + encodeQuery(encodeEdges(value))
|
|
312
|
+
}
|
|
195
313
|
|
|
196
314
|
this._update(
|
|
197
315
|
this._replace(value, this._queryStart - 1 /* ? */, this._fragmentStart - 1 /* # */)
|
|
198
316
|
)
|
|
199
|
-
|
|
200
|
-
if (this._params) this._params._parse(this.search)
|
|
201
317
|
}
|
|
202
318
|
|
|
203
319
|
// https://url.spec.whatwg.org/#dom-url-searchparams
|
|
@@ -217,7 +333,15 @@ class URL {
|
|
|
217
333
|
}
|
|
218
334
|
|
|
219
335
|
set hash(value) {
|
|
220
|
-
|
|
336
|
+
value = String(value)
|
|
337
|
+
|
|
338
|
+
// The fragment runs to the end of the URL, so nothing in it can escape into
|
|
339
|
+
// another component and no delimiter needs encoding here.
|
|
340
|
+
if (value !== '') {
|
|
341
|
+
if (value[0] === '#') value = value.slice(1)
|
|
342
|
+
|
|
343
|
+
value = '#' + encodeEdges(value)
|
|
344
|
+
}
|
|
221
345
|
|
|
222
346
|
this._update(this._replace(value, this._fragmentStart - 1 /* # */))
|
|
223
347
|
}
|
|
@@ -262,7 +386,7 @@ class URL {
|
|
|
262
386
|
try {
|
|
263
387
|
href = binding.parse(input, base || null, components, shouldThrow)
|
|
264
388
|
} catch (err) {
|
|
265
|
-
if (err instanceof TypeError) throw err
|
|
389
|
+
if (err instanceof TypeError || err.code !== undefined) throw err
|
|
266
390
|
|
|
267
391
|
throw errors.INVALID_URL(`Invalid URL '${input}'`, input)
|
|
268
392
|
}
|
|
@@ -290,7 +414,11 @@ class URL {
|
|
|
290
414
|
this._parse(input, null, true)
|
|
291
415
|
} catch (err) {
|
|
292
416
|
if (err instanceof TypeError) throw err
|
|
417
|
+
|
|
418
|
+
return
|
|
293
419
|
}
|
|
420
|
+
|
|
421
|
+
if (this._params) this._params._parse(this.search)
|
|
294
422
|
}
|
|
295
423
|
}
|
|
296
424
|
|
|
@@ -306,6 +434,93 @@ function cannotHaveCredentialsOrPort(url) {
|
|
|
306
434
|
return url.hostname === '' || url.protocol === 'file:'
|
|
307
435
|
}
|
|
308
436
|
|
|
437
|
+
// Whether the URL carries a userinfo section, and so an `@` before its host.
|
|
438
|
+
function hasCredentials(url) {
|
|
439
|
+
return url._hostStart !== url._usernameEnd
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Almost no input contains any of these, so it is worth ruling all three out
|
|
443
|
+
// before rewriting anything.
|
|
444
|
+
function strip(value) {
|
|
445
|
+
if (value.indexOf('\t') === -1 && value.indexOf('\n') === -1 && value.indexOf('\r') === -1) {
|
|
446
|
+
return value
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return value.replace(whitespaceAll, '')
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// https://url.spec.whatwg.org/#host-state
|
|
453
|
+
//
|
|
454
|
+
// Host parsing stops at the first character that starts another component, so
|
|
455
|
+
// anything from there on is dropped rather than spliced into the host.
|
|
456
|
+
function truncateHost(protocol, value) {
|
|
457
|
+
const end = value.search(special.has(protocol) ? hostEnd : hostEndOpaque)
|
|
458
|
+
|
|
459
|
+
return end === -1 ? value : value.slice(0, end)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Only a special scheme other than file has to have a host.
|
|
463
|
+
function cannotHaveEmptyHost(protocol) {
|
|
464
|
+
return protocol !== 'file' && special.has(protocol)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// The index of the colon that separates a host from its port, disregarding the
|
|
468
|
+
// colons of an IPv6 address, or -1 if the host carries no port.
|
|
469
|
+
function portSeparator(host) {
|
|
470
|
+
return host.indexOf(':', host[0] === '[' ? host.indexOf(']') : 0)
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// https://url.spec.whatwg.org/#port-state
|
|
474
|
+
//
|
|
475
|
+
// Parsing stops at the first character that is not a digit, so a value with no
|
|
476
|
+
// leading digits, or one that overflows, leaves the port as it was.
|
|
477
|
+
function parsePort(value) {
|
|
478
|
+
value = /^\d*/.exec(value)[0]
|
|
479
|
+
|
|
480
|
+
if (value === '' || Number(value) > 65535) return null
|
|
481
|
+
|
|
482
|
+
return ':' + value
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function encodeUserinfo(value) {
|
|
486
|
+
if (!userinfoDelimiter.test(value)) return value
|
|
487
|
+
|
|
488
|
+
return value.replace(userinfoDelimiterAll, (match) => escapes[match])
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function encodePath(value) {
|
|
492
|
+
if (!pathDelimiter.test(value)) return value
|
|
493
|
+
|
|
494
|
+
return value.replace(pathDelimiterAll, (match) => escapes[match])
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function encodeQuery(value) {
|
|
498
|
+
if (value.indexOf('#') === -1) return value
|
|
499
|
+
|
|
500
|
+
return value.replaceAll('#', '%23')
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Percent-encodes a leading or trailing run of C0 control or space, which the
|
|
504
|
+
// parser would otherwise strip from a value that lands at either end of the
|
|
505
|
+
// href. Every component that can end a URL encodes them anyway, so this only
|
|
506
|
+
// brings the encoding forward.
|
|
507
|
+
function encodeEdges(value) {
|
|
508
|
+
const len = value.length
|
|
509
|
+
|
|
510
|
+
if (len === 0) return value
|
|
511
|
+
if (value.charCodeAt(0) > 0x20 && value.charCodeAt(len - 1) > 0x20) return value
|
|
512
|
+
|
|
513
|
+
return value.replace(edges, (match) => {
|
|
514
|
+
let encoded = ''
|
|
515
|
+
|
|
516
|
+
for (let i = 0, n = match.length; i < n; i++) {
|
|
517
|
+
encoded += '%' + match.charCodeAt(i).toString(16).padStart(2, '0').toUpperCase()
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return encoded
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
|
|
309
524
|
exports.URL = URL
|
|
310
525
|
exports.URLSearchParams = URLSearchParams
|
|
311
526
|
|
|
@@ -1,22 +1,65 @@
|
|
|
1
1
|
interface URLSearchParams extends Iterable<[name: string, value: string]> {
|
|
2
|
+
/** The total number of search parameters. */
|
|
2
3
|
readonly size: number
|
|
3
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Append a new `name`/`value` pair.
|
|
7
|
+
* @param name - The parameter name.
|
|
8
|
+
* @param value - The parameter value.
|
|
9
|
+
*/
|
|
4
10
|
append(name: string, value: string): void
|
|
11
|
+
/**
|
|
12
|
+
* Remove all pairs with `name`. If `value` is provided, only pairs with both the matching `name`
|
|
13
|
+
* and `value` are removed.
|
|
14
|
+
* @param name - The parameter name to remove.
|
|
15
|
+
* @param value - If provided, only pairs also matching this value are removed.
|
|
16
|
+
*/
|
|
5
17
|
delete(name: string, value?: string): void
|
|
18
|
+
/**
|
|
19
|
+
* Return the first value for `name`, or `null` if not present.
|
|
20
|
+
* @param name - The parameter name to look up.
|
|
21
|
+
*/
|
|
6
22
|
get(name: string): string | undefined
|
|
23
|
+
/**
|
|
24
|
+
* Return all values for `name` as an array.
|
|
25
|
+
* @param name - The parameter name to look up.
|
|
26
|
+
*/
|
|
7
27
|
getAll(name: string): string[]
|
|
28
|
+
/**
|
|
29
|
+
* Return `true` if a pair with `name` exists. If `value` is provided, the pair must also match
|
|
30
|
+
* `value`.
|
|
31
|
+
* @param name - The parameter name to check.
|
|
32
|
+
* @param value - If provided, the pair must also match this value.
|
|
33
|
+
*/
|
|
8
34
|
has(name: string, value?: string): boolean
|
|
35
|
+
/**
|
|
36
|
+
* Set the value for `name`, replacing any existing pairs with that name.
|
|
37
|
+
* @param name - The parameter name.
|
|
38
|
+
* @param value - The value to set.
|
|
39
|
+
*/
|
|
9
40
|
set(name: string, value: string): void
|
|
10
41
|
|
|
42
|
+
/** Returns the serialized string form. */
|
|
11
43
|
toString(): string
|
|
44
|
+
/** Returns the serialized string form. Suitable for JSON serialization. */
|
|
12
45
|
toJSON(): string
|
|
13
46
|
}
|
|
14
47
|
|
|
15
48
|
declare class URLSearchParams {
|
|
49
|
+
/**
|
|
50
|
+
* Create a new `URLSearchParams` instance. `init` may be a query string, an iterable of `[name,
|
|
51
|
+
* value]` pairs, or an object of key-value pairs.
|
|
52
|
+
* @param init - A query string, an iterable of `[name, value]` pairs, or an object of key-value
|
|
53
|
+
* pairs to initialize the params from.
|
|
54
|
+
*/
|
|
16
55
|
constructor(init: string | Record<string, string> | Iterable<[string, string]>)
|
|
17
56
|
}
|
|
18
57
|
|
|
19
58
|
declare namespace URLSearchParams {
|
|
59
|
+
/**
|
|
60
|
+
* Return `true` if `value` is a `URLSearchParams` instance.
|
|
61
|
+
* @param value - The value to test.
|
|
62
|
+
*/
|
|
20
63
|
export function isURLSearchParams(value: unknown): value is URLSearchParams
|
|
21
64
|
}
|
|
22
65
|
|
package/lib/url-search-params.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const kind = Symbol.for('bare.url.search-params.kind')
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
// The URL each instance writes back to, if any. Kept module private so that the
|
|
4
|
+
// setter it drives cannot be pointed at an arbitrary object.
|
|
5
|
+
const urls = new WeakMap()
|
|
5
6
|
|
|
7
|
+
class URLSearchParams {
|
|
6
8
|
static get [kind]() {
|
|
7
9
|
return 0 // Compatibility version
|
|
8
10
|
}
|
|
@@ -11,7 +13,7 @@ class URLSearchParams {
|
|
|
11
13
|
constructor(init, url = null) {
|
|
12
14
|
this._params = null
|
|
13
15
|
|
|
14
|
-
if (url)
|
|
16
|
+
if (url) urls.set(this, url)
|
|
15
17
|
|
|
16
18
|
if (typeof init === 'string') {
|
|
17
19
|
this._parse(init)
|
|
@@ -140,7 +142,7 @@ class URLSearchParams {
|
|
|
140
142
|
|
|
141
143
|
// https://url.spec.whatwg.org/#concept-urlsearchparams-update
|
|
142
144
|
_update() {
|
|
143
|
-
const url =
|
|
145
|
+
const url = urls.get(this)
|
|
144
146
|
|
|
145
147
|
if (url === undefined) return
|
|
146
148
|
|
|
@@ -234,8 +236,11 @@ const escapes = {
|
|
|
234
236
|
'~': '%7E'
|
|
235
237
|
}
|
|
236
238
|
|
|
237
|
-
// A surrogate that is not part of
|
|
238
|
-
|
|
239
|
+
// A surrogate pair, or a surrogate that is not part of one and so encodes no
|
|
240
|
+
// scalar value. Pairs are matched first so that only the capture group can hold
|
|
241
|
+
// a lone surrogate; a lookbehind would say this more directly but isn't
|
|
242
|
+
// supported by every engine.
|
|
243
|
+
const lone = /[\ud800-\udbff][\udc00-\udfff]|([\ud800-\udbff]|[\udc00-\udfff])/g
|
|
239
244
|
|
|
240
245
|
function encode(component) {
|
|
241
246
|
if (unencoded.test(component)) return component
|
|
@@ -249,7 +254,9 @@ function encode(component) {
|
|
|
249
254
|
} catch {
|
|
250
255
|
// encodeURIComponent() rejects lone surrogates, whereas the UTF-8 encoder the
|
|
251
256
|
// serializer is defined in terms of replaces them with U+FFFD.
|
|
252
|
-
encoded = encodeURIComponent(
|
|
257
|
+
encoded = encodeURIComponent(
|
|
258
|
+
component.replace(lone, (match, single) => (single === undefined ? match : '\ufffd'))
|
|
259
|
+
)
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
if (encoded.includes('%20')) encoded = encoded.replaceAll('%20', '+')
|
package/package.json
CHANGED
|
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
|