url-sanitizer 0.7.6 → 0.8.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/README.md +38 -14
- package/dist/url-sanitizer.js +52 -6
- 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 +23 -21
- package/src/mjs/file-reader.js +301 -0
- package/src/mjs/uri-util.js +64 -7
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
|
|
9
9
|
URL sanitizer for Node.js (>=18), browsers and web sites.
|
|
10
|
+
Sanitize not only regular URLs, but also data URLs and blob URLs.
|
|
11
|
+
Also ability to parse URLs and verify URIs.
|
|
10
12
|
**Experimental**
|
|
11
13
|
|
|
12
14
|
|
|
@@ -35,9 +37,8 @@ import urlSanitizer, {
|
|
|
35
37
|
## sanitizeURL(url, opt)
|
|
36
38
|
|
|
37
39
|
Sanitize the given URL.
|
|
38
|
-
* `data` and `file` schemes must be explicitly allowed.
|
|
39
|
-
* `blob`
|
|
40
|
-
See [Figure out how to sanitize the blob URL · Issue #4](https://github.com/asamuzaK/urlSanitizer/issues/4)
|
|
40
|
+
* `blob`, `data` and `file` schemes must be explicitly allowed.
|
|
41
|
+
* Given a `blob` URL, returns a sanitized `data` URL.
|
|
41
42
|
|
|
42
43
|
### Parameters
|
|
43
44
|
|
|
@@ -59,6 +60,7 @@ const res1 = await sanitizeURL('http://example.com/"onmouseover="alert(1)"?<scri
|
|
|
59
60
|
console.log(decodeURIComponent(res1));
|
|
60
61
|
// => 'http://example.com/"onmouseover="alert(1)"?<script>alert('XSS');</script>'
|
|
61
62
|
|
|
63
|
+
|
|
62
64
|
// Parse and purify data URL
|
|
63
65
|
const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></div><p onclick="alert(2)"></p>', {
|
|
64
66
|
allow: ['data']
|
|
@@ -68,7 +70,8 @@ const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></
|
|
|
68
70
|
console.log(decodeURIComponent(res2));
|
|
69
71
|
// => 'data:text/html,<div></div><p></p>'
|
|
70
72
|
|
|
71
|
-
|
|
73
|
+
|
|
74
|
+
// Can parse and purify base64 encoded data
|
|
72
75
|
const base64data3 = btoa('<div><script>alert(1);</script></div>');
|
|
73
76
|
const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
|
|
74
77
|
allow: ['data']
|
|
@@ -78,58 +81,79 @@ const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
|
|
|
78
81
|
console.log(decodeURIComponent(res3));
|
|
79
82
|
// => 'data:text/html,<div></div>'
|
|
80
83
|
|
|
81
|
-
const
|
|
82
|
-
const
|
|
84
|
+
const base64data3_2 = btoa('<div><img src="javascript:alert(1)"></div>');
|
|
85
|
+
const res3_2 = await sanitizeURL(`data:text/html;base64,${base64data3_2}`);
|
|
83
86
|
// => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C/div%3E'
|
|
84
87
|
|
|
85
|
-
console.log(decodeURIComponent(
|
|
88
|
+
console.log(decodeURIComponent(res3_2));
|
|
86
89
|
// => 'data:text/html,<div><img></div>'
|
|
87
90
|
|
|
91
|
+
|
|
92
|
+
// Can parse and sanitize blob URL
|
|
93
|
+
const blob4 = new Blob(['<svg><g onload="alert(1)"/></svg>'], {
|
|
94
|
+
type: 'image/svg+xml'
|
|
95
|
+
});
|
|
96
|
+
const url4 = URL.createObjectURL(blob4);
|
|
97
|
+
const res4 = await func(url, {
|
|
98
|
+
allow: ['blob']
|
|
99
|
+
});
|
|
100
|
+
// => 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E'
|
|
101
|
+
|
|
102
|
+
console.log(decodeURIComponent(res4));
|
|
103
|
+
// => 'data:image/svg+xml,<svg><g></g></svg>'
|
|
104
|
+
|
|
105
|
+
|
|
88
106
|
// Deny if the scheme matches the `deny` list
|
|
89
107
|
const res5 = await sanitizeURL('web+foo://example.com', {
|
|
90
108
|
deny: ['web+foo']
|
|
91
109
|
});
|
|
92
110
|
// => null
|
|
93
111
|
|
|
112
|
+
|
|
94
113
|
// Allow only if the scheme matches the `only` list
|
|
95
114
|
const res6 = await sanitizeURL('http://example.com', {
|
|
96
115
|
only: ['data', 'git', 'https']
|
|
97
116
|
});
|
|
98
117
|
// => null
|
|
99
118
|
|
|
100
|
-
const
|
|
119
|
+
const res6_2 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
|
|
101
120
|
only: ['data', 'git', 'https']
|
|
102
121
|
});
|
|
103
122
|
// => 'https://example.com/%26quot;onmouseover=%26quot;alert(1)%26quot;'
|
|
104
123
|
|
|
105
|
-
console.log(decodeURIComponent(
|
|
124
|
+
console.log(decodeURIComponent(res6_2));
|
|
106
125
|
// => 'https://example.com/"onmouseover="alert(1)"'
|
|
107
126
|
|
|
127
|
+
|
|
108
128
|
// `only` also allows combination of the schemes in the list
|
|
109
|
-
const
|
|
129
|
+
const res7 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)</script>', {
|
|
110
130
|
only: ['data', 'git', 'https']
|
|
111
131
|
});
|
|
112
132
|
// => 'git+https://example.com/foo.git?%26lt;script%26gt;alert(1)%26lt;/script%26gt;'
|
|
113
133
|
|
|
114
|
-
console.log(decodeURIComponent(
|
|
134
|
+
console.log(decodeURIComponent(res7));
|
|
115
135
|
// => 'git+https://example.com/foo.git?<script>alert(1)</script>'
|
|
116
136
|
|
|
137
|
+
|
|
117
138
|
// Remove the tag or quote and the rest of the URL following it.
|
|
118
|
-
const
|
|
139
|
+
const res8 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
|
|
119
140
|
remove: true
|
|
120
141
|
});
|
|
121
142
|
// => 'https://example.com/'
|
|
122
143
|
|
|
123
|
-
const
|
|
144
|
+
const res8_2 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
|
|
124
145
|
remove: true
|
|
125
146
|
});
|
|
126
147
|
// => 'https://example.com/?'
|
|
127
148
|
```
|
|
128
149
|
|
|
129
150
|
|
|
130
|
-
## sanitizeURLSync
|
|
151
|
+
## sanitizeURLSync(url, opt)
|
|
131
152
|
|
|
132
153
|
Synchronous version of the [sanitizeURL()](#sanitizeurlurl-opt).
|
|
154
|
+
* `data` and `file` schemes must be explicitly allowed.
|
|
155
|
+
* `blob` scheme is not supported, returns `null`.
|
|
156
|
+
Use async [sanitizeURL()](#sanitizeurlurl-opt) for `blob`.
|
|
133
157
|
|
|
134
158
|
|
|
135
159
|
## 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;
|
|
@@ -1704,6 +1708,13 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
1704
1708
|
}
|
|
1705
1709
|
return res;
|
|
1706
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
|
+
});
|
|
1707
1718
|
var URISchemes = class {
|
|
1708
1719
|
/* private fields */
|
|
1709
1720
|
#schemes;
|
|
@@ -1848,7 +1859,8 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1848
1859
|
}
|
|
1849
1860
|
/**
|
|
1850
1861
|
* sanitize URL
|
|
1851
|
-
* NOTE: `
|
|
1862
|
+
* NOTE: `data` and `file` schemes must be explicitly allowed
|
|
1863
|
+
* `blob` URLs should be converted to `data` URLs
|
|
1852
1864
|
* `javascript` and `vbscript` schemes can not be allowed
|
|
1853
1865
|
*
|
|
1854
1866
|
* @param {string} url - URL
|
|
@@ -2097,12 +2109,12 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2097
2109
|
var urlSanitizer = new URLSanitizer();
|
|
2098
2110
|
var isURISync = (uri) => urlSanitizer.verify(uri);
|
|
2099
2111
|
var isURI = async (uri) => {
|
|
2100
|
-
const res =
|
|
2112
|
+
const res = urlSanitizer.verify(uri);
|
|
2101
2113
|
return res;
|
|
2102
2114
|
};
|
|
2103
2115
|
var parseURLSync = (url) => urlSanitizer.parse(url);
|
|
2104
2116
|
var parseURL = async (url) => {
|
|
2105
|
-
const res =
|
|
2117
|
+
const res = urlSanitizer.parse(url);
|
|
2106
2118
|
return res;
|
|
2107
2119
|
};
|
|
2108
2120
|
var sanitizeURLSync = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
@@ -2110,9 +2122,43 @@ var sanitizeURLSync = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
|
2110
2122
|
deny: [],
|
|
2111
2123
|
only: []
|
|
2112
2124
|
});
|
|
2113
|
-
var sanitizeURL = async (url, opt
|
|
2114
|
-
|
|
2115
|
-
|
|
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
|
+
let data;
|
|
2136
|
+
try {
|
|
2137
|
+
data = await fetch(url).then((r) => r.blob()).then(createDataURLFromBlob);
|
|
2138
|
+
URL.revokeObjectURL(url);
|
|
2139
|
+
} catch (e) {
|
|
2140
|
+
}
|
|
2141
|
+
if (data) {
|
|
2142
|
+
if (Array.isArray(only)) {
|
|
2143
|
+
if (!only.includes("data")) {
|
|
2144
|
+
only.push("data");
|
|
2145
|
+
}
|
|
2146
|
+
} else if (Array.isArray(allow)) {
|
|
2147
|
+
if (!allow.includes("data")) {
|
|
2148
|
+
allow.push("data");
|
|
2149
|
+
}
|
|
2150
|
+
if (Array.isArray(deny) && deny.includes("data")) {
|
|
2151
|
+
const i = deny.indexOf("data");
|
|
2152
|
+
deny.splice(i, 1);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
res = urlSanitizer.sanitize(data, opt);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
} else {
|
|
2159
|
+
res = urlSanitizer.sanitize(url, opt);
|
|
2160
|
+
}
|
|
2161
|
+
return res || null;
|
|
2116
2162
|
};
|
|
2117
2163
|
export {
|
|
2118
2164
|
urlSanitizer as default,
|