@openclaw/lobster 2026.7.2-beta.7 → 2026.7.34
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/dist/index.js +149 -9
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/.github/dependabot.yml +2 -42
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/.github/workflows/ci.yml +3 -51
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/.github/workflows/package-manager-ci.yml +24 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/README.md +9 -9
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/index.js +246 -41
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/schemes.js +9 -4
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/utils.js +319 -157
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/package.json +4 -4
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/equal.test.js +31 -54
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/parse.test.js +2 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security-normalization.test.js +101 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security.test.js +211 -3
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/uri-js.test.js +1 -1
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/util.test.js +0 -9
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/tsconfig.json +7 -8
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.d.ts +9 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.test-d.ts +17 -0
- package/node_modules/typebox/build/compile/validator.d.mts +4 -1
- package/node_modules/typebox/build/guard/guard.d.mts +1 -1
- package/node_modules/typebox/build/guard/guard.mjs +2 -5
- package/node_modules/typebox/build/guard/string.mjs +4 -16
- package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
- package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
- package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
- package/node_modules/typebox/build/typebox.d.mts +1 -1
- package/node_modules/typebox/build/typebox.mjs +1 -1
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +32 -29
- package/openclaw.plugin.json +0 -1
- package/package.json +5 -5
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/benchmark/encoder.mjs +0 -54
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/scheme-normalization.test.js +0 -16
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security-utf8-encoding.test.js +0 -0
- package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.tst.ts +0 -15
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, normalizeQueryFragmentEncoding,
|
|
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
|
/**
|
|
@@ -137,25 +195,30 @@ function serialize (cmpts, opts) {
|
|
|
137
195
|
const options = Object.assign({}, opts)
|
|
138
196
|
const uriTokens = []
|
|
139
197
|
|
|
198
|
+
if (component.scheme) {
|
|
199
|
+
component.scheme = decodeValidScheme(component.scheme)
|
|
200
|
+
}
|
|
201
|
+
|
|
140
202
|
// find scheme handler
|
|
141
203
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme)
|
|
142
204
|
|
|
143
205
|
// perform scheme specific serialization
|
|
144
206
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
|
|
145
207
|
|
|
208
|
+
const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined
|
|
209
|
+
const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority
|
|
210
|
+
|
|
146
211
|
if (component.path !== undefined) {
|
|
147
212
|
if (!options.skipEscape) {
|
|
148
|
-
component.path =
|
|
149
|
-
|
|
150
|
-
if (component.scheme !== undefined) {
|
|
151
|
-
component.path = component.path.split('%3A').join(':')
|
|
152
|
-
}
|
|
213
|
+
component.path = serializePathEncoding(component.path, pathNoScheme)
|
|
153
214
|
} else {
|
|
154
215
|
component.path = normalizePercentEncoding(component.path)
|
|
155
216
|
}
|
|
156
217
|
}
|
|
157
218
|
|
|
158
219
|
if (options.reference !== 'suffix' && component.scheme) {
|
|
220
|
+
// Scheme handlers may replace the scheme during serialization.
|
|
221
|
+
component.scheme = decodeValidScheme(component.scheme)
|
|
159
222
|
uriTokens.push(component.scheme, ':')
|
|
160
223
|
}
|
|
161
224
|
|
|
@@ -178,6 +241,13 @@ function serialize (cmpts, opts) {
|
|
|
178
241
|
s = removeDotSegments(s)
|
|
179
242
|
}
|
|
180
243
|
|
|
244
|
+
// Dot-segment removal can expose a colon that was not originally in the
|
|
245
|
+
// first segment (for example, "./a:b"). Reapply path-noscheme encoding so
|
|
246
|
+
// the serialized relative reference cannot be reparsed as a URI scheme.
|
|
247
|
+
if (pathNoScheme) {
|
|
248
|
+
s = serializePathEncoding(s, true)
|
|
249
|
+
}
|
|
250
|
+
|
|
181
251
|
if (
|
|
182
252
|
authority === undefined &&
|
|
183
253
|
s[0] === '/' &&
|
|
@@ -191,11 +261,11 @@ function serialize (cmpts, opts) {
|
|
|
191
261
|
}
|
|
192
262
|
|
|
193
263
|
if (component.query !== undefined) {
|
|
194
|
-
uriTokens.push('?', component.query)
|
|
264
|
+
uriTokens.push('?', encodeQuery(component.query))
|
|
195
265
|
}
|
|
196
266
|
|
|
197
267
|
if (component.fragment !== undefined) {
|
|
198
|
-
uriTokens.push('#', component.fragment)
|
|
268
|
+
uriTokens.push('#', encodeFragment(component.fragment))
|
|
199
269
|
}
|
|
200
270
|
return uriTokens.join('')
|
|
201
271
|
}
|
|
@@ -206,6 +276,15 @@ const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:
|
|
|
206
276
|
// with or without a scheme prefix, for the literal-backslash rejection below.
|
|
207
277
|
const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
|
|
208
278
|
|
|
279
|
+
// Captures the leading authority-introducer region after an optional scheme: a
|
|
280
|
+
// run of forward slashes, backslashes, and the characters the WHATWG URL parser
|
|
281
|
+
// removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
|
|
282
|
+
// is exactly "//". Node treats "\" as "/" on special schemes and strips those
|
|
283
|
+
// characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
|
|
284
|
+
// "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
|
|
285
|
+
// the path group (host confusion / SSRF / redirect bypass).
|
|
286
|
+
const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/
|
|
287
|
+
|
|
209
288
|
/**
|
|
210
289
|
* @param {import('./types/index').URIComponent} parsed
|
|
211
290
|
* @param {RegExpMatchArray} matches
|
|
@@ -223,10 +302,86 @@ function getParseError (parsed, matches) {
|
|
|
223
302
|
return undefined
|
|
224
303
|
}
|
|
225
304
|
|
|
305
|
+
/**
|
|
306
|
+
* Checks percent syntax without decoding the represented octets. RFC 3986
|
|
307
|
+
* percent-encoding is byte-oriented, so sequences such as `%FF` are valid even
|
|
308
|
+
* though they are not independently valid UTF-8.
|
|
309
|
+
*
|
|
310
|
+
* @param {string|undefined} component
|
|
311
|
+
* @returns {boolean}
|
|
312
|
+
*/
|
|
313
|
+
function hasMalformedPercentEncoding (component) {
|
|
314
|
+
if (component === undefined) return false
|
|
315
|
+
|
|
316
|
+
let percent = component.indexOf('%')
|
|
317
|
+
while (percent !== -1) {
|
|
318
|
+
if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
|
|
319
|
+
return true
|
|
320
|
+
}
|
|
321
|
+
percent = component.indexOf('%', percent + 3)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return false
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Whether the host is a bracketed IP literal (RFC 3986 `IP-literal`).
|
|
329
|
+
* An unterminated `[` is not a literal, so it must still be validated as a
|
|
330
|
+
* reg-name instead of being waved through as an IP.
|
|
331
|
+
*
|
|
332
|
+
* @param {string} host
|
|
333
|
+
* @returns {boolean}
|
|
334
|
+
*/
|
|
335
|
+
function isIPLiteral (host) {
|
|
336
|
+
return host[0] === '[' && host[host.length - 1] === ']'
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* @param {RegExpMatchArray} matches
|
|
341
|
+
* @returns {boolean}
|
|
342
|
+
*/
|
|
343
|
+
function hasMalformedComponentPercentEncoding (matches) {
|
|
344
|
+
// Bracketed IP literals use a raw "%" as the zone separator for historical
|
|
345
|
+
// compatibility. Their parsing is intentionally left to normalizeIPv6.
|
|
346
|
+
const host = matches[4]
|
|
347
|
+
return hasMalformedPercentEncoding(matches[3]) ||
|
|
348
|
+
(host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host)) ||
|
|
349
|
+
hasMalformedPercentEncoding(matches[6]) ||
|
|
350
|
+
hasMalformedPercentEncoding(matches[7]) ||
|
|
351
|
+
hasMalformedPercentEncoding(matches[8])
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* @param {import('./types/index').URIComponent} parsed
|
|
356
|
+
* @param {import('./types/index').Options} options
|
|
357
|
+
* @param {{ domainHost?: boolean, unicodeSupport?: boolean }|undefined} schemeHandler
|
|
358
|
+
* @param {boolean} isIP
|
|
359
|
+
* @returns {boolean} whether host conversion failed
|
|
360
|
+
*/
|
|
361
|
+
function canonicalizeHost (parsed, options, schemeHandler, isIP) {
|
|
362
|
+
if (
|
|
363
|
+
!options.unicodeSupport &&
|
|
364
|
+
(!schemeHandler || !schemeHandler.unicodeSupport) &&
|
|
365
|
+
parsed.host &&
|
|
366
|
+
!isIPLiteral(parsed.host) &&
|
|
367
|
+
(options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
|
|
368
|
+
isIP === false &&
|
|
369
|
+
nonSimpleDomain(parsed.host)
|
|
370
|
+
) {
|
|
371
|
+
try {
|
|
372
|
+
parsed.host = new URL('http://' + parsed.host).hostname
|
|
373
|
+
} catch (e) {
|
|
374
|
+
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
375
|
+
return true
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return false
|
|
379
|
+
}
|
|
380
|
+
|
|
226
381
|
/**
|
|
227
382
|
* @param {string} uri
|
|
228
383
|
* @param {import('./types/index').Options} [opts]
|
|
229
|
-
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean }}
|
|
384
|
+
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
230
385
|
*/
|
|
231
386
|
function parseWithStatus (uri, opts) {
|
|
232
387
|
const options = Object.assign({}, opts)
|
|
@@ -242,6 +397,11 @@ function parseWithStatus (uri, opts) {
|
|
|
242
397
|
}
|
|
243
398
|
|
|
244
399
|
let malformedAuthorityOrPort = false
|
|
400
|
+
let malformedPercentEncoding = false
|
|
401
|
+
let malformedSchemeSpecific = false
|
|
402
|
+
let malformedHost = false
|
|
403
|
+
let malformedIPLiteral = false
|
|
404
|
+
let malformedScheme = false
|
|
245
405
|
|
|
246
406
|
let isIP = false
|
|
247
407
|
if (options.reference === 'suffix') {
|
|
@@ -265,11 +425,33 @@ function parseWithStatus (uri, opts) {
|
|
|
265
425
|
malformedAuthorityOrPort = true
|
|
266
426
|
}
|
|
267
427
|
|
|
428
|
+
// Reject a malformed or whitespace-smuggled authority introducer. fast-uri
|
|
429
|
+
// only recognizes a literal "//"; anything else in the leading separator run
|
|
430
|
+
// (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
|
|
431
|
+
// Node strips) means the authority fast-uri parses differs from the one Node's
|
|
432
|
+
// URL resolves. Reject rather than rewrite, mirroring the literal-backslash
|
|
433
|
+
// guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
|
|
434
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION)
|
|
435
|
+
if (introducerMatch !== null) {
|
|
436
|
+
const region = introducerMatch[1]
|
|
437
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, '')
|
|
438
|
+
// Two or more leading separators introduce an authority.
|
|
439
|
+
if (normalizedRegion.length >= 2) {
|
|
440
|
+
if (normalizedRegion.slice(0, 2) !== '//') {
|
|
441
|
+
parsed.error = parsed.error || 'URI authority must not contain a literal backslash.'
|
|
442
|
+
malformedAuthorityOrPort = true
|
|
443
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
444
|
+
parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.'
|
|
445
|
+
malformedAuthorityOrPort = true
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
268
450
|
const matches = uri.match(URI_PARSE)
|
|
269
451
|
|
|
270
452
|
if (matches) {
|
|
271
453
|
// store each component
|
|
272
|
-
parsed.scheme = matches[1]
|
|
454
|
+
parsed.scheme = matches[1]
|
|
273
455
|
parsed.userinfo = matches[3]
|
|
274
456
|
parsed.host = matches[4]
|
|
275
457
|
parsed.port = parseInt(matches[5], 10)
|
|
@@ -277,6 +459,21 @@ function parseWithStatus (uri, opts) {
|
|
|
277
459
|
parsed.query = matches[7]
|
|
278
460
|
parsed.fragment = matches[8]
|
|
279
461
|
|
|
462
|
+
if (parsed.scheme !== undefined) {
|
|
463
|
+
const decodedScheme = unescape(parsed.scheme)
|
|
464
|
+
if (VALID_SCHEME.test(decodedScheme)) {
|
|
465
|
+
parsed.scheme = decodedScheme.toLowerCase()
|
|
466
|
+
} else {
|
|
467
|
+
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
|
|
468
|
+
malformedScheme = true
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches)
|
|
473
|
+
if (malformedPercentEncoding) {
|
|
474
|
+
parsed.error = parsed.error || 'URI contains malformed percent-encoding.'
|
|
475
|
+
}
|
|
476
|
+
|
|
280
477
|
// fix port number
|
|
281
478
|
if (isNaN(parsed.port)) {
|
|
282
479
|
parsed.port = matches[5]
|
|
@@ -291,9 +488,17 @@ function parseWithStatus (uri, opts) {
|
|
|
291
488
|
if (parsed.host) {
|
|
292
489
|
const ipv4result = isIPv4(parsed.host)
|
|
293
490
|
if (ipv4result === false) {
|
|
491
|
+
const bracketedIPLiteral = isIPLiteral(parsed.host)
|
|
492
|
+
const hasIPLiteralBracket = parsed.host.indexOf('[') !== -1 || parsed.host.indexOf(']') !== -1
|
|
294
493
|
const ipv6result = normalizeIPv6(parsed.host)
|
|
295
|
-
|
|
296
|
-
|
|
494
|
+
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true
|
|
495
|
+
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true)
|
|
496
|
+
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
|
|
497
|
+
|
|
498
|
+
if (malformedIPLiteral) {
|
|
499
|
+
parsed.error = parsed.error || 'URI host is malformed.'
|
|
500
|
+
malformedAuthorityOrPort = true
|
|
501
|
+
}
|
|
297
502
|
} else {
|
|
298
503
|
isIP = true
|
|
299
504
|
}
|
|
@@ -316,27 +521,16 @@ function parseWithStatus (uri, opts) {
|
|
|
316
521
|
// find scheme handler
|
|
317
522
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme)
|
|
318
523
|
|
|
319
|
-
//
|
|
320
|
-
if (!
|
|
321
|
-
|
|
322
|
-
if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
323
|
-
// convert Unicode IDN -> ASCII IDN
|
|
324
|
-
try {
|
|
325
|
-
parsed.host = new URL('http://' + parsed.host).hostname
|
|
326
|
-
} catch (e) {
|
|
327
|
-
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
// convert IRI -> URI
|
|
524
|
+
// convert Unicode IDN -> ASCII IDN when the effective scheme uses domain hosts
|
|
525
|
+
if (!malformedIPLiteral) {
|
|
526
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP)
|
|
331
527
|
}
|
|
332
528
|
|
|
333
529
|
if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
|
|
334
530
|
if (uri.indexOf('%') !== -1) {
|
|
335
|
-
if (parsed.
|
|
336
|
-
parsed.
|
|
337
|
-
|
|
338
|
-
if (parsed.host !== undefined) {
|
|
339
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP)
|
|
531
|
+
if (parsed.host !== undefined && !malformedIPLiteral) {
|
|
532
|
+
const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true)
|
|
533
|
+
parsed.host = reescapeHostDelimiters(host, isIP)
|
|
340
534
|
}
|
|
341
535
|
}
|
|
342
536
|
if (parsed.path) {
|
|
@@ -353,11 +547,14 @@ function parseWithStatus (uri, opts) {
|
|
|
353
547
|
// perform scheme specific parsing
|
|
354
548
|
if (schemeHandler && schemeHandler.parse) {
|
|
355
549
|
schemeHandler.parse(parsed, options)
|
|
550
|
+
if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
|
|
551
|
+
malformedSchemeSpecific = true
|
|
552
|
+
}
|
|
356
553
|
}
|
|
357
554
|
} else {
|
|
358
555
|
parsed.error = parsed.error || 'URI can not be parsed.'
|
|
359
556
|
}
|
|
360
|
-
return { parsed, malformedAuthorityOrPort }
|
|
557
|
+
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
|
|
361
558
|
}
|
|
362
559
|
|
|
363
560
|
/**
|
|
@@ -381,13 +578,17 @@ function normalizeString (uri, opts) {
|
|
|
381
578
|
/**
|
|
382
579
|
* @param {string} uri
|
|
383
580
|
* @param {import('./types/index').Options} [opts]
|
|
384
|
-
* @returns {{ normalized: string, malformedAuthorityOrPort: boolean }}
|
|
581
|
+
* @returns {{ normalized: string, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
385
582
|
*/
|
|
386
583
|
function normalizeStringWithStatus (uri, opts) {
|
|
387
|
-
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts)
|
|
584
|
+
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts)
|
|
388
585
|
return {
|
|
389
|
-
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
390
|
-
malformedAuthorityOrPort
|
|
586
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
587
|
+
malformedAuthorityOrPort,
|
|
588
|
+
malformedPercentEncoding,
|
|
589
|
+
malformedSchemeSpecific,
|
|
590
|
+
malformedHost,
|
|
591
|
+
malformedScheme
|
|
391
592
|
}
|
|
392
593
|
}
|
|
393
594
|
|
|
@@ -397,14 +598,18 @@ function normalizeStringWithStatus (uri, opts) {
|
|
|
397
598
|
* @returns {string|undefined}
|
|
398
599
|
*/
|
|
399
600
|
function normalizeComparableURI (uri, opts) {
|
|
400
|
-
if (typeof uri
|
|
401
|
-
|
|
402
|
-
return malformedAuthorityOrPort ? undefined : normalized
|
|
601
|
+
if (typeof uri !== 'string' && typeof uri !== 'object') {
|
|
602
|
+
return undefined
|
|
403
603
|
}
|
|
404
604
|
|
|
405
|
-
|
|
406
|
-
|
|
605
|
+
let value
|
|
606
|
+
try {
|
|
607
|
+
value = typeof uri === 'string' ? uri : serialize(uri, opts)
|
|
608
|
+
} catch {
|
|
609
|
+
return undefined
|
|
407
610
|
}
|
|
611
|
+
const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts)
|
|
612
|
+
return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
|
|
408
613
|
}
|
|
409
614
|
|
|
410
615
|
const fastUri = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { isUUID } = require('./utils')
|
|
4
|
-
const URN_REG =
|
|
4
|
+
const URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu
|
|
5
5
|
|
|
6
6
|
const supportedSchemeNames = /** @type {const} */ (['http', 'https', 'ws',
|
|
7
7
|
'wss', 'urn', 'urn:uuid'])
|
|
@@ -113,9 +113,14 @@ function wsSerialize (wsComponent) {
|
|
|
113
113
|
|
|
114
114
|
// reconstruct path from resource name
|
|
115
115
|
if (wsComponent.resourceName) {
|
|
116
|
-
const
|
|
116
|
+
const queryIndex = wsComponent.resourceName.indexOf('?')
|
|
117
|
+
const path = queryIndex === -1
|
|
118
|
+
? wsComponent.resourceName
|
|
119
|
+
: wsComponent.resourceName.slice(0, queryIndex)
|
|
117
120
|
wsComponent.path = (path && path !== '/' ? path : undefined)
|
|
118
|
-
wsComponent.query =
|
|
121
|
+
wsComponent.query = queryIndex === -1
|
|
122
|
+
? undefined
|
|
123
|
+
: wsComponent.resourceName.slice(queryIndex + 1)
|
|
119
124
|
wsComponent.resourceName = undefined
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -132,7 +137,7 @@ function urnParse (urnComponent, options) {
|
|
|
132
137
|
return urnComponent
|
|
133
138
|
}
|
|
134
139
|
const matches = urnComponent.path.match(URN_REG)
|
|
135
|
-
if (matches) {
|
|
140
|
+
if (matches && matches[0] === urnComponent.path) {
|
|
136
141
|
const scheme = options.scheme || urnComponent.scheme || 'urn'
|
|
137
142
|
urnComponent.nid = matches[1].toLowerCase()
|
|
138
143
|
urnComponent.nss = matches[2]
|