url-sanitizer 0.7.0 → 0.7.2

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
@@ -45,6 +45,7 @@ Sanitize the given URL.
45
45
  * `opt.deny` **[Array][4]<[string][1]>** Array of denied schemes, e.g. `['web+foo']`.
46
46
  * `opt.only` **[Array][4]<[string][1]>** Array of specific schemes to allow, e.g. `['git', 'https']`.
47
47
  `only` takes precedence over `allow` and `deny`.
48
+ * `opt.truncate` **[boolean][2]** Truncate strings matching tags and quotes.
48
49
 
49
50
  Returns **[Promise][5]<[string][1]?>** Sanitized URL, `null`able.
50
51
 
@@ -82,11 +83,13 @@ const res4 = await sanitizeURL(`data:text/html;base64,${base64data4}`);
82
83
  console.log(decodeURIComponent(res4));
83
84
  // => 'data:text/html,<div></div>'
84
85
 
86
+ // Deny if the scheme matches the `deny` list
85
87
  const res5 = await sanitizeURL('web+foo://example.com', {
86
88
  deny: ['web+foo']
87
89
  });
88
90
  // => null
89
91
 
92
+ // Allow only if the scheme matches the `only` list
90
93
  const res6 = await sanitizeURL('http://example.com', {
91
94
  only: ['data', 'git', 'https']
92
95
  });
@@ -100,7 +103,7 @@ const res7 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"', {
100
103
  console.log(decodeURIComponent(res7));
101
104
  // => 'https://example.com/&quot;onmouseover=&quot;alert(1)&quot;'
102
105
 
103
- // `only` option also allows combinations of the specified schemes
106
+ // `only` also allows combination of the schemes in the list
104
107
  const res8 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)</script>', {
105
108
  only: ['data', 'git', 'https']
106
109
  });
@@ -108,6 +111,17 @@ const res8 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)
108
111
 
109
112
  console.log(decodeURIComponent(res8));
110
113
  // => 'git+https://example.com/foo.git?&lt;script&gt;alert(1)&lt;/script&gt;'
114
+
115
+ // Truncate the tag or quote and the rest of the URL following it.
116
+ const res9 = await sanitizeURL('https://example.com/" onclick="alert(1)"', {
117
+ truncate: true
118
+ });
119
+ // => 'https://example.com/'
120
+
121
+ const res10 = await sanitizeURL('https://example.com/?<script>alert(1)</script>', {
122
+ truncate: true
123
+ });
124
+ // => 'https://example.com/?'
111
125
  ```
112
126
 
113
127
 
@@ -128,7 +142,7 @@ Returns **[Promise][5]<[ParsedURL](#parsedurl)>** Result.
128
142
 
129
143
  ### ParsedURL
130
144
 
131
- Object with extended properties based on [URL](https://developer.mozilla.org/ja/docs/Web/API/URL) API.
145
+ Object with additional properties based on [URL](https://developer.mozilla.org/ja/docs/Web/API/URL) API.
132
146
 
133
147
  Type: [object][3]
134
148
 
@@ -150,17 +164,16 @@ Type: [object][3]
150
164
  * `port` **[string][1]** Port number of the sanitized URL.
151
165
  * `pathname` **[string][1]** Path of the sanitized URL.
152
166
  * `search` **[string][1]** Query string of the sanitized URL.
153
- * `searchParams` **[object][3]** [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) object.
154
167
  * `hash` **[string][1]** Fragment identifier of the sanitized URL.
155
168
 
156
169
  ```javascript
157
170
  const res1 = await parseURL('javascript:alert(1)');
158
171
  /* => {
159
- input: 'javascript:alert(1)',
160
- valid: false
161
- } */
172
+ input: 'javascript:alert(1)',
173
+ valid: false
174
+ } */
162
175
 
163
- const res2 = await parseURL('https://example.com/?foo=bar#baz');
176
+ const res2 = await parseURL('https://www.example.com/?foo=bar#baz');
164
177
  /* => {
165
178
  input: 'https://www.example.com/?foo=bar#baz',
166
179
  valid: true,
@@ -192,7 +205,7 @@ const res3 = await parseURL('data:image/svg+xml;base64,PHN2Zz48ZyBvbmNsaWNrPSJhb
192
205
  ...
193
206
  } */
194
207
 
195
- // base64 encoded png
208
+ // base64 encoded PNG
196
209
  const res4 = await parseURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==');
197
210
  /* => {
198
211
  input: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
@@ -218,7 +231,7 @@ Synchronous version of the [parseURL()](#parseURL).
218
231
 
219
232
  ## isURI(uri)
220
233
 
221
- Determines whether the given URI is valid.
234
+ Verify if the given URI is valid.
222
235
 
223
236
  ### Parameters
224
237
 
@@ -1597,9 +1597,10 @@ var HEX = 16;
1597
1597
  var REG_BASE64 = /^[\da-z+/\-_=]+$/i;
1598
1598
  var REG_DATA_URL = /data:[^,]*,[^"]+/g;
1599
1599
  var REG_DATA_URL_BASE64 = /data:[^,]*;?base64,[\da-z+/\-_=]+/i;
1600
- var REG_MIME_DOM = /^(?:text\/(?:ht|x)ml|application\/(?:xhtml\+)?xml|image\/svg\+xml)/;
1601
1600
  var REG_HTML_SP = /[<>"'\s]/g;
1602
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))+?/;
1603
+ var REG_MIME_DOM = /^(?:text\/(?:ht|x)ml|application\/(?:xhtml\+)?xml|image\/svg\+xml)/;
1603
1604
  var REG_NUM_REF = /&#(x(?:00)?[\dA-F]{2}|0?\d{1,3});?/ig;
1604
1605
  var REG_SCHEME = /^[a-z][\da-z+\-.]*$/;
1605
1606
  var REG_SCHEME_CUSTOM = /^(?:ext|web)\+[a-z]+$/;
@@ -1607,7 +1608,7 @@ var REG_SCRIPT = /(?:java|vb)script/;
1607
1608
  var REG_URL_ENC = /^%[\dA-F]{2}$/i;
1608
1609
  var REG_URL_ENC_AMP = /%26/g;
1609
1610
  var REG_URL_ENC_HTML_SP = /%26(?:(?:l|g|quo)t|%2339);?/g;
1610
- var getUrlEncodedString = (str) => {
1611
+ var getURLEncodedString = (str) => {
1611
1612
  if (!isString(str)) {
1612
1613
  throw new TypeError(`Expected String but got ${getType(str)}.`);
1613
1614
  }
@@ -1617,17 +1618,11 @@ var getUrlEncodedString = (str) => {
1617
1618
  }
1618
1619
  return chars.join("");
1619
1620
  };
1620
- var escapeUrlEncodedHtmlChars = (ch) => {
1621
- if (isString(ch)) {
1622
- if (REG_URL_ENC.test(ch)) {
1623
- ch = ch.toUpperCase();
1624
- } else {
1625
- throw new Error(`Invalid URL encoded character: ${ch}`);
1626
- }
1627
- } else {
1628
- throw new TypeError(`Expected String but got ${getType(ch)}.`);
1621
+ var escapeURLEncodedHTMLChars = (ch) => {
1622
+ if (isString(ch) && REG_URL_ENC.test(ch)) {
1623
+ ch = ch.toUpperCase();
1629
1624
  }
1630
- const [amp, num, lt, gt, quot, apos] = ["&", "#", "<", ">", '"', "'"].map(getUrlEncodedString);
1625
+ const [amp, num, lt, gt, quot, apos] = ["&", "#", "<", ">", '"', "'"].map(getURLEncodedString);
1631
1626
  let escapedChar;
1632
1627
  if (ch === amp) {
1633
1628
  escapedChar = `${amp}amp;`;
@@ -1644,7 +1639,7 @@ var escapeUrlEncodedHtmlChars = (ch) => {
1644
1639
  }
1645
1640
  return escapedChar;
1646
1641
  };
1647
- var unescapeUrlEncodedHtmlChars = (ch) => {
1642
+ var unescapeURLEncodedHTMLChars = (ch) => {
1648
1643
  let unescapedChar;
1649
1644
  if (/%26lt;?/.test(ch)) {
1650
1645
  unescapedChar = "<";
@@ -1670,13 +1665,13 @@ var parseBase64 = (data) => {
1670
1665
  const textCharCodes = new Set(text_chars_default);
1671
1666
  let parsedData;
1672
1667
  if (uint8arr.every((c) => textCharCodes.has(c))) {
1673
- parsedData = bin.replace(/\s/g, getUrlEncodedString);
1668
+ parsedData = bin.replace(/\s/g, getURLEncodedString);
1674
1669
  } else {
1675
1670
  parsedData = data;
1676
1671
  }
1677
1672
  return parsedData;
1678
1673
  };
1679
- var parseUrlEncodedNumCharRef = (str, nest = 0) => {
1674
+ var parseURLEncodedNumCharRef = (str, nest = 0) => {
1680
1675
  if (!isString(str)) {
1681
1676
  throw new TypeError(`Expected String but got ${getType(str)}.`);
1682
1677
  }
@@ -1706,7 +1701,7 @@ var parseUrlEncodedNumCharRef = (str, nest = 0) => {
1706
1701
  if (textCharCodes.has(num)) {
1707
1702
  res = `${preNum}${String.fromCharCode(num)}${postNum}`;
1708
1703
  if (/#x?$/.test(preNum) || /^#(?:x(?:00)?[2-7]|\d)/.test(postNum)) {
1709
- res = parseUrlEncodedNumCharRef(res, ++nest);
1704
+ res = parseURLEncodedNumCharRef(res, ++nest);
1710
1705
  }
1711
1706
  } else if (num < HEX * HEX) {
1712
1707
  res = `${preNum}${postNum}`;
@@ -1716,7 +1711,7 @@ var parseUrlEncodedNumCharRef = (str, nest = 0) => {
1716
1711
  }
1717
1712
  return res;
1718
1713
  };
1719
- var purifyUrlEncodedDom = (dom) => {
1714
+ var purifyURLEncodedDOM = (dom) => {
1720
1715
  if (!isString(dom)) {
1721
1716
  throw new TypeError(`Expected String but got ${getType(dom)}.`);
1722
1717
  }
@@ -1777,12 +1772,12 @@ var URISchemes = class {
1777
1772
  return this.#schemes.delete(scheme);
1778
1773
  }
1779
1774
  /**
1780
- * is URI
1775
+ * verify URI
1781
1776
  *
1782
1777
  * @param {string} uri - URI input
1783
1778
  * @returns {boolean} - result
1784
1779
  */
1785
- isURI(uri) {
1780
+ verify(uri) {
1786
1781
  let res;
1787
1782
  if (isString(uri)) {
1788
1783
  try {
@@ -1819,6 +1814,7 @@ var URLSanitizer = class extends URISchemes {
1819
1814
  * @param {Array.<string>} opt.allow - array of allowed schemes
1820
1815
  * @param {Array.<string>} opt.deny - array of denied schemes
1821
1816
  * @param {Array.<string>} opt.only - array of specific schemes to allow
1817
+ * @param {boolean} opt.truncate - truncate tag and/or quote and the rest
1822
1818
  * @returns {?string} - sanitized URL
1823
1819
  */
1824
1820
  sanitize(url, opt = { allow: [], deny: [], only: [] }) {
@@ -1826,7 +1822,7 @@ var URLSanitizer = class extends URISchemes {
1826
1822
  this.#nest = 0;
1827
1823
  throw new Error("Data URLs nested too deeply.");
1828
1824
  }
1829
- const { allow, deny, only } = opt ?? {};
1825
+ const { allow, deny, only, truncate } = opt ?? {};
1830
1826
  const schemeMap = /* @__PURE__ */ new Map([
1831
1827
  ["data", false],
1832
1828
  ["file", false],
@@ -1896,7 +1892,7 @@ var URLSanitizer = class extends URISchemes {
1896
1892
  }
1897
1893
  }
1898
1894
  let sanitizedUrl;
1899
- if (super.isURI(url)) {
1895
+ if (super.verify(url)) {
1900
1896
  const { hash, href, pathname, protocol, search } = new URL(url);
1901
1897
  const scheme = protocol.replace(/:$/, "");
1902
1898
  const schemeParts = scheme.split("+");
@@ -1924,7 +1920,7 @@ var URLSanitizer = class extends URISchemes {
1924
1920
  parsedData = parseBase64(data);
1925
1921
  } else {
1926
1922
  try {
1927
- const decodedData = parseUrlEncodedNumCharRef(parsedData);
1923
+ const decodedData = parseURLEncodedNumCharRef(parsedData);
1928
1924
  const { protocol: dataScheme } = new URL(decodedData.trim());
1929
1925
  const dataSchemeParts = dataScheme.replace(/:$/, "").split("+");
1930
1926
  if (dataSchemeParts.some((s) => REG_SCRIPT.test(s))) {
@@ -1976,10 +1972,15 @@ var URLSanitizer = class extends URISchemes {
1976
1972
  } else {
1977
1973
  escapeHtml = true;
1978
1974
  }
1975
+ if (!schemeParts.includes("data") && truncate && REG_HTML_SP_URL_ENC_SHORT.test(urlToSanitize)) {
1976
+ const item = REG_HTML_SP_URL_ENC_SHORT.exec(urlToSanitize);
1977
+ const { index } = item;
1978
+ urlToSanitize = urlToSanitize.substring(0, index);
1979
+ }
1979
1980
  if (urlToSanitize) {
1980
- sanitizedUrl = urlToSanitize.replace(REG_HTML_SP, getUrlEncodedString).replace(REG_URL_ENC_AMP, escapeUrlEncodedHtmlChars);
1981
+ sanitizedUrl = urlToSanitize.replace(REG_HTML_SP, getURLEncodedString).replace(REG_URL_ENC_AMP, escapeURLEncodedHTMLChars);
1981
1982
  if (escapeHtml) {
1982
- sanitizedUrl = sanitizedUrl.replace(REG_HTML_SP_URL_ENC, escapeUrlEncodedHtmlChars);
1983
+ sanitizedUrl = sanitizedUrl.replace(REG_HTML_SP_URL_ENC, escapeURLEncodedHTMLChars);
1983
1984
  this.#nest = 0;
1984
1985
  }
1985
1986
  } else {
@@ -1991,7 +1992,7 @@ var URLSanitizer = class extends URISchemes {
1991
1992
  return sanitizedUrl || null;
1992
1993
  }
1993
1994
  /**
1994
- * object with extended props based on URL API
1995
+ * object with additional properties based on URL API
1995
1996
  *
1996
1997
  * @typedef {object} ParsedURL
1997
1998
  * @property {string} input - URL input
@@ -2018,7 +2019,7 @@ var URLSanitizer = class extends URISchemes {
2018
2019
  *
2019
2020
  * @param {string} url - URL input
2020
2021
  * @param {object} opt - options
2021
- * @returns {ParsedURL} - result with extended props based on URL API
2022
+ * @returns {ParsedURL} - result with enhanced props based on URL API
2022
2023
  */
2023
2024
  parse(url, opt) {
2024
2025
  if (!isString(url)) {
@@ -2058,10 +2059,10 @@ var URLSanitizer = class extends URISchemes {
2058
2059
  parsedData.substring(0, index),
2059
2060
  parsedData.substring(index + htmlChar.length)
2060
2061
  ];
2061
- const unescapedHTMLChar = unescapeUrlEncodedHtmlChars(htmlChar);
2062
- parsedData = `${preHtmlChar}${unescapedHTMLChar}${postHtmlChar}`;
2062
+ const unescapedHtmlChar = unescapeURLEncodedHTMLChars(htmlChar);
2063
+ parsedData = `${preHtmlChar}${unescapedHtmlChar}${postHtmlChar}`;
2063
2064
  }
2064
- purifiedDom = purifyUrlEncodedDom(parsedData);
2065
+ purifiedDom = purifyURLEncodedDOM(parsedData);
2065
2066
  dataUrl.set("data", purifiedDom);
2066
2067
  } else {
2067
2068
  dataUrl.set("data", data);
@@ -2100,12 +2101,17 @@ var URLSanitizer = class extends URISchemes {
2100
2101
  }
2101
2102
  };
2102
2103
  var urlSanitizer = new URLSanitizer();
2103
- var isUri = (uri) => urlSanitizer.isURI(uri);
2104
+ var isURISync = (uri) => urlSanitizer.verify(uri);
2104
2105
  var isURI = async (uri) => {
2105
- const res = await isUri(uri);
2106
+ const res = await isURISync(uri);
2106
2107
  return res;
2107
2108
  };
2108
- var sanitizeUrl = (url, opt) => {
2109
+ var parseURLSync = (url) => urlSanitizer.parse(url);
2110
+ var parseURL = async (url) => {
2111
+ const res = await parseURLSync(url);
2112
+ return res;
2113
+ };
2114
+ var sanitizeURLSync = (url, opt) => {
2109
2115
  const parsedUrl = urlSanitizer.parse(url, opt ?? {
2110
2116
  allow: [],
2111
2117
  deny: [],
@@ -2119,22 +2125,17 @@ var sanitizeUrl = (url, opt) => {
2119
2125
  return res ?? null;
2120
2126
  };
2121
2127
  var sanitizeURL = async (url, opt) => {
2122
- const res = await sanitizeUrl(url, opt);
2123
- return res;
2124
- };
2125
- var parseUrl = (url) => urlSanitizer.parse(url);
2126
- var parseURL = async (url) => {
2127
- const res = await parseUrl(url);
2128
+ const res = await sanitizeURLSync(url, opt);
2128
2129
  return res;
2129
2130
  };
2130
2131
  export {
2131
2132
  urlSanitizer as default,
2132
2133
  isURI,
2133
- isUri as isURISync,
2134
+ isURISync,
2134
2135
  parseURL,
2135
- parseUrl as parseURLSync,
2136
+ parseURLSync,
2136
2137
  sanitizeURL,
2137
- sanitizeUrl as sanitizeURLSync
2138
+ sanitizeURLSync
2138
2139
  };
2139
2140
  /*!
2140
2141
  * URL Sanitizer