@openclaw/lobster 2026.6.33 → 2026.6.35
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/node_modules/fast-uri/.github/dependabot.yml +42 -2
- package/node_modules/fast-uri/.github/workflows/ci.yml +54 -6
- package/node_modules/fast-uri/.github/workflows/lock-threads.yml +1 -1
- package/node_modules/fast-uri/.github/workflows/ossf-scorecard.yml +45 -0
- package/node_modules/fast-uri/README.md +10 -9
- package/node_modules/fast-uri/SECURITY.md +173 -0
- package/node_modules/fast-uri/benchmark/benchmark.mjs +71 -0
- package/node_modules/fast-uri/benchmark/encoder.mjs +54 -0
- package/node_modules/fast-uri/index.js +277 -45
- package/node_modules/fast-uri/lib/schemes.js +384 -7
- package/node_modules/fast-uri/lib/utils.js +458 -95
- package/node_modules/fast-uri/package.json +4 -4
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/fast-uri/test/equal.test.js +94 -3
- package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +24 -1
- package/node_modules/fast-uri/test/fixtures/uri-js-serialize.json +23 -1
- package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/fast-uri/test/ipv6-validation.test.js +154 -0
- package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/fast-uri/test/parse.test.js +150 -3
- package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
- package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/fast-uri/test/scheme-normalization.test.js +16 -0
- package/node_modules/fast-uri/test/scheme-validation.test.js +122 -0
- package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
- package/node_modules/fast-uri/test/security-utf8-encoding.test.js +0 -0
- package/node_modules/fast-uri/test/security.test.js +301 -3
- package/node_modules/fast-uri/test/serialize.test.js +144 -0
- package/node_modules/fast-uri/test/uri-js-compatibility.test.js +0 -3
- package/node_modules/fast-uri/test/uri-js.test.js +4 -4
- package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/fast-uri/test/util.test.js +9 -0
- package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/fast-uri/tsconfig.json +8 -7
- package/node_modules/fast-uri/types/index.d.ts +4 -9
- package/node_modules/fast-uri/types/index.tst.ts +31 -0
- package/npm-shrinkwrap.json +5 -5
- package/package.json +4 -4
- package/node_modules/fast-uri/.github/workflows/package-manager-ci.yml +0 -24
- package/node_modules/fast-uri/types/index.test-d.ts +0 -17
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding,
|
|
3
|
+
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require('./lib/utils')
|
|
4
4
|
const { SCHEMES, getSchemeHandler } = require('./lib/schemes')
|
|
5
5
|
|
|
6
|
+
const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u
|
|
7
|
+
const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} scheme
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
function decodeValidScheme (scheme) {
|
|
14
|
+
const decodedScheme = unescape(String(scheme))
|
|
15
|
+
if (!VALID_SCHEME.test(decodedScheme)) {
|
|
16
|
+
throw new TypeError(MALFORMED_SCHEME_ERROR)
|
|
17
|
+
}
|
|
18
|
+
return decodedScheme
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
/**
|
|
7
22
|
* @template {import('./types/index').URIComponent|string} T
|
|
8
23
|
* @param {T} uri
|
|
@@ -26,7 +41,50 @@ function normalize (uri, options) {
|
|
|
26
41
|
*/
|
|
27
42
|
function resolve (baseURI, relativeURI, options) {
|
|
28
43
|
const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' }
|
|
29
|
-
const
|
|
44
|
+
const {
|
|
45
|
+
parsed: baseParsed,
|
|
46
|
+
malformedAuthorityOrPort: baseMalformed,
|
|
47
|
+
malformedPercentEncoding: baseMalformedPercentEncoding,
|
|
48
|
+
malformedSchemeSpecific: baseMalformedSchemeSpecific,
|
|
49
|
+
malformedHost: baseMalformedHost,
|
|
50
|
+
malformedScheme: baseMalformedScheme
|
|
51
|
+
} = parseWithStatus(baseURI, schemelessOptions)
|
|
52
|
+
const {
|
|
53
|
+
parsed: relativeParsed,
|
|
54
|
+
malformedAuthorityOrPort: relativeMalformed,
|
|
55
|
+
malformedPercentEncoding: relativeMalformedPercentEncoding,
|
|
56
|
+
malformedSchemeSpecific: relativeMalformedSchemeSpecific,
|
|
57
|
+
malformedHost: relativeMalformedHost,
|
|
58
|
+
malformedScheme: relativeMalformedScheme
|
|
59
|
+
} = parseWithStatus(relativeURI, schemelessOptions)
|
|
60
|
+
if (
|
|
61
|
+
baseMalformed ||
|
|
62
|
+
relativeMalformed ||
|
|
63
|
+
baseMalformedPercentEncoding ||
|
|
64
|
+
relativeMalformedPercentEncoding ||
|
|
65
|
+
baseMalformedSchemeSpecific ||
|
|
66
|
+
relativeMalformedSchemeSpecific ||
|
|
67
|
+
baseMalformedHost ||
|
|
68
|
+
relativeMalformedHost ||
|
|
69
|
+
baseMalformedScheme ||
|
|
70
|
+
relativeMalformedScheme
|
|
71
|
+
) {
|
|
72
|
+
throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
|
|
73
|
+
}
|
|
74
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true)
|
|
75
|
+
const resolvedSchemeHandler = getSchemeHandler((options && options.scheme) || resolved.scheme)
|
|
76
|
+
const resolvedHost = resolved.host
|
|
77
|
+
const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
|
|
78
|
+
(isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6)
|
|
79
|
+
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)
|
|
80
|
+
// Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
|
|
81
|
+
// parser can reject them even though fast-uri preserves them safely as RFC
|
|
82
|
+
// 3986 data. A raw non-ASCII host must still fail closed if conversion fails.
|
|
83
|
+
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf('%') !== -1 &&
|
|
84
|
+
!/\P{ASCII}/u.test(resolvedHost)
|
|
85
|
+
if (resolved.error && !encodedASCIIHost) {
|
|
86
|
+
throw new Error(resolved.error)
|
|
87
|
+
}
|
|
30
88
|
schemelessOptions.skipEscape = true
|
|
31
89
|
return serialize(resolved, schemelessOptions)
|
|
32
90
|
}
|
|
@@ -109,7 +167,7 @@ function equal (uriA, uriB, options) {
|
|
|
109
167
|
const normalizedA = normalizeComparableURI(uriA, options)
|
|
110
168
|
const normalizedB = normalizeComparableURI(uriB, options)
|
|
111
169
|
|
|
112
|
-
return normalizedA !== undefined && normalizedB !== undefined && normalizedA
|
|
170
|
+
return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB
|
|
113
171
|
}
|
|
114
172
|
|
|
115
173
|
/**
|
|
@@ -132,30 +190,39 @@ function serialize (cmpts, opts) {
|
|
|
132
190
|
reference: cmpts.reference,
|
|
133
191
|
resourceName: cmpts.resourceName,
|
|
134
192
|
secure: cmpts.secure,
|
|
193
|
+
to: cmpts.to,
|
|
194
|
+
subject: cmpts.subject,
|
|
195
|
+
body: cmpts.body,
|
|
196
|
+
headers: cmpts.headers,
|
|
135
197
|
error: ''
|
|
136
198
|
}
|
|
137
199
|
const options = Object.assign({}, opts)
|
|
138
200
|
const uriTokens = []
|
|
139
201
|
|
|
202
|
+
if (component.scheme) {
|
|
203
|
+
component.scheme = decodeValidScheme(component.scheme)
|
|
204
|
+
}
|
|
205
|
+
|
|
140
206
|
// find scheme handler
|
|
141
207
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme)
|
|
142
208
|
|
|
143
209
|
// perform scheme specific serialization
|
|
144
210
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
|
|
145
211
|
|
|
212
|
+
const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined
|
|
213
|
+
const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority
|
|
214
|
+
|
|
146
215
|
if (component.path !== undefined) {
|
|
147
216
|
if (!options.skipEscape) {
|
|
148
|
-
component.path =
|
|
149
|
-
|
|
150
|
-
if (component.scheme !== undefined) {
|
|
151
|
-
component.path = component.path.split('%3A').join(':')
|
|
152
|
-
}
|
|
217
|
+
component.path = serializePathEncoding(component.path, pathNoScheme)
|
|
153
218
|
} else {
|
|
154
219
|
component.path = normalizePercentEncoding(component.path)
|
|
155
220
|
}
|
|
156
221
|
}
|
|
157
222
|
|
|
158
223
|
if (options.reference !== 'suffix' && component.scheme) {
|
|
224
|
+
// Scheme handlers may replace the scheme during serialization.
|
|
225
|
+
component.scheme = decodeValidScheme(component.scheme)
|
|
159
226
|
uriTokens.push(component.scheme, ':')
|
|
160
227
|
}
|
|
161
228
|
|
|
@@ -178,6 +245,13 @@ function serialize (cmpts, opts) {
|
|
|
178
245
|
s = removeDotSegments(s)
|
|
179
246
|
}
|
|
180
247
|
|
|
248
|
+
// Dot-segment removal can expose a colon that was not originally in the
|
|
249
|
+
// first segment (for example, "./a:b"). Reapply path-noscheme encoding so
|
|
250
|
+
// the serialized relative reference cannot be reparsed as a URI scheme.
|
|
251
|
+
if (pathNoScheme) {
|
|
252
|
+
s = serializePathEncoding(s, true)
|
|
253
|
+
}
|
|
254
|
+
|
|
181
255
|
if (
|
|
182
256
|
authority === undefined &&
|
|
183
257
|
s[0] === '/' &&
|
|
@@ -191,17 +265,30 @@ function serialize (cmpts, opts) {
|
|
|
191
265
|
}
|
|
192
266
|
|
|
193
267
|
if (component.query !== undefined) {
|
|
194
|
-
uriTokens.push('?', component.query)
|
|
268
|
+
uriTokens.push('?', encodeQuery(component.query))
|
|
195
269
|
}
|
|
196
270
|
|
|
197
271
|
if (component.fragment !== undefined) {
|
|
198
|
-
uriTokens.push('#', component.fragment)
|
|
272
|
+
uriTokens.push('#', encodeFragment(component.fragment))
|
|
199
273
|
}
|
|
200
274
|
return uriTokens.join('')
|
|
201
275
|
}
|
|
202
276
|
|
|
203
277
|
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
|
|
204
278
|
|
|
279
|
+
// Captures the authority component (between "//" and the next "/", "?" or "#"),
|
|
280
|
+
// with or without a scheme prefix, for the literal-backslash rejection below.
|
|
281
|
+
const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
|
|
282
|
+
|
|
283
|
+
// Captures the leading authority-introducer region after an optional scheme: a
|
|
284
|
+
// run of forward slashes, backslashes, and the characters the WHATWG URL parser
|
|
285
|
+
// removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
|
|
286
|
+
// is exactly "//". Node treats "\" as "/" on special schemes and strips those
|
|
287
|
+
// characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
|
|
288
|
+
// "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
|
|
289
|
+
// the path group (host confusion / SSRF / redirect bypass).
|
|
290
|
+
const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/
|
|
291
|
+
|
|
205
292
|
/**
|
|
206
293
|
* @param {import('./types/index').URIComponent} parsed
|
|
207
294
|
* @param {RegExpMatchArray} matches
|
|
@@ -219,10 +306,93 @@ function getParseError (parsed, matches) {
|
|
|
219
306
|
return undefined
|
|
220
307
|
}
|
|
221
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Checks percent syntax without decoding the represented octets. RFC 3986
|
|
311
|
+
* percent-encoding is byte-oriented, so sequences such as `%FF` are valid even
|
|
312
|
+
* though they are not independently valid UTF-8.
|
|
313
|
+
*
|
|
314
|
+
* @param {string|undefined} component
|
|
315
|
+
* @returns {boolean}
|
|
316
|
+
*/
|
|
317
|
+
function hasMalformedPercentEncoding (component) {
|
|
318
|
+
if (component === undefined) return false
|
|
319
|
+
|
|
320
|
+
let percent = component.indexOf('%')
|
|
321
|
+
while (percent !== -1) {
|
|
322
|
+
if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
|
|
323
|
+
return true
|
|
324
|
+
}
|
|
325
|
+
percent = component.indexOf('%', percent + 3)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return false
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Whether the host is a bracketed IP literal (RFC 3986 `IP-literal`).
|
|
333
|
+
* An unterminated `[` is not a literal, so it must still be validated as a
|
|
334
|
+
* reg-name instead of being waved through as an IP.
|
|
335
|
+
*
|
|
336
|
+
* @param {string} host
|
|
337
|
+
* @returns {boolean}
|
|
338
|
+
*/
|
|
339
|
+
function isIPLiteral (host) {
|
|
340
|
+
return host[0] === '[' && host[host.length - 1] === ']'
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* @param {RegExpMatchArray} matches
|
|
345
|
+
* @returns {boolean}
|
|
346
|
+
*/
|
|
347
|
+
function hasMalformedComponentPercentEncoding (matches) {
|
|
348
|
+
// Bracketed IP literals use a raw "%" as the zone separator for historical
|
|
349
|
+
// compatibility. Their parsing is intentionally left to normalizeIPv6.
|
|
350
|
+
const host = matches[4]
|
|
351
|
+
return hasMalformedPercentEncoding(matches[3]) ||
|
|
352
|
+
(host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host)) ||
|
|
353
|
+
hasMalformedPercentEncoding(matches[6]) ||
|
|
354
|
+
hasMalformedPercentEncoding(matches[7]) ||
|
|
355
|
+
hasMalformedPercentEncoding(matches[8])
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @param {import('./types/index').URIComponent} parsed
|
|
360
|
+
* @param {import('./types/index').Options} options
|
|
361
|
+
* @param {{ domainHost?: boolean, unicodeSupport?: boolean }|undefined} schemeHandler
|
|
362
|
+
* @param {boolean} isIP
|
|
363
|
+
* @returns {boolean} whether host conversion failed
|
|
364
|
+
*/
|
|
365
|
+
function canonicalizeHost (parsed, options, schemeHandler, isIP) {
|
|
366
|
+
const bracketedIPLiteral =
|
|
367
|
+
parsed.host &&
|
|
368
|
+
parsed.host[0] === '[' &&
|
|
369
|
+
parsed.host[parsed.host.length - 1] === ']'
|
|
370
|
+
|
|
371
|
+
if (
|
|
372
|
+
!options.unicodeSupport &&
|
|
373
|
+
(!schemeHandler || !schemeHandler.unicodeSupport) &&
|
|
374
|
+
parsed.host &&
|
|
375
|
+
!isIPLiteral(parsed.host) &&
|
|
376
|
+
(options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
|
|
377
|
+
isIP === false &&
|
|
378
|
+
!bracketedIPLiteral &&
|
|
379
|
+
nonSimpleDomain(parsed.host)
|
|
380
|
+
) {
|
|
381
|
+
try {
|
|
382
|
+
parsed.host = new URL('http://' + parsed.host).hostname
|
|
383
|
+
} catch (e) {
|
|
384
|
+
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
385
|
+
return true
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return false
|
|
390
|
+
}
|
|
391
|
+
|
|
222
392
|
/**
|
|
223
393
|
* @param {string} uri
|
|
224
394
|
* @param {import('./types/index').Options} [opts]
|
|
225
|
-
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean }}
|
|
395
|
+
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
226
396
|
*/
|
|
227
397
|
function parseWithStatus (uri, opts) {
|
|
228
398
|
const options = Object.assign({}, opts)
|
|
@@ -238,6 +408,11 @@ function parseWithStatus (uri, opts) {
|
|
|
238
408
|
}
|
|
239
409
|
|
|
240
410
|
let malformedAuthorityOrPort = false
|
|
411
|
+
let malformedPercentEncoding = false
|
|
412
|
+
let malformedSchemeSpecific = false
|
|
413
|
+
let malformedHost = false
|
|
414
|
+
let malformedIPLiteral = false
|
|
415
|
+
let malformedScheme = false
|
|
241
416
|
|
|
242
417
|
let isIP = false
|
|
243
418
|
if (options.reference === 'suffix') {
|
|
@@ -248,6 +423,41 @@ function parseWithStatus (uri, opts) {
|
|
|
248
423
|
}
|
|
249
424
|
}
|
|
250
425
|
|
|
426
|
+
// A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
|
|
427
|
+
// not an authority delimiter. Reject it in the authority rather than
|
|
428
|
+
// rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
|
|
429
|
+
// change the resource identified by an otherwise-invalid input, and lets "\"
|
|
430
|
+
// act as a host delimiter here while Node's native URL parses a different
|
|
431
|
+
// host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
|
|
432
|
+
// untouched and remains valid encoded data.
|
|
433
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX)
|
|
434
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
|
|
435
|
+
parsed.error = 'URI authority must not contain a literal backslash.'
|
|
436
|
+
malformedAuthorityOrPort = true
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Reject a malformed or whitespace-smuggled authority introducer. fast-uri
|
|
440
|
+
// only recognizes a literal "//"; anything else in the leading separator run
|
|
441
|
+
// (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
|
|
442
|
+
// Node strips) means the authority fast-uri parses differs from the one Node's
|
|
443
|
+
// URL resolves. Reject rather than rewrite, mirroring the literal-backslash
|
|
444
|
+
// guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
|
|
445
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION)
|
|
446
|
+
if (introducerMatch !== null) {
|
|
447
|
+
const region = introducerMatch[1]
|
|
448
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, '')
|
|
449
|
+
// Two or more leading separators introduce an authority.
|
|
450
|
+
if (normalizedRegion.length >= 2) {
|
|
451
|
+
if (normalizedRegion.slice(0, 2) !== '//') {
|
|
452
|
+
parsed.error = parsed.error || 'URI authority must not contain a literal backslash.'
|
|
453
|
+
malformedAuthorityOrPort = true
|
|
454
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
455
|
+
parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.'
|
|
456
|
+
malformedAuthorityOrPort = true
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
251
461
|
const matches = uri.match(URI_PARSE)
|
|
252
462
|
|
|
253
463
|
if (matches) {
|
|
@@ -260,6 +470,21 @@ function parseWithStatus (uri, opts) {
|
|
|
260
470
|
parsed.query = matches[7]
|
|
261
471
|
parsed.fragment = matches[8]
|
|
262
472
|
|
|
473
|
+
if (parsed.scheme !== undefined) {
|
|
474
|
+
const decodedScheme = unescape(parsed.scheme)
|
|
475
|
+
if (VALID_SCHEME.test(decodedScheme)) {
|
|
476
|
+
parsed.scheme = decodedScheme.toLowerCase()
|
|
477
|
+
} else {
|
|
478
|
+
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
|
|
479
|
+
malformedScheme = true
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches)
|
|
484
|
+
if (malformedPercentEncoding) {
|
|
485
|
+
parsed.error = parsed.error || 'URI contains malformed percent-encoding.'
|
|
486
|
+
}
|
|
487
|
+
|
|
263
488
|
// fix port number
|
|
264
489
|
if (isNaN(parsed.port)) {
|
|
265
490
|
parsed.port = matches[5]
|
|
@@ -274,9 +499,17 @@ function parseWithStatus (uri, opts) {
|
|
|
274
499
|
if (parsed.host) {
|
|
275
500
|
const ipv4result = isIPv4(parsed.host)
|
|
276
501
|
if (ipv4result === false) {
|
|
502
|
+
const bracketedIPLiteral = isIPLiteral(parsed.host)
|
|
503
|
+
const hasIPLiteralBracket = parsed.host.indexOf('[') !== -1 || parsed.host.indexOf(']') !== -1
|
|
277
504
|
const ipv6result = normalizeIPv6(parsed.host)
|
|
278
|
-
|
|
279
|
-
|
|
505
|
+
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true
|
|
506
|
+
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true)
|
|
507
|
+
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
|
|
508
|
+
|
|
509
|
+
if (malformedIPLiteral) {
|
|
510
|
+
parsed.error = parsed.error || 'URI host is malformed.'
|
|
511
|
+
malformedAuthorityOrPort = true
|
|
512
|
+
}
|
|
280
513
|
} else {
|
|
281
514
|
isIP = true
|
|
282
515
|
}
|
|
@@ -299,49 +532,40 @@ function parseWithStatus (uri, opts) {
|
|
|
299
532
|
// find scheme handler
|
|
300
533
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme)
|
|
301
534
|
|
|
302
|
-
//
|
|
303
|
-
if (!
|
|
304
|
-
|
|
305
|
-
if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
306
|
-
// convert Unicode IDN -> ASCII IDN
|
|
307
|
-
try {
|
|
308
|
-
parsed.host = URL.domainToASCII(parsed.host.toLowerCase())
|
|
309
|
-
} catch (e) {
|
|
310
|
-
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
// convert IRI -> URI
|
|
535
|
+
// convert Unicode IDN -> ASCII IDN when the effective scheme uses domain hosts
|
|
536
|
+
if (!malformedIPLiteral) {
|
|
537
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP)
|
|
314
538
|
}
|
|
315
539
|
|
|
316
540
|
if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
|
|
317
541
|
if (uri.indexOf('%') !== -1) {
|
|
318
|
-
if (parsed.
|
|
319
|
-
parsed.
|
|
320
|
-
|
|
321
|
-
if (parsed.host !== undefined) {
|
|
322
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP)
|
|
542
|
+
if (parsed.host !== undefined && !malformedIPLiteral) {
|
|
543
|
+
const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true)
|
|
544
|
+
parsed.host = reescapeHostDelimiters(host, isIP)
|
|
323
545
|
}
|
|
324
546
|
}
|
|
325
547
|
if (parsed.path) {
|
|
326
548
|
parsed.path = normalizePathEncoding(parsed.path)
|
|
327
549
|
}
|
|
550
|
+
if (parsed.query) {
|
|
551
|
+
parsed.query = normalizeQueryFragmentEncoding(parsed.query)
|
|
552
|
+
}
|
|
328
553
|
if (parsed.fragment) {
|
|
329
|
-
|
|
330
|
-
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment))
|
|
331
|
-
} catch {
|
|
332
|
-
parsed.error = parsed.error || 'URI malformed'
|
|
333
|
-
}
|
|
554
|
+
parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment)
|
|
334
555
|
}
|
|
335
556
|
}
|
|
336
557
|
|
|
337
558
|
// perform scheme specific parsing
|
|
338
559
|
if (schemeHandler && schemeHandler.parse) {
|
|
339
560
|
schemeHandler.parse(parsed, options)
|
|
561
|
+
if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
|
|
562
|
+
malformedSchemeSpecific = true
|
|
563
|
+
}
|
|
340
564
|
}
|
|
341
565
|
} else {
|
|
342
566
|
parsed.error = parsed.error || 'URI can not be parsed.'
|
|
343
567
|
}
|
|
344
|
-
return { parsed, malformedAuthorityOrPort }
|
|
568
|
+
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
|
|
345
569
|
}
|
|
346
570
|
|
|
347
571
|
/**
|
|
@@ -365,13 +589,17 @@ function normalizeString (uri, opts) {
|
|
|
365
589
|
/**
|
|
366
590
|
* @param {string} uri
|
|
367
591
|
* @param {import('./types/index').Options} [opts]
|
|
368
|
-
* @returns {{ normalized: string, malformedAuthorityOrPort: boolean }}
|
|
592
|
+
* @returns {{ normalized: string, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
369
593
|
*/
|
|
370
594
|
function normalizeStringWithStatus (uri, opts) {
|
|
371
|
-
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts)
|
|
595
|
+
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts)
|
|
372
596
|
return {
|
|
373
|
-
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
374
|
-
malformedAuthorityOrPort
|
|
597
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
598
|
+
malformedAuthorityOrPort,
|
|
599
|
+
malformedPercentEncoding,
|
|
600
|
+
malformedSchemeSpecific,
|
|
601
|
+
malformedHost,
|
|
602
|
+
malformedScheme
|
|
375
603
|
}
|
|
376
604
|
}
|
|
377
605
|
|
|
@@ -381,14 +609,18 @@ function normalizeStringWithStatus (uri, opts) {
|
|
|
381
609
|
* @returns {string|undefined}
|
|
382
610
|
*/
|
|
383
611
|
function normalizeComparableURI (uri, opts) {
|
|
384
|
-
if (typeof uri
|
|
385
|
-
|
|
386
|
-
return malformedAuthorityOrPort ? undefined : normalized
|
|
612
|
+
if (typeof uri !== 'string' && typeof uri !== 'object') {
|
|
613
|
+
return undefined
|
|
387
614
|
}
|
|
388
615
|
|
|
389
|
-
|
|
390
|
-
|
|
616
|
+
let value
|
|
617
|
+
try {
|
|
618
|
+
value = typeof uri === 'string' ? uri : serialize(uri, opts)
|
|
619
|
+
} catch {
|
|
620
|
+
return undefined
|
|
391
621
|
}
|
|
622
|
+
const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts)
|
|
623
|
+
return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
|
|
392
624
|
}
|
|
393
625
|
|
|
394
626
|
const fastUri = {
|