@limetech/lime-elements 40.13.1 → 40.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21360,6 +21360,12 @@ function requireUtils$1 () {
21360
21360
  /** @type {(value: string) => boolean} */
21361
21361
  const isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
21362
21362
 
21363
+ /** @type {(value: string) => boolean} */
21364
+ const isRegNameCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=]$/u);
21365
+
21366
+ /** @type {(value: string) => boolean} */
21367
+ const isLiteralRegName = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=]*$/u);
21368
+
21363
21369
  /**
21364
21370
  * @param {Array<string>} input
21365
21371
  * @returns {string}
@@ -21683,23 +21689,78 @@ function requireUtils$1 () {
21683
21689
  }
21684
21690
 
21685
21691
  /**
21686
- * Re-escape RFC 3986 gen-delims that must not appear literally in the host.
21687
- * After the URI regex parses, these characters cannot be literal in the host
21688
- * field, so any that appear after decoding came from percent-encoding and
21689
- * must be restored to prevent authority structure changes.
21692
+ * Removes dot segments from the path of a relative reference (a path that does
21693
+ * not start with "/") without changing what it resolves to against any base.
21694
+ * Unlike `removeDotSegments`, leading ".." segments that cannot be collapsed
21695
+ * are kept, the result never becomes root-relative, and a result that would be
21696
+ * empty or start with an empty segment is prefixed with "." (an empty path
21697
+ * means "same document" and a leading "//" would introduce an authority).
21698
+ *
21699
+ * @param {string} path
21700
+ * @returns {string}
21701
+ */
21702
+ function removeDotSegmentsRelative (path) {
21703
+ if (path === '' || path[0] === '/') {
21704
+ return removeDotSegments(path)
21705
+ }
21706
+
21707
+ const segments = path.split('/');
21708
+ const last = segments.length - 1;
21709
+ /** @type {string[]} */
21710
+ const output = [];
21711
+ let trailingDirectory = false;
21712
+
21713
+ for (let i = 0; i <= last; i++) {
21714
+ const segment = segments[i];
21715
+ if (segment === '.' || segment === '..') {
21716
+ if (segment === '..') {
21717
+ if (output.length !== 0 && output[output.length - 1] !== '..') {
21718
+ output.pop();
21719
+ } else {
21720
+ output.push('..');
21721
+ }
21722
+ }
21723
+ trailingDirectory = i === last;
21724
+ } else {
21725
+ output.push(segment);
21726
+ }
21727
+ }
21728
+
21729
+ if (trailingDirectory && output[output.length - 1] !== '..') {
21730
+ output.push('');
21731
+ }
21732
+ if (output.length === 0 || output[0] === '' || output[0].includes(':')) {
21733
+ // An empty path, a leading empty segment ("//"), or a first segment
21734
+ // containing ":" (which would be mistaken for a scheme, RFC 3986 section
21735
+ // 4.2) must stay behind a "." segment so the reference keeps its meaning.
21736
+ output.unshift('.');
21737
+ }
21738
+
21739
+ return output.join('/')
21740
+ }
21741
+
21742
+ /**
21743
+ * Encodes a host according to the RFC 3986 host grammar. Valid IP literals
21744
+ * have already been checked by `normalizeIPv6`, so their address separators
21745
+ * remain literal. Every other host is a reg-name and may contain only
21746
+ * unreserved characters, sub-delimiters, and valid percent escapes.
21690
21747
  *
21691
21748
  * @param {string} host
21692
- * @param {boolean} isIP - true for IPv4/IPv6 hosts (skip colon re-escaping)
21749
+ * @param {boolean} isIP - true for a validated IPv4/IPv6 host
21750
+ * @param {boolean} [allowNonAscii=false] - preserve non-ASCII IRI characters
21693
21751
  * @returns {string}
21694
21752
  */
21695
- const HOST_DELIMS = { '@': '%40', '/': '%2F', '?': '%3F', '#': '%23', ':': '%3A' };
21696
- const HOST_DELIM_RE = /[@/?#:]/g;
21697
- const HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
21753
+ const IP_HOST_DELIMS = { '@': '%40', '/': '%2F', '?': '%3F', '#': '%23' };
21754
+ const IP_HOST_DELIM_RE = /[@/?#]/g;
21698
21755
 
21699
- function reescapeHostDelimiters (host, isIP) {
21700
- const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
21701
- re.lastIndex = 0;
21702
- return host.replace(re, (ch) => HOST_DELIMS[ch])
21756
+ function encodeHost (host, isIP, allowNonAscii = false) {
21757
+ if (!isIP) {
21758
+ if (isLiteralRegName(host)) return host
21759
+ return encodeComponent(host, isRegNameCharacter, allowNonAscii)
21760
+ }
21761
+
21762
+ IP_HOST_DELIM_RE.lastIndex = 0;
21763
+ return host.replace(IP_HOST_DELIM_RE, (ch) => IP_HOST_DELIMS[ch])
21703
21764
  }
21704
21765
 
21705
21766
  /**
@@ -21962,9 +22023,10 @@ function requireUtils$1 () {
21962
22023
  *
21963
22024
  * @param {string} input
21964
22025
  * @param {(value: string) => boolean} isAllowed
22026
+ * @param {boolean} [allowNonAscii=false]
21965
22027
  * @returns {string}
21966
22028
  */
21967
- function encodeComponent (input, isAllowed) {
22029
+ function encodeComponent (input, isAllowed, allowNonAscii = false) {
21968
22030
  let output = '';
21969
22031
 
21970
22032
  for (let i = 0; i < input.length; i++) {
@@ -21985,17 +22047,19 @@ function requireUtils$1 () {
21985
22047
  if (code < 0x80) {
21986
22048
  output += BYTE_HEX[code];
21987
22049
  } else if (code < 0xD800 || code > 0xDFFF) {
21988
- output += percentEncodeNonAscii(code);
22050
+ output += allowNonAscii ? ch : percentEncodeNonAscii(code);
21989
22051
  } else if (code <= 0xDBFF && i + 1 < input.length) {
21990
22052
  const low = input.charCodeAt(i + 1);
21991
22053
  if (low >= 0xDC00 && low <= 0xDFFF) {
21992
- output += percentEncodeNonAscii(0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00));
22054
+ output += allowNonAscii
22055
+ ? ch + input[i + 1]
22056
+ : percentEncodeNonAscii(0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00));
21993
22057
  i++;
21994
22058
  } else {
21995
- output += percentEncodeNonAscii(0xFFFD);
22059
+ output += allowNonAscii ? '\uFFFD' : percentEncodeNonAscii(0xFFFD);
21996
22060
  }
21997
22061
  } else {
21998
- output += percentEncodeNonAscii(0xFFFD);
22062
+ output += allowNonAscii ? '\uFFFD' : percentEncodeNonAscii(0xFFFD);
21999
22063
  }
22000
22064
  }
22001
22065
  }
@@ -22109,9 +22173,10 @@ function requireUtils$1 () {
22109
22173
 
22110
22174
  /**
22111
22175
  * @param {import('../types/index').URIComponent} component
22176
+ * @param {boolean} [allowNonAscii=false] - preserve non-ASCII IRI characters
22112
22177
  * @returns {string|undefined}
22113
22178
  */
22114
- function recomposeAuthority (component) {
22179
+ function recomposeAuthority (component, allowNonAscii = false) {
22115
22180
  const uriTokens = [];
22116
22181
 
22117
22182
  if (component.userinfo !== undefined) {
@@ -22144,7 +22209,7 @@ function requireUtils$1 () {
22144
22209
  if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
22145
22210
  host = `[${ipV6res.escapedHost}]`;
22146
22211
  } else {
22147
- host = reescapeHostDelimiters(host, false);
22212
+ host = encodeHost(host, false, allowNonAscii);
22148
22213
  }
22149
22214
  }
22150
22215
  }
@@ -22168,7 +22233,7 @@ function requireUtils$1 () {
22168
22233
  nonSimpleDomain,
22169
22234
  nonSimpleMailtoDomain,
22170
22235
  recomposeAuthority,
22171
- reescapeHostDelimiters,
22236
+ encodeHost,
22172
22237
  normalizePercentEncoding,
22173
22238
  normalizePathEncoding,
22174
22239
  serializePathEncoding,
@@ -22178,6 +22243,7 @@ function requireUtils$1 () {
22178
22243
  encodeFragment,
22179
22244
  escapePreservingEscapes,
22180
22245
  removeDotSegments,
22246
+ removeDotSegmentsRelative,
22181
22247
  isIPv4,
22182
22248
  isUUID,
22183
22249
  normalizeIPv6,
@@ -22423,7 +22489,10 @@ function requireSchemes$1 () {
22423
22489
  scheme: 'urn',
22424
22490
  parse: urnParse,
22425
22491
  serialize: urnSerialize,
22426
- skipNormalize: true
22492
+ skipNormalize: true,
22493
+ // The NSS is opaque to generic URI path processing. In particular, RFC 2141
22494
+ // and RFC 8141 do not treat slash-delimited dot segments as equivalent.
22495
+ absolutePath: true
22427
22496
  });
22428
22497
 
22429
22498
  const urnuuid = /** @type {SchemeHandler} */ ({
@@ -22904,22 +22973,30 @@ function requireFastUri$1 () {
22904
22973
  if (hasRequiredFastUri$1) return fastUri$1.exports;
22905
22974
  hasRequiredFastUri$1 = 1;
22906
22975
 
22907
- const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = requireUtils$1();
22976
+ const { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, encodeHost, isIPv4, nonSimpleDomain } = requireUtils$1();
22908
22977
  const { SCHEMES, getSchemeHandler } = requireSchemes$1();
22909
22978
 
22910
22979
  const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
22911
22980
  const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.';
22981
+ const MALFORMED_HOST_ERROR = 'URI host is malformed.';
22982
+ // ASCII characters outside the RFC 3986 reg-name set (unreserved, sub-delims
22983
+ // and "%" for pct-encoded): C0 controls, space, DEL, and "#/:<>?@[\]^`{|}.
22984
+ const FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
22912
22985
 
22913
22986
  /**
22987
+ * RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so the
22988
+ * scheme is validated as-is and never decoded. Decoding would let inert input
22989
+ * such as `%6Aavascript:` become a live `javascript:` scheme.
22990
+ *
22914
22991
  * @param {string} scheme
22915
22992
  * @returns {string}
22916
22993
  */
22917
- function decodeValidScheme (scheme) {
22918
- const decodedScheme = unescape(String(scheme));
22919
- if (!VALID_SCHEME.test(decodedScheme)) {
22994
+ function validateScheme (scheme) {
22995
+ const stringScheme = String(scheme);
22996
+ if (!VALID_SCHEME.test(stringScheme)) {
22920
22997
  throw new TypeError(MALFORMED_SCHEME_ERROR)
22921
22998
  }
22922
- return decodedScheme
22999
+ return stringScheme
22923
23000
  }
22924
23001
 
22925
23002
  /**
@@ -22948,6 +23025,7 @@ function requireFastUri$1 () {
22948
23025
  const {
22949
23026
  parsed: baseParsed,
22950
23027
  malformedAuthorityOrPort: baseMalformed,
23028
+ malformedReferenceEdge: baseMalformedReferenceEdge,
22951
23029
  malformedPercentEncoding: baseMalformedPercentEncoding,
22952
23030
  malformedSchemeSpecific: baseMalformedSchemeSpecific,
22953
23031
  malformedHost: baseMalformedHost,
@@ -22956,6 +23034,7 @@ function requireFastUri$1 () {
22956
23034
  const {
22957
23035
  parsed: relativeParsed,
22958
23036
  malformedAuthorityOrPort: relativeMalformed,
23037
+ malformedReferenceEdge: relativeMalformedReferenceEdge,
22959
23038
  malformedPercentEncoding: relativeMalformedPercentEncoding,
22960
23039
  malformedSchemeSpecific: relativeMalformedSchemeSpecific,
22961
23040
  malformedHost: relativeMalformedHost,
@@ -22964,6 +23043,8 @@ function requireFastUri$1 () {
22964
23043
  if (
22965
23044
  baseMalformed ||
22966
23045
  relativeMalformed ||
23046
+ baseMalformedReferenceEdge ||
23047
+ relativeMalformedReferenceEdge ||
22967
23048
  baseMalformedPercentEncoding ||
22968
23049
  relativeMalformedPercentEncoding ||
22969
23050
  baseMalformedSchemeSpecific ||
@@ -22978,8 +23059,22 @@ function requireFastUri$1 () {
22978
23059
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
22979
23060
  const resolvedSchemeHandler = getSchemeHandler((options && options.scheme) || resolved.scheme);
22980
23061
  const resolvedHost = resolved.host;
23062
+ // Inputs are parsed scheme-neutrally during resolution, so enforce the final
23063
+ // HTTP scheme's required host before serializing an invalid HTTP URI.
23064
+ if (
23065
+ (resolved.scheme === 'http' || resolved.scheme === 'https') &&
23066
+ !resolvedHost
23067
+ ) {
23068
+ throw new Error('HTTP URIs must have a host.')
23069
+ }
22981
23070
  const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
22982
23071
  (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
23072
+ // Inputs are parsed scheme-neutrally, so delimiters and controls in a domain
23073
+ // host are only detected here, once the final scheme is known. They always
23074
+ // fail closed, including in percent-encoded hosts.
23075
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
23076
+ throw new Error(MALFORMED_HOST_ERROR)
23077
+ }
22983
23078
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
22984
23079
  // Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
22985
23080
  // parser can reject them even though fast-uri preserves them safely as RFC
@@ -22990,6 +23085,9 @@ function requireFastUri$1 () {
22990
23085
  throw new Error(resolved.error)
22991
23086
  }
22992
23087
  schemelessOptions.skipEscape = true;
23088
+ if (resolvedSchemeHandler && resolvedSchemeHandler.absolutePath) {
23089
+ schemelessOptions.absolutePath = true;
23090
+ }
22993
23091
  return serialize(resolved, schemelessOptions)
22994
23092
  }
22995
23093
 
@@ -23031,7 +23129,10 @@ function requireFastUri$1 () {
23031
23129
  target.userinfo = relative.userinfo;
23032
23130
  copyHost(target, relative);
23033
23131
  target.port = relative.port;
23034
- target.path = removeDotSegments(relative.path || '');
23132
+ const relativeSchemeHandler = getSchemeHandler(relative.scheme);
23133
+ target.path = relativeSchemeHandler && relativeSchemeHandler.absolutePath
23134
+ ? relative.path || ''
23135
+ : removeDotSegments(relative.path || '');
23035
23136
  target.query = relative.query;
23036
23137
  } else {
23037
23138
  if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) {
@@ -23053,14 +23154,17 @@ function requireFastUri$1 () {
23053
23154
  if (relative.path[0] === '/') {
23054
23155
  target.path = removeDotSegments(relative.path);
23055
23156
  } else {
23056
- if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
23157
+ const baseHasAuthority = base.userinfo !== undefined || base.host !== undefined || base.port !== undefined;
23158
+ if (baseHasAuthority && !base.path) {
23057
23159
  target.path = '/' + relative.path;
23058
23160
  } else if (!base.path) {
23059
23161
  target.path = relative.path;
23060
23162
  } else {
23061
23163
  target.path = base.path.slice(0, base.path.lastIndexOf('/') + 1) + relative.path;
23062
23164
  }
23063
- target.path = removeDotSegments(target.path);
23165
+ target.path = base.scheme === undefined && !baseHasAuthority && target.path[0] !== '/'
23166
+ ? removeDotSegmentsRelative(target.path)
23167
+ : removeDotSegments(target.path);
23064
23168
  }
23065
23169
  target.query = relative.query;
23066
23170
  }
@@ -23121,7 +23225,7 @@ function requireFastUri$1 () {
23121
23225
  const uriTokens = [];
23122
23226
 
23123
23227
  if (component.scheme) {
23124
- component.scheme = decodeValidScheme(component.scheme);
23228
+ component.scheme = validateScheme(component.scheme);
23125
23229
  }
23126
23230
 
23127
23231
  // find scheme handler
@@ -23131,7 +23235,11 @@ function requireFastUri$1 () {
23131
23235
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
23132
23236
 
23133
23237
  const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
23134
- const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
23238
+ // A reference with no emitted scheme is path-noscheme: a bare ':' in its
23239
+ // first path segment would reparse as a scheme (RFC 3986 3.3/4.2). Enforced
23240
+ // under skipEscape too (resolve()), so the reapply below gates on this flag.
23241
+ const schemelessReference = (component.scheme === undefined || options.reference === 'suffix') && !hasAuthority;
23242
+ const pathNoScheme = !options.skipEscape && schemelessReference;
23135
23243
 
23136
23244
  if (component.path !== undefined) {
23137
23245
  if (!options.skipEscape) {
@@ -23143,11 +23251,12 @@ function requireFastUri$1 () {
23143
23251
 
23144
23252
  if (options.reference !== 'suffix' && component.scheme) {
23145
23253
  // Scheme handlers may replace the scheme during serialization.
23146
- component.scheme = decodeValidScheme(component.scheme);
23254
+ component.scheme = validateScheme(component.scheme);
23147
23255
  uriTokens.push(component.scheme, ':');
23148
23256
  }
23149
23257
 
23150
- const authority = recomposeAuthority(component);
23258
+ const allowNonAsciiHost = options.unicodeSupport === true || Boolean(schemeHandler && schemeHandler.unicodeSupport);
23259
+ const authority = recomposeAuthority(component, allowNonAsciiHost);
23151
23260
  if (authority !== undefined) {
23152
23261
  if (options.reference !== 'suffix') {
23153
23262
  uriTokens.push('//');
@@ -23163,13 +23272,18 @@ function requireFastUri$1 () {
23163
23272
  let s = component.path;
23164
23273
 
23165
23274
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
23166
- s = removeDotSegments(s);
23275
+ // A relative-path reference keeps its unresolvable leading ".." segments
23276
+ // and must never become root-relative (RFC 3986 section 5.2.4 applies to
23277
+ // merged target paths, not to the reference itself).
23278
+ s = schemelessReference && s[0] !== '/'
23279
+ ? removeDotSegmentsRelative(s)
23280
+ : removeDotSegments(s);
23167
23281
  }
23168
23282
 
23169
23283
  // Dot-segment removal can expose a colon that was not originally in the
23170
23284
  // first segment (for example, "./a:b"). Reapply path-noscheme encoding so
23171
23285
  // the serialized relative reference cannot be reparsed as a URI scheme.
23172
- if (pathNoScheme) {
23286
+ if (schemelessReference) {
23173
23287
  s = serializePathEncoding(s, true);
23174
23288
  }
23175
23289
 
@@ -23289,6 +23403,11 @@ function requireFastUri$1 () {
23289
23403
  parsed.host[0] === '[' &&
23290
23404
  parsed.host[parsed.host.length - 1] === ']';
23291
23405
 
23406
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
23407
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
23408
+ return true
23409
+ }
23410
+
23292
23411
  if (
23293
23412
  !options.unicodeSupport &&
23294
23413
  (!schemeHandler || !schemeHandler.unicodeSupport) &&
@@ -23299,21 +23418,67 @@ function requireFastUri$1 () {
23299
23418
  !bracketedIPLiteral &&
23300
23419
  nonSimpleDomain(parsed.host)
23301
23420
  ) {
23421
+ let url;
23302
23422
  try {
23303
- parsed.host = new URL('http://' + parsed.host).hostname;
23423
+ url = new URL('http://' + parsed.host);
23304
23424
  } catch (e) {
23305
23425
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
23306
23426
  return true
23307
23427
  }
23428
+ // The WHATWG parser must have read the whole input as a host. Anything
23429
+ // else means it found a delimiter fast-uri did not, so its hostname would
23430
+ // describe a different authority.
23431
+ if (!isHostOnlyURL(url)) {
23432
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
23433
+ return true
23434
+ }
23435
+ parsed.host = url.hostname;
23308
23436
  }
23309
23437
 
23310
23438
  return false
23311
23439
  }
23312
23440
 
23441
+ /**
23442
+ * Whether a domain host contains an ASCII character that RFC 3986 does not
23443
+ * allow in a reg-name (unreserved, sub-delims, or pct-encoded). Such a host is
23444
+ * rejected instead of being handed to the WHATWG URL parser, which would treat
23445
+ * "@" as a userinfo delimiter (silently dropping the data before it) and strip
23446
+ * TAB, LF, and CR. Non-ASCII characters remain allowed for IDNs.
23447
+ *
23448
+ * @param {import('./types/index').URIComponent} parsed
23449
+ * @param {import('./types/index').Options} options
23450
+ * @param {{ domainHost?: boolean }|undefined} schemeHandler
23451
+ * @param {boolean} isIP
23452
+ * @returns {boolean}
23453
+ */
23454
+ function hasForbiddenDomainHost (parsed, options, schemeHandler, isIP) {
23455
+ return Boolean(
23456
+ parsed.host &&
23457
+ isIP === false &&
23458
+ !isIPLiteral(parsed.host) &&
23459
+ (options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
23460
+ FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
23461
+ )
23462
+ }
23463
+
23464
+ /**
23465
+ * @param {URL} url
23466
+ * @returns {boolean}
23467
+ */
23468
+ function isHostOnlyURL (url) {
23469
+ return url.username === '' &&
23470
+ url.password === '' &&
23471
+ url.port === '' &&
23472
+ url.pathname === '/' &&
23473
+ url.search === '' &&
23474
+ url.hash === '' &&
23475
+ url.hostname !== ''
23476
+ }
23477
+
23313
23478
  /**
23314
23479
  * @param {string} uri
23315
23480
  * @param {import('./types/index').Options} [opts]
23316
- * @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
23481
+ * @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedReferenceEdge: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
23317
23482
  */
23318
23483
  function parseWithStatus (uri, opts) {
23319
23484
  const options = Object.assign({}, opts);
@@ -23329,6 +23494,12 @@ function requireFastUri$1 () {
23329
23494
  };
23330
23495
 
23331
23496
  let malformedAuthorityOrPort = false;
23497
+ // Check the caller's original reference before suffix mode prepends a scheme
23498
+ // or authority marker and turns its leading edge into interior data.
23499
+ const malformedReferenceEdge = uri.length !== 0 && (
23500
+ uri.charCodeAt(0) <= 0x20 ||
23501
+ uri.charCodeAt(uri.length - 1) <= 0x20
23502
+ );
23332
23503
  let malformedPercentEncoding = false;
23333
23504
  let malformedSchemeSpecific = false;
23334
23505
  let malformedHost = false;
@@ -23379,6 +23550,10 @@ function requireFastUri$1 () {
23379
23550
  }
23380
23551
  }
23381
23552
 
23553
+ if (malformedReferenceEdge) {
23554
+ parsed.error = parsed.error || 'URI must not contain leading or trailing C0 controls or spaces.';
23555
+ }
23556
+
23382
23557
  const matches = uri.match(URI_PARSE);
23383
23558
 
23384
23559
  if (matches) {
@@ -23392,9 +23567,8 @@ function requireFastUri$1 () {
23392
23567
  parsed.fragment = matches[8];
23393
23568
 
23394
23569
  if (parsed.scheme !== undefined) {
23395
- const decodedScheme = unescape(parsed.scheme);
23396
- if (VALID_SCHEME.test(decodedScheme)) {
23397
- parsed.scheme = decodedScheme.toLowerCase();
23570
+ if (VALID_SCHEME.test(parsed.scheme)) {
23571
+ parsed.scheme = parsed.scheme.toLowerCase();
23398
23572
  } else {
23399
23573
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
23400
23574
  malformedScheme = true;
@@ -23438,7 +23612,7 @@ function requireFastUri$1 () {
23438
23612
  }
23439
23613
 
23440
23614
  if (malformedIPLiteral) {
23441
- parsed.error = parsed.error || 'URI host is malformed.';
23615
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
23442
23616
  malformedAuthorityOrPort = true;
23443
23617
  }
23444
23618
  } else {
@@ -23468,14 +23642,24 @@ function requireFastUri$1 () {
23468
23642
  malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
23469
23643
  }
23470
23644
 
23471
- if (uri.indexOf('%') !== -1 && parsed.host !== undefined && !malformedIPLiteral) {
23472
- let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
23473
- if (!isIP) {
23645
+ if (parsed.host !== undefined && !malformedIPLiteral) {
23646
+ let host = parsed.host;
23647
+ if (uri.indexOf('%') !== -1 && !isIP) {
23648
+ host = normalizePercentEncoding(host, true);
23474
23649
  // Fold reg-name case after decoding unreserved octets. The second
23475
23650
  // pass only restores uppercase hex in escapes that remain encoded.
23476
23651
  host = normalizePercentEncoding(host.toLowerCase());
23477
23652
  }
23478
- parsed.host = reescapeHostDelimiters(host, isIP);
23653
+ // Resolution parses both inputs with the internal `null` scheme before
23654
+ // the effective scheme is known. Preserve raw host characters for that
23655
+ // intermediate step so the final scheme can apply domain canonicalization;
23656
+ // the resolved authority is encoded when it is serialized.
23657
+ if (options.scheme !== 'null') {
23658
+ const allowNonAsciiHost = options.unicodeSupport === true || Boolean(schemeHandler && schemeHandler.unicodeSupport);
23659
+ parsed.host = encodeHost(host, isIP, allowNonAsciiHost);
23660
+ } else {
23661
+ parsed.host = host;
23662
+ }
23479
23663
  }
23480
23664
 
23481
23665
  if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
@@ -23500,7 +23684,7 @@ function requireFastUri$1 () {
23500
23684
  } else {
23501
23685
  parsed.error = parsed.error || 'URI can not be parsed.';
23502
23686
  }
23503
- return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
23687
+ return { parsed, malformedAuthorityOrPort, malformedReferenceEdge, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
23504
23688
  }
23505
23689
 
23506
23690
  /**
@@ -23524,13 +23708,14 @@ function requireFastUri$1 () {
23524
23708
  /**
23525
23709
  * @param {string} uri
23526
23710
  * @param {import('./types/index').Options} [opts]
23527
- * @returns {{ normalized: string, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
23711
+ * @returns {{ normalized: string, malformedAuthorityOrPort: boolean, malformedReferenceEdge: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
23528
23712
  */
23529
23713
  function normalizeStringWithStatus (uri, opts) {
23530
- const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
23714
+ const { parsed, malformedAuthorityOrPort, malformedReferenceEdge, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
23531
23715
  return {
23532
- normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
23716
+ normalized: malformedAuthorityOrPort || malformedReferenceEdge || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
23533
23717
  malformedAuthorityOrPort,
23718
+ malformedReferenceEdge,
23534
23719
  malformedPercentEncoding,
23535
23720
  malformedSchemeSpecific,
23536
23721
  malformedHost,
@@ -23554,8 +23739,8 @@ function requireFastUri$1 () {
23554
23739
  } catch {
23555
23740
  return undefined
23556
23741
  }
23557
- const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
23558
- return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
23742
+ const { normalized, malformedAuthorityOrPort, malformedReferenceEdge, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
23743
+ return malformedAuthorityOrPort || malformedReferenceEdge || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
23559
23744
  }
23560
23745
 
23561
23746
  const fastUri = {
@@ -38761,6 +38946,57 @@ function requireUtils () {
38761
38946
  return output.join('')
38762
38947
  }
38763
38948
 
38949
+ /**
38950
+ * Removes dot segments from the path of a relative reference (a path that does
38951
+ * not start with "/") without changing what it resolves to against any base.
38952
+ * Unlike `removeDotSegments`, leading ".." segments that cannot be collapsed
38953
+ * are kept, the result never becomes root-relative, and a result that would be
38954
+ * empty or start with an empty segment is prefixed with "." (an empty path
38955
+ * means "same document" and a leading "//" would introduce an authority).
38956
+ *
38957
+ * @param {string} path
38958
+ * @returns {string}
38959
+ */
38960
+ function removeDotSegmentsRelative (path) {
38961
+ if (path === '' || path[0] === '/') {
38962
+ return removeDotSegments(path)
38963
+ }
38964
+
38965
+ const segments = path.split('/');
38966
+ const last = segments.length - 1;
38967
+ /** @type {string[]} */
38968
+ const output = [];
38969
+ let trailingDirectory = false;
38970
+
38971
+ for (let i = 0; i <= last; i++) {
38972
+ const segment = segments[i];
38973
+ if (segment === '.' || segment === '..') {
38974
+ if (segment === '..') {
38975
+ if (output.length !== 0 && output[output.length - 1] !== '..') {
38976
+ output.pop();
38977
+ } else {
38978
+ output.push('..');
38979
+ }
38980
+ }
38981
+ trailingDirectory = i === last;
38982
+ } else {
38983
+ output.push(segment);
38984
+ }
38985
+ }
38986
+
38987
+ if (trailingDirectory && output[output.length - 1] !== '..') {
38988
+ output.push('');
38989
+ }
38990
+ if (output.length === 0 || output[0] === '' || output[0].includes(':')) {
38991
+ // An empty path, a leading empty segment ("//"), or a first segment
38992
+ // containing ":" (which would be mistaken for a scheme, RFC 3986 section
38993
+ // 4.2) must stay behind a "." segment so the reference keeps its meaning.
38994
+ output.unshift('.');
38995
+ }
38996
+
38997
+ return output.join('/')
38998
+ }
38999
+
38764
39000
  /**
38765
39001
  * Re-escape RFC 3986 gen-delims that must not appear literally in the host.
38766
39002
  * After the URI regex parses, these characters cannot be literal in the host
@@ -39155,6 +39391,7 @@ function requireUtils () {
39155
39391
  encodeFragment,
39156
39392
  escapePreservingEscapes,
39157
39393
  removeDotSegments,
39394
+ removeDotSegmentsRelative,
39158
39395
  isIPv4,
39159
39396
  isUUID,
39160
39397
  normalizeIPv6,
@@ -39449,22 +39686,30 @@ function requireFastUri () {
39449
39686
  if (hasRequiredFastUri) return fastUri.exports;
39450
39687
  hasRequiredFastUri = 1;
39451
39688
 
39452
- const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = requireUtils();
39689
+ const { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = requireUtils();
39453
39690
  const { SCHEMES, getSchemeHandler } = requireSchemes();
39454
39691
 
39455
39692
  const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
39456
39693
  const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.';
39694
+ const MALFORMED_HOST_ERROR = 'URI host is malformed.';
39695
+ // ASCII characters outside the RFC 3986 reg-name set (unreserved, sub-delims
39696
+ // and "%" for pct-encoded): C0 controls, space, DEL, and "#/:<>?@[\]^`{|}.
39697
+ const FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
39457
39698
 
39458
39699
  /**
39700
+ * RFC 3986 section 3.1 does not allow percent-encoding in a scheme, so the
39701
+ * scheme is validated as-is and never decoded. Decoding would let inert input
39702
+ * such as `%6Aavascript:` become a live `javascript:` scheme.
39703
+ *
39459
39704
  * @param {string} scheme
39460
39705
  * @returns {string}
39461
39706
  */
39462
- function decodeValidScheme (scheme) {
39463
- const decodedScheme = unescape(String(scheme));
39464
- if (!VALID_SCHEME.test(decodedScheme)) {
39707
+ function validateScheme (scheme) {
39708
+ const stringScheme = String(scheme);
39709
+ if (!VALID_SCHEME.test(stringScheme)) {
39465
39710
  throw new TypeError(MALFORMED_SCHEME_ERROR)
39466
39711
  }
39467
- return decodedScheme
39712
+ return stringScheme
39468
39713
  }
39469
39714
 
39470
39715
  /**
@@ -39525,6 +39770,12 @@ function requireFastUri () {
39525
39770
  const resolvedHost = resolved.host;
39526
39771
  const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
39527
39772
  (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
39773
+ // Inputs are parsed scheme-neutrally, so delimiters and controls in a domain
39774
+ // host are only detected here, once the final scheme is known. They always
39775
+ // fail closed, including in percent-encoded hosts.
39776
+ if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
39777
+ throw new Error(MALFORMED_HOST_ERROR)
39778
+ }
39528
39779
  canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
39529
39780
  // Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
39530
39781
  // parser can reject them even though fast-uri preserves them safely as RFC
@@ -39582,14 +39833,17 @@ function requireFastUri () {
39582
39833
  if (relative.path[0] === '/') {
39583
39834
  target.path = removeDotSegments(relative.path);
39584
39835
  } else {
39585
- if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
39836
+ const baseHasAuthority = base.userinfo !== undefined || base.host !== undefined || base.port !== undefined;
39837
+ if (baseHasAuthority && !base.path) {
39586
39838
  target.path = '/' + relative.path;
39587
39839
  } else if (!base.path) {
39588
39840
  target.path = relative.path;
39589
39841
  } else {
39590
39842
  target.path = base.path.slice(0, base.path.lastIndexOf('/') + 1) + relative.path;
39591
39843
  }
39592
- target.path = removeDotSegments(target.path);
39844
+ target.path = base.scheme === undefined && !baseHasAuthority && target.path[0] !== '/'
39845
+ ? removeDotSegmentsRelative(target.path)
39846
+ : removeDotSegments(target.path);
39593
39847
  }
39594
39848
  target.query = relative.query;
39595
39849
  }
@@ -39645,7 +39899,7 @@ function requireFastUri () {
39645
39899
  const uriTokens = [];
39646
39900
 
39647
39901
  if (component.scheme) {
39648
- component.scheme = decodeValidScheme(component.scheme);
39902
+ component.scheme = validateScheme(component.scheme);
39649
39903
  }
39650
39904
 
39651
39905
  // find scheme handler
@@ -39655,7 +39909,11 @@ function requireFastUri () {
39655
39909
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
39656
39910
 
39657
39911
  const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined;
39658
- const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority;
39912
+ // A reference with no emitted scheme is path-noscheme: a bare ':' in its
39913
+ // first path segment would reparse as a scheme (RFC 3986 3.3/4.2). Enforced
39914
+ // under skipEscape too (resolve()), so the reapply below gates on this flag.
39915
+ const schemelessReference = (component.scheme === undefined || options.reference === 'suffix') && !hasAuthority;
39916
+ const pathNoScheme = !options.skipEscape && schemelessReference;
39659
39917
 
39660
39918
  if (component.path !== undefined) {
39661
39919
  if (!options.skipEscape) {
@@ -39667,7 +39925,7 @@ function requireFastUri () {
39667
39925
 
39668
39926
  if (options.reference !== 'suffix' && component.scheme) {
39669
39927
  // Scheme handlers may replace the scheme during serialization.
39670
- component.scheme = decodeValidScheme(component.scheme);
39928
+ component.scheme = validateScheme(component.scheme);
39671
39929
  uriTokens.push(component.scheme, ':');
39672
39930
  }
39673
39931
 
@@ -39687,13 +39945,18 @@ function requireFastUri () {
39687
39945
  let s = component.path;
39688
39946
 
39689
39947
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
39690
- s = removeDotSegments(s);
39948
+ // A relative-path reference keeps its unresolvable leading ".." segments
39949
+ // and must never become root-relative (RFC 3986 section 5.2.4 applies to
39950
+ // merged target paths, not to the reference itself).
39951
+ s = schemelessReference && s[0] !== '/'
39952
+ ? removeDotSegmentsRelative(s)
39953
+ : removeDotSegments(s);
39691
39954
  }
39692
39955
 
39693
39956
  // Dot-segment removal can expose a colon that was not originally in the
39694
39957
  // first segment (for example, "./a:b"). Reapply path-noscheme encoding so
39695
39958
  // the serialized relative reference cannot be reparsed as a URI scheme.
39696
- if (pathNoScheme) {
39959
+ if (schemelessReference) {
39697
39960
  s = serializePathEncoding(s, true);
39698
39961
  }
39699
39962
 
@@ -39808,6 +40071,11 @@ function requireFastUri () {
39808
40071
  * @returns {boolean} whether host conversion failed
39809
40072
  */
39810
40073
  function canonicalizeHost (parsed, options, schemeHandler, isIP) {
40074
+ if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
40075
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
40076
+ return true
40077
+ }
40078
+
39811
40079
  if (
39812
40080
  !options.unicodeSupport &&
39813
40081
  (!schemeHandler || !schemeHandler.unicodeSupport) &&
@@ -39817,16 +40085,62 @@ function requireFastUri () {
39817
40085
  isIP === false &&
39818
40086
  nonSimpleDomain(parsed.host)
39819
40087
  ) {
40088
+ let url;
39820
40089
  try {
39821
- parsed.host = new URL('http://' + parsed.host).hostname;
40090
+ url = new URL('http://' + parsed.host);
39822
40091
  } catch (e) {
39823
40092
  parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
39824
40093
  return true
39825
40094
  }
40095
+ // The WHATWG parser must have read the whole input as a host. Anything
40096
+ // else means it found a delimiter fast-uri did not, so its hostname would
40097
+ // describe a different authority.
40098
+ if (!isHostOnlyURL(url)) {
40099
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
40100
+ return true
40101
+ }
40102
+ parsed.host = url.hostname;
39826
40103
  }
39827
40104
  return false
39828
40105
  }
39829
40106
 
40107
+ /**
40108
+ * Whether a domain host contains an ASCII character that RFC 3986 does not
40109
+ * allow in a reg-name (unreserved, sub-delims, or pct-encoded). Such a host is
40110
+ * rejected instead of being handed to the WHATWG URL parser, which would treat
40111
+ * "@" as a userinfo delimiter (silently dropping the data before it) and strip
40112
+ * TAB, LF, and CR. Non-ASCII characters remain allowed for IDNs.
40113
+ *
40114
+ * @param {import('./types/index').URIComponent} parsed
40115
+ * @param {import('./types/index').Options} options
40116
+ * @param {{ domainHost?: boolean }|undefined} schemeHandler
40117
+ * @param {boolean} isIP
40118
+ * @returns {boolean}
40119
+ */
40120
+ function hasForbiddenDomainHost (parsed, options, schemeHandler, isIP) {
40121
+ return Boolean(
40122
+ parsed.host &&
40123
+ isIP === false &&
40124
+ !isIPLiteral(parsed.host) &&
40125
+ (options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
40126
+ FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
40127
+ )
40128
+ }
40129
+
40130
+ /**
40131
+ * @param {URL} url
40132
+ * @returns {boolean}
40133
+ */
40134
+ function isHostOnlyURL (url) {
40135
+ return url.username === '' &&
40136
+ url.password === '' &&
40137
+ url.port === '' &&
40138
+ url.pathname === '/' &&
40139
+ url.search === '' &&
40140
+ url.hash === '' &&
40141
+ url.hostname !== ''
40142
+ }
40143
+
39830
40144
  /**
39831
40145
  * @param {string} uri
39832
40146
  * @param {import('./types/index').Options} [opts]
@@ -39909,9 +40223,8 @@ function requireFastUri () {
39909
40223
  parsed.fragment = matches[8];
39910
40224
 
39911
40225
  if (parsed.scheme !== undefined) {
39912
- const decodedScheme = unescape(parsed.scheme);
39913
- if (VALID_SCHEME.test(decodedScheme)) {
39914
- parsed.scheme = decodedScheme.toLowerCase();
40226
+ if (VALID_SCHEME.test(parsed.scheme)) {
40227
+ parsed.scheme = parsed.scheme.toLowerCase();
39915
40228
  } else {
39916
40229
  parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
39917
40230
  malformedScheme = true;
@@ -39945,7 +40258,7 @@ function requireFastUri () {
39945
40258
  parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
39946
40259
 
39947
40260
  if (malformedIPLiteral) {
39948
- parsed.error = parsed.error || 'URI host is malformed.';
40261
+ parsed.error = parsed.error || MALFORMED_HOST_ERROR;
39949
40262
  malformedAuthorityOrPort = true;
39950
40263
  }
39951
40264
  } else {
@@ -39982,7 +40295,11 @@ function requireFastUri () {
39982
40295
  // pass only restores uppercase hex in escapes that remain encoded.
39983
40296
  host = normalizePercentEncoding(host.toLowerCase());
39984
40297
  }
39985
- parsed.host = reescapeHostDelimiters(host, isIP);
40298
+ // Resolution parses both inputs with the internal `null` scheme before
40299
+ // the effective scheme is known. Keep raw host delimiters for that step
40300
+ // so resolve() can reject them for domain-host schemes; the resolved
40301
+ // authority is encoded when it is serialized.
40302
+ parsed.host = isIP || options.scheme !== 'null' ? reescapeHostDelimiters(host, isIP) : host;
39986
40303
  }
39987
40304
 
39988
40305
  if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {