url-sanitizer 0.7.4 → 0.7.6

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,8 @@ import urlSanitizer, {
36
36
 
37
37
  Sanitize the given URL.
38
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)
39
41
 
40
42
  ### Parameters
41
43
 
@@ -61,7 +63,7 @@ console.log(decodeURIComponent(res1));
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%2Fdiv%3E%3Cp%3E%3C%2Fp%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>'
@@ -71,14 +73,14 @@ const base64data3 = btoa('<div><script>alert(1);</script></div>');
71
73
  const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
72
74
  allow: ['data']
73
75
  })
74
- // => 'data:text/html,%3Cdiv%3E%3C%2Fdiv%3E'
76
+ // => 'data:text/html,%3Cdiv%3E%3C/div%3E'
75
77
 
76
78
  console.log(decodeURIComponent(res3));
77
79
  // => 'data:text/html,<div></div>'
78
80
 
79
81
  const base64data4 = btoa('<div><img src="javascript:alert(1)"></div>');
80
82
  const res4 = await sanitizeURL(`data:text/html;base64,${base64data4}`);
81
- // => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C%2Fdiv%3E'
83
+ // => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C/div%3E'
82
84
 
83
85
  console.log(decodeURIComponent(res4));
84
86
  // => 'data:text/html,<div><img></div>'
@@ -152,7 +154,7 @@ Type: [object][3]
152
154
  * `valid` **[boolean][2]** Is valid URI.
153
155
  * `data` **[object][3]** Parsed result of data URL, `null`able.
154
156
  * `data.mime` **[string][1]** MIME type.
155
- * `data.base64` **[boolean][2]** `true` if base64 encoded.
157
+ * `data.base64` **[boolean][2]** Is base64 encoded.
156
158
  * `data.data` **[string][1]** Data part of the data URL.
157
159
  * `href` **[string][1]** Sanitized URL input.
158
160
  * `origin` **[string][1]** Scheme, domain and port of the sanitized URL.
@@ -196,12 +198,12 @@ const res3 = await parseURL('data:image/svg+xml;base64,PHN2Zz48ZyBvbmNsaWNrPSJhb
196
198
  data: {
197
199
  mime: 'image/svg+xml',
198
200
  base64: false,
199
- data: '%3Csvg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E'
201
+ data: '%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E'
200
202
  },
201
- href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E',
203
+ href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
202
204
  origin: 'null',
203
205
  protocol: 'data:',
204
- pathname: 'image/svg+xml,%3Csvg%3E%3Cg%3E%3C%2Fg%3E%3C%2Fsvg%3E',
206
+ pathname: 'image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
205
207
  ...
206
208
  } */
207
209
 
@@ -231,7 +233,7 @@ Synchronous version of the [parseURL()](#parseURL).
231
233
 
232
234
  ## isURI(uri)
233
235
 
234
- Verify if the given URI is valid.
236
+ Verify if the given URI is valid and registered.
235
237
 
236
238
  ### Parameters
237
239
 
@@ -239,6 +241,7 @@ Verify if the given URI is valid.
239
241
 
240
242
  Returns **[Promise][5]<[boolean][2]>** Result.
241
243
  * Always `true` for `web+*` and `ext+*` schemes, except `web+javascript`, `web+vbscript`, `ext+javascript`, `ext+vbscript`.
244
+ * `false` for `javascript` and `vbscript` schemes.
242
245
 
243
246
  ```javascript
244
247
  const res1 = await isURI('https://example.com/foo');
@@ -1595,16 +1595,25 @@ var uri_schemes_default = [
1595
1595
  // bundle/mjs/uri-util.js
1596
1596
  var HEX = 16;
1597
1597
  var REG_BASE64 = /^[\da-z+/\-_=]+$/i;
1598
- var REG_DATA_URL = /data:[^,]*,[^"]+/g;
1598
+ var REG_DATA_URL = /data:[^,]*,/;
1599
1599
  var REG_DATA_URL_BASE64 = /data:[^,]*;?base64,[\da-z+/\-_=]+/i;
1600
+ var REG_DATA_URL_G = /data:[^,]*,[^"]+/g;
1601
+ var REG_END_COLON = /:$/;
1602
+ var REG_END_NUM = /(?:#|%23)$/;
1603
+ var REG_END_QUEST = /(?<!(?:#|%23).*)(?:\?|%3F)$/;
1600
1604
  var REG_HTML_SP = /[<>"'\s]/g;
1601
- var REG_HTML_SP_URL_ENC = /%(?:2(?:2|7)|3(?:C|E))/g;
1602
- var REG_HTML_SP_URL_ENC_SHORT = /%(?:2(?:2|7)|3(?:C|E))+?/;
1605
+ var REG_HTML_URL_ENC = /%(?:2(?:2|7)|3(?:C|E))/g;
1606
+ var REG_HTML_URL_ENC_SHORT = /%(?:2(?:2|7)|3(?:C|E))+?/;
1603
1607
  var REG_MIME_DOM = /^(?:text\/(?:ht|x)ml|application\/(?:xhtml\+)?xml|image\/svg\+xml)/;
1608
+ var REG_NUM_DECI = /^\d+/;
1609
+ var REG_NUM_HEAD = /#x?$/;
1610
+ var REG_NUM_HEAD_ASCII = /^#(?:x(?:00)?[2-7]|\d)/;
1611
+ var REG_NUM_HEX = /^x[\dA-F]+/i;
1604
1612
  var REG_NUM_REF = /&#(x(?:00)?[\dA-F]{2}|0?\d{1,3});?/ig;
1605
1613
  var REG_SCHEME = /^[a-z][\da-z+\-.]*$/;
1606
1614
  var REG_SCHEME_CUSTOM = /^(?:ext|web)\+[a-z]+$/;
1607
1615
  var REG_SCRIPT = /(?:java|vb)script/;
1616
+ var REG_SCRIPT_BLOB = /(?:java|vb)script|blob/;
1608
1617
  var REG_URL_ENC = /^%[\dA-F]{2}$/i;
1609
1618
  var REG_URL_ENC_AMP = /%26/g;
1610
1619
  var getURLEncodedString = (str) => {
@@ -1671,9 +1680,9 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
1671
1680
  for (const item of items) {
1672
1681
  const [numCharRef, value] = item;
1673
1682
  let num;
1674
- if (/^x[\dA-F]+/i.test(value)) {
1683
+ if (REG_NUM_HEX.test(value)) {
1675
1684
  num = parseInt(`0${value}`, HEX);
1676
- } else if (/^[\d]+/.test(value)) {
1685
+ } else if (REG_NUM_DECI.test(value)) {
1677
1686
  num = parseInt(value);
1678
1687
  }
1679
1688
  if (Number.isInteger(num)) {
@@ -1684,7 +1693,7 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
1684
1693
  ];
1685
1694
  if (textCharCodes.has(num)) {
1686
1695
  res = `${preNum}${String.fromCharCode(num)}${postNum}`;
1687
- if (/#x?$/.test(preNum) || /^#(?:x(?:00)?[2-7]|\d)/.test(postNum)) {
1696
+ if (REG_NUM_HEAD.test(preNum) || REG_NUM_HEAD_ASCII.test(postNum)) {
1688
1697
  res = parseURLEncodedNumCharRef(res, ++nest);
1689
1698
  }
1690
1699
  } else if (num < HEX * HEX) {
@@ -1752,7 +1761,7 @@ var URISchemes = class {
1752
1761
  /**
1753
1762
  * verify URI
1754
1763
  *
1755
- * @param {string} uri - URI input
1764
+ * @param {string} uri - URI
1756
1765
  * @returns {boolean} - result
1757
1766
  */
1758
1767
  verify(uri) {
@@ -1760,7 +1769,7 @@ var URISchemes = class {
1760
1769
  if (isString(uri)) {
1761
1770
  try {
1762
1771
  const { protocol } = new URL(uri);
1763
- const scheme = protocol.replace(/:$/, "");
1772
+ const scheme = protocol.replace(REG_END_COLON, "");
1764
1773
  const schemeParts = scheme.split("+");
1765
1774
  res = !REG_SCRIPT.test(scheme) && REG_SCHEME_CUSTOM.test(scheme) || schemeParts.every((s) => this.#schemes.has(s));
1766
1775
  } catch (e) {
@@ -1783,18 +1792,18 @@ var URLSanitizer = class extends URISchemes {
1783
1792
  this.#recurse = /* @__PURE__ */ new Set();
1784
1793
  }
1785
1794
  /**
1786
- * purify URL encoded DOM
1795
+ * replace matched data URLs
1787
1796
  *
1788
- * @param {string} dom - DOM input
1789
- * @returns {string} - purified DOM
1797
+ * @param {string} data - data URL
1798
+ * @returns {string} - replaced data URL
1790
1799
  */
1791
- purify(dom) {
1792
- if (!isString(dom)) {
1793
- throw new TypeError(`Expected String but got ${getType(dom)}.`);
1800
+ replace(data) {
1801
+ if (!isString(data)) {
1802
+ throw new TypeError(`Expected String but got ${getType(data)}.`);
1794
1803
  }
1795
- let purifiedDom = import_dompurify.default.sanitize(decodeURIComponent(dom));
1796
- if (purifiedDom) {
1797
- const matchedDataUrls = purifiedDom.matchAll(REG_DATA_URL);
1804
+ let replacedData = data;
1805
+ if (REG_DATA_URL.test(replacedData)) {
1806
+ const matchedDataUrls = replacedData.matchAll(REG_DATA_URL_G);
1798
1807
  const items = [...matchedDataUrls].reverse();
1799
1808
  for (const item of items) {
1800
1809
  let [dataUrl] = item;
@@ -1808,24 +1817,41 @@ var URLSanitizer = class extends URISchemes {
1808
1817
  });
1809
1818
  const { index } = item;
1810
1819
  const [preDataUrl, postDataUrl] = [
1811
- purifiedDom.substring(0, index),
1812
- purifiedDom.substring(index + dataUrl.length)
1820
+ replacedData.substring(0, index),
1821
+ replacedData.substring(index + dataUrl.length)
1813
1822
  ];
1814
1823
  if (parsedDataUrl) {
1815
- purifiedDom = `${preDataUrl}${parsedDataUrl}${postDataUrl}`;
1824
+ replacedData = `${preDataUrl}${parsedDataUrl}${postDataUrl}`;
1816
1825
  } else {
1817
- purifiedDom = `${preDataUrl}${postDataUrl}`;
1826
+ replacedData = `${preDataUrl}${postDataUrl}`;
1818
1827
  }
1819
1828
  }
1820
1829
  }
1821
- return encodeURIComponent(purifiedDom);
1830
+ return replacedData;
1831
+ }
1832
+ /**
1833
+ * purify URL encoded DOM
1834
+ *
1835
+ * @param {string} dom - DOM string
1836
+ * @returns {string} - purified DOM string
1837
+ */
1838
+ purify(dom) {
1839
+ if (!isString(dom)) {
1840
+ throw new TypeError(`Expected String but got ${getType(dom)}.`);
1841
+ }
1842
+ let purifiedDom = import_dompurify.default.sanitize(decodeURIComponent(dom));
1843
+ if (purifiedDom && REG_DATA_URL.test(purifiedDom)) {
1844
+ purifiedDom = this.replace(purifiedDom);
1845
+ }
1846
+ purifiedDom = purifiedDom.replace(REG_END_NUM, "").replace(REG_END_QUEST, "");
1847
+ return encodeURI(purifiedDom);
1822
1848
  }
1823
1849
  /**
1824
1850
  * sanitize URL
1825
- * NOTE: `data` and/or `file` schemes must be explicitly allowed
1826
- * `javascript` and/or `vbscript` schemes can not be allowed
1851
+ * NOTE: `blob`, `data` and `file` schemes must be explicitly allowed
1852
+ * `javascript` and `vbscript` schemes can not be allowed
1827
1853
  *
1828
- * @param {string} url - URL input
1854
+ * @param {string} url - URL
1829
1855
  * @param {object} opt - options
1830
1856
  * @param {Array.<string>} opt.allow - array of allowed schemes
1831
1857
  * @param {Array.<string>} opt.deny - array of denied schemes
@@ -1840,6 +1866,7 @@ var URLSanitizer = class extends URISchemes {
1840
1866
  }
1841
1867
  const { allow, deny, only, remove } = opt ?? {};
1842
1868
  const schemeMap = /* @__PURE__ */ new Map([
1869
+ ["blob", false],
1843
1870
  ["data", false],
1844
1871
  ["file", false],
1845
1872
  ["javascrpt", false],
@@ -1855,7 +1882,7 @@ var URLSanitizer = class extends URISchemes {
1855
1882
  for (let item of items) {
1856
1883
  if (isString(item)) {
1857
1884
  item = item.trim();
1858
- if (!REG_SCRIPT.test(item)) {
1885
+ if (!REG_SCRIPT_BLOB.test(item)) {
1859
1886
  if (super.has(item)) {
1860
1887
  schemeMap.set(item, true);
1861
1888
  } else {
@@ -1879,7 +1906,7 @@ var URLSanitizer = class extends URISchemes {
1879
1906
  for (let item of items) {
1880
1907
  if (isString(item)) {
1881
1908
  item = item.trim();
1882
- if (!REG_SCRIPT.test(item)) {
1909
+ if (!REG_SCRIPT_BLOB.test(item)) {
1883
1910
  if (super.has(item)) {
1884
1911
  schemeMap.set(item, true);
1885
1912
  } else {
@@ -1910,7 +1937,7 @@ var URLSanitizer = class extends URISchemes {
1910
1937
  let sanitizedUrl;
1911
1938
  if (super.verify(url)) {
1912
1939
  const { hash, href, pathname, protocol, search } = new URL(url);
1913
- const scheme = protocol.replace(/:$/, "");
1940
+ const scheme = protocol.replace(REG_END_COLON, "");
1914
1941
  const schemeParts = scheme.split("+");
1915
1942
  let bool;
1916
1943
  if (restrictScheme) {
@@ -1924,9 +1951,10 @@ var URLSanitizer = class extends URISchemes {
1924
1951
  }
1925
1952
  }
1926
1953
  if (bool) {
1927
- let escapeHtml;
1954
+ const isDataUrl = schemeParts.includes("data");
1955
+ let finalize;
1928
1956
  let urlToSanitize = href;
1929
- if (schemeParts.includes("data")) {
1957
+ if (isDataUrl) {
1930
1958
  const [head, ...body] = pathname.split(",");
1931
1959
  const data = `${body.join(",")}${search}${hash}`;
1932
1960
  const mediaType = head.split(";");
@@ -1934,58 +1962,32 @@ var URLSanitizer = class extends URISchemes {
1934
1962
  let parsedData = data;
1935
1963
  if (isBase64) {
1936
1964
  parsedData = parseBase64(data);
1937
- } else {
1938
- try {
1939
- const decodedData = parseURLEncodedNumCharRef(parsedData);
1940
- const { protocol: dataScheme } = new URL(decodedData.trim());
1941
- const dataSchemeParts = dataScheme.replace(/:$/, "").split("+");
1942
- if (dataSchemeParts.some((s) => REG_SCRIPT.test(s))) {
1943
- urlToSanitize = "";
1944
- }
1945
- } catch (e) {
1965
+ }
1966
+ try {
1967
+ const decodedData = parseURLEncodedNumCharRef(parsedData);
1968
+ const { protocol: dataScheme } = new URL(decodedData.trim());
1969
+ const dataSchemeParts = dataScheme.replace(REG_END_COLON, "").split("+");
1970
+ if (dataSchemeParts.some((s) => REG_SCRIPT_BLOB.test(s))) {
1971
+ urlToSanitize = "";
1946
1972
  }
1973
+ } catch (e) {
1947
1974
  }
1948
- const containsDataUrl = /data:[^,]*,/.test(parsedData);
1975
+ const containsDataUrl = REG_DATA_URL.test(parsedData);
1949
1976
  if (parsedData !== data || containsDataUrl) {
1950
1977
  if (containsDataUrl) {
1951
- const matchedDataUrls = parsedData.matchAll(REG_DATA_URL);
1952
- const items = [...matchedDataUrls].reverse();
1953
- for (const item of items) {
1954
- let [dataUrl] = item;
1955
- if (REG_DATA_URL_BASE64.test(dataUrl)) {
1956
- [dataUrl] = REG_DATA_URL_BASE64.exec(dataUrl);
1957
- }
1958
- this.#nest++;
1959
- this.#recurse.add(dataUrl);
1960
- const parsedDataUrl = this.sanitize(dataUrl, {
1961
- allow: ["data"]
1962
- });
1963
- if (parsedDataUrl) {
1964
- const { index } = item;
1965
- const [preDataUrl, postDataUrl] = [
1966
- parsedData.substring(0, index),
1967
- parsedData.substring(index + dataUrl.length)
1968
- ];
1969
- parsedData = `${preDataUrl}${parsedDataUrl}${postDataUrl}`;
1970
- }
1971
- }
1972
- if (this.#recurse.has(url)) {
1973
- this.#recurse.delete(url);
1974
- } else {
1975
- escapeHtml = true;
1976
- }
1978
+ parsedData = this.replace(parsedData);
1977
1979
  } else if (this.#recurse.has(url)) {
1978
1980
  this.#recurse.delete(url);
1979
1981
  } else {
1980
- escapeHtml = true;
1982
+ finalize = true;
1981
1983
  }
1982
1984
  } else if (this.#recurse.has(url)) {
1983
1985
  this.#recurse.delete(url);
1984
1986
  } else {
1985
- escapeHtml = true;
1987
+ finalize = true;
1986
1988
  }
1987
- if (REG_MIME_DOM.test(head)) {
1988
- parsedData = this.purify(parsedData).replace(/%23$/, "").replace(/%3F$/, "");
1989
+ if (!head || REG_MIME_DOM.test(head)) {
1990
+ parsedData = this.purify(parsedData);
1989
1991
  }
1990
1992
  if (urlToSanitize && parsedData) {
1991
1993
  if (isBase64 && parsedData !== data) {
@@ -1996,18 +1998,18 @@ var URLSanitizer = class extends URISchemes {
1996
1998
  urlToSanitize = "";
1997
1999
  }
1998
2000
  } else {
1999
- escapeHtml = true;
2001
+ finalize = true;
2000
2002
  }
2001
- if (!schemeParts.includes("data") && remove && REG_HTML_SP_URL_ENC_SHORT.test(urlToSanitize)) {
2002
- const item = REG_HTML_SP_URL_ENC_SHORT.exec(urlToSanitize);
2003
+ if (!isDataUrl && remove && REG_HTML_URL_ENC_SHORT.test(urlToSanitize)) {
2004
+ const item = REG_HTML_URL_ENC_SHORT.exec(urlToSanitize);
2003
2005
  const { index } = item;
2004
2006
  urlToSanitize = urlToSanitize.substring(0, index);
2005
2007
  }
2006
2008
  if (urlToSanitize) {
2007
2009
  sanitizedUrl = urlToSanitize.replace(REG_HTML_SP, getURLEncodedString).replace(REG_URL_ENC_AMP, escapeURLEncodedHTMLChars);
2008
- if (escapeHtml) {
2009
- if (!schemeParts.includes("data")) {
2010
- sanitizedUrl = sanitizedUrl.replace(REG_HTML_SP_URL_ENC, escapeURLEncodedHTMLChars);
2010
+ if (finalize) {
2011
+ if (!isDataUrl) {
2012
+ sanitizedUrl = sanitizedUrl.replace(REG_HTML_URL_ENC, escapeURLEncodedHTMLChars);
2011
2013
  }
2012
2014
  this.#nest = 0;
2013
2015
  }
@@ -2027,7 +2029,7 @@ var URLSanitizer = class extends URISchemes {
2027
2029
  * @property {boolean} valid - is valid URI
2028
2030
  * @property {object} data - parsed result of data URL, `null`able
2029
2031
  * @property {string} data.mime - MIME type
2030
- * @property {boolean} data.base64 - `true` if base64 encoded
2032
+ * @property {boolean} data.base64 - is base64 encoded
2031
2033
  * @property {string} data.data - data part of the data URL
2032
2034
  * @property {string} href - sanitized URL input
2033
2035
  * @property {string} origin - scheme, domain and port of the sanitized URL
@@ -2039,22 +2041,21 @@ var URLSanitizer = class extends URISchemes {
2039
2041
  * @property {string} port - port number of the sanitized URL
2040
2042
  * @property {string} pathname - path of the sanitized URL
2041
2043
  * @property {string} search - query string of the sanitized URL
2042
- * @property {object} searchParams - URLSearchParams object
2043
2044
  * @property {string} hash - fragment identifier of the sanitized URL
2044
2045
  */
2045
2046
  /**
2046
2047
  * parse sanitized URL
2047
2048
  *
2048
- * @param {string} url - URL input
2049
+ * @param {string} url - URL
2049
2050
  * @param {object} opt - options
2050
- * @returns {ParsedURL} - result with enhanced props based on URL API
2051
+ * @returns {ParsedURL} - result with additional props based on URL API
2051
2052
  */
2052
2053
  parse(url, opt) {
2053
2054
  if (!isString(url)) {
2054
2055
  throw new TypeError(`Expected String but got ${getType(url)}.`);
2055
2056
  }
2056
2057
  const sanitizedUrl = this.sanitize(url, opt ?? {
2057
- allow: ["data", "file"]
2058
+ allow: ["blob", "data", "file"]
2058
2059
  });
2059
2060
  const parsedUrl = /* @__PURE__ */ new Map([
2060
2061
  ["input", url]
@@ -2062,9 +2063,10 @@ var URLSanitizer = class extends URISchemes {
2062
2063
  if (sanitizedUrl) {
2063
2064
  const urlObj = new URL(sanitizedUrl);
2064
2065
  const { pathname, protocol } = urlObj;
2065
- const schemeParts = protocol.replace(/:$/, "").split("+");
2066
+ const schemeParts = protocol.replace(REG_END_COLON, "").split("+");
2067
+ const isDataUrl = schemeParts.includes("data");
2066
2068
  parsedUrl.set("valid", true);
2067
- if (schemeParts.includes("data")) {
2069
+ if (isDataUrl) {
2068
2070
  const dataUrl = /* @__PURE__ */ new Map();
2069
2071
  const [head, ...body] = pathname.split(",");
2070
2072
  const data = `${body.join(",")}`;
@@ -2103,19 +2105,11 @@ var parseURL = async (url) => {
2103
2105
  const res = await parseURLSync(url);
2104
2106
  return res;
2105
2107
  };
2106
- var sanitizeURLSync = (url, opt) => {
2107
- const parsedUrl = urlSanitizer.parse(url, opt ?? {
2108
- allow: [],
2109
- deny: [],
2110
- only: []
2111
- });
2112
- let res;
2113
- if (parsedUrl) {
2114
- const { href } = parsedUrl;
2115
- res = href;
2116
- }
2117
- return res ?? null;
2118
- };
2108
+ var sanitizeURLSync = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
2109
+ allow: [],
2110
+ deny: [],
2111
+ only: []
2112
+ });
2119
2113
  var sanitizeURL = async (url, opt) => {
2120
2114
  const res = await sanitizeURLSync(url, opt);
2121
2115
  return res;