url-sanitizer 4.2.2 → 5.0.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 +45 -48
- package/dist/url-sanitizer-wo-dompurify.min.js +1 -1
- package/dist/url-sanitizer-wo-dompurify.min.js.map +3 -3
- package/dist/url-sanitizer.min.js +4 -4
- package/dist/url-sanitizer.min.js.map +3 -3
- package/package.json +16 -19
- package/src/index.js +2 -5
- package/src/lib/iana/uri-schemes.json +1 -1
- package/src/mjs/constant.js +18 -0
- package/src/mjs/regexp.js +17 -11
- package/src/mjs/sanitizer.js +476 -319
- package/src/mjs/text-chars.js +6 -6
- package/src/mjs/uri-util.js +195 -139
- package/types/index.d.ts +1 -1
- package/types/mjs/constant.d.ts +3 -0
- package/types/mjs/regexp.d.ts +3 -2
- package/types/mjs/sanitizer.d.ts +15 -8
- package/types/mjs/text-chars.d.ts +1 -1
- package/types/mjs/uri-util.d.ts +9 -3
package/src/mjs/text-chars.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/* shared */
|
|
6
6
|
import textChars from '../lib/file/text-chars.json' with { type: 'json' };
|
|
7
|
-
import { HEX } from './constant.js';
|
|
7
|
+
import { BYTE_RANGE, HEX } from './constant.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* A set of valid text character codes.
|
|
@@ -47,13 +47,13 @@ export const WINDOWS1252_TO_UNICODE = new Map([
|
|
|
47
47
|
]);
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Generates
|
|
50
|
+
* Generates hexadecimal escape strings for non-text characters.
|
|
51
51
|
* @private
|
|
52
52
|
* @returns {Map<number, string>} Generated control char codes.
|
|
53
53
|
*/
|
|
54
|
-
const
|
|
54
|
+
const generateNonTextCharCodes = () => {
|
|
55
55
|
const charCodes = new Map();
|
|
56
|
-
for (let i = 0; i <
|
|
56
|
+
for (let i = 0; i < BYTE_RANGE; i++) {
|
|
57
57
|
if (!TEXT_CHAR_CODES.has(i) && !WINDOWS1252_TO_UNICODE.has(i)) {
|
|
58
58
|
charCodes.set(i, `\\x${i.toString(HEX).padStart(2, '0').toUpperCase()}`);
|
|
59
59
|
}
|
|
@@ -62,7 +62,7 @@ const generateCtrlCharCodes = () => {
|
|
|
62
62
|
};
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
|
-
* A mapping of
|
|
65
|
+
* A mapping of non-text character codes to their hexadecimal escape strings.
|
|
66
66
|
* @type {Map<number, string>}
|
|
67
67
|
*/
|
|
68
|
-
export const
|
|
68
|
+
export const NON_TEXT_CHAR_CODES = generateNonTextCharCodes();
|
package/src/mjs/uri-util.js
CHANGED
|
@@ -7,8 +7,15 @@ import uriSchemes from '../lib/iana/uri-schemes.json' with { type: 'json' };
|
|
|
7
7
|
import { getType, isString } from './common.js';
|
|
8
8
|
|
|
9
9
|
/* constants */
|
|
10
|
-
import { CHUNK_SIZE, DECI, HEX, MAX_BLOB_SIZE, MAX_NEST } from './constant.js';
|
|
11
10
|
import {
|
|
11
|
+
BYTE_RANGE,
|
|
12
|
+
DECI,
|
|
13
|
+
HEX,
|
|
14
|
+
MAX_NEST,
|
|
15
|
+
TRUNCATE_LENGTH
|
|
16
|
+
} from './constant.js';
|
|
17
|
+
import {
|
|
18
|
+
REG_AMP,
|
|
12
19
|
REG_HASH,
|
|
13
20
|
REG_NUM_REF,
|
|
14
21
|
REG_PCT_ENC,
|
|
@@ -18,7 +25,7 @@ import {
|
|
|
18
25
|
REG_URL_ENC
|
|
19
26
|
} from './regexp.js';
|
|
20
27
|
import {
|
|
21
|
-
|
|
28
|
+
NON_TEXT_CHAR_CODES,
|
|
22
29
|
TEXT_CHAR_CODES,
|
|
23
30
|
WINDOWS1252_TO_UNICODE
|
|
24
31
|
} from './text-chars.js';
|
|
@@ -38,15 +45,13 @@ const ESCAPE_MAP = {
|
|
|
38
45
|
[ENC_APOS]: `${ENC_AMP}${ENC_NUM}39;`
|
|
39
46
|
};
|
|
40
47
|
const HEX_TABLE = Array.from(
|
|
41
|
-
{ length:
|
|
48
|
+
{ length: BYTE_RANGE },
|
|
42
49
|
(_, i) => `%${i.toString(HEX).padStart(2, '0').toUpperCase()}`
|
|
43
50
|
);
|
|
44
51
|
const IS_NODE = globalThis.process?.versions?.node !== undefined;
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
'g'
|
|
49
|
-
);
|
|
52
|
+
const NON_TEXT_PATTERN = `[${[...NON_TEXT_CHAR_CODES.values()].join('')}]`;
|
|
53
|
+
const REG_NON_TEXT = new RegExp(NON_TEXT_PATTERN);
|
|
54
|
+
const REG_NON_TEXT_G = new RegExp(NON_TEXT_PATTERN, 'g');
|
|
50
55
|
|
|
51
56
|
/* encoder / decoder */
|
|
52
57
|
const encoder = new TextEncoder();
|
|
@@ -70,43 +75,122 @@ export class URISchemes {
|
|
|
70
75
|
return [...this.#schemes];
|
|
71
76
|
}
|
|
72
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Extracts the scheme from URI.
|
|
80
|
+
* @param {string} uri - The URI string.
|
|
81
|
+
* @returns {string|null} The extracted scheme, or null.
|
|
82
|
+
*/
|
|
83
|
+
getScheme(uri) {
|
|
84
|
+
if (!isString(uri)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const parsed = this.parse(uri);
|
|
88
|
+
if (parsed) {
|
|
89
|
+
return parsed.protocol.replace(/:$/, '');
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
73
94
|
/**
|
|
74
95
|
* Checks if the specified scheme is currently registered.
|
|
75
96
|
* @param {string} scheme - The target scheme.
|
|
76
97
|
* @returns {boolean} True if the scheme is registered.
|
|
77
98
|
*/
|
|
78
99
|
has(scheme) {
|
|
79
|
-
|
|
80
|
-
|
|
100
|
+
const normalized = this.normalize(scheme, true);
|
|
101
|
+
if (normalized) {
|
|
102
|
+
return this.#schemes.has(normalized);
|
|
81
103
|
}
|
|
82
|
-
return
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Normalizes the URI string using NFKC.
|
|
109
|
+
* @param {string} uri - The URL to normalize.
|
|
110
|
+
* @param {boolean} [isScheme] - True if uri is a scheme.
|
|
111
|
+
* @returns {string|null} The normalized URI string, or null.
|
|
112
|
+
*/
|
|
113
|
+
normalize(uri, isScheme = false) {
|
|
114
|
+
if (!isString(uri)) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const normalized = uri.normalize('NFKC');
|
|
118
|
+
if (isScheme) {
|
|
119
|
+
return normalized.trim().toLowerCase();
|
|
120
|
+
}
|
|
121
|
+
return normalized;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Parse the URI string.
|
|
126
|
+
* @param {string} uri - The URI string.
|
|
127
|
+
* @param {string} [base] - The base URI string.
|
|
128
|
+
* @returns {URL|null} The parsed URL object, or null.
|
|
129
|
+
*/
|
|
130
|
+
parse(uri, base) {
|
|
131
|
+
if (!isString(uri)) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const normalized = this.normalize(uri);
|
|
135
|
+
if (base && isString(base)) {
|
|
136
|
+
return URL.parse(normalized, this.normalize(base));
|
|
137
|
+
}
|
|
138
|
+
return URL.parse(normalized);
|
|
83
139
|
}
|
|
84
140
|
|
|
85
141
|
/**
|
|
86
142
|
* Verifies if the given URI is valid and its scheme is allowed.
|
|
87
143
|
* @param {string} uri - The URI string to verify.
|
|
88
144
|
* @param {Set<string>} [schemes] - The set of allowed schemes.
|
|
89
|
-
* @returns {boolean} True if the URI is
|
|
145
|
+
* @returns {boolean} True if the URI is valid and permitted.
|
|
90
146
|
*/
|
|
91
|
-
verify(uri, schemes
|
|
147
|
+
verify(uri, schemes) {
|
|
92
148
|
if (!isString(uri)) {
|
|
93
149
|
return false;
|
|
94
150
|
}
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
151
|
+
const parsedUrl = this.parse(uri);
|
|
152
|
+
return this.verifyParsed(parsedUrl, schemes);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Verifies if the pre-parsed URL object is valid and its scheme is allowed.
|
|
157
|
+
* @param {URL} parsedUrl - The parsed URL object to verify.
|
|
158
|
+
* @param {Set<string>} [schemes] - The set of allowed schemes.
|
|
159
|
+
* @returns {boolean} True if the parsed URL is valid and permitted.
|
|
160
|
+
*/
|
|
161
|
+
verifyParsed(parsedUrl, schemes) {
|
|
162
|
+
if (!parsedUrl || !parsedUrl.protocol) {
|
|
98
163
|
return false;
|
|
99
164
|
}
|
|
100
|
-
const scheme = parsedUrl.protocol.replace(/:$/, '');
|
|
165
|
+
const scheme = parsedUrl.protocol.toLowerCase().replace(/:$/, '');
|
|
101
166
|
const parts = scheme.split('+');
|
|
102
167
|
const isScript = parts.some(s => REG_SCRIPT.test(s));
|
|
103
168
|
if (isScript) {
|
|
104
169
|
return false;
|
|
105
170
|
}
|
|
106
|
-
|
|
171
|
+
if (!schemes) {
|
|
172
|
+
schemes = this.#schemes;
|
|
173
|
+
}
|
|
174
|
+
return (
|
|
175
|
+
schemes.has(scheme) ||
|
|
176
|
+
REG_SCHEME_EXT.test(scheme) ||
|
|
177
|
+
parts.every(s => schemes.has(s))
|
|
178
|
+
);
|
|
107
179
|
}
|
|
108
180
|
}
|
|
109
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Extracts an array of schemes.
|
|
184
|
+
* @param {string} protocol - The URL protocol (e.g., "data:", "git+https:").
|
|
185
|
+
* @returns {string[]} An array of scheme parts.
|
|
186
|
+
*/
|
|
187
|
+
export const getSchemeParts = protocol => {
|
|
188
|
+
if (!isString(protocol)) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
return protocol.replace(/:$/, '').split('+');
|
|
192
|
+
};
|
|
193
|
+
|
|
110
194
|
/**
|
|
111
195
|
* Gets the URL-encoded representation of a given string.
|
|
112
196
|
* @param {string} str - The target string.
|
|
@@ -117,11 +201,11 @@ export const getURLEncodedString = str => {
|
|
|
117
201
|
throw new TypeError(`Expected String but got ${getType(str)}.`);
|
|
118
202
|
}
|
|
119
203
|
const bytes = encoder.encode(str);
|
|
120
|
-
|
|
204
|
+
const encoded = new Array(bytes.length);
|
|
121
205
|
for (let i = 0; i < bytes.length; i++) {
|
|
122
|
-
encoded
|
|
206
|
+
encoded[i] = HEX_TABLE[bytes[i]];
|
|
123
207
|
}
|
|
124
|
-
return encoded;
|
|
208
|
+
return encoded.join('');
|
|
125
209
|
};
|
|
126
210
|
|
|
127
211
|
/**
|
|
@@ -129,7 +213,6 @@ export const getURLEncodedString = str => {
|
|
|
129
213
|
* @param {string} ch - A URL-encoded (percent-encoded) character.
|
|
130
214
|
* @returns {string} The escaped HTML special character, or the given character.
|
|
131
215
|
*/
|
|
132
|
-
|
|
133
216
|
export const escapeURLEncodedHTMLChars = ch => {
|
|
134
217
|
if (!isString(ch)) {
|
|
135
218
|
return ch;
|
|
@@ -139,65 +222,15 @@ export const escapeURLEncodedHTMLChars = ch => {
|
|
|
139
222
|
};
|
|
140
223
|
|
|
141
224
|
/**
|
|
142
|
-
*
|
|
143
|
-
* @param {string}
|
|
144
|
-
* @returns {string} The
|
|
145
|
-
*/
|
|
146
|
-
export const parseBase64 = data => {
|
|
147
|
-
if (!isString(data)) {
|
|
148
|
-
throw new TypeError(`Expected String but got ${getType(data)}.`);
|
|
149
|
-
}
|
|
150
|
-
const cleanData = data.replace(/\s/g, '');
|
|
151
|
-
let binStr;
|
|
152
|
-
try {
|
|
153
|
-
binStr = atob(cleanData);
|
|
154
|
-
} catch {
|
|
155
|
-
throw new Error(`Invalid base64 data: ${data}`);
|
|
156
|
-
}
|
|
157
|
-
let bytes;
|
|
158
|
-
if (IS_NODE && globalThis.Buffer) {
|
|
159
|
-
// Use 'latin1' to correctly map the binary string directly to bytes.
|
|
160
|
-
// @see https://nodejs.org/docs/latest/api/buffer.html#buffers-and-character-encodings
|
|
161
|
-
bytes = globalThis.Buffer.from(binStr, 'latin1');
|
|
162
|
-
} else {
|
|
163
|
-
const len = binStr.length;
|
|
164
|
-
bytes = new Uint8Array(len);
|
|
165
|
-
for (let i = 0; i < len; i++) {
|
|
166
|
-
bytes[i] = binStr.charCodeAt(i);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
try {
|
|
170
|
-
const text = decoder.decode(bytes);
|
|
171
|
-
if (REG_BINARY.test(text)) {
|
|
172
|
-
return cleanData;
|
|
173
|
-
}
|
|
174
|
-
return text;
|
|
175
|
-
} catch {
|
|
176
|
-
return cleanData;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Replaces URL-encoded numeric character references.
|
|
182
|
-
* @param {string} match - The matched substring.
|
|
183
|
-
* @param {string} value - The captured numeric value.
|
|
184
|
-
* @returns {string} The resolved character or the original match.
|
|
225
|
+
* Truncate URL string.
|
|
226
|
+
* @param {string|unknown} url - A URL string
|
|
227
|
+
* @returns {string} - The truncated string
|
|
185
228
|
*/
|
|
186
|
-
export const
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (TEXT_CHAR_CODES.has(num)) {
|
|
192
|
-
return String.fromCharCode(num);
|
|
193
|
-
} else if (WINDOWS1252_TO_UNICODE.has(num)) {
|
|
194
|
-
const codePoint = WINDOWS1252_TO_UNICODE.get(num);
|
|
195
|
-
return String.fromCodePoint(codePoint);
|
|
196
|
-
} else if (CTRL_CHAR_CODES.has(num)) {
|
|
197
|
-
return '';
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return match;
|
|
229
|
+
export const truncateURL = url => {
|
|
230
|
+
const str = isString(url) ? url : String(url);
|
|
231
|
+
const truncated =
|
|
232
|
+
str.length > TRUNCATE_LENGTH ? `${str.slice(0, TRUNCATE_LENGTH)}...` : str;
|
|
233
|
+
return truncated;
|
|
201
234
|
};
|
|
202
235
|
|
|
203
236
|
/**
|
|
@@ -213,7 +246,30 @@ export const trimTrailingEmptyQueryAndHash = url => {
|
|
|
213
246
|
};
|
|
214
247
|
|
|
215
248
|
/**
|
|
216
|
-
*
|
|
249
|
+
* Replaces URL-encoded numeric character references.
|
|
250
|
+
* @param {string} match - The matched substring.
|
|
251
|
+
* @param {string} value - The captured numeric value.
|
|
252
|
+
* @returns {string} The resolved character or the original match.
|
|
253
|
+
*/
|
|
254
|
+
export const replaceNumCharRef = (match, value) => {
|
|
255
|
+
const num = /^x/i.test(value)
|
|
256
|
+
? Number.parseInt(value.slice(1), HEX)
|
|
257
|
+
: Number.parseInt(value, DECI);
|
|
258
|
+
if (!Number.isInteger(num) || num < 0 || num >= BYTE_RANGE) {
|
|
259
|
+
return match;
|
|
260
|
+
}
|
|
261
|
+
if (NON_TEXT_CHAR_CODES.has(num)) {
|
|
262
|
+
return '';
|
|
263
|
+
}
|
|
264
|
+
if (TEXT_CHAR_CODES.has(num)) {
|
|
265
|
+
return String.fromCharCode(num);
|
|
266
|
+
}
|
|
267
|
+
const codePoint = WINDOWS1252_TO_UNICODE.get(num);
|
|
268
|
+
return String.fromCodePoint(codePoint);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Parses URL-encoded numeric character references.
|
|
217
273
|
* @param {string} str - The target string to parse.
|
|
218
274
|
* @param {number} [nest] - The current nesting depth for recursive parsing.
|
|
219
275
|
* @returns {string} The decoded and parsed string.
|
|
@@ -238,84 +294,84 @@ export const parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
238
294
|
}
|
|
239
295
|
});
|
|
240
296
|
}
|
|
241
|
-
res = res.replace(
|
|
297
|
+
res = res.replace(REG_NON_TEXT_G, '');
|
|
242
298
|
let depth = 0;
|
|
243
|
-
for (; depth + nest
|
|
299
|
+
for (; depth + nest < MAX_NEST; depth++) {
|
|
244
300
|
const previousRes = res;
|
|
245
301
|
// Decode '&' before decoding numeric references.
|
|
246
|
-
res = res.replace(
|
|
302
|
+
res = res.replace(REG_AMP, '&').replace(REG_NUM_REF, replaceNumCharRef);
|
|
247
303
|
if (res === previousRes) {
|
|
248
304
|
break;
|
|
249
305
|
}
|
|
250
306
|
}
|
|
251
|
-
if (depth + nest
|
|
307
|
+
if (depth + nest >= MAX_NEST && /&#/.test(res)) {
|
|
252
308
|
throw new Error('Character references nested too deeply.');
|
|
253
309
|
}
|
|
254
310
|
return res;
|
|
255
311
|
};
|
|
256
312
|
|
|
257
313
|
/**
|
|
258
|
-
*
|
|
259
|
-
* @
|
|
260
|
-
* @param {
|
|
261
|
-
* @
|
|
262
|
-
|
|
263
|
-
const convertFromBuffer = async blob => {
|
|
264
|
-
const mimeStr = blob.type ? `${blob.type};base64` : 'base64';
|
|
265
|
-
const buffer = await blob.arrayBuffer();
|
|
266
|
-
const base64 = globalThis.Buffer.from(buffer).toString('base64');
|
|
267
|
-
return `data:${mimeStr},${base64}`;
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Converts Blob to data URL from FileReader.
|
|
272
|
-
* @private
|
|
273
|
-
* @param {Blob} blob - The target Blob object.
|
|
274
|
-
* @returns {Promise<string|null>} A promise resolving to the data URL, or null.
|
|
275
|
-
*/
|
|
276
|
-
const convertFromFileReader = blob =>
|
|
277
|
-
new Promise((resolve, reject) => {
|
|
278
|
-
const reader = new globalThis.FileReader();
|
|
279
|
-
reader.addEventListener('error', () => reject(reader.error));
|
|
280
|
-
reader.addEventListener('abort', () => resolve(null));
|
|
281
|
-
reader.addEventListener('load', () => resolve(reader.result));
|
|
282
|
-
reader.readAsDataURL(blob);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Converts Blob to data URL from btoa.
|
|
287
|
-
* @private
|
|
288
|
-
* @param {Blob} blob - The target Blob object.
|
|
289
|
-
* @returns {Promise<string|null>} A promise resolving to the data URL, or null.
|
|
314
|
+
* Extracts the components of a parsed Data URL.
|
|
315
|
+
* @param {string} pathname - The pathname of the URL.
|
|
316
|
+
* @param {string} [search] - The search (query) string of the URL.
|
|
317
|
+
* @param {string} [hash] - The hash (fragment) of the URL.
|
|
318
|
+
* @returns {object} An object containing the media type, parsed media types array, data string, and a boolean indicating if it is base64-encoded.
|
|
290
319
|
*/
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
320
|
+
export const extractDataUrlComponents = (pathname, search = '', hash = '') => {
|
|
321
|
+
if (!isString(pathname)) {
|
|
322
|
+
return { mediaType: '', mediaTypes: [], data: '', isBase64: false };
|
|
323
|
+
}
|
|
324
|
+
const comma = pathname.indexOf(',');
|
|
325
|
+
if (comma === -1) {
|
|
326
|
+
return {
|
|
327
|
+
mediaType: pathname,
|
|
328
|
+
mediaTypes: pathname.split(';'),
|
|
329
|
+
data: '',
|
|
330
|
+
isBase64: false
|
|
331
|
+
};
|
|
298
332
|
}
|
|
299
|
-
|
|
333
|
+
const mediaType = pathname.slice(0, comma);
|
|
334
|
+
const data = `${pathname.slice(comma + 1)}${search}${hash}`;
|
|
335
|
+
const mediaTypes = mediaType.split(';');
|
|
336
|
+
const isBase64 = /^base64$/i.test(mediaTypes.at(-1));
|
|
337
|
+
return { mediaType, mediaTypes, data, isBase64 };
|
|
300
338
|
};
|
|
301
339
|
|
|
302
340
|
/**
|
|
303
|
-
*
|
|
304
|
-
* @param {
|
|
305
|
-
* @
|
|
306
|
-
* @returns {Promise<string|null>} A promise resolving to the data URL, or null.
|
|
341
|
+
* Parses base64-encoded data.
|
|
342
|
+
* @param {string} data - The base64-encoded string.
|
|
343
|
+
* @returns {string} The parsed text, or the original base64 if binary.
|
|
307
344
|
*/
|
|
308
|
-
export const
|
|
309
|
-
if (!
|
|
310
|
-
|
|
311
|
-
} else if (Number.isInteger(maxSize) && blob.size > maxSize) {
|
|
312
|
-
const msg = `Blob size (${blob.size} bytes) exceeds max (${maxSize} bytes).`;
|
|
313
|
-
throw new DOMException(msg, 'NotReadableError');
|
|
345
|
+
export const parseBase64 = data => {
|
|
346
|
+
if (!isString(data)) {
|
|
347
|
+
throw new TypeError(`Expected String but got ${getType(data)}.`);
|
|
314
348
|
}
|
|
349
|
+
const cleanData = data.replace(/\s/g, '');
|
|
350
|
+
let binStr;
|
|
351
|
+
try {
|
|
352
|
+
binStr = atob(cleanData);
|
|
353
|
+
} catch {
|
|
354
|
+
throw new Error(`Invalid base64 data: ${truncateURL(data)}`);
|
|
355
|
+
}
|
|
356
|
+
let bytes;
|
|
315
357
|
if (IS_NODE && globalThis.Buffer) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
358
|
+
// Use 'latin1' to correctly map the binary string directly to bytes.
|
|
359
|
+
// @see https://nodejs.org/docs/latest/api/buffer.html#buffers-and-character-encodings
|
|
360
|
+
bytes = globalThis.Buffer.from(binStr, 'latin1');
|
|
361
|
+
} else {
|
|
362
|
+
const len = binStr.length;
|
|
363
|
+
bytes = new Uint8Array(len);
|
|
364
|
+
for (let i = 0; i < len; i++) {
|
|
365
|
+
bytes[i] = binStr.charCodeAt(i);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const text = decoder.decode(bytes);
|
|
370
|
+
if (REG_NON_TEXT.test(text)) {
|
|
371
|
+
return cleanData;
|
|
372
|
+
}
|
|
373
|
+
return text;
|
|
374
|
+
} catch {
|
|
375
|
+
return cleanData;
|
|
319
376
|
}
|
|
320
|
-
return convertFromBtoa(blob);
|
|
321
377
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
* @copyright asamuzaK (Kazz)
|
|
6
6
|
* @see {@link https://github.com/asamuzaK/urlSanitizer/blob/main/LICENSE}
|
|
7
7
|
*/
|
|
8
|
-
export { default, inspectURL,
|
|
8
|
+
export { default, inspectURL, isURISync, isValidURI, sanitizeURL, sanitizeURLSync } from './mjs/sanitizer.js';
|
package/types/mjs/constant.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export declare const BYTE_RANGE: number;
|
|
1
2
|
export declare const CHUNK_SIZE: number;
|
|
2
3
|
export declare const DECI: number;
|
|
3
4
|
export declare const HEX: number;
|
|
4
5
|
export declare const MAX_BLOB_SIZE: number;
|
|
5
6
|
export declare const MAX_NEST: number;
|
|
7
|
+
export declare const MAX_URL_LENGTH: number;
|
|
8
|
+
export declare const TRUNCATE_LENGTH: number;
|
|
6
9
|
export declare const TYPE_FROM: number;
|
|
7
10
|
export declare const TYPE_TO: number;
|
package/types/mjs/regexp.d.ts
CHANGED
|
@@ -7,9 +7,10 @@ export declare const REG_QUERY: RegExp;
|
|
|
7
7
|
export declare const REG_SCHEME: RegExp;
|
|
8
8
|
export declare const REG_SCHEME_EXT: RegExp;
|
|
9
9
|
export declare const REG_SCRIPT: RegExp;
|
|
10
|
-
export declare const
|
|
10
|
+
export declare const REG_SCRIPT_OR_BLOB: RegExp;
|
|
11
11
|
export declare const REG_TAG_QUOT: RegExp;
|
|
12
12
|
export declare const REG_URL_ENC: RegExp;
|
|
13
|
-
export declare const REG_AMP_ENC: RegExp;
|
|
14
13
|
export declare const REG_PCT_ENC: RegExp;
|
|
14
|
+
export declare const REG_AMP: RegExp;
|
|
15
|
+
export declare const REG_AMP_ENC: RegExp;
|
|
15
16
|
export declare const REG_VERIFY_RELATIVE: RegExp;
|
package/types/mjs/sanitizer.d.ts
CHANGED
|
@@ -21,8 +21,18 @@ export type InspectedURLResult = {
|
|
|
21
21
|
search?: string;
|
|
22
22
|
hash?: string;
|
|
23
23
|
};
|
|
24
|
-
export declare const logDebug: (
|
|
25
|
-
declare class
|
|
24
|
+
export declare const logDebug: (message: string, error?: Error) => void;
|
|
25
|
+
export declare class SanitizeContext {
|
|
26
|
+
debug: boolean;
|
|
27
|
+
domPurify: object;
|
|
28
|
+
nest: number;
|
|
29
|
+
recurse: Set<any>;
|
|
30
|
+
schemeMap: Map<string, boolean>;
|
|
31
|
+
constructor(opt: object, domPurifyInstance: object);
|
|
32
|
+
enter(url: string): boolean;
|
|
33
|
+
leave(url: string): void;
|
|
34
|
+
}
|
|
35
|
+
export declare class URLSanitizer extends URISchemes {
|
|
26
36
|
#private;
|
|
27
37
|
private static #uponSanitizeAttribute;
|
|
28
38
|
constructor();
|
|
@@ -34,6 +44,7 @@ declare class URLSanitizer extends URISchemes {
|
|
|
34
44
|
private #isSchemeAllowed;
|
|
35
45
|
private #sanitizeDataURL;
|
|
36
46
|
private #sanitizeStandardURL;
|
|
47
|
+
private #executeSanitize;
|
|
37
48
|
sanitize(url: string, opt?: {
|
|
38
49
|
allow?: string[];
|
|
39
50
|
deny?: string[];
|
|
@@ -43,7 +54,7 @@ declare class URLSanitizer extends URISchemes {
|
|
|
43
54
|
maxBlobSize?: number;
|
|
44
55
|
maxLength?: number;
|
|
45
56
|
}): string | null;
|
|
46
|
-
inspect(url: string
|
|
57
|
+
inspect(url: string): InspectedURLResult;
|
|
47
58
|
get(): string[];
|
|
48
59
|
has(scheme: string): boolean;
|
|
49
60
|
add(scheme: string): string[];
|
|
@@ -51,7 +62,6 @@ declare class URLSanitizer extends URISchemes {
|
|
|
51
62
|
reset(): void;
|
|
52
63
|
verify(uri: string, schemes?: Set<string>): boolean;
|
|
53
64
|
}
|
|
54
|
-
export { URLSanitizer };
|
|
55
65
|
declare const urlSanitizer: URLSanitizer;
|
|
56
66
|
export declare const sanitizeURL: (url: string, opt?: {
|
|
57
67
|
allow?: string[];
|
|
@@ -73,9 +83,6 @@ export declare const sanitizeURLSync: (url: string, opt?: {
|
|
|
73
83
|
maxLength?: number;
|
|
74
84
|
}) => string | null;
|
|
75
85
|
export declare const inspectURL: (url: string) => Promise<InspectedURLResult>;
|
|
76
|
-
export declare const
|
|
77
|
-
export declare const inspectURLSync: (url: string) => InspectedURLResult;
|
|
78
|
-
export declare const parseURLSync: (url: string) => InspectedURLResult;
|
|
79
|
-
export declare const isURI: (uri: string) => Promise<boolean>;
|
|
86
|
+
export declare const isValidURI: (uri: string) => boolean;
|
|
80
87
|
export declare const isURISync: (uri: string) => boolean;
|
|
81
88
|
export default urlSanitizer;
|
package/types/mjs/uri-util.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
export declare class URISchemes {
|
|
2
2
|
#private;
|
|
3
3
|
get(): string[];
|
|
4
|
+
getScheme(uri: string): string | null;
|
|
4
5
|
has(scheme: string): boolean;
|
|
6
|
+
normalize(uri: string, isScheme?: boolean): string | null;
|
|
7
|
+
parse(uri: string, base?: string): URL | null;
|
|
5
8
|
verify(uri: string, schemes?: Set<string>): boolean;
|
|
9
|
+
verifyParsed(parsedUrl: URL, schemes?: Set<string>): boolean;
|
|
6
10
|
}
|
|
11
|
+
export declare const getSchemeParts: (protocol: string) => string[];
|
|
7
12
|
export declare const getURLEncodedString: (str: string) => string;
|
|
8
13
|
export declare const escapeURLEncodedHTMLChars: (ch: string) => string;
|
|
9
|
-
export declare const
|
|
10
|
-
export declare const replaceNumCharRef: (match: string, value: string) => string;
|
|
14
|
+
export declare const truncateURL: (url: string | unknown) => string;
|
|
11
15
|
export declare const trimTrailingEmptyQueryAndHash: (url: string) => string;
|
|
16
|
+
export declare const replaceNumCharRef: (match: string, value: string) => string;
|
|
12
17
|
export declare const parseURLEncodedNumCharRef: (str: string, nest?: number) => string;
|
|
13
|
-
export declare const
|
|
18
|
+
export declare const extractDataUrlComponents: (pathname: string, search?: string, hash?: string) => object;
|
|
19
|
+
export declare const parseBase64: (data: string) => string;
|