@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.
Files changed (45) hide show
  1. package/dist/index.js +149 -9
  2. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/.github/dependabot.yml +2 -42
  3. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/.github/workflows/ci.yml +3 -51
  4. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/.github/workflows/package-manager-ci.yml +24 -0
  5. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/README.md +9 -9
  6. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/index.js +246 -41
  7. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/schemes.js +9 -4
  8. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/lib/utils.js +319 -157
  9. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/package.json +4 -4
  10. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
  11. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/equal.test.js +31 -54
  12. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
  13. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  14. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
  15. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  16. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  17. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/parse.test.js +2 -0
  18. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  19. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
  20. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  21. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security.test.js +211 -3
  22. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/uri-js.test.js +1 -1
  23. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  24. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/util.test.js +0 -9
  25. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  26. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/tsconfig.json +7 -8
  27. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.d.ts +9 -0
  28. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/types/index.test-d.ts +17 -0
  29. package/node_modules/typebox/build/compile/validator.d.mts +4 -1
  30. package/node_modules/typebox/build/guard/guard.d.mts +1 -1
  31. package/node_modules/typebox/build/guard/guard.mjs +2 -5
  32. package/node_modules/typebox/build/guard/string.mjs +4 -16
  33. package/node_modules/typebox/build/schema/engine/_functions.mjs +15 -34
  34. package/node_modules/typebox/build/type/action/readonly_object.d.mts +0 -5
  35. package/node_modules/typebox/build/type/action/readonly_object.mjs +0 -8
  36. package/node_modules/typebox/build/typebox.d.mts +1 -1
  37. package/node_modules/typebox/build/typebox.mjs +1 -1
  38. package/node_modules/typebox/package.json +1 -1
  39. package/node_modules/typebox/readme.md +32 -29
  40. package/openclaw.plugin.json +0 -1
  41. package/package.json +5 -5
  42. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/benchmark/encoder.mjs +0 -54
  43. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/scheme-normalization.test.js +0 -16
  44. package/node_modules/@clawdbot/lobster/node_modules/fast-uri/test/security-utf8-encoding.test.js +0 -0
  45. 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, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require('./lib/utils')
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 resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true)
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.toLowerCase() === normalizedB.toLowerCase()
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 = escapePreservingEscapes(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] === undefined ? undefined : matches[1].toLowerCase()
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
- parsed.host = ipv6result.host.toLowerCase()
296
- isIP = ipv6result.isIPV6
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
- // check if scheme can't handle IRIs
320
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
321
- // if host component is a domain name
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.scheme !== undefined) {
336
- parsed.scheme = unescape(parsed.scheme)
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 === 'string') {
401
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts)
402
- return malformedAuthorityOrPort ? undefined : normalized
601
+ if (typeof uri !== 'string' && typeof uri !== 'object') {
602
+ return undefined
403
603
  }
404
604
 
405
- if (typeof uri === 'object') {
406
- return serialize(uri, opts)
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 = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu
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 [path, query] = wsComponent.resourceName.split('?')
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 = 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]