bare-url 2.5.1 → 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 CHANGED
@@ -5,109 +5,90 @@
5
5
  #include <stdlib.h>
6
6
  #include <string.h>
7
7
  #include <url.h>
8
- #include <url/character-set.h>
9
- #include <url/percent-encode.h>
10
8
  #include <utf.h>
11
9
  #include <utf/string.h>
12
10
 
13
- // https://url.spec.whatwg.org/#concept-urlencoded-byte-serializer
14
- //
15
- // Everything except ASCII alphanumerics and `*-._` is percent-encoded. A space
16
- // (0x20) is a special case, encoded as `+` rather than `%20`, so it is left out
17
- // of the set and handled separately below.
18
- static url_character_set_t bare_url__form_urlencoded_percent_encode_set = {
19
- // 00 01 02 03 04 05 06 07
20
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
21
- // 08 09 0a 0b 0c 0d 0e 0f
22
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
23
- // 10 11 12 13 14 15 16 17
24
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
25
- // 18 19 1a 1b 1c 1d 1e 1f
26
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
27
- // 20 21 22 23 24 25 26 27
28
- 0x00 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
29
- // 28 29 2a 2b 2c 2d 2e 2f
30
- 0x01 | 0x02 | 0x00 | 0x08 | 0x10 | 0x00 | 0x00 | 0x80,
31
- // 30 31 32 33 34 35 36 37
32
- 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
33
- // 38 39 3a 3b 3c 3d 3e 3f
34
- 0x00 | 0x00 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
35
- // 40 41 42 43 44 45 46 47
36
- 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
37
- // 48 49 4a 4b 4c 4d 4e 4f
38
- 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
39
- // 50 51 52 53 54 55 56 57
40
- 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
41
- // 58 59 5a 5b 5c 5d 5e 5f
42
- 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x00,
43
- // 60 61 62 63 64 65 66 67
44
- 0x01 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
45
- // 68 69 6a 6b 6c 6d 6e 6f
46
- 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
47
- // 70 71 72 73 74 75 76 77
48
- 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00 | 0x00,
49
- // 78 79 7a 7b 7c 7d 7e 7f
50
- 0x00 | 0x00 | 0x00 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
51
- // 80 81 82 83 84 85 86 87
52
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
53
- // 88 89 8a 8b 8c 8d 8e 8f
54
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
55
- // 90 91 92 93 94 95 96 97
56
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
57
- // 98 99 9a 9b 9c 9d 9e 9f
58
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
59
- // a0 a1 a2 a3 a4 a5 a6 a7
60
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
61
- // a8 a9 aa ab ac ad ae af
62
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
63
- // b0 b1 b2 b3 b4 b5 b6 b7
64
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
65
- // b8 b9 ba bb bc bd be bf
66
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
67
- // c0 c1 c2 c3 c4 c5 c6 c7
68
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
69
- // c8 c9 ca cb cc cd ce cf
70
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
71
- // d0 d1 d2 d3 d4 d5 d6 d7
72
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
73
- // d8 d9 da db dc dd de df
74
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
75
- // e0 e1 e2 e3 e4 e5 e6 e7
76
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
77
- // e8 e9 ea eb ec ed ee ef
78
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
79
- // f0 f1 f2 f3 f4 f5 f6 f7
80
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
81
- // f8 f9 fa fb fc fd fe ff
82
- 0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40 | 0x80,
83
- };
84
-
85
- // https://url.spec.whatwg.org/#concept-urlencoded-serializer
86
- static void
87
- bare_url__serialize_form_urlencoded_component(js_env_t *env, js_value_t *value, utf8_string_t *result) {
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) {
88
32
  int err;
89
33
 
34
+ js_string_encoding_t encoding;
35
+ const void *data;
90
36
  size_t len;
91
- err = js_get_value_string_utf8(env, value, NULL, 0, &len);
92
- assert(err == 0);
93
37
 
94
- utf8_t *input = malloc(len);
95
- err = js_get_value_string_utf8(env, value, input, len, NULL);
38
+ err = js_get_string_view(env, value, &encoding, &data, &len, &result->view);
96
39
  assert(err == 0);
97
40
 
98
- for (size_t i = 0; i < len; i++) {
99
- utf8_t c = input[i];
41
+ result->heap = NULL;
100
42
 
101
- if (c == 0x20 /* space */) {
102
- err = utf8_string_append_character(result, '+');
103
- } else {
104
- err = url__percent_encode_character(c, bare_url__form_urlencoded_percent_encode_set, result);
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;
105
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));
106
70
 
107
- assert(err == 0);
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);
108
75
  }
109
76
 
110
- free(input);
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);
111
92
  }
112
93
 
113
94
  static js_value_t *
@@ -134,17 +115,14 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
134
115
  url_init(&base);
135
116
 
136
117
  if (has_base) {
137
- size_t len;
138
- err = js_get_value_string_utf8(env, argv[1], NULL, 0, &len);
139
- assert(err == 0);
118
+ utf8_t stack[BARE_URL_STACK_STRING_MAX];
140
119
 
141
- utf8_t *input = malloc(len);
142
- err = js_get_value_string_utf8(env, argv[1], input, len, NULL);
143
- assert(err == 0);
120
+ bare_url_string_t input;
121
+ bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
144
122
 
145
- err = url_parse(&base, input, len, NULL);
123
+ err = url_parse(&base, input.data, input.len, NULL);
146
124
 
147
- free(input);
125
+ bare_url__free_string(env, &input);
148
126
 
149
127
  if (err < 0) {
150
128
  url_destroy(&base);
@@ -155,22 +133,17 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
155
133
  }
156
134
  }
157
135
 
158
- size_t len;
159
- err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
160
- assert(err == 0);
161
-
162
- utf8_t *input = malloc(len);
163
- err = js_get_value_string_utf8(env, argv[0], input, len, NULL);
164
- assert(err == 0);
136
+ utf8_t stack[BARE_URL_STACK_STRING_MAX];
165
137
 
166
- js_value_t *handle;
138
+ bare_url_string_t input;
139
+ bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
167
140
 
168
141
  url_t url;
169
142
  url_init(&url);
170
143
 
171
- err = url_parse(&url, input, len, has_base ? &base : NULL);
144
+ err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
172
145
 
173
- free(input);
146
+ bare_url__free_string(env, &input);
174
147
 
175
148
  if (err < 0) {
176
149
  url_destroy(&base);
@@ -182,7 +155,7 @@ bare_url_parse(js_env_t *env, js_callback_info_t *info) {
182
155
  }
183
156
 
184
157
  js_value_t *href;
185
- err = js_create_string_utf8(env, url.href.data, url.href.len, &href);
158
+ err = js_create_string_latin1(env, (const latin1_t *) url.href.data, url.href.len, &href);
186
159
  assert(err == 0);
187
160
 
188
161
  uint32_t *components;
@@ -217,17 +190,14 @@ bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
217
190
  url_init(&base);
218
191
 
219
192
  if (has_base) {
220
- size_t len;
221
- err = js_get_value_string_utf8(env, argv[1], NULL, 0, &len);
222
- assert(err == 0);
193
+ utf8_t stack[BARE_URL_STACK_STRING_MAX];
223
194
 
224
- utf8_t *input = malloc(len);
225
- err = js_get_value_string_utf8(env, argv[1], input, len, NULL);
226
- assert(err == 0);
195
+ bare_url_string_t input;
196
+ bare_url__read_string(env, argv[1], stack, sizeof(stack), &input);
227
197
 
228
- err = url_parse(&base, input, len, NULL);
198
+ err = url_parse(&base, input.data, input.len, NULL);
229
199
 
230
- free(input);
200
+ bare_url__free_string(env, &input);
231
201
 
232
202
  if (err < 0) {
233
203
  url_destroy(&base);
@@ -240,20 +210,17 @@ bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
240
210
  }
241
211
  }
242
212
 
243
- size_t len;
244
- err = js_get_value_string_utf8(env, argv[0], NULL, 0, &len);
245
- assert(err == 0);
213
+ utf8_t stack[BARE_URL_STACK_STRING_MAX];
246
214
 
247
- utf8_t *input = malloc(len);
248
- err = js_get_value_string_utf8(env, argv[0], input, len, NULL);
249
- assert(err == 0);
215
+ bare_url_string_t input;
216
+ bare_url__read_string(env, argv[0], stack, sizeof(stack), &input);
250
217
 
251
218
  url_t url;
252
219
  url_init(&url);
253
220
 
254
- err = url_parse(&url, input, len, has_base ? &base : NULL);
221
+ err = url_parse(&url, input.data, input.len, has_base ? &base : NULL);
255
222
 
256
- free(input);
223
+ bare_url__free_string(env, &input);
257
224
 
258
225
  url_destroy(&base);
259
226
  url_destroy(&url);
@@ -265,56 +232,6 @@ bare_url_can_parse(js_env_t *env, js_callback_info_t *info) {
265
232
  return result;
266
233
  }
267
234
 
268
- static js_value_t *
269
- bare_url_serialize_form_urlencoded(js_env_t *env, js_callback_info_t *info) {
270
- int err;
271
-
272
- size_t argc = 1;
273
- js_value_t *argv[1];
274
-
275
- err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
276
- assert(err == 0);
277
-
278
- assert(argc == 1);
279
-
280
- uint32_t len;
281
- err = js_get_array_length(env, argv[0], &len);
282
- assert(err == 0);
283
-
284
- utf8_string_t result;
285
- utf8_string_init(&result);
286
-
287
- for (uint32_t i = 0; i + 1 < len; i += 2) {
288
- js_value_t *pair[2];
289
-
290
- err = js_get_element(env, argv[0], i, &pair[0]);
291
- assert(err == 0);
292
-
293
- err = js_get_element(env, argv[0], i + 1, &pair[1]);
294
- assert(err == 0);
295
-
296
- if (i != 0) {
297
- err = utf8_string_append_character(&result, '&');
298
- assert(err == 0);
299
- }
300
-
301
- bare_url__serialize_form_urlencoded_component(env, pair[0], &result);
302
-
303
- err = utf8_string_append_character(&result, '=');
304
- assert(err == 0);
305
-
306
- bare_url__serialize_form_urlencoded_component(env, pair[1], &result);
307
- }
308
-
309
- js_value_t *output;
310
- err = js_create_string_utf8(env, result.data, result.len, &output);
311
- assert(err == 0);
312
-
313
- utf8_string_destroy(&result);
314
-
315
- return output;
316
- }
317
-
318
235
  static js_value_t *
319
236
  bare_url_exports(js_env_t *env, js_value_t *exports) {
320
237
  int err;
@@ -330,7 +247,6 @@ bare_url_exports(js_env_t *env, js_value_t *exports) {
330
247
 
331
248
  V("parse", bare_url_parse)
332
249
  V("canParse", bare_url_can_parse)
333
- V("serializeSearchParams", bare_url_serialize_form_urlencoded)
334
250
  #undef V
335
251
 
336
252
  return exports;
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._components = new Uint32Array(8)
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._components[0]) + ':'
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._components[0]))
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._components[0] + 3 /* :// */, this._components[1])
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._components[0] + 3 /* :// */, this._components[1]))
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._components[1] + 1 /* : */, this._components[2] - 1 /* @ */)
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._components[1] + 1 /* : */
83
- let end = this._components[2] - 1 /* @ */
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._components[2], this._components[5])
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._components[2], this._components[value.includes(':') ? 5 : 3])
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._components[2], this._components[3])
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._components[2], this._components[3]))
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._components[3] + 1 /* : */, this._components[5])
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._components[3] + 1 /* : */
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._components[5]))
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._components[5], this._components[6] - 1 /* ? */)
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._components[5], this._components[6] - 1 /* ? */))
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._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
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._components[6] - 1 /* ? */, this._components[7] - 1 /* # */)
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._components[7] - 1 /* # */)
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._components[7] - 1 /* # */))
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
- this._href = binding.parse(
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
- if (/%2f|%5c/i.test(url.pathname)) {
306
- throw errors.INVALID_FILE_URL_PATH(
307
- 'The file: URL path must not include encoded \\ or / characters'
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
- if (/%2f/i.test(url.pathname)) {
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
- if (/%00/i.test(url.pathname)) {
321
- throw errors.INVALID_FILE_URL_PATH('The file: URL path must not include encoded NUL characters')
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(url.pathname))
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
- resolved = resolved
351
- .replaceAll('%', '%25') // Must be first
352
- .replaceAll('#', '%23')
353
- .replaceAll('?', '%3f')
354
- .replaceAll('\n', '%0a')
355
- .replaceAll('\r', '%0d')
356
- .replaceAll('\t', '%09')
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
- if (!isWindows) {
359
- resolved = resolved.replaceAll('\\', '%5c')
407
+ if (!isWindows) {
408
+ resolved = resolved.replaceAll('\\', '%5c')
409
+ }
360
410
  }
361
411
 
362
412
  return new URL('file:' + resolved)
@@ -1,5 +1,3 @@
1
- const binding = require('../binding')
2
-
3
1
  const kind = Symbol.for('bare.url.search-params.kind')
4
2
 
5
3
  class URLSearchParams {
@@ -11,17 +9,21 @@ class URLSearchParams {
11
9
 
12
10
  // https://url.spec.whatwg.org/#dom-urlsearchparams-urlsearchparams
13
11
  constructor(init, url = null) {
14
- this._params = new Map()
12
+ this._params = null
15
13
 
16
14
  if (url) URLSearchParams._urls.set(this, url)
17
15
 
18
16
  if (typeof init === 'string') {
19
17
  this._parse(init)
20
- } else if (init) {
21
- for (const [name, value] of typeof init[Symbol.iterator] === 'function'
22
- ? init
23
- : Object.entries(init)) {
24
- this.append(name, value)
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
+ }
25
27
  }
26
28
  }
27
29
  }
@@ -147,41 +149,51 @@ class URLSearchParams {
147
149
 
148
150
  // https://url.spec.whatwg.org/#concept-urlencoded-parser
149
151
  _parse(input) {
150
- if (input[0] === '?') input = input.substring(1)
152
+ const params = new Map()
151
153
 
152
- this._params = new Map()
154
+ this._params = params
153
155
 
154
- for (let sequence of input.split('&')) {
155
- if (sequence.length === 0) continue
156
+ const len = input.length
156
157
 
157
- sequence = sequence.replace(/\+/g, ' ')
158
+ let start = input.charCodeAt(0) === 0x3f /* ? */ ? 1 : 0
158
159
 
159
- let i = sequence.indexOf('=')
160
- if (i === -1) i = sequence.length
160
+ while (start < len) {
161
+ let end = input.indexOf('&', start)
162
+ if (end === -1) end = len
161
163
 
162
- const name = decodeURIComponent(sequence.substring(0, i))
163
- const value = decodeURIComponent(sequence.substring(i + 1, sequence.length))
164
+ if (end !== start) {
165
+ let split = input.indexOf('=', start)
166
+ if (split === -1 || split > end) split = end
164
167
 
165
- let list = this._params.get(name)
168
+ const name = decode(input.slice(start, split))
169
+ const value = split === end ? '' : decode(input.slice(split + 1, end))
170
+
171
+ const list = params.get(name)
166
172
 
167
- if (list === undefined) {
168
- list = []
169
- this._params.set(name, list)
173
+ if (list === undefined) params.set(name, [value])
174
+ else list.push(value)
170
175
  }
171
176
 
172
- list.push(value)
177
+ start = end + 1
173
178
  }
174
179
  }
175
180
 
176
181
  // https://url.spec.whatwg.org/#concept-urlencoded-serializer
177
182
  _serialize() {
178
- const pairs = []
183
+ let result = ''
184
+ let separator = ''
179
185
 
180
186
  for (const [name, values] of this._params) {
181
- for (const value of values) pairs.push(String(name), String(value))
187
+ // The name is shared by all of its values, so it is only encoded once.
188
+ const encoded = encode(String(name))
189
+
190
+ for (const value of values) {
191
+ result += separator + encoded + '=' + encode(String(value))
192
+ separator = '&'
193
+ }
182
194
  }
183
195
 
184
- return binding.serializeSearchParams(pairs)
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.1",
3
+ "version": "2.5.2",
4
4
  "description": "WHATWG URL implementation for JavaScript",
5
5
  "exports": {
6
6
  "./package": "./package.json",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file