trousse 3.6.0 → 3.7.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.
- package/dist/urls.js +48 -16
- package/package.json +1 -1
package/dist/urls.js
CHANGED
|
@@ -427,6 +427,18 @@ const resolveUrl = (url, base) => {
|
|
|
427
427
|
if (!parsed || !isHttpUrl(parsed)) return;
|
|
428
428
|
return parsed.href;
|
|
429
429
|
};
|
|
430
|
+
const valueShapeEscapeRegex = /(%(?:26|23|25|2B|3B))/gi;
|
|
431
|
+
const nameShapeEscapeRegex = /(%(?:26|23|25|2B|3B|3D))/gi;
|
|
432
|
+
const decodeQueryPart = (part, shapeRegex) => {
|
|
433
|
+
return part.replace(plusRegex, "%20").split(shapeRegex).map((chunk, index) => index % 2 ? chunk.toUpperCase() : decodePercent(chunk)).join("");
|
|
434
|
+
};
|
|
435
|
+
const canonicalizeQuery = (search) => {
|
|
436
|
+
return `?${search.slice(1).split("&").map((pair) => {
|
|
437
|
+
const at = pair.indexOf("=");
|
|
438
|
+
if (at === -1) return decodeQueryPart(pair, nameShapeEscapeRegex);
|
|
439
|
+
return `${decodeQueryPart(pair.slice(0, at), nameShapeEscapeRegex)}=${decodeQueryPart(pair.slice(at + 1), valueShapeEscapeRegex)}`;
|
|
440
|
+
}).join("&")}`;
|
|
441
|
+
};
|
|
430
442
|
const decodeAndNormalizeEncoding = (value) => {
|
|
431
443
|
if (!value.includes("%")) return value;
|
|
432
444
|
return value.replace(percentEscapeRegex, (hex) => {
|
|
@@ -471,7 +483,10 @@ const normalizeUrl = (url, options) => {
|
|
|
471
483
|
if (options.collapseSlashes) pathname = pathname.replace(repeatedSlashesRegex, "/");
|
|
472
484
|
if (options.stripTrailingSlash && pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
473
485
|
parsed.pathname = pathname;
|
|
474
|
-
if (options.normalizeEncoding && parsed.search.
|
|
486
|
+
if (options.normalizeEncoding && parsed.search.length > 1) {
|
|
487
|
+
parsed.search = canonicalizeQuery(parsed.search);
|
|
488
|
+
parsed.search = parsed.search.replace(percentEscapeRegex, (hex) => hex.toUpperCase());
|
|
489
|
+
}
|
|
475
490
|
if (options.normalizeEncoding && parsed.hash.includes("%")) parsed.hash = decodeAndNormalizeEncoding(parsed.hash);
|
|
476
491
|
if (options.stripQuery) parsed.search = "";
|
|
477
492
|
if (parsed.search && (options.stripQueryParams || options.lowercaseQuery)) {
|
|
@@ -522,7 +537,15 @@ const createUrlVariants = (url) => {
|
|
|
522
537
|
const sameQuerySpellings = [
|
|
523
538
|
["+", "%20"],
|
|
524
539
|
[":", "%3A"],
|
|
525
|
-
["@", "%40"]
|
|
540
|
+
["@", "%40"],
|
|
541
|
+
["/", "%2F"],
|
|
542
|
+
[",", "%2C"],
|
|
543
|
+
["(", "%28"],
|
|
544
|
+
[")", "%29"],
|
|
545
|
+
["*", "%2A"],
|
|
546
|
+
["!", "%21"],
|
|
547
|
+
["'", "%27"],
|
|
548
|
+
["$", "%24"]
|
|
526
549
|
];
|
|
527
550
|
const percentEscapeRegex = /%[0-9a-f]{2}/gi;
|
|
528
551
|
const addLowercaseHexSpellings = (url, spellings) => {
|
|
@@ -532,25 +555,34 @@ const addLowercaseHexSpellings = (url, spellings) => {
|
|
|
532
555
|
});
|
|
533
556
|
return [.../* @__PURE__ */ new Set([...spellings, ...lowercaseHexSpellings])];
|
|
534
557
|
};
|
|
558
|
+
const maxMixedQuerySpellings = 4;
|
|
559
|
+
const respellQuery = (spelling, queryIndex, pairs, toEncoded) => {
|
|
560
|
+
const fragmentIndex = spelling.indexOf("#");
|
|
561
|
+
const queryEnd = fragmentIndex === -1 ? spelling.length : fragmentIndex;
|
|
562
|
+
let query = spelling.slice(queryIndex, queryEnd);
|
|
563
|
+
for (const [written, encoded] of pairs) query = toEncoded ? query.replaceAll(written, encoded) : query.replaceAll(encoded, written);
|
|
564
|
+
return `${spelling.slice(0, queryIndex)}${query}${spelling.slice(queryEnd)}`;
|
|
565
|
+
};
|
|
535
566
|
const createUrlSpellings = (url) => {
|
|
536
567
|
const uppercaseHexUrl = url.replace(percentEscapeRegex, (hex) => hex.toUpperCase());
|
|
537
|
-
let spellings = [.../* @__PURE__ */ new Set([url, uppercaseHexUrl])];
|
|
538
568
|
const hashIndex = url.indexOf("#");
|
|
539
569
|
const queryEnd = hashIndex === -1 ? url.length : hashIndex;
|
|
540
570
|
const queryIndex = url.indexOf("?");
|
|
541
|
-
if (queryIndex === -1 || queryIndex > queryEnd) return addLowercaseHexSpellings(url,
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
571
|
+
if (queryIndex === -1 || queryIndex > queryEnd) return addLowercaseHexSpellings(url, [.../* @__PURE__ */ new Set([url, uppercaseHexUrl])]);
|
|
572
|
+
const query = uppercaseHexUrl.slice(queryIndex, queryEnd);
|
|
573
|
+
const present = sameQuerySpellings.filter(([written, encoded]) => {
|
|
574
|
+
return query.includes(written) || query.includes(encoded);
|
|
575
|
+
});
|
|
576
|
+
const bases = [.../* @__PURE__ */ new Set([url, uppercaseHexUrl])];
|
|
577
|
+
const throughout = bases.flatMap((base) => [respellQuery(base, queryIndex, present, false), respellQuery(base, queryIndex, present, true)]);
|
|
578
|
+
let mixes = bases;
|
|
579
|
+
if (present.length <= maxMixedQuerySpellings) for (const pair of present) mixes = mixes.flatMap((spelling) => [respellQuery(spelling, queryIndex, [pair], false), respellQuery(spelling, queryIndex, [pair], true)]);
|
|
580
|
+
return addLowercaseHexSpellings(url, [.../* @__PURE__ */ new Set([
|
|
581
|
+
url,
|
|
582
|
+
uppercaseHexUrl,
|
|
583
|
+
...throughout,
|
|
584
|
+
...mixes
|
|
585
|
+
])]);
|
|
554
586
|
};
|
|
555
587
|
const genericSecondLevels = [
|
|
556
588
|
"ac",
|
package/package.json
CHANGED