url-sanitizer 0.7.6 → 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 CHANGED
@@ -35,9 +35,8 @@ import urlSanitizer, {
35
35
  ## sanitizeURL(url, opt)
36
36
 
37
37
  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)
38
+ * `blob`, `data` and `file` schemes must be explicitly allowed.
39
+ * Given a `blob` URL, returns a sanitized `data` URL.
41
40
 
42
41
  ### Parameters
43
42
 
@@ -59,6 +58,7 @@ const res1 = await sanitizeURL('http://example.com/"onmouseover="alert(1)"?<scri
59
58
  console.log(decodeURIComponent(res1));
60
59
  // => 'http://example.com/&quot;onmouseover=&quot;alert(1)&quot;?&lt;script&gt;alert(&#39;XSS&#39;);&lt;/script&gt;'
61
60
 
61
+
62
62
  // Parse and purify data URL
63
63
  const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></div><p onclick="alert(2)"></p>', {
64
64
  allow: ['data']
@@ -68,7 +68,8 @@ const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></
68
68
  console.log(decodeURIComponent(res2));
69
69
  // => 'data:text/html,<div></div><p></p>'
70
70
 
71
- // Can also parse and purify base64 encoded data
71
+
72
+ // Can parse and purify base64 encoded data
72
73
  const base64data3 = btoa('<div><script>alert(1);</script></div>');
73
74
  const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
74
75
  allow: ['data']
@@ -78,58 +79,79 @@ const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
78
79
  console.log(decodeURIComponent(res3));
79
80
  // => 'data:text/html,<div></div>'
80
81
 
81
- const base64data4 = btoa('<div><img src="javascript:alert(1)"></div>');
82
- const res4 = await sanitizeURL(`data:text/html;base64,${base64data4}`);
82
+ const base64data3_2 = btoa('<div><img src="javascript:alert(1)"></div>');
83
+ const res3_2 = await sanitizeURL(`data:text/html;base64,${base64data3_2}`);
83
84
  // => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C/div%3E'
84
85
 
85
- console.log(decodeURIComponent(res4));
86
+ console.log(decodeURIComponent(res3_2));
86
87
  // => 'data:text/html,<div><img></div>'
87
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
+
88
104
  // Deny if the scheme matches the `deny` list
89
105
  const res5 = await sanitizeURL('web+foo://example.com', {
90
106
  deny: ['web+foo']
91
107
  });
92
108
  // => null
93
109
 
110
+
94
111
  // Allow only if the scheme matches the `only` list
95
112
  const res6 = await sanitizeURL('http://example.com', {
96
113
  only: ['data', 'git', 'https']
97
114
  });
98
115
  // => null
99
116
 
100
- const res7 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
117
+ const res6_2 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
101
118
  only: ['data', 'git', 'https']
102
119
  });
103
120
  // => 'https://example.com/%26quot;onmouseover=%26quot;alert(1)%26quot;'
104
121
 
105
- console.log(decodeURIComponent(res7));
122
+ console.log(decodeURIComponent(res6_2));
106
123
  // => 'https://example.com/&quot;onmouseover=&quot;alert(1)&quot;'
107
124
 
125
+
108
126
  // `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>', {
127
+ const res7 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)</script>', {
110
128
  only: ['data', 'git', 'https']
111
129
  });
112
130
  // => 'git+https://example.com/foo.git?%26lt;script%26gt;alert(1)%26lt;/script%26gt;'
113
131
 
114
- console.log(decodeURIComponent(res8));
132
+ console.log(decodeURIComponent(res7));
115
133
  // => 'git+https://example.com/foo.git?&lt;script&gt;alert(1)&lt;/script&gt;'
116
134
 
135
+
117
136
  // Remove the tag or quote and the rest of the URL following it.
118
- const res9 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
137
+ const res8 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
119
138
  remove: true
120
139
  });
121
140
  // => 'https://example.com/'
122
141
 
123
- const res10 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
142
+ const res8_2 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
124
143
  remove: true
125
144
  });
126
145
  // => 'https://example.com/?'
127
146
  ```
128
147
 
129
148
 
130
- ## sanitizeURLSync
149
+ ## sanitizeURLSync(url, opt)
131
150
 
132
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`.
133
155
 
134
156
 
135
157
  ## 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,39 @@ 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
+ 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;
2116
2158
  };
2117
2159
  export {
2118
2160
  urlSanitizer as default,