url-sanitizer 0.7.5 → 0.8.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/README.md +36 -12
- package/dist/url-sanitizer.js +53 -10
- package/dist/url-sanitizer.js.map +3 -3
- package/dist/url-sanitizer.min.js +3 -3
- package/dist/url-sanitizer.min.js.map +4 -4
- package/package.json +9 -7
- package/src/mjs/file-reader.js +304 -0
- package/src/mjs/uri-util.js +64 -11
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ import urlSanitizer, {
|
|
|
36
36
|
|
|
37
37
|
Sanitize the given URL.
|
|
38
38
|
* `blob`, `data` and `file` schemes must be explicitly allowed.
|
|
39
|
+
* Given a `blob` URL, returns a sanitized `data` URL.
|
|
39
40
|
|
|
40
41
|
### Parameters
|
|
41
42
|
|
|
@@ -57,16 +58,18 @@ const res1 = await sanitizeURL('http://example.com/"onmouseover="alert(1)"?<scri
|
|
|
57
58
|
console.log(decodeURIComponent(res1));
|
|
58
59
|
// => 'http://example.com/"onmouseover="alert(1)"?<script>alert('XSS');</script>'
|
|
59
60
|
|
|
61
|
+
|
|
60
62
|
// Parse and purify data URL
|
|
61
63
|
const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></div><p onclick="alert(2)"></p>', {
|
|
62
64
|
allow: ['data']
|
|
63
65
|
})
|
|
64
|
-
//
|
|
66
|
+
// => 'data:text/html,%3Cdiv%3E%3C/div%3E%3Cp%3E%3C/p%3E'
|
|
65
67
|
|
|
66
68
|
console.log(decodeURIComponent(res2));
|
|
67
69
|
// => 'data:text/html,<div></div><p></p>'
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
|
|
72
|
+
// Can parse and purify base64 encoded data
|
|
70
73
|
const base64data3 = btoa('<div><script>alert(1);</script></div>');
|
|
71
74
|
const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
|
|
72
75
|
allow: ['data']
|
|
@@ -76,58 +79,79 @@ const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
|
|
|
76
79
|
console.log(decodeURIComponent(res3));
|
|
77
80
|
// => 'data:text/html,<div></div>'
|
|
78
81
|
|
|
79
|
-
const
|
|
80
|
-
const
|
|
82
|
+
const base64data3_2 = btoa('<div><img src="javascript:alert(1)"></div>');
|
|
83
|
+
const res3_2 = await sanitizeURL(`data:text/html;base64,${base64data3_2}`);
|
|
81
84
|
// => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C/div%3E'
|
|
82
85
|
|
|
83
|
-
console.log(decodeURIComponent(
|
|
86
|
+
console.log(decodeURIComponent(res3_2));
|
|
84
87
|
// => 'data:text/html,<div><img></div>'
|
|
85
88
|
|
|
89
|
+
|
|
90
|
+
// Can parse and sanitize blob URL
|
|
91
|
+
const blob4 = new Blob(['<svg><g onload="alert(1)"/></svg>'], {
|
|
92
|
+
type: 'image/svg+xml'
|
|
93
|
+
});
|
|
94
|
+
const url4 = URL.createObjectURL(blob4);
|
|
95
|
+
const res4 = await func(url, {
|
|
96
|
+
allow: ['blob']
|
|
97
|
+
});
|
|
98
|
+
// => 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E'
|
|
99
|
+
|
|
100
|
+
console.log(decodeURIComponent(res4));
|
|
101
|
+
// => 'data:image/svg+xml,<svg><g></g></svg>'
|
|
102
|
+
|
|
103
|
+
|
|
86
104
|
// Deny if the scheme matches the `deny` list
|
|
87
105
|
const res5 = await sanitizeURL('web+foo://example.com', {
|
|
88
106
|
deny: ['web+foo']
|
|
89
107
|
});
|
|
90
108
|
// => null
|
|
91
109
|
|
|
110
|
+
|
|
92
111
|
// Allow only if the scheme matches the `only` list
|
|
93
112
|
const res6 = await sanitizeURL('http://example.com', {
|
|
94
113
|
only: ['data', 'git', 'https']
|
|
95
114
|
});
|
|
96
115
|
// => null
|
|
97
116
|
|
|
98
|
-
const
|
|
117
|
+
const res6_2 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
|
|
99
118
|
only: ['data', 'git', 'https']
|
|
100
119
|
});
|
|
101
120
|
// => 'https://example.com/%26quot;onmouseover=%26quot;alert(1)%26quot;'
|
|
102
121
|
|
|
103
|
-
console.log(decodeURIComponent(
|
|
122
|
+
console.log(decodeURIComponent(res6_2));
|
|
104
123
|
// => 'https://example.com/"onmouseover="alert(1)"'
|
|
105
124
|
|
|
125
|
+
|
|
106
126
|
// `only` also allows combination of the schemes in the list
|
|
107
|
-
const
|
|
127
|
+
const res7 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)</script>', {
|
|
108
128
|
only: ['data', 'git', 'https']
|
|
109
129
|
});
|
|
110
130
|
// => 'git+https://example.com/foo.git?%26lt;script%26gt;alert(1)%26lt;/script%26gt;'
|
|
111
131
|
|
|
112
|
-
console.log(decodeURIComponent(
|
|
132
|
+
console.log(decodeURIComponent(res7));
|
|
113
133
|
// => 'git+https://example.com/foo.git?<script>alert(1)</script>'
|
|
114
134
|
|
|
135
|
+
|
|
115
136
|
// Remove the tag or quote and the rest of the URL following it.
|
|
116
|
-
const
|
|
137
|
+
const res8 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
|
|
117
138
|
remove: true
|
|
118
139
|
});
|
|
119
140
|
// => 'https://example.com/'
|
|
120
141
|
|
|
121
|
-
const
|
|
142
|
+
const res8_2 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
|
|
122
143
|
remove: true
|
|
123
144
|
});
|
|
124
145
|
// => 'https://example.com/?'
|
|
125
146
|
```
|
|
126
147
|
|
|
127
148
|
|
|
128
|
-
## sanitizeURLSync
|
|
149
|
+
## sanitizeURLSync(url, opt)
|
|
129
150
|
|
|
130
151
|
Synchronous version of the [sanitizeURL()](#sanitizeurlurl-opt).
|
|
152
|
+
* `data` and `file` schemes must be explicitly allowed.
|
|
153
|
+
* `blob` scheme is not supported, returns `null`.
|
|
154
|
+
Use async [sanitizeURL()](#sanitizeurlurl-opt) for `blob`.
|
|
131
155
|
|
|
132
156
|
|
|
133
157
|
## parseURL(url)
|
package/dist/url-sanitizer.js
CHANGED
|
@@ -998,6 +998,10 @@ var require_purify = __commonJS({
|
|
|
998
998
|
}
|
|
999
999
|
});
|
|
1000
1000
|
|
|
1001
|
+
// bundle/mjs/file-reader.js
|
|
1002
|
+
var FileReader = window.FileReader;
|
|
1003
|
+
var fileReader = new FileReader();
|
|
1004
|
+
|
|
1001
1005
|
// bundle/mjs/common.js
|
|
1002
1006
|
var TYPE_FROM = 8;
|
|
1003
1007
|
var TYPE_TO = -1;
|
|
@@ -1613,6 +1617,7 @@ var REG_NUM_REF = /&#(x(?:00)?[\dA-F]{2}|0?\d{1,3});?/ig;
|
|
|
1613
1617
|
var REG_SCHEME = /^[a-z][\da-z+\-.]*$/;
|
|
1614
1618
|
var REG_SCHEME_CUSTOM = /^(?:ext|web)\+[a-z]+$/;
|
|
1615
1619
|
var REG_SCRIPT = /(?:java|vb)script/;
|
|
1620
|
+
var REG_SCRIPT_BLOB = /(?:java|vb)script|blob/;
|
|
1616
1621
|
var REG_URL_ENC = /^%[\dA-F]{2}$/i;
|
|
1617
1622
|
var REG_URL_ENC_AMP = /%26/g;
|
|
1618
1623
|
var getURLEncodedString = (str) => {
|
|
@@ -1703,6 +1708,13 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
1703
1708
|
}
|
|
1704
1709
|
return res;
|
|
1705
1710
|
};
|
|
1711
|
+
var createDataURLFromBlob = (blob) => new Promise((resolve, reject) => {
|
|
1712
|
+
const reader = new FileReader();
|
|
1713
|
+
reader.addEventListener("error", () => reject(reader.error));
|
|
1714
|
+
reader.addEventListener("abort", () => resolve(reader.result));
|
|
1715
|
+
reader.addEventListener("load", () => resolve(reader.result));
|
|
1716
|
+
reader.readAsDataURL(blob);
|
|
1717
|
+
});
|
|
1706
1718
|
var URISchemes = class {
|
|
1707
1719
|
/* private fields */
|
|
1708
1720
|
#schemes;
|
|
@@ -1847,7 +1859,8 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1847
1859
|
}
|
|
1848
1860
|
/**
|
|
1849
1861
|
* sanitize URL
|
|
1850
|
-
* NOTE: `
|
|
1862
|
+
* NOTE: `data` and `file` schemes must be explicitly allowed
|
|
1863
|
+
* `blob` URLs should be converted to `data` URLs
|
|
1851
1864
|
* `javascript` and `vbscript` schemes can not be allowed
|
|
1852
1865
|
*
|
|
1853
1866
|
* @param {string} url - URL
|
|
@@ -1881,7 +1894,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1881
1894
|
for (let item of items) {
|
|
1882
1895
|
if (isString(item)) {
|
|
1883
1896
|
item = item.trim();
|
|
1884
|
-
if (!
|
|
1897
|
+
if (!REG_SCRIPT_BLOB.test(item)) {
|
|
1885
1898
|
if (super.has(item)) {
|
|
1886
1899
|
schemeMap.set(item, true);
|
|
1887
1900
|
} else {
|
|
@@ -1905,7 +1918,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1905
1918
|
for (let item of items) {
|
|
1906
1919
|
if (isString(item)) {
|
|
1907
1920
|
item = item.trim();
|
|
1908
|
-
if (!
|
|
1921
|
+
if (!REG_SCRIPT_BLOB.test(item)) {
|
|
1909
1922
|
if (super.has(item)) {
|
|
1910
1923
|
schemeMap.set(item, true);
|
|
1911
1924
|
} else {
|
|
@@ -1966,7 +1979,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1966
1979
|
const decodedData = parseURLEncodedNumCharRef(parsedData);
|
|
1967
1980
|
const { protocol: dataScheme } = new URL(decodedData.trim());
|
|
1968
1981
|
const dataSchemeParts = dataScheme.replace(REG_END_COLON, "").split("+");
|
|
1969
|
-
if (dataSchemeParts.some((s) =>
|
|
1982
|
+
if (dataSchemeParts.some((s) => REG_SCRIPT_BLOB.test(s))) {
|
|
1970
1983
|
urlToSanitize = "";
|
|
1971
1984
|
}
|
|
1972
1985
|
} catch (e) {
|
|
@@ -2047,7 +2060,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2047
2060
|
*
|
|
2048
2061
|
* @param {string} url - URL
|
|
2049
2062
|
* @param {object} opt - options
|
|
2050
|
-
* @returns {ParsedURL} - result with
|
|
2063
|
+
* @returns {ParsedURL} - result with additional props based on URL API
|
|
2051
2064
|
*/
|
|
2052
2065
|
parse(url, opt) {
|
|
2053
2066
|
if (!isString(url)) {
|
|
@@ -2096,12 +2109,12 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2096
2109
|
var urlSanitizer = new URLSanitizer();
|
|
2097
2110
|
var isURISync = (uri) => urlSanitizer.verify(uri);
|
|
2098
2111
|
var isURI = async (uri) => {
|
|
2099
|
-
const res =
|
|
2112
|
+
const res = urlSanitizer.verify(uri);
|
|
2100
2113
|
return res;
|
|
2101
2114
|
};
|
|
2102
2115
|
var parseURLSync = (url) => urlSanitizer.parse(url);
|
|
2103
2116
|
var parseURL = async (url) => {
|
|
2104
|
-
const res =
|
|
2117
|
+
const res = urlSanitizer.parse(url);
|
|
2105
2118
|
return res;
|
|
2106
2119
|
};
|
|
2107
2120
|
var sanitizeURLSync = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
@@ -2109,9 +2122,39 @@ var sanitizeURLSync = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
|
2109
2122
|
deny: [],
|
|
2110
2123
|
only: []
|
|
2111
2124
|
});
|
|
2112
|
-
var sanitizeURL = async (url, opt
|
|
2113
|
-
|
|
2114
|
-
|
|
2125
|
+
var sanitizeURL = async (url, opt = {
|
|
2126
|
+
allow: [],
|
|
2127
|
+
deny: [],
|
|
2128
|
+
only: []
|
|
2129
|
+
}) => {
|
|
2130
|
+
const { protocol } = new URL(url);
|
|
2131
|
+
let res;
|
|
2132
|
+
if (protocol === "blob:") {
|
|
2133
|
+
const { allow, deny, only } = opt;
|
|
2134
|
+
if (Array.isArray(allow) && allow.includes("blob") && !(Array.isArray(deny) && deny.includes("blob")) || Array.isArray(only) && only.includes("blob")) {
|
|
2135
|
+
const blob = await fetch(url).then((r) => r.blob());
|
|
2136
|
+
const data = await createDataURLFromBlob(blob);
|
|
2137
|
+
if (data) {
|
|
2138
|
+
if (Array.isArray(only)) {
|
|
2139
|
+
if (!only.includes("data")) {
|
|
2140
|
+
only.push("data");
|
|
2141
|
+
}
|
|
2142
|
+
} else if (Array.isArray(allow)) {
|
|
2143
|
+
if (!allow.includes("data")) {
|
|
2144
|
+
allow.push("data");
|
|
2145
|
+
}
|
|
2146
|
+
if (Array.isArray(deny) && deny.includes("data")) {
|
|
2147
|
+
const i = deny.indexOf("data");
|
|
2148
|
+
deny.splice(i, 1);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
res = urlSanitizer.sanitize(data, opt);
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
} else {
|
|
2155
|
+
res = urlSanitizer.sanitize(url, opt);
|
|
2156
|
+
}
|
|
2157
|
+
return res || null;
|
|
2115
2158
|
};
|
|
2116
2159
|
export {
|
|
2117
2160
|
urlSanitizer as default,
|