@ez1219/sanitize-url 7.1.2-rc.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/.cz-config.js +72 -0
- package/.eslintignore +2 -0
- package/.eslintrc +6 -0
- package/.github/CODEOWNERS +1 -0
- package/.github/ISSUE_TEMPLATE/bug_report_form.yml +47 -0
- package/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/.github/ISSUE_TEMPLATE/feature_request_form.yml +30 -0
- package/.github/dependabot.yml +16 -0
- package/.github/pull_request_template.md +17 -0
- package/.github/workflows/ci.yml +30 -0
- package/.github/workflows/enforce-conventional-pr-title.yml +39 -0
- package/.github/workflows/publish.yml +65 -0
- package/.github/workflows/security.yml +22 -0
- package/.github/workflows/stale-cleanup.yml +22 -0
- package/.husky/commit-msg +2 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/CHANGELOG.md +155 -0
- package/CONTRIBUTING.md +79 -0
- package/LICENSE +21 -0
- package/README.md +39 -0
- package/SECURITY.md +41 -0
- package/commitlint.config.js +70 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +11 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +88 -0
- package/package.json +56 -0
- package/src/__tests__/index.test.ts +299 -0
- package/src/constants.ts +10 -0
- package/src/index.ts +114 -0
- package/tsconfig.json +12 -0
- package/vitest.config.ts +17 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BLANK_URL,
|
|
3
|
+
ctrlCharactersRegex,
|
|
4
|
+
htmlCtrlEntityRegex,
|
|
5
|
+
htmlEntitiesRegex,
|
|
6
|
+
invalidProtocolRegex,
|
|
7
|
+
relativeFirstCharacters,
|
|
8
|
+
whitespaceEscapeCharsRegex,
|
|
9
|
+
urlSchemeRegex,
|
|
10
|
+
} from "./constants";
|
|
11
|
+
|
|
12
|
+
function isRelativeUrlWithoutProtocol(url: string): boolean {
|
|
13
|
+
return relativeFirstCharacters.indexOf(url[0]) > -1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function decodeHtmlCharacters(str: string) {
|
|
17
|
+
const removedNullByte = str.replace(ctrlCharactersRegex, "");
|
|
18
|
+
return removedNullByte.replace(htmlEntitiesRegex, (match, dec) => {
|
|
19
|
+
return String.fromCharCode(dec);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isValidUrl(url: string): boolean {
|
|
24
|
+
if (typeof URL !== "undefined" && typeof URL.canParse === "function") {
|
|
25
|
+
return URL.canParse(url);
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return Boolean(new URL(url));
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function decodeURI(uri: string): string {
|
|
35
|
+
try {
|
|
36
|
+
return decodeURIComponent(uri);
|
|
37
|
+
} catch (e: unknown) {
|
|
38
|
+
// Ignoring error
|
|
39
|
+
// It is possible that the URI contains a `%` not associated
|
|
40
|
+
// with URI/URL-encoding.
|
|
41
|
+
return uri;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function sanitizeUrl(url?: string): string {
|
|
46
|
+
if (!url) {
|
|
47
|
+
return BLANK_URL;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let charsToDecode;
|
|
51
|
+
let decodedUrl = decodeURI(url.trim());
|
|
52
|
+
|
|
53
|
+
do {
|
|
54
|
+
decodedUrl = decodeHtmlCharacters(decodedUrl)
|
|
55
|
+
.replace(htmlCtrlEntityRegex, "")
|
|
56
|
+
.replace(ctrlCharactersRegex, "")
|
|
57
|
+
.replace(whitespaceEscapeCharsRegex, "")
|
|
58
|
+
.trim();
|
|
59
|
+
|
|
60
|
+
decodedUrl = decodeURI(decodedUrl);
|
|
61
|
+
|
|
62
|
+
charsToDecode =
|
|
63
|
+
decodedUrl.match(ctrlCharactersRegex) ||
|
|
64
|
+
decodedUrl.match(htmlEntitiesRegex) ||
|
|
65
|
+
decodedUrl.match(htmlCtrlEntityRegex) ||
|
|
66
|
+
decodedUrl.match(whitespaceEscapeCharsRegex);
|
|
67
|
+
} while (charsToDecode && charsToDecode.length > 0);
|
|
68
|
+
|
|
69
|
+
const sanitizedUrl = decodedUrl;
|
|
70
|
+
|
|
71
|
+
if (!sanitizedUrl) {
|
|
72
|
+
return BLANK_URL;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (isRelativeUrlWithoutProtocol(sanitizedUrl)) {
|
|
76
|
+
return sanitizedUrl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Remove any leading whitespace before checking the URL scheme
|
|
80
|
+
const trimmedUrl = sanitizedUrl.trimStart();
|
|
81
|
+
const urlSchemeParseResults = trimmedUrl.match(urlSchemeRegex);
|
|
82
|
+
|
|
83
|
+
if (!urlSchemeParseResults) {
|
|
84
|
+
return sanitizedUrl;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const urlScheme = urlSchemeParseResults[0].toLowerCase().trim();
|
|
88
|
+
|
|
89
|
+
if (invalidProtocolRegex.test(urlScheme)) {
|
|
90
|
+
return BLANK_URL;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const backSanitized = trimmedUrl.replace(/\\/g, "/");
|
|
94
|
+
|
|
95
|
+
// Handle special cases for mailto: and custom deep-link protocols
|
|
96
|
+
if (urlScheme === "mailto:" || urlScheme.includes("://")) {
|
|
97
|
+
return backSanitized;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// For http and https URLs, perform additional validation
|
|
101
|
+
if (urlScheme === "http:" || urlScheme === "https:") {
|
|
102
|
+
if (!isValidUrl(backSanitized)) {
|
|
103
|
+
return BLANK_URL;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const url = new URL(backSanitized);
|
|
107
|
+
url.protocol = url.protocol.toLowerCase();
|
|
108
|
+
url.hostname = url.hostname.toLowerCase();
|
|
109
|
+
|
|
110
|
+
return url.toString();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return backSanitized;
|
|
114
|
+
}
|
package/tsconfig.json
ADDED
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: "happy-dom",
|
|
7
|
+
coverage: {
|
|
8
|
+
enabled: true,
|
|
9
|
+
thresholds: {
|
|
10
|
+
lines: 80,
|
|
11
|
+
statements: 80,
|
|
12
|
+
branches: 80,
|
|
13
|
+
functions: 80,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|