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 CHANGED
@@ -7,6 +7,8 @@
7
7
  [![license](https://img.shields.io/github/license/asamuzaK/urlSanitizer)](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` URLs are not currently allowed and will return `null`.
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/&quot;onmouseover=&quot;alert(1)&quot;?&lt;script&gt;alert(&#39;XSS&#39;);&lt;/script&gt;'
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
- // Can also parse and purify base64 encoded data
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 base64data4 = btoa('<div><img src="javascript:alert(1)"></div>');
82
- const res4 = await sanitizeURL(`data:text/html;base64,${base64data4}`);
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(res4));
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 res7 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
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(res7));
124
+ console.log(decodeURIComponent(res6_2));
106
125
  // => 'https://example.com/&quot;onmouseover=&quot;alert(1)&quot;'
107
126
 
127
+
108
128
  // `only` also allows combination of the schemes in the list
109
- const res8 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)</script>', {
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(res8));
134
+ console.log(decodeURIComponent(res7));
115
135
  // => 'git+https://example.com/foo.git?&lt;script&gt;alert(1)&lt;/script&gt;'
116
136
 
137
+
117
138
  // Remove the tag or quote and the rest of the URL following it.
118
- const res9 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
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 res10 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
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)
@@ -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: `blob`, `data` and `file` schemes must be explicitly allowed
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 = await isURISync(uri);
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 = await parseURLSync(url);
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
- const res = await sanitizeURLSync(url, opt);
2115
- return res;
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,