url-sanitizer 0.2.2 → 0.3.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
@@ -17,7 +17,7 @@ For browsers and web sites, standalone ESM builds are available in `dist/` direc
17
17
  * node_modules/url-sanitizer/dist/url-sanitizer.js
18
18
  * node_modules/url-sanitizer/dist/url-sanitizer.min.js
19
19
 
20
- Or, download the source code from [Releases](https://github.com/asamuzaK/urlSanitizer/releases).
20
+ Or, download them from [Releases](https://github.com/asamuzaK/urlSanitizer/releases).
21
21
 
22
22
  ## Usage
23
23
 
@@ -30,13 +30,15 @@ import urlSanitizer, {
30
30
  ## sanitizeURL(url, opt)
31
31
 
32
32
  Sanitize the given URL.
33
- * `data` and/or `file` schemes must be explicitly allowed.
34
- * `javascript` and/or `vbscript` schemes can not be allowed.
33
+ * `data` and `file` schemes must be explicitly allowed.
34
+ * `javascript` and `vbscript` schemes can not be allowed.
35
35
 
36
36
  ### Parameters
37
37
 
38
38
  * `url` **[string][1]** URL input
39
- * `opt` **[object][4]** options
39
+ * `opt` **[object][3]** options
40
+ * `opt.allow` **[Array][4]<[string][1]>** array of allowed schemes
41
+ * `opt.deny` **[Array][4]<[string][1]>** array of denied schemes
40
42
 
41
43
  Returns **[Promise][5]<[string][1]?>** sanitized URL, `null`able
42
44
 
@@ -56,21 +58,6 @@ const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
56
58
  allow: ['data']
57
59
  }).then(res => decodeURIComponent(res));
58
60
  // -> 'data:text/html,&lt;script&gt;alert(1);&lt;/script&gt;'
59
-
60
- // Also an option if you don't want to escape tags and quotes in data URL
61
- // But use it with care
62
- const res4 = await sanitizeURL('data:image/svg+xml,%3Csvg%3E%3C/svg%3E', {
63
- allow: ['data'],
64
- escapeTags: false
65
- }).then(res => decodeURIComponent(res));
66
- // -> 'data:image/svg+xml,<svg></svg>'
67
-
68
- const res5 = await sanitizeURL('data:text/html,%3Cscript%3Ealert(1);%3C/script%3E', {
69
- allow: ['data'],
70
- escapeTags: false
71
- }).then(res => decodeURIComponent(res));
72
- // WATCH OUT!!!
73
- // -> 'data:text/html,<script>alert(1);</script>'
74
61
  ```
75
62
 
76
63
  ## sanitizeURLSync
@@ -85,8 +72,8 @@ Determines whether the given URI is valid.
85
72
 
86
73
  * `uri` **[string][1]** URI input
87
74
 
88
- Returns **[Promise][5]<[boolean][3]>** result
89
- * Always `true` for `web+*` and/or `ext+*` schemes
75
+ Returns **[Promise][5]<[boolean][2]>** result
76
+ * Always `true` for `web+*` and `ext+*` schemes
90
77
 
91
78
  ```javascript
92
79
  const res1 = await isURI('https://example.com/foo');
@@ -114,7 +101,7 @@ Get an array of URI schemes registered at [iana.org](https://www.iana.org/assign
114
101
  * Historical schemes omitted.
115
102
  * `moz-extension` scheme added.
116
103
 
117
- Returns **[Array][2]<[string][1]>** array of registered URI schemes
104
+ Returns **[Array][4]<[string][1]>** array of registered URI schemes
118
105
 
119
106
  ```javascript
120
107
  const schemes = urlSanitizer.get();
@@ -129,7 +116,7 @@ Check if the given scheme is registered.
129
116
 
130
117
  * `scheme` **[string][1]** scheme
131
118
 
132
- Returns **[boolean][3]** result
119
+ Returns **[boolean][2]** result
133
120
 
134
121
  ```javascript
135
122
  const res1 = urlSanitizer.has('https');
@@ -142,17 +129,23 @@ const res2 = urlSanitizer.has('foo');
142
129
  ### urlSanitizer.add(scheme)
143
130
 
144
131
  Add a scheme to the list of URI schemes.
145
- * `javascript` and/or `vbscript` schemes can not be registered. It throws.
132
+ * `javascript` and `vbscript` schemes can not be registered. It throws.
146
133
 
147
134
  #### Parameters
148
135
 
149
136
  * `scheme` **[string][1]** scheme
150
137
 
151
- Returns **[Array][2]<[string][1]>** array of registered URI schemes
138
+ Returns **[Array][4]<[string][1]>** array of registered URI schemes
152
139
 
153
140
  ```javascript
141
+ console.log(isURISync('foo'));
142
+ // -> false;
143
+
154
144
  const res = urlSanitizer.add('foo');
155
145
  // -> ['aaa', 'aaas', 'about', 'acap', 'acct', 'acd', ... 'foo', ...];
146
+
147
+ console.log(isURISync('foo'));
148
+ // -> true;
156
149
  ```
157
150
 
158
151
  ### urlSanitizer.remove(scheme)
@@ -163,23 +156,29 @@ Remove a scheme from the list of URI schemes.
163
156
 
164
157
  * `scheme` **[string][1]** scheme
165
158
 
166
- Returns **[boolean][3]** result
159
+ Returns **[boolean][2]** result
167
160
  * `true` if the scheme is successfully removed, `false` otherwise.
168
161
 
169
162
  ```javascript
163
+ console.log(isURISync('aaa'));
164
+ // -> true;
165
+
170
166
  const res1 = urlSanitizer.remove('aaa');
171
167
  // -> true
172
168
 
169
+ console.log(isURISync('aaa'));
170
+ // -> false;
171
+
173
172
  const res2 = urlSanitizer.remove('foo');
174
173
  // -> false
175
174
  ```
176
175
 
177
176
  [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
178
177
 
179
- [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
178
+ [2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
180
179
 
181
- [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
180
+ [3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
182
181
 
183
- [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
182
+ [4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
184
183
 
185
184
  [5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
@@ -4,6 +4,241 @@ var TYPE_TO = -1;
4
4
  var getType = (o) => Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);
5
5
  var isString = (o) => typeof o === "string" || o instanceof String;
6
6
 
7
+ // src/lib/file/text-chars.json
8
+ var text_chars_default = [
9
+ 7,
10
+ 8,
11
+ 9,
12
+ 10,
13
+ 11,
14
+ 12,
15
+ 13,
16
+ 27,
17
+ 32,
18
+ 33,
19
+ 34,
20
+ 35,
21
+ 36,
22
+ 37,
23
+ 38,
24
+ 39,
25
+ 40,
26
+ 41,
27
+ 42,
28
+ 43,
29
+ 44,
30
+ 45,
31
+ 46,
32
+ 47,
33
+ 48,
34
+ 49,
35
+ 50,
36
+ 51,
37
+ 52,
38
+ 53,
39
+ 54,
40
+ 55,
41
+ 56,
42
+ 57,
43
+ 58,
44
+ 59,
45
+ 60,
46
+ 61,
47
+ 62,
48
+ 63,
49
+ 64,
50
+ 65,
51
+ 66,
52
+ 67,
53
+ 68,
54
+ 69,
55
+ 70,
56
+ 71,
57
+ 72,
58
+ 73,
59
+ 74,
60
+ 75,
61
+ 76,
62
+ 77,
63
+ 78,
64
+ 79,
65
+ 80,
66
+ 81,
67
+ 82,
68
+ 83,
69
+ 84,
70
+ 85,
71
+ 86,
72
+ 87,
73
+ 88,
74
+ 89,
75
+ 90,
76
+ 91,
77
+ 92,
78
+ 93,
79
+ 94,
80
+ 95,
81
+ 96,
82
+ 97,
83
+ 98,
84
+ 99,
85
+ 100,
86
+ 101,
87
+ 102,
88
+ 103,
89
+ 104,
90
+ 105,
91
+ 106,
92
+ 107,
93
+ 108,
94
+ 109,
95
+ 110,
96
+ 111,
97
+ 112,
98
+ 113,
99
+ 114,
100
+ 115,
101
+ 116,
102
+ 117,
103
+ 118,
104
+ 119,
105
+ 120,
106
+ 121,
107
+ 122,
108
+ 123,
109
+ 124,
110
+ 125,
111
+ 126,
112
+ 128,
113
+ 129,
114
+ 130,
115
+ 131,
116
+ 132,
117
+ 133,
118
+ 134,
119
+ 135,
120
+ 136,
121
+ 137,
122
+ 138,
123
+ 139,
124
+ 140,
125
+ 141,
126
+ 142,
127
+ 143,
128
+ 144,
129
+ 145,
130
+ 146,
131
+ 147,
132
+ 148,
133
+ 149,
134
+ 150,
135
+ 151,
136
+ 152,
137
+ 153,
138
+ 154,
139
+ 155,
140
+ 156,
141
+ 157,
142
+ 158,
143
+ 159,
144
+ 160,
145
+ 161,
146
+ 162,
147
+ 163,
148
+ 164,
149
+ 165,
150
+ 166,
151
+ 167,
152
+ 168,
153
+ 169,
154
+ 170,
155
+ 171,
156
+ 172,
157
+ 173,
158
+ 174,
159
+ 175,
160
+ 176,
161
+ 177,
162
+ 178,
163
+ 179,
164
+ 180,
165
+ 181,
166
+ 182,
167
+ 183,
168
+ 184,
169
+ 185,
170
+ 186,
171
+ 187,
172
+ 188,
173
+ 189,
174
+ 190,
175
+ 191,
176
+ 192,
177
+ 193,
178
+ 194,
179
+ 195,
180
+ 196,
181
+ 197,
182
+ 198,
183
+ 199,
184
+ 200,
185
+ 201,
186
+ 202,
187
+ 203,
188
+ 204,
189
+ 205,
190
+ 206,
191
+ 207,
192
+ 208,
193
+ 209,
194
+ 210,
195
+ 211,
196
+ 212,
197
+ 213,
198
+ 214,
199
+ 215,
200
+ 216,
201
+ 217,
202
+ 218,
203
+ 219,
204
+ 220,
205
+ 221,
206
+ 222,
207
+ 223,
208
+ 224,
209
+ 225,
210
+ 226,
211
+ 227,
212
+ 228,
213
+ 229,
214
+ 230,
215
+ 231,
216
+ 232,
217
+ 233,
218
+ 234,
219
+ 235,
220
+ 236,
221
+ 237,
222
+ 238,
223
+ 239,
224
+ 240,
225
+ 241,
226
+ 242,
227
+ 243,
228
+ 244,
229
+ 245,
230
+ 246,
231
+ 247,
232
+ 248,
233
+ 249,
234
+ 250,
235
+ 251,
236
+ 252,
237
+ 253,
238
+ 254,
239
+ 255
240
+ ];
241
+
7
242
  // src/lib/iana/uri-schemes.json
8
243
  var uri_schemes_default = [
9
244
  "aaa",
@@ -399,13 +634,7 @@ var parseBase64 = (data) => {
399
634
  }
400
635
  const bin = atob(data);
401
636
  const uint8arr = Uint8Array.from([...bin].map((c) => c.charCodeAt(0)));
402
- const textChars = /* @__PURE__ */ new Set([7, 8, 9, 10, 11, 12, 13, 27]);
403
- for (let i = 32; i < 127; i++) {
404
- textChars.add(i);
405
- }
406
- for (let i = 128; i <= 255; i++) {
407
- textChars.add(i);
408
- }
637
+ const textChars = new Set(text_chars_default);
409
638
  let parsedData;
410
639
  if (uint8arr.every((c) => textChars.has(c))) {
411
640
  parsedData = bin.replace(/\s/g, getUrlEncodedString);
@@ -490,6 +719,15 @@ var URISchemes = class {
490
719
  }
491
720
  };
492
721
  var URLSanitizer = class extends URISchemes {
722
+ /* private fields */
723
+ #recurse;
724
+ /**
725
+ * construct
726
+ */
727
+ constructor() {
728
+ super();
729
+ this.#recurse = /* @__PURE__ */ new Map();
730
+ }
493
731
  /**
494
732
  * sanitize URL
495
733
  * NOTE: `data` and/or `file` schemes must be explicitly allowed
@@ -499,13 +737,12 @@ var URLSanitizer = class extends URISchemes {
499
737
  * @param {object} opt - options
500
738
  * @param {Array.<string>} opt.allow - array of allowed schemes
501
739
  * @param {Array.<string>} opt.deny - array of denied schemes
502
- * @param {boolean} opt.escapeTags - escape tags and quotes in data URL
503
740
  * @returns {?string} - sanitized URL
504
741
  */
505
- sanitize(url, opt = { allow: [], deny: [], escapeTags: true }) {
742
+ sanitize(url, opt = { allow: [], deny: [] }) {
506
743
  let sanitizedUrl;
507
744
  if (super.isURI(url)) {
508
- const { allow, deny, escapeTags } = opt ?? {};
745
+ const { allow, deny } = opt ?? {};
509
746
  const { href, pathname, protocol } = new URL(url);
510
747
  const scheme = protocol.replace(/:$/, "");
511
748
  const schemeParts = scheme.split("+");
@@ -555,66 +792,57 @@ var URLSanitizer = class extends URISchemes {
555
792
  if (isBase64) {
556
793
  let parsedData = parseBase64(data);
557
794
  if (parsedData !== data) {
558
- const regDataUrl = /data:[^,]*;?base64,[\dA-Za-z+/\-_=]+/g;
559
- if (regDataUrl.test(parsedData)) {
560
- const dataUrlArr = [];
561
- let arr = regDataUrl.exec(parsedData);
562
- do {
563
- if (arr) {
564
- dataUrlArr.push(arr);
565
- }
566
- } while (arr = regDataUrl.exec(parsedData));
567
- if (dataUrlArr.length) {
568
- for (const i of dataUrlArr) {
569
- const [dataUrl] = i;
795
+ if (/data:[^,]*,/.test(parsedData)) {
796
+ const regDataUrl = /data:[^,]*,[^"]+/g;
797
+ const regBase64DataUrl = /data:[^,]*;?base64,[\dA-Za-z+/\-_=]+/;
798
+ const matchedDataUrls = parsedData.matchAll(regDataUrl);
799
+ const items = [...matchedDataUrls].reverse();
800
+ if (items.length) {
801
+ for (const item of items) {
802
+ const { index } = item;
803
+ let [dataUrl] = item;
804
+ if (regBase64DataUrl.test(dataUrl)) {
805
+ [dataUrl] = regBase64DataUrl.exec(dataUrl);
806
+ }
807
+ const [beforeDataUrl, afterDataUrl] = [
808
+ parsedData.substring(0, index),
809
+ parsedData.substring(index + dataUrl.length)
810
+ ];
811
+ this.#recurse.set(dataUrl, dataUrl);
570
812
  const parsedDataUrl = this.sanitize(dataUrl, {
571
813
  allow: ["data"]
572
814
  });
573
815
  if (parsedDataUrl) {
574
- parsedData = parsedData.replace(dataUrl, parsedDataUrl);
816
+ parsedData = [
817
+ beforeDataUrl,
818
+ parsedDataUrl,
819
+ afterDataUrl
820
+ ].join("");
575
821
  }
576
- type = 0;
577
822
  }
578
823
  }
579
- } else if (/data:[^,]*,/.test(parsedData) && !(escapeTags ?? true)) {
580
- const dataArr = parsedData.split(/data:[^,]*,/);
581
- const l = dataArr.length;
582
- let i = 1;
583
- while (i < l) {
584
- const dataItem = dataArr[i].replace(regEncodedChars, escapeUrlEncodedHtmlChars);
585
- parsedData = parsedData.replace(dataArr[i], dataItem);
586
- i++;
587
- }
588
- type = 0;
589
824
  }
590
- urlToSanitize = `${scheme}:${mediaType.join(";")},${parsedData}`;
591
- if (escapeTags ?? true) {
825
+ if (this.#recurse.has(url)) {
826
+ this.#recurse.delete(url);
827
+ } else {
592
828
  type = 1;
593
- } else if (!Number.isInteger(type)) {
594
- type = 2;
595
829
  }
596
- } else if (escapeTags ?? true) {
597
- type = 1;
830
+ urlToSanitize = `${scheme}:${mediaType.join(";")},${parsedData}`;
598
831
  } else {
599
- type = 2;
832
+ type = 1;
600
833
  }
601
- } else if (escapeTags ?? true) {
602
- type = 1;
834
+ } else if (this.#recurse.has(url)) {
835
+ this.#recurse.delete(url);
603
836
  } else {
604
- type = 2;
837
+ type = 1;
605
838
  }
606
839
  } else {
607
840
  type = 1;
608
841
  }
609
- switch (type) {
610
- case 1:
611
- sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString).replace(regAmp, escapeUrlEncodedHtmlChars).replace(regEncodedChars, escapeUrlEncodedHtmlChars);
612
- break;
613
- case 2:
614
- sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString).replace(regAmp, escapeUrlEncodedHtmlChars);
615
- break;
616
- default:
617
- sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString);
842
+ if (type === 1) {
843
+ sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString).replace(regAmp, escapeUrlEncodedHtmlChars).replace(regEncodedChars, escapeUrlEncodedHtmlChars);
844
+ } else {
845
+ sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString).replace(regAmp, escapeUrlEncodedHtmlChars);
618
846
  }
619
847
  }
620
848
  }
@@ -629,8 +857,7 @@ var isURI = async (uri) => {
629
857
  };
630
858
  var sanitizeUrl = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
631
859
  allow: [],
632
- deny: [],
633
- escapeTags: true
860
+ deny: []
634
861
  });
635
862
  var sanitizeURL = async (url, opt) => {
636
863
  const res = await sanitizeUrl(url, opt);