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 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/&quot;onmouseover=&quot;alert(1)&quot;?&lt;script&gt;alert(&#39;XSS&#39;);&lt;/script&gt;'
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
- // -> 'data:text/html,%3Cdiv%3E%3C/div%3E%3Cp%3E%3C/p%3E'
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
- // Can also parse and purify base64 encoded data
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 base64data4 = btoa('<div><img src="javascript:alert(1)"></div>');
80
- 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}`);
81
84
  // => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C/div%3E'
82
85
 
83
- console.log(decodeURIComponent(res4));
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 res7 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
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(res7));
122
+ console.log(decodeURIComponent(res6_2));
104
123
  // => 'https://example.com/&quot;onmouseover=&quot;alert(1)&quot;'
105
124
 
125
+
106
126
  // `only` also allows combination of the schemes in the list
107
- 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>', {
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(res8));
132
+ console.log(decodeURIComponent(res7));
113
133
  // => 'git+https://example.com/foo.git?&lt;script&gt;alert(1)&lt;/script&gt;'
114
134
 
135
+
115
136
  // Remove the tag or quote and the rest of the URL following it.
116
- const res9 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
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 res10 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
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)
@@ -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: `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
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 (!REG_SCRIPT.test(item)) {
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 (!REG_SCRIPT.test(item)) {
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) => REG_SCRIPT.test(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 enhanced props based on URL API
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 = await isURISync(uri);
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 = await parseURLSync(url);
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
- const res = await sanitizeURLSync(url, opt);
2114
- 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;
2115
2158
  };
2116
2159
  export {
2117
2160
  urlSanitizer as default,