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 +28 -29
- package/dist/url-sanitizer.js +282 -55
- package/dist/url-sanitizer.js.map +3 -3
- package/dist/url-sanitizer.min.js +1 -1
- package/dist/url-sanitizer.min.js.map +4 -4
- package/package.json +3 -5
- package/src/lib/file/text-chars.json +233 -0
- package/src/mjs/uri-util.js +54 -62
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
[
|
|
2
|
+
7,
|
|
3
|
+
8,
|
|
4
|
+
9,
|
|
5
|
+
10,
|
|
6
|
+
11,
|
|
7
|
+
12,
|
|
8
|
+
13,
|
|
9
|
+
27,
|
|
10
|
+
32,
|
|
11
|
+
33,
|
|
12
|
+
34,
|
|
13
|
+
35,
|
|
14
|
+
36,
|
|
15
|
+
37,
|
|
16
|
+
38,
|
|
17
|
+
39,
|
|
18
|
+
40,
|
|
19
|
+
41,
|
|
20
|
+
42,
|
|
21
|
+
43,
|
|
22
|
+
44,
|
|
23
|
+
45,
|
|
24
|
+
46,
|
|
25
|
+
47,
|
|
26
|
+
48,
|
|
27
|
+
49,
|
|
28
|
+
50,
|
|
29
|
+
51,
|
|
30
|
+
52,
|
|
31
|
+
53,
|
|
32
|
+
54,
|
|
33
|
+
55,
|
|
34
|
+
56,
|
|
35
|
+
57,
|
|
36
|
+
58,
|
|
37
|
+
59,
|
|
38
|
+
60,
|
|
39
|
+
61,
|
|
40
|
+
62,
|
|
41
|
+
63,
|
|
42
|
+
64,
|
|
43
|
+
65,
|
|
44
|
+
66,
|
|
45
|
+
67,
|
|
46
|
+
68,
|
|
47
|
+
69,
|
|
48
|
+
70,
|
|
49
|
+
71,
|
|
50
|
+
72,
|
|
51
|
+
73,
|
|
52
|
+
74,
|
|
53
|
+
75,
|
|
54
|
+
76,
|
|
55
|
+
77,
|
|
56
|
+
78,
|
|
57
|
+
79,
|
|
58
|
+
80,
|
|
59
|
+
81,
|
|
60
|
+
82,
|
|
61
|
+
83,
|
|
62
|
+
84,
|
|
63
|
+
85,
|
|
64
|
+
86,
|
|
65
|
+
87,
|
|
66
|
+
88,
|
|
67
|
+
89,
|
|
68
|
+
90,
|
|
69
|
+
91,
|
|
70
|
+
92,
|
|
71
|
+
93,
|
|
72
|
+
94,
|
|
73
|
+
95,
|
|
74
|
+
96,
|
|
75
|
+
97,
|
|
76
|
+
98,
|
|
77
|
+
99,
|
|
78
|
+
100,
|
|
79
|
+
101,
|
|
80
|
+
102,
|
|
81
|
+
103,
|
|
82
|
+
104,
|
|
83
|
+
105,
|
|
84
|
+
106,
|
|
85
|
+
107,
|
|
86
|
+
108,
|
|
87
|
+
109,
|
|
88
|
+
110,
|
|
89
|
+
111,
|
|
90
|
+
112,
|
|
91
|
+
113,
|
|
92
|
+
114,
|
|
93
|
+
115,
|
|
94
|
+
116,
|
|
95
|
+
117,
|
|
96
|
+
118,
|
|
97
|
+
119,
|
|
98
|
+
120,
|
|
99
|
+
121,
|
|
100
|
+
122,
|
|
101
|
+
123,
|
|
102
|
+
124,
|
|
103
|
+
125,
|
|
104
|
+
126,
|
|
105
|
+
128,
|
|
106
|
+
129,
|
|
107
|
+
130,
|
|
108
|
+
131,
|
|
109
|
+
132,
|
|
110
|
+
133,
|
|
111
|
+
134,
|
|
112
|
+
135,
|
|
113
|
+
136,
|
|
114
|
+
137,
|
|
115
|
+
138,
|
|
116
|
+
139,
|
|
117
|
+
140,
|
|
118
|
+
141,
|
|
119
|
+
142,
|
|
120
|
+
143,
|
|
121
|
+
144,
|
|
122
|
+
145,
|
|
123
|
+
146,
|
|
124
|
+
147,
|
|
125
|
+
148,
|
|
126
|
+
149,
|
|
127
|
+
150,
|
|
128
|
+
151,
|
|
129
|
+
152,
|
|
130
|
+
153,
|
|
131
|
+
154,
|
|
132
|
+
155,
|
|
133
|
+
156,
|
|
134
|
+
157,
|
|
135
|
+
158,
|
|
136
|
+
159,
|
|
137
|
+
160,
|
|
138
|
+
161,
|
|
139
|
+
162,
|
|
140
|
+
163,
|
|
141
|
+
164,
|
|
142
|
+
165,
|
|
143
|
+
166,
|
|
144
|
+
167,
|
|
145
|
+
168,
|
|
146
|
+
169,
|
|
147
|
+
170,
|
|
148
|
+
171,
|
|
149
|
+
172,
|
|
150
|
+
173,
|
|
151
|
+
174,
|
|
152
|
+
175,
|
|
153
|
+
176,
|
|
154
|
+
177,
|
|
155
|
+
178,
|
|
156
|
+
179,
|
|
157
|
+
180,
|
|
158
|
+
181,
|
|
159
|
+
182,
|
|
160
|
+
183,
|
|
161
|
+
184,
|
|
162
|
+
185,
|
|
163
|
+
186,
|
|
164
|
+
187,
|
|
165
|
+
188,
|
|
166
|
+
189,
|
|
167
|
+
190,
|
|
168
|
+
191,
|
|
169
|
+
192,
|
|
170
|
+
193,
|
|
171
|
+
194,
|
|
172
|
+
195,
|
|
173
|
+
196,
|
|
174
|
+
197,
|
|
175
|
+
198,
|
|
176
|
+
199,
|
|
177
|
+
200,
|
|
178
|
+
201,
|
|
179
|
+
202,
|
|
180
|
+
203,
|
|
181
|
+
204,
|
|
182
|
+
205,
|
|
183
|
+
206,
|
|
184
|
+
207,
|
|
185
|
+
208,
|
|
186
|
+
209,
|
|
187
|
+
210,
|
|
188
|
+
211,
|
|
189
|
+
212,
|
|
190
|
+
213,
|
|
191
|
+
214,
|
|
192
|
+
215,
|
|
193
|
+
216,
|
|
194
|
+
217,
|
|
195
|
+
218,
|
|
196
|
+
219,
|
|
197
|
+
220,
|
|
198
|
+
221,
|
|
199
|
+
222,
|
|
200
|
+
223,
|
|
201
|
+
224,
|
|
202
|
+
225,
|
|
203
|
+
226,
|
|
204
|
+
227,
|
|
205
|
+
228,
|
|
206
|
+
229,
|
|
207
|
+
230,
|
|
208
|
+
231,
|
|
209
|
+
232,
|
|
210
|
+
233,
|
|
211
|
+
234,
|
|
212
|
+
235,
|
|
213
|
+
236,
|
|
214
|
+
237,
|
|
215
|
+
238,
|
|
216
|
+
239,
|
|
217
|
+
240,
|
|
218
|
+
241,
|
|
219
|
+
242,
|
|
220
|
+
243,
|
|
221
|
+
244,
|
|
222
|
+
245,
|
|
223
|
+
246,
|
|
224
|
+
247,
|
|
225
|
+
248,
|
|
226
|
+
249,
|
|
227
|
+
250,
|
|
228
|
+
251,
|
|
229
|
+
252,
|
|
230
|
+
253,
|
|
231
|
+
254,
|
|
232
|
+
255
|
|
233
|
+
]
|
package/src/mjs/uri-util.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/* shared */
|
|
6
6
|
import { getType, isString } from './common.js';
|
|
7
|
+
import textCharTable from '../lib/file/text-chars.json' assert { type: 'json' };
|
|
7
8
|
import uriSchemes from '../lib/iana/uri-schemes.json' assert { type: 'json' };
|
|
8
9
|
|
|
9
10
|
/* constants */
|
|
@@ -64,7 +65,6 @@ export const escapeUrlEncodedHtmlChars = ch => {
|
|
|
64
65
|
/**
|
|
65
66
|
* parse base64
|
|
66
67
|
*
|
|
67
|
-
* @see {@link https://github.com/file/file/blob/master/src/encoding.c}
|
|
68
68
|
* @param {string} data - base64 data
|
|
69
69
|
* @returns {string} - parsed data / base64 data
|
|
70
70
|
*/
|
|
@@ -74,13 +74,7 @@ export const parseBase64 = data => {
|
|
|
74
74
|
}
|
|
75
75
|
const bin = atob(data);
|
|
76
76
|
const uint8arr = Uint8Array.from([...bin].map(c => c.charCodeAt(0)));
|
|
77
|
-
const textChars = new Set(
|
|
78
|
-
for (let i = 0x20; i < 0x7f; i++) {
|
|
79
|
-
textChars.add(i);
|
|
80
|
-
}
|
|
81
|
-
for (let i = 0x80; i <= 0xff; i++) {
|
|
82
|
-
textChars.add(i);
|
|
83
|
-
}
|
|
77
|
+
const textChars = new Set(textCharTable);
|
|
84
78
|
let parsedData;
|
|
85
79
|
if (uint8arr.every(c => textChars.has(c))) {
|
|
86
80
|
parsedData = bin.replace(/\s/g, getUrlEncodedString);
|
|
@@ -180,6 +174,17 @@ export class URISchemes {
|
|
|
180
174
|
|
|
181
175
|
/* URL sanitizer */
|
|
182
176
|
export class URLSanitizer extends URISchemes {
|
|
177
|
+
/* private fields */
|
|
178
|
+
#recurse;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* construct
|
|
182
|
+
*/
|
|
183
|
+
constructor() {
|
|
184
|
+
super();
|
|
185
|
+
this.#recurse = new Map();
|
|
186
|
+
}
|
|
187
|
+
|
|
183
188
|
/**
|
|
184
189
|
* sanitize URL
|
|
185
190
|
* NOTE: `data` and/or `file` schemes must be explicitly allowed
|
|
@@ -189,13 +194,12 @@ export class URLSanitizer extends URISchemes {
|
|
|
189
194
|
* @param {object} opt - options
|
|
190
195
|
* @param {Array.<string>} opt.allow - array of allowed schemes
|
|
191
196
|
* @param {Array.<string>} opt.deny - array of denied schemes
|
|
192
|
-
* @param {boolean} opt.escapeTags - escape tags and quotes in data URL
|
|
193
197
|
* @returns {?string} - sanitized URL
|
|
194
198
|
*/
|
|
195
|
-
sanitize(url, opt = { allow: [], deny: []
|
|
199
|
+
sanitize(url, opt = { allow: [], deny: [] }) {
|
|
196
200
|
let sanitizedUrl;
|
|
197
201
|
if (super.isURI(url)) {
|
|
198
|
-
const { allow, deny
|
|
202
|
+
const { allow, deny } = opt ?? {};
|
|
199
203
|
const { href, pathname, protocol } = new URL(url);
|
|
200
204
|
const scheme = protocol.replace(/:$/, '');
|
|
201
205
|
const schemeParts = scheme.split('+');
|
|
@@ -247,71 +251,60 @@ export class URLSanitizer extends URISchemes {
|
|
|
247
251
|
if (isBase64) {
|
|
248
252
|
let parsedData = parseBase64(data);
|
|
249
253
|
if (parsedData !== data) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
254
|
+
if (/data:[^,]*,/.test(parsedData)) {
|
|
255
|
+
const regDataUrl = /data:[^,]*,[^"]+/g;
|
|
256
|
+
const regBase64DataUrl = /data:[^,]*;?base64,[\dA-Za-z+/\-_=]+/;
|
|
257
|
+
const matchedDataUrls = parsedData.matchAll(regDataUrl);
|
|
258
|
+
const items = [...matchedDataUrls].reverse();
|
|
259
|
+
if (items.length) {
|
|
260
|
+
for (const item of items) {
|
|
261
|
+
const { index } = item;
|
|
262
|
+
let [dataUrl] = item;
|
|
263
|
+
if (regBase64DataUrl.test(dataUrl)) {
|
|
264
|
+
[dataUrl] = regBase64DataUrl.exec(dataUrl);
|
|
265
|
+
}
|
|
266
|
+
const [beforeDataUrl, afterDataUrl] = [
|
|
267
|
+
parsedData.substring(0, index),
|
|
268
|
+
parsedData.substring(index + dataUrl.length)
|
|
269
|
+
];
|
|
270
|
+
this.#recurse.set(dataUrl, dataUrl);
|
|
262
271
|
const parsedDataUrl = this.sanitize(dataUrl, {
|
|
263
272
|
allow: ['data']
|
|
264
273
|
});
|
|
265
274
|
if (parsedDataUrl) {
|
|
266
|
-
parsedData =
|
|
275
|
+
parsedData = [
|
|
276
|
+
beforeDataUrl,
|
|
277
|
+
parsedDataUrl,
|
|
278
|
+
afterDataUrl
|
|
279
|
+
].join('');
|
|
267
280
|
}
|
|
268
|
-
type = 0;
|
|
269
281
|
}
|
|
270
282
|
}
|
|
271
|
-
} else if (/data:[^,]*,/.test(parsedData) &&
|
|
272
|
-
!(escapeTags ?? true)) {
|
|
273
|
-
const dataArr = parsedData.split(/data:[^,]*,/);
|
|
274
|
-
const l = dataArr.length;
|
|
275
|
-
let i = 1;
|
|
276
|
-
while (i < l) {
|
|
277
|
-
const dataItem = dataArr[i]
|
|
278
|
-
.replace(regEncodedChars, escapeUrlEncodedHtmlChars);
|
|
279
|
-
parsedData = parsedData.replace(dataArr[i], dataItem);
|
|
280
|
-
i++;
|
|
281
|
-
}
|
|
282
|
-
type = 0;
|
|
283
283
|
}
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
if (this.#recurse.has(url)) {
|
|
285
|
+
this.#recurse.delete(url);
|
|
286
|
+
} else {
|
|
286
287
|
type = 1;
|
|
287
|
-
} else if (!Number.isInteger(type)) {
|
|
288
|
-
type = 2;
|
|
289
288
|
}
|
|
290
|
-
|
|
291
|
-
type = 1;
|
|
289
|
+
urlToSanitize = `${scheme}:${mediaType.join(';')},${parsedData}`;
|
|
292
290
|
} else {
|
|
293
|
-
type =
|
|
291
|
+
type = 1;
|
|
294
292
|
}
|
|
295
|
-
} else if ((
|
|
296
|
-
|
|
293
|
+
} else if (this.#recurse.has(url)) {
|
|
294
|
+
this.#recurse.delete(url);
|
|
297
295
|
} else {
|
|
298
|
-
type =
|
|
296
|
+
type = 1;
|
|
299
297
|
}
|
|
300
298
|
} else {
|
|
301
299
|
type = 1;
|
|
302
300
|
}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString)
|
|
311
|
-
.replace(regAmp, escapeUrlEncodedHtmlChars);
|
|
312
|
-
break;
|
|
313
|
-
default:
|
|
314
|
-
sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString);
|
|
301
|
+
if (type === 1) {
|
|
302
|
+
sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString)
|
|
303
|
+
.replace(regAmp, escapeUrlEncodedHtmlChars)
|
|
304
|
+
.replace(regEncodedChars, escapeUrlEncodedHtmlChars);
|
|
305
|
+
} else {
|
|
306
|
+
sanitizedUrl = urlToSanitize.replace(regChars, getUrlEncodedString)
|
|
307
|
+
.replace(regAmp, escapeUrlEncodedHtmlChars);
|
|
315
308
|
}
|
|
316
309
|
}
|
|
317
310
|
}
|
|
@@ -350,8 +343,7 @@ export const isURI = async uri => {
|
|
|
350
343
|
*/
|
|
351
344
|
const sanitizeUrl = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
352
345
|
allow: [],
|
|
353
|
-
deny: []
|
|
354
|
-
escapeTags: true
|
|
346
|
+
deny: []
|
|
355
347
|
});
|
|
356
348
|
|
|
357
349
|
/**
|
|
@@ -359,7 +351,7 @@ const sanitizeUrl = (url, opt) => urlSanitizer.sanitize(url, opt ?? {
|
|
|
359
351
|
*
|
|
360
352
|
* @param {string} url - URL input
|
|
361
353
|
* @param {object} opt - options
|
|
362
|
-
* @returns {Promise
|
|
354
|
+
* @returns {Promise.<?string>} - sanitized URL
|
|
363
355
|
*/
|
|
364
356
|
export const sanitizeURL = async (url, opt) => {
|
|
365
357
|
const res = await sanitizeUrl(url, opt);
|