urlpurify 1.6.0 → 1.6.1
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/clean.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fixMalformedProtocol, isHttpUrl, isIpAddress, parseUrl, stripWww } from "./node_modules/trousse/dist/urls.mjs";
|
|
1
|
+
import { fixMalformedProtocol, isHttpUrl, isIpAddress, normalizeUrl, parseUrl, stripWww } from "./node_modules/trousse/dist/urls.mjs";
|
|
2
2
|
import { defaultTrackingParams, defaultUnwrappers } from "./defaults.mjs";
|
|
3
3
|
//#region src/clean.ts
|
|
4
4
|
const replacementCharacter = "�";
|
|
@@ -73,6 +73,9 @@ const applyUnwrappers = (url, unwrappers) => {
|
|
|
73
73
|
const target = cleanTarget(raw);
|
|
74
74
|
if (target) return target;
|
|
75
75
|
}
|
|
76
|
+
const collapsed = parseUrl(normalizeUrl(url.href, { collapseSlashes: true }));
|
|
77
|
+
if (!collapsed || collapsed.href === url.href) return;
|
|
78
|
+
return applyUnwrappers(collapsed, unwrappers);
|
|
76
79
|
};
|
|
77
80
|
const unwrapUrl = (url, unwrappers = defaultUnwrappers) => {
|
|
78
81
|
const parsed = parseUrl(url);
|
|
@@ -4,18 +4,22 @@ const testRegex = (regex, value) => {
|
|
|
4
4
|
regex.lastIndex = 0;
|
|
5
5
|
return regex.test(value);
|
|
6
6
|
};
|
|
7
|
+
const testRegexAnyCase = (regex, lowerValue, value) => {
|
|
8
|
+
return testRegex(regex, lowerValue) || lowerValue !== value && testRegex(regex, value);
|
|
9
|
+
};
|
|
7
10
|
const isAnyOf = (value, patterns, parser) => {
|
|
8
11
|
if (isNullish(value)) return false;
|
|
9
12
|
const parsedValue = parser ? parser(value) : value.toLowerCase().trim();
|
|
13
|
+
const originalValue = parser ? parsedValue : value.trim();
|
|
10
14
|
return (typeof patterns === "string" || patterns instanceof RegExp ? [patterns] : patterns).some((pattern) => {
|
|
11
|
-
if (pattern instanceof RegExp) return
|
|
15
|
+
if (pattern instanceof RegExp) return testRegexAnyCase(pattern, parsedValue, originalValue);
|
|
12
16
|
return parsedValue === (parser ? pattern : pattern.toLowerCase().trim());
|
|
13
17
|
});
|
|
14
18
|
};
|
|
15
19
|
const endsWithAnyOf = (value, patterns) => {
|
|
16
20
|
const lowerValue = value.toLowerCase();
|
|
17
21
|
return patterns.some((pattern) => {
|
|
18
|
-
if (pattern instanceof RegExp) return
|
|
22
|
+
if (pattern instanceof RegExp) return testRegexAnyCase(pattern, lowerValue, value);
|
|
19
23
|
return pattern && lowerValue.endsWith(pattern.toLowerCase());
|
|
20
24
|
});
|
|
21
25
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { endsWithAnyOf, isAnyOf } from "./matching.mjs";
|
|
2
2
|
//#region node_modules/trousse/dist/urls.js
|
|
3
|
-
const
|
|
3
|
+
const httpSchemes = ["http:", "https:"];
|
|
4
4
|
const parseUrl = (value, base) => {
|
|
5
5
|
if (value instanceof URL && base === void 0) return value;
|
|
6
6
|
if (URL.parse) return URL.parse(value, base) ?? void 0;
|
|
@@ -31,9 +31,9 @@ const isSubdomainOf = (url, domains) => {
|
|
|
31
31
|
return endsWithAnyOf(hostname, (typeof domains === "string" ? [domains] : domains).map((domain) => `.${normalizeDomain(domain)}`));
|
|
32
32
|
};
|
|
33
33
|
const isHttpUrl = (url) => {
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
36
|
-
return
|
|
34
|
+
const scheme = parseUrl(url)?.protocol;
|
|
35
|
+
if (!scheme) return false;
|
|
36
|
+
return httpSchemes.includes(scheme);
|
|
37
37
|
};
|
|
38
38
|
const isHostOrSubdomainOf = (url, domains) => {
|
|
39
39
|
return isHostOf(url, domains) || isSubdomainOf(url, domains);
|
|
@@ -44,11 +44,25 @@ const decodeSegment = (segment) => {
|
|
|
44
44
|
return decodeURIComponent(segment);
|
|
45
45
|
} catch {}
|
|
46
46
|
};
|
|
47
|
+
const strippedParamsCache = /* @__PURE__ */ new WeakMap();
|
|
48
|
+
const getStrippedParamsSet = (params) => {
|
|
49
|
+
let cached = strippedParamsCache.get(params);
|
|
50
|
+
if (!cached) {
|
|
51
|
+
cached = new Set(params.map((param) => param.toLowerCase()));
|
|
52
|
+
strippedParamsCache.set(params, cached);
|
|
53
|
+
}
|
|
54
|
+
return cached;
|
|
55
|
+
};
|
|
47
56
|
const ipv4Regex = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
|
48
57
|
const ipv6Regex = /^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i;
|
|
49
|
-
const wwwPrefixRegex = /^www
|
|
58
|
+
const wwwPrefixRegex = /^(?:www\.)+(?=.*\.)/;
|
|
50
59
|
const ipv6BracketsRegex = /^\[|\]$/g;
|
|
60
|
+
const unreservedCharsRegex = /[a-zA-Z0-9._~-]/;
|
|
51
61
|
const httpsLetterRegex = /s/i;
|
|
62
|
+
const schemePrefixRegex = /^https?:\/\//;
|
|
63
|
+
const percentEscapeOrLettersRegex = /%[0-9A-Fa-f]{2}|[A-Z]+/g;
|
|
64
|
+
const plusRegex = /\+/g;
|
|
65
|
+
const repeatedSlashesRegex = /(?<!:|%3a)\/{2,}/gi;
|
|
52
66
|
const leadingWhitespaceChars = [
|
|
53
67
|
" ",
|
|
54
68
|
" ",
|
|
@@ -64,35 +78,39 @@ const localhostRegex = /^localhost(?:[:/?#]|$)/i;
|
|
|
64
78
|
const validUrlRegex = /^https?:\/\/(?:www\.|[a-vx-z0-9])/i;
|
|
65
79
|
const schemeSeparator = String.raw`\.*[:\s=\\/][:\s=.\\/]*`;
|
|
66
80
|
const leadingSchemeSeparator = String.raw`\.*(?::[:\s=.\\/]*|[\s=\\/][:\s=.\\/]+)`;
|
|
67
|
-
const
|
|
68
|
-
const singleMalformedRegex = /* @__PURE__ */ new RegExp(String.raw`^\/?(
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
81
|
+
const doubledSchemeRegex = /* @__PURE__ */ new RegExp(String.raw`^\/?[htps]{2,7}${leadingSchemeSeparator}([htps]{2,7})(?!:\d|[^/?#\\]*@)(${schemeSeparator})[.,:/]*(www[./]+)?`, "i");
|
|
82
|
+
const singleMalformedRegex = /* @__PURE__ */ new RegExp(String.raw`^\/?(h[htps():]{1,10}|t{1,2}ps?)(${leadingSchemeSeparator})[.,:/]*(www[./]+)?`, "i");
|
|
83
|
+
const isSchemeToken = (token, separator) => {
|
|
84
|
+
const lowered = token.toLowerCase();
|
|
85
|
+
return `${token}${separator}`.includes(":") || lowered.includes("t") && lowered.includes("p");
|
|
86
|
+
};
|
|
87
|
+
const fixMalformedScheme = (url) => {
|
|
88
|
+
if (validUrlRegex.test(url) && !doubledSchemeRegex.test(url)) return url;
|
|
89
|
+
const doubledMatch = doubledSchemeRegex.exec(url);
|
|
90
|
+
if (doubledMatch && isSchemeToken(doubledMatch[1], doubledMatch[2])) {
|
|
73
91
|
const inner = doubledMatch[1];
|
|
74
|
-
const www = doubledMatch[
|
|
92
|
+
const www = doubledMatch[3];
|
|
75
93
|
const rest = url.slice(doubledMatch[0].length);
|
|
76
|
-
return
|
|
94
|
+
return `${httpsLetterRegex.test(inner) ? "https://" : "http://"}${www ? "www." : ""}${rest}`;
|
|
77
95
|
}
|
|
78
96
|
const singleMatch = singleMalformedRegex.exec(url);
|
|
79
|
-
if (singleMatch) {
|
|
97
|
+
if (singleMatch && isSchemeToken(singleMatch[1], singleMatch[2])) {
|
|
80
98
|
const fullMatch = singleMatch[0];
|
|
81
|
-
const www = singleMatch[
|
|
99
|
+
const www = singleMatch[3];
|
|
82
100
|
const rest = url.slice(fullMatch.length);
|
|
83
|
-
return
|
|
101
|
+
return `${httpsLetterRegex.test(fullMatch) ? "https://" : "http://"}${www ? "www." : ""}${rest}`;
|
|
84
102
|
}
|
|
85
103
|
return url;
|
|
86
104
|
};
|
|
87
|
-
const
|
|
88
|
-
if (url.startsWith("[")) return parseUrl(`${
|
|
105
|
+
const addMissingScheme = (url, scheme = "https") => {
|
|
106
|
+
if (url.startsWith("[")) return parseUrl(`${scheme}://${url}`) ? `${scheme}://${url}` : url;
|
|
89
107
|
const colonIndex = url.indexOf(":");
|
|
90
108
|
if (colonIndex > 0) {
|
|
91
109
|
const beforeColon = url.slice(0, colonIndex);
|
|
92
110
|
if (!beforeColon.includes(".") && !beforeColon.includes("/") && !localhostRegex.test(url)) return url;
|
|
93
111
|
}
|
|
94
112
|
if (url.startsWith("//") && !url.startsWith("///")) {
|
|
95
|
-
const parsed = parseUrl(`${
|
|
113
|
+
const parsed = parseUrl(`${scheme}:${url}`);
|
|
96
114
|
if (!parsed) return url;
|
|
97
115
|
const hostname = parsed.hostname;
|
|
98
116
|
if (hostname.includes(".") || hostname === "localhost" || isIpAddress(hostname)) return parsed.href;
|
|
@@ -106,14 +124,83 @@ const addMissingProtocol = (url, protocol = "https") => {
|
|
|
106
124
|
}
|
|
107
125
|
const firstChar = url.charAt(0);
|
|
108
126
|
if (leadingWhitespaceChars.includes(firstChar)) return url;
|
|
109
|
-
return `${
|
|
127
|
+
return `${scheme}://${url}`;
|
|
128
|
+
};
|
|
129
|
+
const decodeAndNormalizeEncoding = (value) => {
|
|
130
|
+
if (!value.includes("%")) return value;
|
|
131
|
+
return value.replace(/%([0-9A-Fa-f]{2})/g, (_match, hex) => {
|
|
132
|
+
const charCode = Number.parseInt(hex, 16);
|
|
133
|
+
const char = String.fromCharCode(charCode);
|
|
134
|
+
if (unreservedCharsRegex.test(char)) return char;
|
|
135
|
+
return `%${hex.toUpperCase()}`;
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const decodeQueryKey = (pair) => {
|
|
139
|
+
const key = pair.split("=")[0].replace(plusRegex, " ");
|
|
140
|
+
try {
|
|
141
|
+
return decodeURIComponent(key);
|
|
142
|
+
} catch {
|
|
143
|
+
return key;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const compareQueryPairs = (a, b) => {
|
|
147
|
+
const keyA = decodeQueryKey(a);
|
|
148
|
+
const keyB = decodeQueryKey(b);
|
|
149
|
+
if (keyA < keyB) return -1;
|
|
150
|
+
if (keyA > keyB) return 1;
|
|
151
|
+
return 0;
|
|
152
|
+
};
|
|
153
|
+
const lowercaseQueryPair = (pair) => {
|
|
154
|
+
return pair.replace(percentEscapeOrLettersRegex, (match) => {
|
|
155
|
+
return match.startsWith("%") ? match : match.toLowerCase();
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
const normalizeUrl = (url, options) => {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = new URL(options.normalizeUnicode ? url.normalize("NFC") : url);
|
|
161
|
+
if (options.stripHostTrailingDot && parsed.hostname.endsWith(".")) parsed.hostname = parsed.hostname.slice(0, -1);
|
|
162
|
+
if (options.stripAuthentication) {
|
|
163
|
+
parsed.username = "";
|
|
164
|
+
parsed.password = "";
|
|
165
|
+
}
|
|
166
|
+
if (options.stripWww) parsed.hostname = stripWww(parsed.hostname);
|
|
167
|
+
if (options.stripHash) parsed.hash = "";
|
|
168
|
+
let pathname = parsed.pathname;
|
|
169
|
+
if (options.normalizeEncoding) pathname = decodeAndNormalizeEncoding(pathname);
|
|
170
|
+
if (options.collapseSlashes) pathname = pathname.replace(repeatedSlashesRegex, "/");
|
|
171
|
+
if (options.stripTrailingSlash && pathname.length > 1 && pathname.endsWith("/")) pathname = pathname.slice(0, -1);
|
|
172
|
+
parsed.pathname = pathname;
|
|
173
|
+
if (options.normalizeEncoding && parsed.search.includes("%")) parsed.search = decodeAndNormalizeEncoding(parsed.search);
|
|
174
|
+
if (options.normalizeEncoding && parsed.hash.includes("%")) parsed.hash = decodeAndNormalizeEncoding(parsed.hash);
|
|
175
|
+
if (options.stripQuery) parsed.search = "";
|
|
176
|
+
if (parsed.search && (options.stripQueryParams || options.lowercaseQuery)) {
|
|
177
|
+
let pairs = parsed.search.slice(1).split("&");
|
|
178
|
+
if (options.stripQueryParams) {
|
|
179
|
+
const strippedSet = getStrippedParamsSet(options.stripQueryParams);
|
|
180
|
+
pairs = pairs.filter((pair) => pair && !strippedSet.has(decodeQueryKey(pair).toLowerCase()));
|
|
181
|
+
}
|
|
182
|
+
if (options.lowercaseQuery) pairs = pairs.map(lowercaseQueryPair);
|
|
183
|
+
parsed.search = pairs.join("&");
|
|
184
|
+
}
|
|
185
|
+
if (options.sortQueryParams && parsed.search.includes("&")) parsed.search = parsed.search.slice(1).split("&").filter(Boolean).sort(compareQueryPairs).join("&");
|
|
186
|
+
if (options.stripEmptyQuery && parsed.search === "") parsed.search = "";
|
|
187
|
+
let result = parsed.href;
|
|
188
|
+
if (options.stripRootSlash && result === `${parsed.origin}/`) result = parsed.origin;
|
|
189
|
+
if (options.stripScheme ?? options.stripProtocol) result = result.replace(schemePrefixRegex, "");
|
|
190
|
+
return result;
|
|
191
|
+
} catch {
|
|
192
|
+
return url;
|
|
193
|
+
}
|
|
110
194
|
};
|
|
111
195
|
const stripWww = (hostname) => {
|
|
112
|
-
|
|
113
|
-
return stripped.includes(".") ? stripped : hostname;
|
|
196
|
+
return hostname.replace(wwwPrefixRegex, "");
|
|
114
197
|
};
|
|
115
198
|
const isIpAddress = (hostname) => {
|
|
116
199
|
return ipv4Regex.test(hostname) || ipv6Regex.test(hostname.replace(ipv6BracketsRegex, ""));
|
|
117
200
|
};
|
|
201
|
+
/** @deprecated Use `fixMalformedScheme` instead. */
|
|
202
|
+
const fixMalformedProtocol = fixMalformedScheme;
|
|
203
|
+
/** @deprecated Use `addMissingScheme` instead. */
|
|
204
|
+
const addMissingProtocol = addMissingScheme;
|
|
118
205
|
//#endregion
|
|
119
|
-
export { addMissingProtocol, decodeSegment, fixMalformedProtocol, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isSubdomainOf, parseUrl, stripWww };
|
|
206
|
+
export { addMissingProtocol, addMissingScheme, decodeSegment, fixMalformedProtocol, fixMalformedScheme, isHostOf, isHostOrSubdomainOf, isHttpUrl, isIpAddress, isSubdomainOf, normalizeUrl, parseUrl, stripWww };
|