@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.
Files changed (41) hide show
  1. package/node_modules/fast-uri/.github/dependabot.yml +42 -2
  2. package/node_modules/fast-uri/.github/workflows/ci.yml +54 -6
  3. package/node_modules/fast-uri/.github/workflows/lock-threads.yml +1 -1
  4. package/node_modules/fast-uri/.github/workflows/ossf-scorecard.yml +45 -0
  5. package/node_modules/fast-uri/README.md +10 -9
  6. package/node_modules/fast-uri/SECURITY.md +173 -0
  7. package/node_modules/fast-uri/benchmark/benchmark.mjs +71 -0
  8. package/node_modules/fast-uri/benchmark/encoder.mjs +54 -0
  9. package/node_modules/fast-uri/index.js +277 -45
  10. package/node_modules/fast-uri/lib/schemes.js +384 -7
  11. package/node_modules/fast-uri/lib/utils.js +458 -95
  12. package/node_modules/fast-uri/package.json +4 -4
  13. package/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
  14. package/node_modules/fast-uri/test/equal.test.js +94 -3
  15. package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +24 -1
  16. package/node_modules/fast-uri/test/fixtures/uri-js-serialize.json +23 -1
  17. package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  18. package/node_modules/fast-uri/test/ipv6-validation.test.js +154 -0
  19. package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  20. package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  21. package/node_modules/fast-uri/test/parse.test.js +150 -3
  22. package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
  23. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  24. package/node_modules/fast-uri/test/scheme-normalization.test.js +16 -0
  25. package/node_modules/fast-uri/test/scheme-validation.test.js +122 -0
  26. package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  27. package/node_modules/fast-uri/test/security-utf8-encoding.test.js +0 -0
  28. package/node_modules/fast-uri/test/security.test.js +301 -3
  29. package/node_modules/fast-uri/test/serialize.test.js +144 -0
  30. package/node_modules/fast-uri/test/uri-js-compatibility.test.js +0 -3
  31. package/node_modules/fast-uri/test/uri-js.test.js +4 -4
  32. package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  33. package/node_modules/fast-uri/test/util.test.js +9 -0
  34. package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  35. package/node_modules/fast-uri/tsconfig.json +8 -7
  36. package/node_modules/fast-uri/types/index.d.ts +4 -9
  37. package/node_modules/fast-uri/types/index.tst.ts +31 -0
  38. package/npm-shrinkwrap.json +5 -5
  39. package/package.json +4 -4
  40. package/node_modules/fast-uri/.github/workflows/package-manager-ci.yml +0 -24
  41. 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, 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
  /**
@@ -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 = escapePreservingEscapes(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
- parsed.host = ipv6result.host.toLowerCase()
279
- isIP = ipv6result.isIPV6
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
- // check if scheme can't handle IRIs
303
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
304
- // if host component is a domain name
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.scheme !== undefined) {
319
- parsed.scheme = unescape(parsed.scheme)
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
- try {
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 === 'string') {
385
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts)
386
- return malformedAuthorityOrPort ? undefined : normalized
612
+ if (typeof uri !== 'string' && typeof uri !== 'object') {
613
+ return undefined
387
614
  }
388
615
 
389
- if (typeof uri === 'object') {
390
- return serialize(uri, opts)
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 = {