url-sanitizer 0.7.3 → 0.7.5
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 +10 -9
- package/dist/url-sanitizer.js +130 -145
- package/dist/url-sanitizer.js.map +2 -2
- package/dist/url-sanitizer.min.js +3 -3
- package/dist/url-sanitizer.min.js.map +3 -3
- package/package.json +2 -2
- package/src/mjs/uri-util.js +97 -124
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ import urlSanitizer, {
|
|
|
35
35
|
## sanitizeURL(url, opt)
|
|
36
36
|
|
|
37
37
|
Sanitize the given URL.
|
|
38
|
-
* `data` and `file` schemes must be explicitly allowed.
|
|
38
|
+
* `blob`, `data` and `file` schemes must be explicitly allowed.
|
|
39
39
|
|
|
40
40
|
### Parameters
|
|
41
41
|
|
|
@@ -61,7 +61,7 @@ console.log(decodeURIComponent(res1));
|
|
|
61
61
|
const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></div><p onclick="alert(2)"></p>', {
|
|
62
62
|
allow: ['data']
|
|
63
63
|
})
|
|
64
|
-
// -> 'data:text/html,%3Cdiv%3E%3C%
|
|
64
|
+
// -> 'data:text/html,%3Cdiv%3E%3C/div%3E%3Cp%3E%3C/p%3E'
|
|
65
65
|
|
|
66
66
|
console.log(decodeURIComponent(res2));
|
|
67
67
|
// => 'data:text/html,<div></div><p></p>'
|
|
@@ -71,14 +71,14 @@ const base64data3 = btoa('<div><script>alert(1);</script></div>');
|
|
|
71
71
|
const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
|
|
72
72
|
allow: ['data']
|
|
73
73
|
})
|
|
74
|
-
// => 'data:text/html,%3Cdiv%3E%3C%
|
|
74
|
+
// => 'data:text/html,%3Cdiv%3E%3C/div%3E'
|
|
75
75
|
|
|
76
76
|
console.log(decodeURIComponent(res3));
|
|
77
77
|
// => 'data:text/html,<div></div>'
|
|
78
78
|
|
|
79
79
|
const base64data4 = btoa('<div><img src="javascript:alert(1)"></div>');
|
|
80
80
|
const res4 = await sanitizeURL(`data:text/html;base64,${base64data4}`);
|
|
81
|
-
// => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C%
|
|
81
|
+
// => 'data:text/html,%3Cdiv%3E%3Cimg%3E%3C/div%3E'
|
|
82
82
|
|
|
83
83
|
console.log(decodeURIComponent(res4));
|
|
84
84
|
// => 'data:text/html,<div><img></div>'
|
|
@@ -152,7 +152,7 @@ Type: [object][3]
|
|
|
152
152
|
* `valid` **[boolean][2]** Is valid URI.
|
|
153
153
|
* `data` **[object][3]** Parsed result of data URL, `null`able.
|
|
154
154
|
* `data.mime` **[string][1]** MIME type.
|
|
155
|
-
* `data.base64` **[boolean][2]**
|
|
155
|
+
* `data.base64` **[boolean][2]** Is base64 encoded.
|
|
156
156
|
* `data.data` **[string][1]** Data part of the data URL.
|
|
157
157
|
* `href` **[string][1]** Sanitized URL input.
|
|
158
158
|
* `origin` **[string][1]** Scheme, domain and port of the sanitized URL.
|
|
@@ -196,12 +196,12 @@ const res3 = await parseURL('data:image/svg+xml;base64,PHN2Zz48ZyBvbmNsaWNrPSJhb
|
|
|
196
196
|
data: {
|
|
197
197
|
mime: 'image/svg+xml',
|
|
198
198
|
base64: false,
|
|
199
|
-
data: '%3Csvg%3E%3Cg%3E%3C%
|
|
199
|
+
data: '%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E'
|
|
200
200
|
},
|
|
201
|
-
href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C%
|
|
201
|
+
href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
202
202
|
origin: 'null',
|
|
203
203
|
protocol: 'data:',
|
|
204
|
-
pathname: 'image/svg+xml,%3Csvg%3E%3Cg%3E%3C%
|
|
204
|
+
pathname: 'image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
205
205
|
...
|
|
206
206
|
} */
|
|
207
207
|
|
|
@@ -231,7 +231,7 @@ Synchronous version of the [parseURL()](#parseURL).
|
|
|
231
231
|
|
|
232
232
|
## isURI(uri)
|
|
233
233
|
|
|
234
|
-
Verify if the given URI is valid.
|
|
234
|
+
Verify if the given URI is valid and registered.
|
|
235
235
|
|
|
236
236
|
### Parameters
|
|
237
237
|
|
|
@@ -239,6 +239,7 @@ Verify if the given URI is valid.
|
|
|
239
239
|
|
|
240
240
|
Returns **[Promise][5]<[boolean][2]>** Result.
|
|
241
241
|
* Always `true` for `web+*` and `ext+*` schemes, except `web+javascript`, `web+vbscript`, `ext+javascript`, `ext+vbscript`.
|
|
242
|
+
* `false` for `javascript` and `vbscript` schemes.
|
|
242
243
|
|
|
243
244
|
```javascript
|
|
244
245
|
const res1 = await isURI('https://example.com/foo');
|
package/dist/url-sanitizer.js
CHANGED
|
@@ -1595,19 +1595,26 @@ 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:[^,]
|
|
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
|
|
1602
|
-
var
|
|
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/;
|
|
1608
1616
|
var REG_URL_ENC = /^%[\dA-F]{2}$/i;
|
|
1609
1617
|
var REG_URL_ENC_AMP = /%26/g;
|
|
1610
|
-
var REG_URL_ENC_HTML_SP = /%26(?:(?:l|g|quo)t|%2339);?/g;
|
|
1611
1618
|
var getURLEncodedString = (str) => {
|
|
1612
1619
|
if (!isString(str)) {
|
|
1613
1620
|
throw new TypeError(`Expected String but got ${getType(str)}.`);
|
|
@@ -1639,21 +1646,6 @@ var escapeURLEncodedHTMLChars = (ch) => {
|
|
|
1639
1646
|
}
|
|
1640
1647
|
return escapedChar;
|
|
1641
1648
|
};
|
|
1642
|
-
var unescapeURLEncodedHTMLChars = (ch) => {
|
|
1643
|
-
let unescapedChar;
|
|
1644
|
-
if (/%26lt;?/.test(ch)) {
|
|
1645
|
-
unescapedChar = "<";
|
|
1646
|
-
} else if (/%26gt;?/.test(ch)) {
|
|
1647
|
-
unescapedChar = ">";
|
|
1648
|
-
} else if (/%26quot;?/.test(ch)) {
|
|
1649
|
-
unescapedChar = '"';
|
|
1650
|
-
} else if (/%26%2339;?/.test(ch)) {
|
|
1651
|
-
unescapedChar = "'";
|
|
1652
|
-
} else {
|
|
1653
|
-
unescapedChar = ch;
|
|
1654
|
-
}
|
|
1655
|
-
return unescapedChar;
|
|
1656
|
-
};
|
|
1657
1649
|
var parseBase64 = (data) => {
|
|
1658
1650
|
if (!isString(data)) {
|
|
1659
1651
|
throw new TypeError(`Expected String but got ${getType(data)}.`);
|
|
@@ -1687,9 +1679,9 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
1687
1679
|
for (const item of items) {
|
|
1688
1680
|
const [numCharRef, value] = item;
|
|
1689
1681
|
let num;
|
|
1690
|
-
if (
|
|
1682
|
+
if (REG_NUM_HEX.test(value)) {
|
|
1691
1683
|
num = parseInt(`0${value}`, HEX);
|
|
1692
|
-
} else if (
|
|
1684
|
+
} else if (REG_NUM_DECI.test(value)) {
|
|
1693
1685
|
num = parseInt(value);
|
|
1694
1686
|
}
|
|
1695
1687
|
if (Number.isInteger(num)) {
|
|
@@ -1700,7 +1692,7 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
1700
1692
|
];
|
|
1701
1693
|
if (textCharCodes.has(num)) {
|
|
1702
1694
|
res = `${preNum}${String.fromCharCode(num)}${postNum}`;
|
|
1703
|
-
if (
|
|
1695
|
+
if (REG_NUM_HEAD.test(preNum) || REG_NUM_HEAD_ASCII.test(postNum)) {
|
|
1704
1696
|
res = parseURLEncodedNumCharRef(res, ++nest);
|
|
1705
1697
|
}
|
|
1706
1698
|
} else if (num < HEX * HEX) {
|
|
@@ -1711,12 +1703,6 @@ var parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
1711
1703
|
}
|
|
1712
1704
|
return res;
|
|
1713
1705
|
};
|
|
1714
|
-
var purifyURLEncodedDOM = (dom) => {
|
|
1715
|
-
if (!isString(dom)) {
|
|
1716
|
-
throw new TypeError(`Expected String but got ${getType(dom)}.`);
|
|
1717
|
-
}
|
|
1718
|
-
return encodeURIComponent(import_dompurify.default.sanitize(decodeURIComponent(dom)));
|
|
1719
|
-
};
|
|
1720
1706
|
var URISchemes = class {
|
|
1721
1707
|
/* private fields */
|
|
1722
1708
|
#schemes;
|
|
@@ -1774,7 +1760,7 @@ var URISchemes = class {
|
|
|
1774
1760
|
/**
|
|
1775
1761
|
* verify URI
|
|
1776
1762
|
*
|
|
1777
|
-
* @param {string} uri - URI
|
|
1763
|
+
* @param {string} uri - URI
|
|
1778
1764
|
* @returns {boolean} - result
|
|
1779
1765
|
*/
|
|
1780
1766
|
verify(uri) {
|
|
@@ -1782,7 +1768,7 @@ var URISchemes = class {
|
|
|
1782
1768
|
if (isString(uri)) {
|
|
1783
1769
|
try {
|
|
1784
1770
|
const { protocol } = new URL(uri);
|
|
1785
|
-
const scheme = protocol.replace(
|
|
1771
|
+
const scheme = protocol.replace(REG_END_COLON, "");
|
|
1786
1772
|
const schemeParts = scheme.split("+");
|
|
1787
1773
|
res = !REG_SCRIPT.test(scheme) && REG_SCHEME_CUSTOM.test(scheme) || schemeParts.every((s) => this.#schemes.has(s));
|
|
1788
1774
|
} catch (e) {
|
|
@@ -1804,17 +1790,72 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1804
1790
|
this.#nest = 0;
|
|
1805
1791
|
this.#recurse = /* @__PURE__ */ new Set();
|
|
1806
1792
|
}
|
|
1793
|
+
/**
|
|
1794
|
+
* replace matched data URLs
|
|
1795
|
+
*
|
|
1796
|
+
* @param {string} data - data URL
|
|
1797
|
+
* @returns {string} - replaced data URL
|
|
1798
|
+
*/
|
|
1799
|
+
replace(data) {
|
|
1800
|
+
if (!isString(data)) {
|
|
1801
|
+
throw new TypeError(`Expected String but got ${getType(data)}.`);
|
|
1802
|
+
}
|
|
1803
|
+
let replacedData = data;
|
|
1804
|
+
if (REG_DATA_URL.test(replacedData)) {
|
|
1805
|
+
const matchedDataUrls = replacedData.matchAll(REG_DATA_URL_G);
|
|
1806
|
+
const items = [...matchedDataUrls].reverse();
|
|
1807
|
+
for (const item of items) {
|
|
1808
|
+
let [dataUrl] = item;
|
|
1809
|
+
if (REG_DATA_URL_BASE64.test(dataUrl)) {
|
|
1810
|
+
[dataUrl] = REG_DATA_URL_BASE64.exec(dataUrl);
|
|
1811
|
+
}
|
|
1812
|
+
this.#nest++;
|
|
1813
|
+
this.#recurse.add(dataUrl);
|
|
1814
|
+
const parsedDataUrl = this.sanitize(dataUrl, {
|
|
1815
|
+
allow: ["data"]
|
|
1816
|
+
});
|
|
1817
|
+
const { index } = item;
|
|
1818
|
+
const [preDataUrl, postDataUrl] = [
|
|
1819
|
+
replacedData.substring(0, index),
|
|
1820
|
+
replacedData.substring(index + dataUrl.length)
|
|
1821
|
+
];
|
|
1822
|
+
if (parsedDataUrl) {
|
|
1823
|
+
replacedData = `${preDataUrl}${parsedDataUrl}${postDataUrl}`;
|
|
1824
|
+
} else {
|
|
1825
|
+
replacedData = `${preDataUrl}${postDataUrl}`;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
return replacedData;
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* purify URL encoded DOM
|
|
1833
|
+
*
|
|
1834
|
+
* @param {string} dom - DOM string
|
|
1835
|
+
* @returns {string} - purified DOM string
|
|
1836
|
+
*/
|
|
1837
|
+
purify(dom) {
|
|
1838
|
+
if (!isString(dom)) {
|
|
1839
|
+
throw new TypeError(`Expected String but got ${getType(dom)}.`);
|
|
1840
|
+
}
|
|
1841
|
+
let purifiedDom = import_dompurify.default.sanitize(decodeURIComponent(dom));
|
|
1842
|
+
if (purifiedDom && REG_DATA_URL.test(purifiedDom)) {
|
|
1843
|
+
purifiedDom = this.replace(purifiedDom);
|
|
1844
|
+
}
|
|
1845
|
+
purifiedDom = purifiedDom.replace(REG_END_NUM, "").replace(REG_END_QUEST, "");
|
|
1846
|
+
return encodeURI(purifiedDom);
|
|
1847
|
+
}
|
|
1807
1848
|
/**
|
|
1808
1849
|
* sanitize URL
|
|
1809
|
-
* NOTE: `data` and
|
|
1810
|
-
* `javascript` and
|
|
1850
|
+
* NOTE: `blob`, `data` and `file` schemes must be explicitly allowed
|
|
1851
|
+
* `javascript` and `vbscript` schemes can not be allowed
|
|
1811
1852
|
*
|
|
1812
|
-
* @param {string} url - URL
|
|
1853
|
+
* @param {string} url - URL
|
|
1813
1854
|
* @param {object} opt - options
|
|
1814
1855
|
* @param {Array.<string>} opt.allow - array of allowed schemes
|
|
1815
1856
|
* @param {Array.<string>} opt.deny - array of denied schemes
|
|
1816
1857
|
* @param {Array.<string>} opt.only - array of specific schemes to allow
|
|
1817
|
-
* @param {boolean} opt.
|
|
1858
|
+
* @param {boolean} opt.remove - remove tag and/or quote and the rest
|
|
1818
1859
|
* @returns {?string} - sanitized URL
|
|
1819
1860
|
*/
|
|
1820
1861
|
sanitize(url, opt = { allow: [], deny: [], only: [] }) {
|
|
@@ -1822,8 +1863,9 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1822
1863
|
this.#nest = 0;
|
|
1823
1864
|
throw new Error("Data URLs nested too deeply.");
|
|
1824
1865
|
}
|
|
1825
|
-
const { allow, deny, only,
|
|
1866
|
+
const { allow, deny, only, remove } = opt ?? {};
|
|
1826
1867
|
const schemeMap = /* @__PURE__ */ new Map([
|
|
1868
|
+
["blob", false],
|
|
1827
1869
|
["data", false],
|
|
1828
1870
|
["file", false],
|
|
1829
1871
|
["javascrpt", false],
|
|
@@ -1894,7 +1936,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1894
1936
|
let sanitizedUrl;
|
|
1895
1937
|
if (super.verify(url)) {
|
|
1896
1938
|
const { hash, href, pathname, protocol, search } = new URL(url);
|
|
1897
|
-
const scheme = protocol.replace(
|
|
1939
|
+
const scheme = protocol.replace(REG_END_COLON, "");
|
|
1898
1940
|
const schemeParts = scheme.split("+");
|
|
1899
1941
|
let bool;
|
|
1900
1942
|
if (restrictScheme) {
|
|
@@ -1908,79 +1950,66 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1908
1950
|
}
|
|
1909
1951
|
}
|
|
1910
1952
|
if (bool) {
|
|
1911
|
-
|
|
1953
|
+
const isDataUrl = schemeParts.includes("data");
|
|
1954
|
+
let finalize;
|
|
1912
1955
|
let urlToSanitize = href;
|
|
1913
|
-
if (
|
|
1956
|
+
if (isDataUrl) {
|
|
1914
1957
|
const [head, ...body] = pathname.split(",");
|
|
1915
1958
|
const data = `${body.join(",")}${search}${hash}`;
|
|
1916
1959
|
const mediaType = head.split(";");
|
|
1960
|
+
const isBase64 = mediaType[mediaType.length - 1] === "base64";
|
|
1917
1961
|
let parsedData = data;
|
|
1918
|
-
if (
|
|
1919
|
-
mediaType.pop();
|
|
1962
|
+
if (isBase64) {
|
|
1920
1963
|
parsedData = parseBase64(data);
|
|
1921
|
-
}
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
}
|
|
1929
|
-
} catch (e) {
|
|
1964
|
+
}
|
|
1965
|
+
try {
|
|
1966
|
+
const decodedData = parseURLEncodedNumCharRef(parsedData);
|
|
1967
|
+
const { protocol: dataScheme } = new URL(decodedData.trim());
|
|
1968
|
+
const dataSchemeParts = dataScheme.replace(REG_END_COLON, "").split("+");
|
|
1969
|
+
if (dataSchemeParts.some((s) => REG_SCRIPT.test(s))) {
|
|
1970
|
+
urlToSanitize = "";
|
|
1930
1971
|
}
|
|
1972
|
+
} catch (e) {
|
|
1931
1973
|
}
|
|
1932
|
-
const containsDataUrl =
|
|
1974
|
+
const containsDataUrl = REG_DATA_URL.test(parsedData);
|
|
1933
1975
|
if (parsedData !== data || containsDataUrl) {
|
|
1934
1976
|
if (containsDataUrl) {
|
|
1935
|
-
|
|
1936
|
-
const items = [...matchedDataUrls].reverse();
|
|
1937
|
-
for (const item of items) {
|
|
1938
|
-
let [dataUrl] = item;
|
|
1939
|
-
if (REG_DATA_URL_BASE64.test(dataUrl)) {
|
|
1940
|
-
[dataUrl] = REG_DATA_URL_BASE64.exec(dataUrl);
|
|
1941
|
-
}
|
|
1942
|
-
this.#nest++;
|
|
1943
|
-
this.#recurse.add(dataUrl);
|
|
1944
|
-
const parsedDataUrl = this.sanitize(dataUrl, {
|
|
1945
|
-
allow: ["data"]
|
|
1946
|
-
});
|
|
1947
|
-
if (parsedDataUrl) {
|
|
1948
|
-
const { index } = item;
|
|
1949
|
-
const [preDataUrl, postDataUrl] = [
|
|
1950
|
-
parsedData.substring(0, index),
|
|
1951
|
-
parsedData.substring(index + dataUrl.length)
|
|
1952
|
-
];
|
|
1953
|
-
parsedData = `${preDataUrl}${parsedDataUrl}${postDataUrl}`;
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
if (this.#recurse.has(url)) {
|
|
1957
|
-
this.#recurse.delete(url);
|
|
1958
|
-
} else {
|
|
1959
|
-
escapeHtml = true;
|
|
1960
|
-
}
|
|
1977
|
+
parsedData = this.replace(parsedData);
|
|
1961
1978
|
} else if (this.#recurse.has(url)) {
|
|
1962
1979
|
this.#recurse.delete(url);
|
|
1963
1980
|
} else {
|
|
1964
|
-
|
|
1981
|
+
finalize = true;
|
|
1965
1982
|
}
|
|
1966
|
-
urlToSanitize = `${scheme}:${mediaType.join(";")},${parsedData}`;
|
|
1967
1983
|
} else if (this.#recurse.has(url)) {
|
|
1968
1984
|
this.#recurse.delete(url);
|
|
1969
1985
|
} else {
|
|
1970
|
-
|
|
1986
|
+
finalize = true;
|
|
1987
|
+
}
|
|
1988
|
+
if (!head || REG_MIME_DOM.test(head)) {
|
|
1989
|
+
parsedData = this.purify(parsedData);
|
|
1990
|
+
}
|
|
1991
|
+
if (urlToSanitize && parsedData) {
|
|
1992
|
+
if (isBase64 && parsedData !== data) {
|
|
1993
|
+
mediaType.pop();
|
|
1994
|
+
}
|
|
1995
|
+
urlToSanitize = `${scheme}:${mediaType.join(";")},${parsedData}`;
|
|
1996
|
+
} else {
|
|
1997
|
+
urlToSanitize = "";
|
|
1971
1998
|
}
|
|
1972
1999
|
} else {
|
|
1973
|
-
|
|
2000
|
+
finalize = true;
|
|
1974
2001
|
}
|
|
1975
|
-
if (!
|
|
1976
|
-
const item =
|
|
2002
|
+
if (!isDataUrl && remove && REG_HTML_URL_ENC_SHORT.test(urlToSanitize)) {
|
|
2003
|
+
const item = REG_HTML_URL_ENC_SHORT.exec(urlToSanitize);
|
|
1977
2004
|
const { index } = item;
|
|
1978
2005
|
urlToSanitize = urlToSanitize.substring(0, index);
|
|
1979
2006
|
}
|
|
1980
2007
|
if (urlToSanitize) {
|
|
1981
2008
|
sanitizedUrl = urlToSanitize.replace(REG_HTML_SP, getURLEncodedString).replace(REG_URL_ENC_AMP, escapeURLEncodedHTMLChars);
|
|
1982
|
-
if (
|
|
1983
|
-
|
|
2009
|
+
if (finalize) {
|
|
2010
|
+
if (!isDataUrl) {
|
|
2011
|
+
sanitizedUrl = sanitizedUrl.replace(REG_HTML_URL_ENC, escapeURLEncodedHTMLChars);
|
|
2012
|
+
}
|
|
1984
2013
|
this.#nest = 0;
|
|
1985
2014
|
}
|
|
1986
2015
|
} else {
|
|
@@ -1999,7 +2028,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
1999
2028
|
* @property {boolean} valid - is valid URI
|
|
2000
2029
|
* @property {object} data - parsed result of data URL, `null`able
|
|
2001
2030
|
* @property {string} data.mime - MIME type
|
|
2002
|
-
* @property {boolean} data.base64 -
|
|
2031
|
+
* @property {boolean} data.base64 - is base64 encoded
|
|
2003
2032
|
* @property {string} data.data - data part of the data URL
|
|
2004
2033
|
* @property {string} href - sanitized URL input
|
|
2005
2034
|
* @property {string} origin - scheme, domain and port of the sanitized URL
|
|
@@ -2011,13 +2040,12 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2011
2040
|
* @property {string} port - port number of the sanitized URL
|
|
2012
2041
|
* @property {string} pathname - path of the sanitized URL
|
|
2013
2042
|
* @property {string} search - query string of the sanitized URL
|
|
2014
|
-
* @property {object} searchParams - URLSearchParams object
|
|
2015
2043
|
* @property {string} hash - fragment identifier of the sanitized URL
|
|
2016
2044
|
*/
|
|
2017
2045
|
/**
|
|
2018
2046
|
* parse sanitized URL
|
|
2019
2047
|
*
|
|
2020
|
-
* @param {string} url - URL
|
|
2048
|
+
* @param {string} url - URL
|
|
2021
2049
|
* @param {object} opt - options
|
|
2022
2050
|
* @returns {ParsedURL} - result with enhanced props based on URL API
|
|
2023
2051
|
*/
|
|
@@ -2026,7 +2054,7 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2026
2054
|
throw new TypeError(`Expected String but got ${getType(url)}.`);
|
|
2027
2055
|
}
|
|
2028
2056
|
const sanitizedUrl = this.sanitize(url, opt ?? {
|
|
2029
|
-
allow: ["data", "file"]
|
|
2057
|
+
allow: ["blob", "data", "file"]
|
|
2030
2058
|
});
|
|
2031
2059
|
const parsedUrl = /* @__PURE__ */ new Map([
|
|
2032
2060
|
["input", url]
|
|
@@ -2034,9 +2062,10 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2034
2062
|
if (sanitizedUrl) {
|
|
2035
2063
|
const urlObj = new URL(sanitizedUrl);
|
|
2036
2064
|
const { pathname, protocol } = urlObj;
|
|
2037
|
-
const schemeParts = protocol.replace(
|
|
2065
|
+
const schemeParts = protocol.replace(REG_END_COLON, "").split("+");
|
|
2066
|
+
const isDataUrl = schemeParts.includes("data");
|
|
2038
2067
|
parsedUrl.set("valid", true);
|
|
2039
|
-
if (
|
|
2068
|
+
if (isDataUrl) {
|
|
2040
2069
|
const dataUrl = /* @__PURE__ */ new Map();
|
|
2041
2070
|
const [head, ...body] = pathname.split(",");
|
|
2042
2071
|
const data = `${body.join(",")}`;
|
|
@@ -2047,51 +2076,15 @@ var URLSanitizer = class extends URISchemes {
|
|
|
2047
2076
|
}
|
|
2048
2077
|
dataUrl.set("mime", mediaType.join(";"));
|
|
2049
2078
|
dataUrl.set("base64", isBase64);
|
|
2050
|
-
|
|
2051
|
-
if (!isBase64 && REG_MIME_DOM.test(head)) {
|
|
2052
|
-
let parsedData = data;
|
|
2053
|
-
const matchedHtmlChars = parsedData.matchAll(REG_URL_ENC_HTML_SP);
|
|
2054
|
-
const items = [...matchedHtmlChars].reverse();
|
|
2055
|
-
for (const item of items) {
|
|
2056
|
-
const [htmlChar] = item;
|
|
2057
|
-
const { index } = item;
|
|
2058
|
-
const [preHtmlChar, postHtmlChar] = [
|
|
2059
|
-
parsedData.substring(0, index),
|
|
2060
|
-
parsedData.substring(index + htmlChar.length)
|
|
2061
|
-
];
|
|
2062
|
-
const unescapedHtmlChar = unescapeURLEncodedHTMLChars(htmlChar);
|
|
2063
|
-
parsedData = `${preHtmlChar}${unescapedHtmlChar}${postHtmlChar}`;
|
|
2064
|
-
}
|
|
2065
|
-
purifiedDom = purifyURLEncodedDOM(parsedData);
|
|
2066
|
-
dataUrl.set("data", purifiedDom);
|
|
2067
|
-
} else {
|
|
2068
|
-
dataUrl.set("data", data);
|
|
2069
|
-
}
|
|
2079
|
+
dataUrl.set("data", data);
|
|
2070
2080
|
parsedUrl.set("data", Object.fromEntries(dataUrl));
|
|
2071
|
-
for (const key in urlObj) {
|
|
2072
|
-
let value = urlObj[key];
|
|
2073
|
-
if (isString(value)) {
|
|
2074
|
-
if (isString(purifiedDom)) {
|
|
2075
|
-
switch (key) {
|
|
2076
|
-
case "href":
|
|
2077
|
-
value = `${protocol}${mediaType.join(";")},${purifiedDom}`;
|
|
2078
|
-
break;
|
|
2079
|
-
case "pathname":
|
|
2080
|
-
value = `${mediaType.join(";")},${purifiedDom}`;
|
|
2081
|
-
break;
|
|
2082
|
-
default:
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
parsedUrl.set(key, value);
|
|
2086
|
-
}
|
|
2087
|
-
}
|
|
2088
2081
|
} else {
|
|
2089
2082
|
parsedUrl.set("data", null);
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2083
|
+
}
|
|
2084
|
+
for (const key in urlObj) {
|
|
2085
|
+
const value = urlObj[key];
|
|
2086
|
+
if (isString(value)) {
|
|
2087
|
+
parsedUrl.set(key, value);
|
|
2095
2088
|
}
|
|
2096
2089
|
}
|
|
2097
2090
|
} else {
|
|
@@ -2111,19 +2104,11 @@ var parseURL = async (url) => {
|
|
|
2111
2104
|
const res = await parseURLSync(url);
|
|
2112
2105
|
return res;
|
|
2113
2106
|
};
|
|
2114
|
-
var sanitizeURLSync = (url, opt) => {
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
});
|
|
2120
|
-
let res;
|
|
2121
|
-
if (parsedUrl) {
|
|
2122
|
-
const { href } = parsedUrl;
|
|
2123
|
-
res = href;
|
|
2124
|
-
}
|
|
2125
|
-
return res ?? null;
|
|
2126
|
-
};
|
|
2107
|
+
var sanitizeURLSync = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
2108
|
+
allow: [],
|
|
2109
|
+
deny: [],
|
|
2110
|
+
only: []
|
|
2111
|
+
});
|
|
2127
2112
|
var sanitizeURL = async (url, opt) => {
|
|
2128
2113
|
const res = await sanitizeURLSync(url, opt);
|
|
2129
2114
|
return res;
|