url-sanitizer 4.2.2 → 5.0.1
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 +47 -49
- 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 +20 -26
- 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 +559 -442
- package/src/mjs/text-chars.js +6 -6
- package/src/mjs/uri-util.js +201 -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 +20 -11
- 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,128 @@ 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);
|
|
103
|
+
}
|
|
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;
|
|
81
116
|
}
|
|
82
|
-
|
|
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, true);
|
|
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
|
+
* @param {boolean} [allowCustom] - True if web+*, ext+* schemes are allowed.
|
|
160
|
+
* @returns {boolean} True if the parsed URL is valid and permitted.
|
|
161
|
+
*/
|
|
162
|
+
verifyParsed(parsedUrl, schemes, allowCustom = false) {
|
|
163
|
+
if (!parsedUrl || !parsedUrl.protocol) {
|
|
98
164
|
return false;
|
|
99
165
|
}
|
|
100
|
-
const scheme = parsedUrl.protocol.replace(/:$/, '');
|
|
166
|
+
const scheme = parsedUrl.protocol.toLowerCase().replace(/:$/, '');
|
|
101
167
|
const parts = scheme.split('+');
|
|
102
168
|
const isScript = parts.some(s => REG_SCRIPT.test(s));
|
|
103
169
|
if (isScript) {
|
|
104
170
|
return false;
|
|
105
171
|
}
|
|
106
|
-
|
|
172
|
+
if (!schemes) {
|
|
173
|
+
schemes = this.#schemes;
|
|
174
|
+
}
|
|
175
|
+
if (schemes.has(scheme)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
if (parts.length > 1) {
|
|
179
|
+
return (
|
|
180
|
+
(allowCustom && REG_SCHEME_EXT.test(scheme)) ||
|
|
181
|
+
parts.every(s => schemes.has(s))
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
107
185
|
}
|
|
108
186
|
}
|
|
109
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Extracts an array of schemes.
|
|
190
|
+
* @param {string} protocol - The URL protocol (e.g., "data:", "git+https:").
|
|
191
|
+
* @returns {string[]} An array of scheme parts.
|
|
192
|
+
*/
|
|
193
|
+
export const getSchemeParts = protocol => {
|
|
194
|
+
if (!isString(protocol)) {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
return protocol.replace(/:$/, '').split('+');
|
|
198
|
+
};
|
|
199
|
+
|
|
110
200
|
/**
|
|
111
201
|
* Gets the URL-encoded representation of a given string.
|
|
112
202
|
* @param {string} str - The target string.
|
|
@@ -117,11 +207,11 @@ export const getURLEncodedString = str => {
|
|
|
117
207
|
throw new TypeError(`Expected String but got ${getType(str)}.`);
|
|
118
208
|
}
|
|
119
209
|
const bytes = encoder.encode(str);
|
|
120
|
-
|
|
210
|
+
const encoded = new Array(bytes.length);
|
|
121
211
|
for (let i = 0; i < bytes.length; i++) {
|
|
122
|
-
encoded
|
|
212
|
+
encoded[i] = HEX_TABLE[bytes[i]];
|
|
123
213
|
}
|
|
124
|
-
return encoded;
|
|
214
|
+
return encoded.join('');
|
|
125
215
|
};
|
|
126
216
|
|
|
127
217
|
/**
|
|
@@ -129,7 +219,6 @@ export const getURLEncodedString = str => {
|
|
|
129
219
|
* @param {string} ch - A URL-encoded (percent-encoded) character.
|
|
130
220
|
* @returns {string} The escaped HTML special character, or the given character.
|
|
131
221
|
*/
|
|
132
|
-
|
|
133
222
|
export const escapeURLEncodedHTMLChars = ch => {
|
|
134
223
|
if (!isString(ch)) {
|
|
135
224
|
return ch;
|
|
@@ -139,65 +228,15 @@ export const escapeURLEncodedHTMLChars = ch => {
|
|
|
139
228
|
};
|
|
140
229
|
|
|
141
230
|
/**
|
|
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.
|
|
231
|
+
* Truncate URL string.
|
|
232
|
+
* @param {string|unknown} url - A URL string
|
|
233
|
+
* @returns {string} - The truncated string
|
|
185
234
|
*/
|
|
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;
|
|
235
|
+
export const truncateURL = url => {
|
|
236
|
+
const str = isString(url) ? url : String(url);
|
|
237
|
+
const truncated =
|
|
238
|
+
str.length > TRUNCATE_LENGTH ? `${str.slice(0, TRUNCATE_LENGTH)}...` : str;
|
|
239
|
+
return truncated;
|
|
201
240
|
};
|
|
202
241
|
|
|
203
242
|
/**
|
|
@@ -213,7 +252,30 @@ export const trimTrailingEmptyQueryAndHash = url => {
|
|
|
213
252
|
};
|
|
214
253
|
|
|
215
254
|
/**
|
|
216
|
-
*
|
|
255
|
+
* Replaces URL-encoded numeric character references.
|
|
256
|
+
* @param {string} match - The matched substring.
|
|
257
|
+
* @param {string} value - The captured numeric value.
|
|
258
|
+
* @returns {string} The resolved character or the original match.
|
|
259
|
+
*/
|
|
260
|
+
export const replaceNumCharRef = (match, value) => {
|
|
261
|
+
const num = /^x/i.test(value)
|
|
262
|
+
? Number.parseInt(value.slice(1), HEX)
|
|
263
|
+
: Number.parseInt(value, DECI);
|
|
264
|
+
if (!Number.isInteger(num) || num < 0 || num >= BYTE_RANGE) {
|
|
265
|
+
return match;
|
|
266
|
+
}
|
|
267
|
+
if (NON_TEXT_CHAR_CODES.has(num)) {
|
|
268
|
+
return '';
|
|
269
|
+
}
|
|
270
|
+
if (TEXT_CHAR_CODES.has(num)) {
|
|
271
|
+
return String.fromCharCode(num);
|
|
272
|
+
}
|
|
273
|
+
const codePoint = WINDOWS1252_TO_UNICODE.get(num);
|
|
274
|
+
return String.fromCodePoint(codePoint);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Parses URL-encoded numeric character references.
|
|
217
279
|
* @param {string} str - The target string to parse.
|
|
218
280
|
* @param {number} [nest] - The current nesting depth for recursive parsing.
|
|
219
281
|
* @returns {string} The decoded and parsed string.
|
|
@@ -238,84 +300,84 @@ export const parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
238
300
|
}
|
|
239
301
|
});
|
|
240
302
|
}
|
|
241
|
-
res = res.replace(
|
|
303
|
+
res = res.replace(REG_NON_TEXT_G, '');
|
|
242
304
|
let depth = 0;
|
|
243
|
-
for (; depth + nest
|
|
305
|
+
for (; depth + nest < MAX_NEST; depth++) {
|
|
244
306
|
const previousRes = res;
|
|
245
307
|
// Decode '&' before decoding numeric references.
|
|
246
|
-
res = res.replace(
|
|
308
|
+
res = res.replace(REG_AMP, '&').replace(REG_NUM_REF, replaceNumCharRef);
|
|
247
309
|
if (res === previousRes) {
|
|
248
310
|
break;
|
|
249
311
|
}
|
|
250
312
|
}
|
|
251
|
-
if (depth + nest
|
|
313
|
+
if (depth + nest >= MAX_NEST && /&#/.test(res)) {
|
|
252
314
|
throw new Error('Character references nested too deeply.');
|
|
253
315
|
}
|
|
254
316
|
return res;
|
|
255
317
|
};
|
|
256
318
|
|
|
257
319
|
/**
|
|
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.
|
|
320
|
+
* Extracts the components of a parsed Data URL.
|
|
321
|
+
* @param {string} pathname - The pathname of the URL.
|
|
322
|
+
* @param {string} [search] - The search (query) string of the URL.
|
|
323
|
+
* @param {string} [hash] - The hash (fragment) of the URL.
|
|
324
|
+
* @returns {object} An object containing the media type, parsed media types array, data string, and a boolean indicating if it is base64-encoded.
|
|
275
325
|
*/
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
* @param {Blob} blob - The target Blob object.
|
|
289
|
-
* @returns {Promise<string|null>} A promise resolving to the data URL, or null.
|
|
290
|
-
*/
|
|
291
|
-
const convertFromBtoa = async blob => {
|
|
292
|
-
const mimeStr = blob.type ? `${blob.type};base64` : 'base64';
|
|
293
|
-
const buffer = await blob.arrayBuffer();
|
|
294
|
-
const uint8arr = new Uint8Array(buffer);
|
|
295
|
-
const chunks = [];
|
|
296
|
-
for (let i = 0; i < uint8arr.length; i += CHUNK_SIZE) {
|
|
297
|
-
chunks.push(String.fromCharCode(...uint8arr.subarray(i, i + CHUNK_SIZE)));
|
|
326
|
+
export const extractDataUrlComponents = (pathname, search = '', hash = '') => {
|
|
327
|
+
if (!isString(pathname)) {
|
|
328
|
+
return { mediaType: '', mediaTypes: [], data: '', isBase64: false };
|
|
329
|
+
}
|
|
330
|
+
const comma = pathname.indexOf(',');
|
|
331
|
+
if (comma === -1) {
|
|
332
|
+
return {
|
|
333
|
+
mediaType: pathname,
|
|
334
|
+
mediaTypes: pathname.split(';'),
|
|
335
|
+
data: '',
|
|
336
|
+
isBase64: false
|
|
337
|
+
};
|
|
298
338
|
}
|
|
299
|
-
|
|
339
|
+
const mediaType = pathname.slice(0, comma);
|
|
340
|
+
const data = `${pathname.slice(comma + 1)}${search}${hash}`;
|
|
341
|
+
const mediaTypes = mediaType.split(';');
|
|
342
|
+
const isBase64 = /^base64$/i.test(mediaTypes.at(-1));
|
|
343
|
+
return { mediaType, mediaTypes, data, isBase64 };
|
|
300
344
|
};
|
|
301
345
|
|
|
302
346
|
/**
|
|
303
|
-
*
|
|
304
|
-
* @param {
|
|
305
|
-
* @
|
|
306
|
-
* @returns {Promise<string|null>} A promise resolving to the data URL, or null.
|
|
347
|
+
* Parses base64-encoded data.
|
|
348
|
+
* @param {string} data - The base64-encoded string.
|
|
349
|
+
* @returns {string} The parsed text, or the original base64 if binary.
|
|
307
350
|
*/
|
|
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');
|
|
351
|
+
export const parseBase64 = data => {
|
|
352
|
+
if (!isString(data)) {
|
|
353
|
+
throw new TypeError(`Expected String but got ${getType(data)}.`);
|
|
314
354
|
}
|
|
355
|
+
const cleanData = data.replace(/\s/g, '');
|
|
356
|
+
let binStr;
|
|
357
|
+
try {
|
|
358
|
+
binStr = atob(cleanData);
|
|
359
|
+
} catch {
|
|
360
|
+
throw new Error(`Invalid base64 data: ${truncateURL(data)}`);
|
|
361
|
+
}
|
|
362
|
+
let bytes;
|
|
315
363
|
if (IS_NODE && globalThis.Buffer) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
364
|
+
// Use 'latin1' to correctly map the binary string directly to bytes.
|
|
365
|
+
// @see https://nodejs.org/docs/latest/api/buffer.html#buffers-and-character-encodings
|
|
366
|
+
bytes = globalThis.Buffer.from(binStr, 'latin1');
|
|
367
|
+
} else {
|
|
368
|
+
const len = binStr.length;
|
|
369
|
+
bytes = new Uint8Array(len);
|
|
370
|
+
for (let i = 0; i < len; i++) {
|
|
371
|
+
bytes[i] = binStr.charCodeAt(i);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
const text = decoder.decode(bytes);
|
|
376
|
+
if (REG_NON_TEXT.test(text)) {
|
|
377
|
+
return cleanData;
|
|
378
|
+
}
|
|
379
|
+
return text;
|
|
380
|
+
} catch {
|
|
381
|
+
return cleanData;
|
|
319
382
|
}
|
|
320
|
-
return convertFromBtoa(blob);
|
|
321
383
|
};
|
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
|
@@ -7,6 +7,7 @@ export type InspectedDataURL = {
|
|
|
7
7
|
export type InspectedURLResult = {
|
|
8
8
|
input: string;
|
|
9
9
|
valid: boolean;
|
|
10
|
+
relative?: boolean;
|
|
10
11
|
reason?: string;
|
|
11
12
|
data?: InspectedDataURL | null;
|
|
12
13
|
href?: string;
|
|
@@ -21,18 +22,30 @@ export type InspectedURLResult = {
|
|
|
21
22
|
search?: string;
|
|
22
23
|
hash?: string;
|
|
23
24
|
};
|
|
24
|
-
export declare const logDebug: (
|
|
25
|
-
declare class
|
|
25
|
+
export declare const logDebug: (message: string, error?: Error) => void;
|
|
26
|
+
export declare class SanitizeContext {
|
|
27
|
+
debug: boolean;
|
|
28
|
+
domPurify: object;
|
|
29
|
+
nest: number;
|
|
30
|
+
recurse: Set<any>;
|
|
31
|
+
schemeMap: Map<string, boolean>;
|
|
32
|
+
constructor(opt: object, domPurifyInstance: object);
|
|
33
|
+
enter(url: string): boolean;
|
|
34
|
+
leave(url: string): void;
|
|
35
|
+
}
|
|
36
|
+
export declare class URLSanitizer extends URISchemes {
|
|
26
37
|
#private;
|
|
27
|
-
private static #uponSanitizeAttribute;
|
|
28
38
|
constructor();
|
|
29
|
-
private #
|
|
30
|
-
private #purify;
|
|
39
|
+
private #executeSanitize;
|
|
31
40
|
private #process;
|
|
32
41
|
private #resolveSchemeRules;
|
|
42
|
+
private #registerScheme;
|
|
43
|
+
private #isValidScheme;
|
|
33
44
|
private #parseAndVerifyURL;
|
|
34
45
|
private #isSchemeAllowed;
|
|
35
46
|
private #sanitizeDataURL;
|
|
47
|
+
private #purify;
|
|
48
|
+
private #handleSanitizeAttribute;
|
|
36
49
|
private #sanitizeStandardURL;
|
|
37
50
|
sanitize(url: string, opt?: {
|
|
38
51
|
allow?: string[];
|
|
@@ -43,7 +56,7 @@ declare class URLSanitizer extends URISchemes {
|
|
|
43
56
|
maxBlobSize?: number;
|
|
44
57
|
maxLength?: number;
|
|
45
58
|
}): string | null;
|
|
46
|
-
inspect(url: string
|
|
59
|
+
inspect(url: string): InspectedURLResult;
|
|
47
60
|
get(): string[];
|
|
48
61
|
has(scheme: string): boolean;
|
|
49
62
|
add(scheme: string): string[];
|
|
@@ -51,7 +64,6 @@ declare class URLSanitizer extends URISchemes {
|
|
|
51
64
|
reset(): void;
|
|
52
65
|
verify(uri: string, schemes?: Set<string>): boolean;
|
|
53
66
|
}
|
|
54
|
-
export { URLSanitizer };
|
|
55
67
|
declare const urlSanitizer: URLSanitizer;
|
|
56
68
|
export declare const sanitizeURL: (url: string, opt?: {
|
|
57
69
|
allow?: string[];
|
|
@@ -73,9 +85,6 @@ export declare const sanitizeURLSync: (url: string, opt?: {
|
|
|
73
85
|
maxLength?: number;
|
|
74
86
|
}) => string | null;
|
|
75
87
|
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>;
|
|
88
|
+
export declare const isValidURI: (uri: string) => boolean;
|
|
80
89
|
export declare const isURISync: (uri: string) => boolean;
|
|
81
90
|
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>, allowCustom?: boolean): 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;
|