@coroboros/uri 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE.md +21 -0
- package/README.md +990 -0
- package/dist/index.cjs +1665 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +560 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +560 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1639 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,990 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/logo.png" width="288" height="288" alt="@coroboros/uri"/>
|
|
4
|
+
|
|
5
|
+
<!-- omit in toc -->
|
|
6
|
+
# @coroboros/uri
|
|
7
|
+
|
|
8
|
+
**RFC-3986 URI toolkit for Node.js. IDN (RFC-3987), IPv6 zone identifiers (RFC 6874), Sitemap protocol. Zero dependencies.**
|
|
9
|
+
|
|
10
|
+
Parses URIs per **RFC-3986 Appendix B**. Recomposes per §5.3. Resolves references per §5.2. Validates IPs, domains (**RFC 1034 / 1123**), HTTP(S) URLs, and Sitemap URLs. Encodes and decodes URI strings and components.
|
|
11
|
+
|
|
12
|
+
[](https://www.npmjs.com/package/@coroboros/uri)
|
|
13
|
+
[](https://github.com/coroboros/uri/actions/workflows/ci.yml)
|
|
14
|
+
[](https://opensource.org/licenses/MIT)
|
|
15
|
+
[](https://github.com/coroboros/uri)
|
|
16
|
+
[](https://coroboros.com)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<!-- omit in toc -->
|
|
21
|
+
## Contents
|
|
22
|
+
|
|
23
|
+
- [Requirements](#requirements)
|
|
24
|
+
- [Install](#install)
|
|
25
|
+
- [Usage](#usage)
|
|
26
|
+
- [Compliance](#compliance)
|
|
27
|
+
- [API](#api)
|
|
28
|
+
- [Errors](#errors)
|
|
29
|
+
- [Limitations](#limitations)
|
|
30
|
+
- [Contributing](#contributing)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- Node.js `>=22` LTS. Use [fnm](https://github.com/Schniz/fnm) for version management — Rust-based, faster than nvm.
|
|
36
|
+
- Any of the following package managers: `pnpm`, `npm`, `yarn`, `bun`.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pnpm add @coroboros/uri
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @coroboros/uri
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
yarn add @coroboros/uri
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
bun add @coroboros/uri
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
// ESM (recommended)
|
|
60
|
+
import { parseURI, checkHttpsURL, encodeWebURL } from '@coroboros/uri';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
// CommonJS
|
|
65
|
+
const { parseURI, checkHttpsURL, encodeWebURL } = require('@coroboros/uri');
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { parseURI, checkHttpsURL, encodeWebURL } from '@coroboros/uri';
|
|
70
|
+
|
|
71
|
+
// Parse — get every RFC-3986 component
|
|
72
|
+
parseURI('foo://user:pass@xn--fiq228c.com:8042/over/there?name=ferret#nose');
|
|
73
|
+
// { scheme: 'foo', host: 'xn--fiq228c.com', hostPunydecoded: '中文.com', port: 8042, … }
|
|
74
|
+
|
|
75
|
+
// Validate strictly — throws URIError with a stable code on invalid input
|
|
76
|
+
try {
|
|
77
|
+
const url = checkHttpsURL('https://example.com/path?q=1#x');
|
|
78
|
+
url.valid; // true
|
|
79
|
+
} catch (err) {
|
|
80
|
+
// err.code is one of the documented codes (see [Errors](#errors))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Encode — RFC-3986 compliant, IDN-aware, sub-2048-char HTTP(S)
|
|
84
|
+
encodeWebURL('https://www.中文.com./Over There?a=B#Anchôr');
|
|
85
|
+
// 'https://www.xn--fiq228c.com./Over%20There?a=B#Anch%C3%B4r'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Compliance
|
|
89
|
+
|
|
90
|
+
`@coroboros/uri` implements:
|
|
91
|
+
|
|
92
|
+
- **RFC-3986** — generic URI syntax: parse (Appendix B), recompose (§5.3), reference resolution (§5.2), percent-encoding (§2.1, §6.2.2.1), and character validation (§3.1–§3.5).
|
|
93
|
+
- **RFC-3987** — Internationalized Domain Names via Punycode, through Node's `node:url` (`domainToASCII` / `domainToUnicode`).
|
|
94
|
+
- **RFC 6874 §2** — IPv6 zone identifiers inside a URI: the `%25` delimiter and `ZoneID = 1*( unreserved / pct-encoded )` grammar.
|
|
95
|
+
- **RFC 1034 / RFC 1123** — domain-name rules: label length, character set, label separation.
|
|
96
|
+
- **sitemaps.org** — the Sitemap protocol: required XML-entity escaping and the 2,048-character URL ceiling.
|
|
97
|
+
|
|
98
|
+
See [`bench/baseline.md`](bench/baseline.md) for performance numbers vs native `URL` / `URL.canParse`. The toolkit trades raw speed for RFC-3986 fidelity — full per-character validation, IDN handling, RFC 6874 zone identifiers, and explicit coded errors.
|
|
99
|
+
|
|
100
|
+
**Generic URI syntax**
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+
|
|
104
|
+
**Example URIs**
|
|
105
|
+
|
|
106
|
+

|
|
107
|
+
|
|
108
|
+
## API
|
|
109
|
+
|
|
110
|
+
### Types
|
|
111
|
+
|
|
112
|
+
<details>
|
|
113
|
+
<summary><em>ParsedURI</em></summary>
|
|
114
|
+
|
|
115
|
+
<br>
|
|
116
|
+
|
|
117
|
+
Return shape of [`parseURI`](#parsing).
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
interface ParsedURI {
|
|
121
|
+
scheme: string | null;
|
|
122
|
+
authority: string | null;
|
|
123
|
+
authorityPunydecoded: string | null;
|
|
124
|
+
userinfo: string | null;
|
|
125
|
+
host: string | null;
|
|
126
|
+
hostPunydecoded: string | null;
|
|
127
|
+
port: number | string | null;
|
|
128
|
+
path: string | null;
|
|
129
|
+
pathqf: string | null;
|
|
130
|
+
query: string | null;
|
|
131
|
+
fragment: string | null;
|
|
132
|
+
href: string | null;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Fields default to `null` when the corresponding URI part is missing. `port` is a `number` when parseable as an integer, a `string` otherwise.
|
|
137
|
+
|
|
138
|
+
</details>
|
|
139
|
+
|
|
140
|
+
<details>
|
|
141
|
+
<summary><em>URIComponents</em></summary>
|
|
142
|
+
|
|
143
|
+
<br>
|
|
144
|
+
|
|
145
|
+
Input shape of [`recomposeURI`](#parsing). Every field is optional; `scheme` and `path` are required at runtime.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
interface URIComponents {
|
|
149
|
+
scheme?: string | null;
|
|
150
|
+
userinfo?: string | null;
|
|
151
|
+
host?: string | null;
|
|
152
|
+
port?: number | string | null;
|
|
153
|
+
path?: string | null;
|
|
154
|
+
query?: string | null;
|
|
155
|
+
fragment?: string | null;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
</details>
|
|
160
|
+
|
|
161
|
+
<details>
|
|
162
|
+
<summary><em>CheckedURI</em></summary>
|
|
163
|
+
|
|
164
|
+
<br>
|
|
165
|
+
|
|
166
|
+
Return shape of every [`check*`](#checkers) function on success — `ParsedURI` extended with a `valid: true` discriminant.
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
interface CheckedURI extends ParsedURI {
|
|
170
|
+
valid: true;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
</details>
|
|
175
|
+
|
|
176
|
+
### Punycode
|
|
177
|
+
|
|
178
|
+
<details>
|
|
179
|
+
<summary><em>punycode(domain)</em></summary>
|
|
180
|
+
|
|
181
|
+
<br>
|
|
182
|
+
|
|
183
|
+
Returns the Punycode ASCII serialization of a domain. Returns the empty string when the input is not a valid domain.
|
|
184
|
+
|
|
185
|
+
**Parameters**
|
|
186
|
+
|
|
187
|
+
| Option | Type | Default | Description |
|
|
188
|
+
| --- | --- | --- | --- |
|
|
189
|
+
| `domain` | `string` | *(required)* | The domain to serialize. |
|
|
190
|
+
|
|
191
|
+
**Returns** — `string`. The ASCII form (or `''` on invalid input).
|
|
192
|
+
|
|
193
|
+
**Notes**
|
|
194
|
+
|
|
195
|
+
- Wraps Node's `url.domainToASCII` and normalizes the error case: the native function throws when called without an argument and returns `'null'` / `'undefined'` / `'nan'` for the corresponding non-domain inputs.
|
|
196
|
+
- IPv6 literals are passed through unchanged (the native function rejects them).
|
|
197
|
+
|
|
198
|
+
**Examples**
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
punycode(); // ''
|
|
202
|
+
punycode('a.b.c.d.e.fg'); // 'a.b.c.d.e.fg'
|
|
203
|
+
punycode('xn--iñvalid.com'); // ''
|
|
204
|
+
punycode('中文.com'); // 'xn--fiq228c.com'
|
|
205
|
+
punycode('xn--fiq228c.com'); // 'xn--fiq228c.com'
|
|
206
|
+
punycode('2001:db8:85a3:8d3:1319:8a2e:370:7348'); // '2001:db8:85a3:8d3:1319:8a2e:370:7348'
|
|
207
|
+
punycode('127.0.0.1'); // '127.0.0.1'
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
</details>
|
|
211
|
+
|
|
212
|
+
<details>
|
|
213
|
+
<summary><em>punydecode(domain)</em></summary>
|
|
214
|
+
|
|
215
|
+
<br>
|
|
216
|
+
|
|
217
|
+
Returns the Unicode serialization of a domain. Returns the empty string when the input is not a valid domain.
|
|
218
|
+
|
|
219
|
+
**Parameters**
|
|
220
|
+
|
|
221
|
+
| Option | Type | Default | Description |
|
|
222
|
+
| --- | --- | --- | --- |
|
|
223
|
+
| `domain` | `string` | *(required)* | The domain to deserialize. |
|
|
224
|
+
|
|
225
|
+
**Returns** — `string`. The Unicode form (or `''` on invalid input).
|
|
226
|
+
|
|
227
|
+
**Notes**
|
|
228
|
+
|
|
229
|
+
- Wraps Node's `url.domainToUnicode` and normalizes the same error edges as [`punycode`](#punycodedomain).
|
|
230
|
+
|
|
231
|
+
**Examples**
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
punydecode(); // ''
|
|
235
|
+
punydecode('xn--fiq228c.com'); // '中文.com'
|
|
236
|
+
punydecode('中文.com'); // '中文.com'
|
|
237
|
+
punydecode('xn--iñvalid.com'); // ''
|
|
238
|
+
punydecode('2001:db8:85a3:8d3:1319:8a2e:370:7348'); // '2001:db8:85a3:8d3:1319:8a2e:370:7348'
|
|
239
|
+
punydecode('127.0.0.1'); // '127.0.0.1'
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
</details>
|
|
243
|
+
|
|
244
|
+
### Parsing
|
|
245
|
+
|
|
246
|
+
<details>
|
|
247
|
+
<summary><em>parseURI(uri)</em></summary>
|
|
248
|
+
|
|
249
|
+
<br>
|
|
250
|
+
|
|
251
|
+
Parses a URI into its **RFC-3986 Appendix B** components, with IPv4/IPv6 host support and IDN (Punycode) awareness.
|
|
252
|
+
|
|
253
|
+
**Parameters**
|
|
254
|
+
|
|
255
|
+
| Option | Type | Default | Description |
|
|
256
|
+
| --- | --- | --- | --- |
|
|
257
|
+
| `uri` | `string` | *(required)* | The URI string to parse. |
|
|
258
|
+
|
|
259
|
+
**Returns** — [`ParsedURI`](#types).
|
|
260
|
+
|
|
261
|
+
**Notes**
|
|
262
|
+
|
|
263
|
+
- Scheme and host are lowercased per **RFC-3986 §6.2.2.1**.
|
|
264
|
+
- Authority and its components are `null` when the authority is absent or empty.
|
|
265
|
+
- A present-but-empty query or fragment (`?` or `#` with nothing after) is preserved as `''`, distinct from a missing one (`null`).
|
|
266
|
+
- For strict validation, prefer [`checkURI`](#checkers).
|
|
267
|
+
|
|
268
|
+
**Examples**
|
|
269
|
+
|
|
270
|
+
```ts
|
|
271
|
+
parseURI('foo://user:pass@xn--fiq228c.com:8042/over/there?name=ferret#nose');
|
|
272
|
+
// {
|
|
273
|
+
// scheme: 'foo',
|
|
274
|
+
// authority: 'user:pass@xn--fiq228c.com:8042',
|
|
275
|
+
// authorityPunydecoded: 'user:pass@中文.com:8042',
|
|
276
|
+
// userinfo: 'user:pass',
|
|
277
|
+
// host: 'xn--fiq228c.com',
|
|
278
|
+
// hostPunydecoded: '中文.com',
|
|
279
|
+
// port: 8042,
|
|
280
|
+
// path: '/over/there',
|
|
281
|
+
// pathqf: '/over/there?name=ferret#nose',
|
|
282
|
+
// query: 'name=ferret',
|
|
283
|
+
// fragment: 'nose',
|
|
284
|
+
// href: 'foo://user:pass@xn--fiq228c.com:8042/over/there?name=ferret#nose',
|
|
285
|
+
// }
|
|
286
|
+
|
|
287
|
+
parseURI('urn:isbn:0-486-27557-4');
|
|
288
|
+
// { scheme: 'urn', authority: null, path: 'isbn:0-486-27557-4', href: 'urn:isbn:0-486-27557-4', … }
|
|
289
|
+
|
|
290
|
+
parseURI('http://user:pass@[fe80::7:8%eth0]:8080');
|
|
291
|
+
// { scheme: 'http', host: 'fe80::7:8%eth0', port: 8080, path: '', href: 'http://user:pass@[fe80::7:8%eth0]:8080/', … }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
</details>
|
|
295
|
+
|
|
296
|
+
<details>
|
|
297
|
+
<summary><em>recomposeURI(components)</em></summary>
|
|
298
|
+
|
|
299
|
+
<br>
|
|
300
|
+
|
|
301
|
+
Recomposes a URI from its components per **RFC-3986 §5.3**, with basic validity checking. Returns the empty string when the rules below are not met.
|
|
302
|
+
|
|
303
|
+
**Parameters**
|
|
304
|
+
|
|
305
|
+
| Option | Type | Default | Description |
|
|
306
|
+
| --- | --- | --- | --- |
|
|
307
|
+
| `components` | [`URIComponents`](#types) | *(required)* | The components to recompose. |
|
|
308
|
+
|
|
309
|
+
**Returns** — `string`. The recomposed URI (or `''` on invalid input).
|
|
310
|
+
|
|
311
|
+
**Notes**
|
|
312
|
+
|
|
313
|
+
- `scheme` is required and must be at least one character.
|
|
314
|
+
- `path` is required and may be empty.
|
|
315
|
+
- If `host` is present, `path` must be empty or start with `/`.
|
|
316
|
+
- If `host` is absent, `path` must not start with `//`.
|
|
317
|
+
- `host`, if present, must be at least three characters.
|
|
318
|
+
- `userinfo` is ignored when empty.
|
|
319
|
+
- `port` is ignored when not parseable as an integer in `0–65535`.
|
|
320
|
+
- `query` and `fragment` are ignored when empty.
|
|
321
|
+
- A trailing `/` is added to any URI with a host and an empty path.
|
|
322
|
+
|
|
323
|
+
**Examples**
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
recomposeURI({
|
|
327
|
+
scheme: 'foo',
|
|
328
|
+
userinfo: 'user:pass',
|
|
329
|
+
host: 'bar.com',
|
|
330
|
+
port: 8080,
|
|
331
|
+
path: '/over/there',
|
|
332
|
+
query: 'a=b',
|
|
333
|
+
fragment: 'anchor',
|
|
334
|
+
}); // 'foo://user:pass@bar.com:8080/over/there?a=b#anchor'
|
|
335
|
+
|
|
336
|
+
recomposeURI({ scheme: 'foo', path: '' });
|
|
337
|
+
// 'foo:'
|
|
338
|
+
|
|
339
|
+
recomposeURI({
|
|
340
|
+
scheme: 'foo',
|
|
341
|
+
userinfo: 'user:pass',
|
|
342
|
+
host: 'fe80::7:8%eth0',
|
|
343
|
+
port: '8080',
|
|
344
|
+
path: '/over/there',
|
|
345
|
+
query: 'a=b',
|
|
346
|
+
fragment: 'anchor',
|
|
347
|
+
}); // 'foo://user:pass@[fe80::7:8%eth0]:8080/over/there?a=b#anchor'
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
</details>
|
|
351
|
+
|
|
352
|
+
### Reference resolution
|
|
353
|
+
|
|
354
|
+
<details>
|
|
355
|
+
<summary><em>resolveURI(base, reference)</em></summary>
|
|
356
|
+
|
|
357
|
+
<br>
|
|
358
|
+
|
|
359
|
+
Resolves a URI reference against an absolute base URI per **RFC-3986 §5.2**: the §5.2.2 strict transform, the §5.2.3 merge, the §5.2.4 `remove_dot_segments`, then recomposes per §5.3.
|
|
360
|
+
|
|
361
|
+
**Parameters**
|
|
362
|
+
|
|
363
|
+
| Option | Type | Default | Description |
|
|
364
|
+
| --- | --- | --- | --- |
|
|
365
|
+
| `base` | `string` | *(required)* | The absolute base URI. |
|
|
366
|
+
| `reference` | `string` | *(required)* | The URI reference to resolve. |
|
|
367
|
+
|
|
368
|
+
**Returns** — `string`. The resolved URI, or `''` when the base is not absolute or an argument is not a string.
|
|
369
|
+
|
|
370
|
+
**Notes**
|
|
371
|
+
|
|
372
|
+
- The strict algorithm is used: a reference scheme equal to the base scheme is not ignored.
|
|
373
|
+
- A fragment on the base is stripped before resolution per **RFC-3986 §5.1**.
|
|
374
|
+
|
|
375
|
+
**Examples**
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
resolveURI('http://a/b/c/d;p?q', '../../g'); // 'http://a/g'
|
|
379
|
+
resolveURI('https://example.com/a/b', './c?x#y'); // 'https://example.com/a/c?x#y'
|
|
380
|
+
resolveURI('/not-absolute', 'g'); // '' — base is not absolute
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
</details>
|
|
384
|
+
|
|
385
|
+
<details>
|
|
386
|
+
<summary><em>removeDotSegments(path)</em></summary>
|
|
387
|
+
|
|
388
|
+
<br>
|
|
389
|
+
|
|
390
|
+
Removes the `.` and `..` complete path segments from a path per **RFC-3986 §5.2.4** verbatim.
|
|
391
|
+
|
|
392
|
+
**Parameters**
|
|
393
|
+
|
|
394
|
+
| Option | Type | Default | Description |
|
|
395
|
+
| --- | --- | --- | --- |
|
|
396
|
+
| `path` | `string` | *(required)* | The path to normalize. |
|
|
397
|
+
|
|
398
|
+
**Returns** — `string`. The normalized path.
|
|
399
|
+
|
|
400
|
+
**Examples**
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
removeDotSegments('/a/b/c/./../../g'); // '/a/g'
|
|
404
|
+
removeDotSegments('mid/content=5/../6'); // 'mid/6'
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
</details>
|
|
408
|
+
|
|
409
|
+
### Validators
|
|
410
|
+
|
|
411
|
+
<details>
|
|
412
|
+
<summary><em>isDomainLabel(label)</em></summary>
|
|
413
|
+
|
|
414
|
+
<br>
|
|
415
|
+
|
|
416
|
+
Tests whether a label is a valid domain label per **RFC 1034**. By convention, an uppercased label is considered invalid (`DNS names are case-insensitive, but Coroboros normalizes on lowercase`).
|
|
417
|
+
|
|
418
|
+
**Parameters**
|
|
419
|
+
|
|
420
|
+
| Option | Type | Default | Description |
|
|
421
|
+
| --- | --- | --- | --- |
|
|
422
|
+
| `label` | `string` | *(required)* | The label to test. |
|
|
423
|
+
|
|
424
|
+
**Returns** — `boolean`.
|
|
425
|
+
|
|
426
|
+
**Notes**
|
|
427
|
+
|
|
428
|
+
- Length is one to 63 characters.
|
|
429
|
+
- Allowed characters: lowercase letters, digits, hyphen.
|
|
430
|
+
- Cannot start or end with a hyphen.
|
|
431
|
+
- No consecutive hyphens.
|
|
432
|
+
- Can start or end with a digit.
|
|
433
|
+
|
|
434
|
+
**Examples**
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
isDomainLabel('a'); // true
|
|
438
|
+
isDomainLabel('1a3'); // true
|
|
439
|
+
isDomainLabel('a'.repeat(64)); // false
|
|
440
|
+
isDomainLabel('A'); // false
|
|
441
|
+
isDomainLabel('-a'); // false
|
|
442
|
+
isDomainLabel('la--bel'); // false
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
</details>
|
|
446
|
+
|
|
447
|
+
<details>
|
|
448
|
+
<summary><em>isDomain(name)</em></summary>
|
|
449
|
+
|
|
450
|
+
<br>
|
|
451
|
+
|
|
452
|
+
Tests whether a name is a valid domain per **RFC 1034**, with FQDN and IDN support.
|
|
453
|
+
|
|
454
|
+
**Parameters**
|
|
455
|
+
|
|
456
|
+
| Option | Type | Default | Description |
|
|
457
|
+
| --- | --- | --- | --- |
|
|
458
|
+
| `name` | `string` | *(required)* | The domain to test. |
|
|
459
|
+
|
|
460
|
+
**Returns** — `boolean`.
|
|
461
|
+
|
|
462
|
+
**Notes**
|
|
463
|
+
|
|
464
|
+
- [`isDomainLabel`](#validators) rules apply to each label.
|
|
465
|
+
- Total length is at most 255 octets including label-length octets.
|
|
466
|
+
- Labels are separated by `.`.
|
|
467
|
+
- Must have at least one extension label.
|
|
468
|
+
- All labels must differ.
|
|
469
|
+
- The last label can be empty (root label `.`).
|
|
470
|
+
- Labels starting with `xn--` are valid only when the ASCII serialization is a valid Punycode and the decoded form has valid characters.
|
|
471
|
+
|
|
472
|
+
**Examples**
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
isDomain('a.b'); // true
|
|
476
|
+
isDomain('a.b.'); // true
|
|
477
|
+
isDomain('中文.com'); // true
|
|
478
|
+
isDomain('xn--fiq228c.com'); // true
|
|
479
|
+
isDomain('www.中文.com'); // true
|
|
480
|
+
|
|
481
|
+
isDomain('a'); // false
|
|
482
|
+
isDomain('a.a'); // false
|
|
483
|
+
isDomain('中文.xn--fiq228c.com'); // false
|
|
484
|
+
isDomain('xn--\'-6xd.com'); // false — valid Punycode for ॐ, but ॐ is not a valid character
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
</details>
|
|
488
|
+
|
|
489
|
+
<details>
|
|
490
|
+
<summary><em>isIP(ip)</em></summary>
|
|
491
|
+
|
|
492
|
+
<br>
|
|
493
|
+
|
|
494
|
+
Tests whether a string is a valid IPv4 or IPv6 address.
|
|
495
|
+
|
|
496
|
+
**Parameters**
|
|
497
|
+
|
|
498
|
+
| Option | Type | Default | Description |
|
|
499
|
+
| --- | --- | --- | --- |
|
|
500
|
+
| `ip` | `string` | *(required)* | The address to test. |
|
|
501
|
+
|
|
502
|
+
**Returns** — `boolean`.
|
|
503
|
+
|
|
504
|
+
**Examples**
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
isIP('23.71.254.72'); // true
|
|
508
|
+
isIP('1:2:3:4::6:7:8'); // true
|
|
509
|
+
isIP('100..100.100.100'); // false
|
|
510
|
+
isIP('3ffe:b00::1::a'); // false
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
</details>
|
|
514
|
+
|
|
515
|
+
<details>
|
|
516
|
+
<summary><em>isIPv4(ip)</em></summary>
|
|
517
|
+
|
|
518
|
+
<br>
|
|
519
|
+
|
|
520
|
+
Tests whether a string is a valid IPv4 address. Returns `false` for IPv6.
|
|
521
|
+
|
|
522
|
+
```ts
|
|
523
|
+
isIPv4('8.8.8.8'); // true
|
|
524
|
+
isIPv4('1:2::8'); // false
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
</details>
|
|
528
|
+
|
|
529
|
+
<details>
|
|
530
|
+
<summary><em>isIPv6(ip)</em></summary>
|
|
531
|
+
|
|
532
|
+
<br>
|
|
533
|
+
|
|
534
|
+
Tests whether a string is a valid IPv6 address. Returns `false` for IPv4. The standalone validator is lenient regarding zone identifiers — see [`checkURI`](#checkers) for the strict **RFC 6874** form expected inside a URI.
|
|
535
|
+
|
|
536
|
+
```ts
|
|
537
|
+
isIPv6('2001:0000:1234:0000:0000:C1C0:ABCD:0876'); // true
|
|
538
|
+
isIPv6('212.58.241.131'); // false
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
</details>
|
|
542
|
+
|
|
543
|
+
### Checkers
|
|
544
|
+
|
|
545
|
+
<details>
|
|
546
|
+
<summary><em>checkURI(uri)</em></summary>
|
|
547
|
+
|
|
548
|
+
<br>
|
|
549
|
+
|
|
550
|
+
Strictly validates a URI per **RFC-3986**. Returns the parsed components with `valid: true` on success; throws `URIError` with a stable [error code](#errors) on the first failure.
|
|
551
|
+
|
|
552
|
+
**Parameters**
|
|
553
|
+
|
|
554
|
+
| Option | Type | Default | Description |
|
|
555
|
+
| --- | --- | --- | --- |
|
|
556
|
+
| `uri` | `string` | *(required)* | The URI to validate. |
|
|
557
|
+
|
|
558
|
+
**Returns** — [`CheckedURI`](#types).
|
|
559
|
+
|
|
560
|
+
**Throws** — `URIError` with one of: `URI_INVALID_TYPE`, `URI_MISSING_SCHEME`, `URI_EMPTY_SCHEME`, `URI_MISSING_PATH`, `URI_INVALID_PATH`, `URI_INVALID_HOST`, `URI_INVALID_SCHEME_CHAR`, `URI_INVALID_USERINFO_CHAR`, `URI_INVALID_PORT`, `URI_INVALID_PATH_CHAR`, `URI_INVALID_QUERY_CHAR`, `URI_INVALID_FRAGMENT_CHAR`, `URI_INVALID_PERCENT_ENCODING`.
|
|
561
|
+
|
|
562
|
+
**Notes**
|
|
563
|
+
|
|
564
|
+
- Scheme is required and non-empty (**RFC-3986 §3.1**).
|
|
565
|
+
- Path is required and may be empty.
|
|
566
|
+
- If authority is present, path must be empty or start with `/`; otherwise path must not start with `//`.
|
|
567
|
+
- Authority components: host must be a valid IP or domain; `userinfo` only allows the characters from **RFC-3986 §3.2.1**; `port` must be an integer in `0–65535`.
|
|
568
|
+
- Path, query, and fragment only allow the characters from **RFC-3986 §3.3 / §3.4 / §3.5**.
|
|
569
|
+
- IPv6 zone identifiers must use the `%25` delimiter and a non-empty `ZoneID` of `unreserved` / `pct-encoded` characters (**RFC 6874 §2**).
|
|
570
|
+
|
|
571
|
+
**Examples**
|
|
572
|
+
|
|
573
|
+
```ts
|
|
574
|
+
checkURI('foo://user:pass@xn--fiq228c.com:8042/over/there?name=ferret#nose');
|
|
575
|
+
// { scheme: 'foo', host: 'xn--fiq228c.com', valid: true, … }
|
|
576
|
+
|
|
577
|
+
checkURI(); // throws URIError — URI_INVALID_TYPE
|
|
578
|
+
checkURI('://example.com'); // throws URIError — URI_MISSING_SCHEME
|
|
579
|
+
checkURI('foo:////bar'); // throws URIError — URI_INVALID_PATH
|
|
580
|
+
checkURI('foo://xn--iñvalid.com'); // throws URIError — URI_INVALID_HOST
|
|
581
|
+
checkURI('fôo:bar'); // throws URIError — URI_INVALID_SCHEME_CHAR
|
|
582
|
+
checkURI('foo://üser:pass@bar.com'); // throws URIError — URI_INVALID_USERINFO_CHAR
|
|
583
|
+
checkURI('foo://bar.com:80g80'); // throws URIError — URI_INVALID_PORT
|
|
584
|
+
checkURI('foo://bar.com/°'); // throws URIError — URI_INVALID_PATH_CHAR
|
|
585
|
+
checkURI('foo://bar.com/over/there?quêry=5'); // throws URIError — URI_INVALID_QUERY_CHAR
|
|
586
|
+
checkURI('foo://bar.com/over/there?query=5#anch#r'); // throws URIError — URI_INVALID_FRAGMENT_CHAR
|
|
587
|
+
checkURI('http://www.bar.baz/foo%2'); // throws URIError — URI_INVALID_PERCENT_ENCODING
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
</details>
|
|
591
|
+
|
|
592
|
+
<details>
|
|
593
|
+
<summary><em>checkHttpURL(uri)</em></summary>
|
|
594
|
+
|
|
595
|
+
<br>
|
|
596
|
+
|
|
597
|
+
Validates a URI as an HTTP URL on top of [`checkURI`](#checkers).
|
|
598
|
+
|
|
599
|
+
**Adds**
|
|
600
|
+
|
|
601
|
+
- `scheme` must be `http` or `HTTP` — else `URI_INVALID_SCHEME`.
|
|
602
|
+
- `authority` is required — else `URI_MISSING_AUTHORITY`.
|
|
603
|
+
- URL must be shorter than 2,048 characters — else `URI_MAX_LENGTH_URL`.
|
|
604
|
+
|
|
605
|
+
**Returns** — [`CheckedURI`](#types). Throws `URIError` with any of `checkURI`'s codes plus the three above.
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
checkHttpURL('http://user:pass@xn--fiq228c.com:8042/over/there?name=ferret#nose');
|
|
609
|
+
// { scheme: 'http', host: 'xn--fiq228c.com', valid: true, … }
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
</details>
|
|
613
|
+
|
|
614
|
+
<details>
|
|
615
|
+
<summary><em>checkHttpsURL(uri)</em></summary>
|
|
616
|
+
|
|
617
|
+
<br>
|
|
618
|
+
|
|
619
|
+
Same as [`checkHttpURL`](#checkers) but `scheme` must be `https` or `HTTPS`.
|
|
620
|
+
|
|
621
|
+
</details>
|
|
622
|
+
|
|
623
|
+
<details>
|
|
624
|
+
<summary><em>checkWebURL(uri)</em></summary>
|
|
625
|
+
|
|
626
|
+
<br>
|
|
627
|
+
|
|
628
|
+
Same as [`checkHttpURL`](#checkers) but `scheme` can be `http` / `HTTP` or `https` / `HTTPS`.
|
|
629
|
+
|
|
630
|
+
</details>
|
|
631
|
+
|
|
632
|
+
<details>
|
|
633
|
+
<summary><em>checkHttpSitemapURL(uri)</em></summary>
|
|
634
|
+
|
|
635
|
+
<br>
|
|
636
|
+
|
|
637
|
+
Validates a URI as an HTTP URL fit for an XML sitemap on top of [`checkHttpURL`](#checkers).
|
|
638
|
+
|
|
639
|
+
**Adds**
|
|
640
|
+
|
|
641
|
+
- The URL must be all lowercase (scheme, host, path, query, fragment) — else `URI_INVALID_CHAR`.
|
|
642
|
+
- Specific characters must be escaped — the table below lists them.
|
|
643
|
+
- Percent-encoded sitemap escapes (`&`, `'`, `"`, `<`, `>`) must be well-formed — else `URI_INVALID_SITEMAP_ENCODING`.
|
|
644
|
+
|
|
645
|
+
**Sitemap-escaped characters**
|
|
646
|
+
|
|
647
|
+
| Character | Value | Escape code |
|
|
648
|
+
| :----------- | :---: | :---------: |
|
|
649
|
+
| Ampersand | `&` | `&` |
|
|
650
|
+
| Single quote | `'` | `'` |
|
|
651
|
+
| Double quote | `"` | `"` |
|
|
652
|
+
| Less than | `<` | `<` |
|
|
653
|
+
| Greater than | `>` | `>` |
|
|
654
|
+
| Asterisk | `*` | `%2A` |
|
|
655
|
+
|
|
656
|
+
For plain-text sitemaps no escaping is required — use [`checkHttpURL`](#checkers) instead, but the URL must still be lowercase.
|
|
657
|
+
|
|
658
|
+
**Returns** — [`CheckedURI`](#types). Throws `URIError` with the union of `checkHttpURL`'s codes plus `URI_INVALID_CHAR`, `URI_INVALID_SITEMAP_ENCODING`.
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
checkHttpSitemapURL('http://user:pass@xn--fiq228c.com:8042/over/there?name=ferret&catch=rabbits#nose');
|
|
662
|
+
// { scheme: 'http', host: 'xn--fiq228c.com', valid: true, … }
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
</details>
|
|
666
|
+
|
|
667
|
+
<details>
|
|
668
|
+
<summary><em>checkHttpsSitemapURL(uri)</em></summary>
|
|
669
|
+
|
|
670
|
+
<br>
|
|
671
|
+
|
|
672
|
+
Same as [`checkHttpSitemapURL`](#checkers) but `scheme` must be `https`.
|
|
673
|
+
|
|
674
|
+
</details>
|
|
675
|
+
|
|
676
|
+
<details>
|
|
677
|
+
<summary><em>checkSitemapURL(uri)</em></summary>
|
|
678
|
+
|
|
679
|
+
<br>
|
|
680
|
+
|
|
681
|
+
Same as [`checkHttpSitemapURL`](#checkers) but `scheme` can be `http` or `https`.
|
|
682
|
+
|
|
683
|
+
</details>
|
|
684
|
+
|
|
685
|
+
### Encoders
|
|
686
|
+
|
|
687
|
+
<details>
|
|
688
|
+
<summary><em>encodeURIComponentString(component, options)</em></summary>
|
|
689
|
+
|
|
690
|
+
<br>
|
|
691
|
+
|
|
692
|
+
Encodes a URI component per **RFC-3986**, with per-type rules and an optional Sitemap-aware mode. Returns the empty string when the input is not a string.
|
|
693
|
+
|
|
694
|
+
**Parameters**
|
|
695
|
+
|
|
696
|
+
| Option | Type | Default | Description |
|
|
697
|
+
| --- | --- | --- | --- |
|
|
698
|
+
| `component` | `string` | *(required)* | The component to encode. |
|
|
699
|
+
| `options.type` | `'userinfo' \| 'path' \| 'query' \| 'fragment'` | *(none)* | The component type. Without a type, native `encodeURIComponent` is used (RFC-2396, outdated). |
|
|
700
|
+
| `options.lowercase` | `boolean` | `false` | Lowercase the component before encoding. |
|
|
701
|
+
| `options.sitemap` | `boolean` | `false` | Escape Sitemap's special characters (see [`checkHttpSitemapURL`](#checkers)). |
|
|
702
|
+
|
|
703
|
+
**Returns** — `string`. The encoded component (or `''` on invalid input).
|
|
704
|
+
|
|
705
|
+
**Notes**
|
|
706
|
+
|
|
707
|
+
- Only `userinfo`, `path`, `query`, and `fragment` can be percent-encoded. `scheme` and `authority` (host and port) cannot.
|
|
708
|
+
- Pass a component type. Without it, native `encodeURIComponent` over-escapes `!`, `*`, `'`, `(`, `)`, which **RFC-3986** treats as valid sub-delims.
|
|
709
|
+
|
|
710
|
+
**Examples**
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
encodeURIComponentString('cômpön€nt'); // 'c%C3%B4mp%C3%B6n%E2%82%ACnt'
|
|
714
|
+
encodeURIComponentString('AbC', { lowercase: true }); // 'abc'
|
|
715
|
+
encodeURIComponentString('*', { sitemap: true }); // '%2A'
|
|
716
|
+
encodeURIComponentString("A#/?@[]&'*"); // 'A%23%2F%3F%40%5B%5D%26\'*' — outdated RFC-2396
|
|
717
|
+
encodeURIComponentString("A#/?@[]&'*", { type: 'userinfo' }); // 'A%23%2F%3F%40%5B%5D&\'*'
|
|
718
|
+
encodeURIComponentString("A#/?@[]&'*", { type: 'path' }); // 'A%23/%3F@%5B%5D&\'*'
|
|
719
|
+
encodeURIComponentString("A#/?@[]&'*", { type: 'fragment', sitemap: true });
|
|
720
|
+
// 'a%23/?@%5B%5D&'%2A'
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
</details>
|
|
724
|
+
|
|
725
|
+
<details>
|
|
726
|
+
<summary><em>encodeURIString(uri, options)</em></summary>
|
|
727
|
+
|
|
728
|
+
<br>
|
|
729
|
+
|
|
730
|
+
Encodes a URI string per **RFC-3986** with basic validity checking and IDN support. The native `encodeURI` is **RFC-2396**, which is outdated and over-encodes; this function fixes both issues.
|
|
731
|
+
|
|
732
|
+
**Parameters**
|
|
733
|
+
|
|
734
|
+
| Option | Type | Default | Description |
|
|
735
|
+
| --- | --- | --- | --- |
|
|
736
|
+
| `uri` | `string` | *(required)* | The URI to encode. |
|
|
737
|
+
| `options.lowercase` | `boolean` | `false` | Lowercase the entire URI including path, query, and fragment. |
|
|
738
|
+
|
|
739
|
+
**Returns** — `string`. The encoded URI.
|
|
740
|
+
|
|
741
|
+
**Throws** — `URIError` with one of: `URI_INVALID_TYPE`, `URI_MISSING_SCHEME`, `URI_EMPTY_SCHEME`, `URI_MISSING_PATH`, `URI_INVALID_PATH`, `URI_INVALID_HOST`, `URI_INVALID_SCHEME_CHAR`, `URI_INVALID_PORT`.
|
|
742
|
+
|
|
743
|
+
**Notes**
|
|
744
|
+
|
|
745
|
+
- Only `userinfo`, `path`, `query`, and `fragment` can be percent-encoded; `scheme` and `host` cannot.
|
|
746
|
+
- IDN hosts are serialized to Punycode.
|
|
747
|
+
- `[` and `]` are not percent-encoded — they delimit IPv6 hosts.
|
|
748
|
+
- By default only scheme and host are lowercased (**RFC-3986 §6.2.2.1**). Path, query, and fragment are case-sensitive — see [Limitations](#limitations) for the `lowercase` flag's scope.
|
|
749
|
+
|
|
750
|
+
**Examples**
|
|
751
|
+
|
|
752
|
+
```ts
|
|
753
|
+
encodeURIString('HTTPS://WWW.中文.COM./Over/There?a=B&b=c#Anchor');
|
|
754
|
+
// 'https://www.xn--fiq228c.com./Over/There?a=B&b=c#Anchor'
|
|
755
|
+
|
|
756
|
+
encodeURIString('HTTPS://WWW.中文.COM./Over/There?a=B&b=c#Anchor', { lowercase: true });
|
|
757
|
+
// 'https://www.xn--fiq228c.com./over/there?a=b&b=c#anchor'
|
|
758
|
+
|
|
759
|
+
encodeURIString('foo://usër:pâss@bar.baz:8080/Ovër There?ù=B&b=c#Anchôr');
|
|
760
|
+
// 'foo://us%C3%ABr:p%C3%A2ss@bar.baz:8080/Ov%C3%ABr%20There?%C3%B9=B&b=c#Anch%C3%B4r'
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
</details>
|
|
764
|
+
|
|
765
|
+
<details>
|
|
766
|
+
<summary><em>encodeWebURL(uri, options)</em></summary>
|
|
767
|
+
|
|
768
|
+
<br>
|
|
769
|
+
|
|
770
|
+
Encodes an HTTP or HTTPS URL per **RFC-3986**, on top of [`encodeURIString`](#encoders). Uses the same fixed-encode logic but enforces the HTTP(S) constraints.
|
|
771
|
+
|
|
772
|
+
**Adds**
|
|
773
|
+
|
|
774
|
+
- `scheme` must be `http` / `HTTP` or `https` / `HTTPS` — else `URI_INVALID_SCHEME`.
|
|
775
|
+
- `authority` is required — else `URI_MISSING_AUTHORITY`.
|
|
776
|
+
- URL must be shorter than 2,048 characters — else `URI_MAX_LENGTH_URL`.
|
|
777
|
+
|
|
778
|
+
**Parameters and options** — identical to [`encodeURIString`](#encoders).
|
|
779
|
+
|
|
780
|
+
**Examples**
|
|
781
|
+
|
|
782
|
+
```ts
|
|
783
|
+
encodeWebURL('HTTPS://WWW.中文.COM./Over/There?a=B&b=c#Anchor');
|
|
784
|
+
// 'https://www.xn--fiq228c.com./Over/There?a=B&b=c#Anchor'
|
|
785
|
+
|
|
786
|
+
encodeWebURL('http://usër:pâss@bar.baz:8080/Ovër There?ù=B&b=c#Anchôr');
|
|
787
|
+
// 'http://us%C3%ABr:p%C3%A2ss@bar.baz:8080/Ov%C3%ABr%20There?%C3%B9=B&b=c#Anch%C3%B4r'
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
</details>
|
|
791
|
+
|
|
792
|
+
<details>
|
|
793
|
+
<summary><em>encodeSitemapURL(uri)</em></summary>
|
|
794
|
+
|
|
795
|
+
<br>
|
|
796
|
+
|
|
797
|
+
Encodes an HTTP or HTTPS URL for an XML sitemap on top of [`encodeWebURL`](#encoders) — applies Sitemap escape codes and lowercases the URL.
|
|
798
|
+
|
|
799
|
+
**Adds**
|
|
800
|
+
|
|
801
|
+
- Sitemap's special characters are escaped (see [`checkHttpSitemapURL`](#checkers)).
|
|
802
|
+
- The output is fully lowercased.
|
|
803
|
+
|
|
804
|
+
**Examples**
|
|
805
|
+
|
|
806
|
+
```ts
|
|
807
|
+
encodeSitemapURL("http://user:p'âss@bar.baz/it's *ver/there?a=b&b=c#anch*r");
|
|
808
|
+
// 'http://user:p'%C3%A2ss@bar.baz/it's%20%2Aver/there?a=b&b=c#anch%2Ar'
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
</details>
|
|
812
|
+
|
|
813
|
+
### Decoders
|
|
814
|
+
|
|
815
|
+
<details>
|
|
816
|
+
<summary><em>decodeURIComponentString(component, options)</em></summary>
|
|
817
|
+
|
|
818
|
+
<br>
|
|
819
|
+
|
|
820
|
+
Decodes a URI component string. Returns the empty string when the input cannot be decoded (`decodeURIComponent` would throw).
|
|
821
|
+
|
|
822
|
+
**Parameters**
|
|
823
|
+
|
|
824
|
+
| Option | Type | Default | Description |
|
|
825
|
+
| --- | --- | --- | --- |
|
|
826
|
+
| `component` | `string` | *(required)* | The component to decode. |
|
|
827
|
+
| `options.lowercase` | `boolean` | `false` | Lowercase the result. |
|
|
828
|
+
| `options.sitemap` | `boolean` | `false` | Decode Sitemap escape codes (see [`checkHttpSitemapURL`](#checkers)). |
|
|
829
|
+
|
|
830
|
+
**Returns** — `string`. The decoded component (or `''` on invalid input).
|
|
831
|
+
|
|
832
|
+
**Examples**
|
|
833
|
+
|
|
834
|
+
```ts
|
|
835
|
+
decodeURIComponentString('%2A'); // '*'
|
|
836
|
+
decodeURIComponentString(''&%2A', { sitemap: true }); // "'&*"
|
|
837
|
+
decodeURIComponentString('SITE&maP', { sitemap: true, lowercase: true });
|
|
838
|
+
// 'site&map'
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
</details>
|
|
842
|
+
|
|
843
|
+
<details>
|
|
844
|
+
<summary><em>decodeURIString(uri, options)</em></summary>
|
|
845
|
+
|
|
846
|
+
<br>
|
|
847
|
+
|
|
848
|
+
Decodes a URI string per **RFC-3986** with basic validity checking and IDN support — the inverse of [`encodeURIString`](#encoders).
|
|
849
|
+
|
|
850
|
+
**Parameters**
|
|
851
|
+
|
|
852
|
+
| Option | Type | Default | Description |
|
|
853
|
+
| --- | --- | --- | --- |
|
|
854
|
+
| `uri` | `string` | *(required)* | The URI to decode. |
|
|
855
|
+
| `options.lowercase` | `boolean` | `false` | Lowercase the entire URI including path, query, and fragment. |
|
|
856
|
+
|
|
857
|
+
**Returns** — `string`. The decoded URI.
|
|
858
|
+
|
|
859
|
+
**Throws** — `URIError` with one of: `URI_INVALID_TYPE`, `URI_MISSING_SCHEME`, `URI_EMPTY_SCHEME`, `URI_MISSING_PATH`, `URI_INVALID_PATH`, `URI_INVALID_HOST`, `URI_INVALID_SCHEME_CHAR`, `URI_INVALID_PORT`.
|
|
860
|
+
|
|
861
|
+
**Notes**
|
|
862
|
+
|
|
863
|
+
- A component that cannot be decoded is silently passed through (preserves the encoded form).
|
|
864
|
+
- IDN hosts are returned in Unicode form (Punydecoded).
|
|
865
|
+
- See [Limitations](#limitations) for the `lowercase` flag's scope.
|
|
866
|
+
|
|
867
|
+
**Examples**
|
|
868
|
+
|
|
869
|
+
```ts
|
|
870
|
+
decodeURIString('http://user%:pass@xn--fiq228c.com/%?query=%E0%A5%90#anch#or');
|
|
871
|
+
// 'http://中文.com/?query=ॐ'
|
|
872
|
+
|
|
873
|
+
decodeURIString('HTTPS://WWW.xn--fiq228c.COM./Over/There?a=B&b=c#Anchor');
|
|
874
|
+
// 'https://www.中文.com./Over/There?a=B&b=c#Anchor'
|
|
875
|
+
|
|
876
|
+
decodeURIString('foo://us%C3%ABr:p%C3%A2ss@bar.baz:8080/Ov%C3%ABr%20There?%C3%B9=B&b=c#Anch%C3%B4r');
|
|
877
|
+
// 'foo://usër:pâss@bar.baz:8080/Ovër There?ù=B&b=c#Anchôr'
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
</details>
|
|
881
|
+
|
|
882
|
+
<details>
|
|
883
|
+
<summary><em>decodeWebURL(uri, options)</em></summary>
|
|
884
|
+
|
|
885
|
+
<br>
|
|
886
|
+
|
|
887
|
+
Decodes an HTTP or HTTPS URL per **RFC-3986** on top of [`decodeURIString`](#decoders) — the inverse of [`encodeWebURL`](#encoders).
|
|
888
|
+
|
|
889
|
+
**Adds**
|
|
890
|
+
|
|
891
|
+
- `scheme` must be `http` / `HTTP` or `https` / `HTTPS` — else `URI_INVALID_SCHEME`.
|
|
892
|
+
- `authority` is required — else `URI_MISSING_AUTHORITY`.
|
|
893
|
+
- URL must be shorter than 2,048 characters — else `URI_MAX_LENGTH_URL`.
|
|
894
|
+
|
|
895
|
+
**Examples**
|
|
896
|
+
|
|
897
|
+
```ts
|
|
898
|
+
decodeWebURL('HTTPS://WWW.xn--fiq228c.COM./Over/There?a=B&b=c#Anchor');
|
|
899
|
+
// 'https://www.中文.com./Over/There?a=B&b=c#Anchor'
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
</details>
|
|
903
|
+
|
|
904
|
+
<details>
|
|
905
|
+
<summary><em>decodeSitemapURL(uri, options)</em></summary>
|
|
906
|
+
|
|
907
|
+
<br>
|
|
908
|
+
|
|
909
|
+
Decodes an HTTP or HTTPS URL coming from an XML sitemap — the inverse of [`encodeSitemapURL`](#encoders). Sitemap escape codes are converted back to their characters.
|
|
910
|
+
|
|
911
|
+
**Examples**
|
|
912
|
+
|
|
913
|
+
```ts
|
|
914
|
+
decodeSitemapURL('HTTP://bar.BAZ/IT'S%20OVER%2Athere%2A?a=b&c=d');
|
|
915
|
+
// "http://bar.baz/IT'S OVER*there*?a=b&c=d"
|
|
916
|
+
|
|
917
|
+
decodeSitemapURL('http://bar.baz/IT'S%20OVER%2Athere%2A?A=b&c=D', { lowercase: true });
|
|
918
|
+
// "http://bar.baz/it's over*there*?a=b&c=d"
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
</details>
|
|
922
|
+
|
|
923
|
+
## Errors
|
|
924
|
+
|
|
925
|
+
Errors emitted by `@coroboros/uri` are native `URIError` instances with an additional `code` property:
|
|
926
|
+
|
|
927
|
+
```ts
|
|
928
|
+
{
|
|
929
|
+
name: 'URIError',
|
|
930
|
+
code: URIErrorCode,
|
|
931
|
+
message: string,
|
|
932
|
+
stack: string,
|
|
933
|
+
}
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
The `code` field is a stable string discriminant safe for runtime branching.
|
|
937
|
+
|
|
938
|
+
<details>
|
|
939
|
+
<summary><em>Error codes</em></summary>
|
|
940
|
+
|
|
941
|
+
<br>
|
|
942
|
+
|
|
943
|
+
| Code | Description | Module |
|
|
944
|
+
| --- | --- | --- |
|
|
945
|
+
| `URI_INVALID_TYPE` | URI variable type is not valid. | `src/checkers` |
|
|
946
|
+
| `URI_MISSING_SCHEME` | URI scheme is missing. | `src/checkers` |
|
|
947
|
+
| `URI_EMPTY_SCHEME` | URI scheme is empty. | `src/checkers` |
|
|
948
|
+
| `URI_INVALID_SCHEME` | URI scheme is not valid. | `src/checkers`, `src/decoders`, `src/encoders` |
|
|
949
|
+
| `URI_INVALID_SCHEME_CHAR` | URI scheme contains an invalid character. | `src/checkers`, `src/decoders`, `src/encoders` |
|
|
950
|
+
| `URI_MISSING_PATH` | URI path is missing. | `src/checkers` |
|
|
951
|
+
| `URI_INVALID_PATH` | URI path is not valid per **RFC-3986**. | `src/checkers` |
|
|
952
|
+
| `URI_MISSING_AUTHORITY` | URI authority is missing. | `src/checkers`, `src/decoders`, `src/encoders` |
|
|
953
|
+
| `URI_INVALID_HOST` | URI host is not a valid IP or domain. | `src/checkers` |
|
|
954
|
+
| `URI_INVALID_PORT` | URI port is not a number. | `src/checkers`, `src/decoders`, `src/encoders` |
|
|
955
|
+
| `URI_INVALID_CHAR` | URI contains an invalid character. | `src/checkers` |
|
|
956
|
+
| `URI_INVALID_USERINFO_CHAR` | URI userinfo contains an invalid character. | `src/checkers` |
|
|
957
|
+
| `URI_INVALID_PATH_CHAR` | URI path contains an invalid character. | `src/checkers` |
|
|
958
|
+
| `URI_INVALID_QUERY_CHAR` | URI query contains an invalid character. | `src/checkers` |
|
|
959
|
+
| `URI_INVALID_FRAGMENT_CHAR` | URI fragment contains an invalid character. | `src/checkers` |
|
|
960
|
+
| `URI_INVALID_PERCENT_ENCODING` | A percent-encoding character is not valid. | `src/checkers` |
|
|
961
|
+
| `URI_INVALID_SITEMAP_ENCODING` | URI contains an invalid sitemap escape code. | `src/checkers` |
|
|
962
|
+
| `URI_MAX_LENGTH_URL` | Maximum URL length of 2,048 characters has been reached. | `src/checkers` |
|
|
963
|
+
|
|
964
|
+
</details>
|
|
965
|
+
|
|
966
|
+
## Limitations
|
|
967
|
+
|
|
968
|
+
- A present-but-empty query or fragment (a bare `?` or `#`) is preserved and round-trips, distinct from an absent one (**RFC-3986 §5.3**).
|
|
969
|
+
- A port must be a string of ASCII digits (**RFC-3986 §3.2.3**) — values like `0x1F` are rejected.
|
|
970
|
+
- `userinfo` is delimited by the last `@`, and a non-IPv6 host/port by the last `:` (**RFC-3986 §3.2**).
|
|
971
|
+
- Percent-encoding hex is case-insensitive: `%3a` and `%3A` are both accepted (**RFC-3986 §6.2.2.1**).
|
|
972
|
+
- Inside a URI, an IPv6 zone identifier must use the `%25` delimiter and a non-empty `ZoneID` of `unreserved` / `pct-encoded` characters (**RFC 6874 §2**). The standalone [`isIPv6`](#validators) validator stays lenient.
|
|
973
|
+
- `encodeSitemapURL` escapes all five XML entities `& ' " < >`, and a Sitemap URL must be shorter than 2,048 characters (sitemaps.org). For example, `encodeSitemapURL('http://example.com/a&b<c>d')` returns `'http://example.com/a&b<c>d'`.
|
|
974
|
+
- This is a strict **RFC-3986** toolkit, not a WHATWG URL parser — it does not apply WHATWG host/IPv4 leniency.
|
|
975
|
+
- IPv6 addresses are not canonicalized to **RFC 5952** form.
|
|
976
|
+
- The `lowercase` option lowercases the entire input including path, query, and fragment, which are case-sensitive per **RFC-3986 §6.2.2.1**. Use `lowercase` for Sitemap or convenience, not as RFC normalization. By default only scheme and host are lowercased, which is the RFC-compliant behavior.
|
|
977
|
+
|
|
978
|
+
## Contributing
|
|
979
|
+
|
|
980
|
+
Bug reports and PRs welcome.
|
|
981
|
+
|
|
982
|
+
- Open an issue before submitting non-trivial PRs.
|
|
983
|
+
- Commits follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
984
|
+
- Run `pnpm lint && pnpm typecheck && pnpm test` before pushing.
|
|
985
|
+
- Run `pnpm bench` against `bench/baseline.md` when touching parser, encoders, or decoders — no regression beyond 10 % on any bucket at fixed feature set.
|
|
986
|
+
- Target the `main` branch.
|
|
987
|
+
|
|
988
|
+
## License
|
|
989
|
+
|
|
990
|
+
[MIT](LICENSE.md)
|