url-sanitizer 2.3.1 → 4.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 +166 -74
- package/dist/url-sanitizer-wo-dompurify.min.js +1 -1
- package/dist/url-sanitizer-wo-dompurify.min.js.map +4 -4
- package/dist/url-sanitizer.min.js +4 -4
- package/dist/url-sanitizer.min.js.map +4 -4
- package/package.json +16 -14
- package/src/lib/file/text-chars.json +1 -1
- package/src/mjs/common.js +7 -6
- package/src/mjs/constant.js +39 -19
- package/src/mjs/dompurify.js +6 -0
- package/src/mjs/file-reader.js +125 -68
- package/src/mjs/regexp.js +79 -0
- package/src/mjs/sanitizer.js +416 -249
- package/src/mjs/text-chars.js +68 -0
- package/src/mjs/uri-util.js +176 -172
- package/types/mjs/common.d.ts +2 -2
- package/types/mjs/constant.d.ts +5 -17
- package/types/mjs/dompurify.d.ts +1 -1
- package/types/mjs/file-reader.d.ts +10 -10
- package/types/mjs/regexp.d.ts +12 -0
- package/types/mjs/sanitizer.d.ts +26 -15
- package/types/mjs/text-chars.d.ts +3 -0
- package/types/mjs/uri-util.d.ts +9 -10
- package/src/mjs/lib-util.js +0 -37
- package/types/mjs/lib-util.d.ts +0 -3
package/package.json
CHANGED
|
@@ -19,35 +19,34 @@
|
|
|
19
19
|
"exports": {
|
|
20
20
|
".": {
|
|
21
21
|
"types": "./types/index.d.ts",
|
|
22
|
-
"browser": "./dist/url-sanitizer
|
|
22
|
+
"browser": "./dist/url-sanitizer.min.js",
|
|
23
23
|
"default": "./src/index.js"
|
|
24
24
|
},
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"dompurify": "^3.4.
|
|
28
|
+
"dompurify": "^3.4.7",
|
|
29
29
|
"jsdom": "^29.1.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@braintree/sanitize-url": "^7.1.2",
|
|
33
33
|
"@types/jsdom": "^28.0.3",
|
|
34
|
-
"@types/node": "^25.
|
|
34
|
+
"@types/node": "^25.9.1",
|
|
35
35
|
"c8": "^11.0.0",
|
|
36
36
|
"chai": "^6.2.2",
|
|
37
|
-
"commander": "^
|
|
37
|
+
"commander": "^15.0.0",
|
|
38
38
|
"copyfiles-ng": "^2.4.7",
|
|
39
39
|
"esbuild": "^0.28.0",
|
|
40
40
|
"eslint": "^9.39.4",
|
|
41
|
-
"eslint-plugin-jsdoc": "^
|
|
41
|
+
"eslint-plugin-jsdoc": "^63.0.0",
|
|
42
42
|
"eslint-plugin-regexp": "^3.1.0",
|
|
43
43
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
44
44
|
"fast-check": "^4.8.0",
|
|
45
45
|
"globals": "^17.6.0",
|
|
46
46
|
"http-server": "^14.1.1",
|
|
47
47
|
"mitata": "^1.0.34",
|
|
48
|
-
"mocha": "^11.7.
|
|
48
|
+
"mocha": "^11.7.6",
|
|
49
49
|
"neostandard": "^0.13.0",
|
|
50
|
-
"npm-run-all2": "^8.0.4",
|
|
51
50
|
"papaparse": "^5.5.3",
|
|
52
51
|
"sinon": "^22.0.0",
|
|
53
52
|
"strict-url-sanitise": "^0.0.1",
|
|
@@ -69,20 +68,23 @@
|
|
|
69
68
|
"scripts": {
|
|
70
69
|
"bench": "node benchmark/benchmark.js",
|
|
71
70
|
"build": "npm run include && npm run tsc && npm run lint && npm test && npm run copy && npm run bundle",
|
|
72
|
-
"bundle": "npm
|
|
71
|
+
"bundle": "npm run bundle:min && npm run bundle:min-wo-dompurify",
|
|
73
72
|
"bundle:min": "esbuild --format=esm --platform=browser --outfile=dist/url-sanitizer.min.js --bundle --minify --sourcemap bundle/index.js",
|
|
74
73
|
"bundle:min-wo-dompurify": "esbuild --format=esm --platform=browser --outfile=dist/url-sanitizer-wo-dompurify.min.js --bundle --minify --sourcemap bundle_wo_dompurify/index.js",
|
|
75
74
|
"char": "node scripts/index char -i",
|
|
76
|
-
"copy": "npm
|
|
77
|
-
"copy:src": "copyfiles-ng src/**/* bundle --up=1 --verbose --exclude=src/mjs/dompurify.js --exclude=src/mjs/file-reader.js
|
|
78
|
-
"copy:src-wo-dompurify": "copyfiles-ng src/**/* bundle_wo_dompurify --up=1 --verbose --exclude=src/mjs/dompurify.js --exclude=src/mjs/file-reader.js
|
|
79
|
-
"copy:repl": "copyfiles-ng repl/dompurify.js repl/file-reader.js
|
|
80
|
-
"copy:repl-wo-dompurify": "copyfiles-ng repl/dompurify-global.js repl/file-reader.js
|
|
75
|
+
"copy": "npm run copy:src && npm run copy:src-wo-dompurify && npm run copy:repl && npm run copy:repl-wo-dompurify",
|
|
76
|
+
"copy:src": "copyfiles-ng src/**/* bundle --up=1 --verbose --exclude=src/mjs/dompurify.js --exclude=src/mjs/file-reader.js",
|
|
77
|
+
"copy:src-wo-dompurify": "copyfiles-ng src/**/* bundle_wo_dompurify --up=1 --verbose --exclude=src/mjs/dompurify.js --exclude=src/mjs/file-reader.js",
|
|
78
|
+
"copy:repl": "copyfiles-ng repl/dompurify.js repl/file-reader.js bundle/mjs --up=1 --verbose",
|
|
79
|
+
"copy:repl-wo-dompurify": "copyfiles-ng repl/dompurify-global.js repl/file-reader.js bundle_wo_dompurify/mjs --up=1 --verbose && node scripts/index rename --oldpath=bundle_wo_dompurify/mjs/dompurify-global.js --newpath=bundle_wo_dompurify/mjs/dompurify.js -i",
|
|
81
80
|
"include": "node scripts/index include --dir=iana -i",
|
|
82
81
|
"lint": "eslint --fix .",
|
|
83
82
|
"test": "c8 --reporter=text mocha --exit test/*.test.js",
|
|
84
83
|
"test:bdd": "http-server ./ -c-1 -o test/bdd/url-sanitizer.test.html",
|
|
85
84
|
"tsc": "node scripts/index clean --dir=types -i && npx tsc"
|
|
86
85
|
},
|
|
87
|
-
"
|
|
86
|
+
"engines": {
|
|
87
|
+
"node": ">=22.13"
|
|
88
|
+
},
|
|
89
|
+
"version": "4.0.0"
|
|
88
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
[7,8,9,10,11,12,13,
|
|
1
|
+
[7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255]
|
package/src/mjs/common.js
CHANGED
|
@@ -6,16 +6,17 @@
|
|
|
6
6
|
import { TYPE_FROM, TYPE_TO } from './constant.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* @
|
|
9
|
+
* Gets the type of the given object.
|
|
10
|
+
* Returns the object class name (e.g., "String", "Array", "Null", "Undefined").
|
|
11
|
+
* @param {unknown} o - The object to check.
|
|
12
|
+
* @returns {string} The specific type of the object.
|
|
12
13
|
*/
|
|
13
14
|
export const getType = o =>
|
|
14
15
|
Object.prototype.toString.call(o).slice(TYPE_FROM, TYPE_TO);
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
|
-
* is string
|
|
18
|
-
* @param {
|
|
19
|
-
* @returns {boolean}
|
|
18
|
+
* Checks if the given value is a string.
|
|
19
|
+
* @param {unknown} o - The value to check.
|
|
20
|
+
* @returns {boolean} True if the value is a string, false otherwise.
|
|
20
21
|
*/
|
|
21
22
|
export const isString = o => typeof o === 'string' || o instanceof String;
|
package/src/mjs/constant.js
CHANGED
|
@@ -2,26 +2,46 @@
|
|
|
2
2
|
* constant.js
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* The chunk size in bytes (8KB) used for processing data arrays to prevent stack overflow.
|
|
7
|
+
* @type {number}
|
|
8
|
+
*/
|
|
6
9
|
export const CHUNK_SIZE = 8 * 1024;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The radix used for decimal string conversions and calculations.
|
|
13
|
+
* @type {number}
|
|
14
|
+
*/
|
|
15
|
+
export const DECI = 10;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The radix used for hexadecimal string conversions and calculations.
|
|
19
|
+
* @type {number}
|
|
20
|
+
*/
|
|
7
21
|
export const HEX = 16;
|
|
8
|
-
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The maximum allowed size for a Blob in bytes (16MB).
|
|
25
|
+
* @type {number}
|
|
26
|
+
*/
|
|
27
|
+
export const MAX_BLOB_SIZE = 16 * 1024 * 1024;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The maximum allowed nesting depth for recurse.
|
|
31
|
+
* @type {number}
|
|
32
|
+
*/
|
|
33
|
+
export const MAX_NEST = 16;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The start index for extracting the core type name from an object.
|
|
37
|
+
* (e.g., skips "[object " in "[object String]")
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
9
40
|
export const TYPE_FROM = 8;
|
|
10
|
-
export const TYPE_TO = -1;
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export const
|
|
18
|
-
export const REG_NUM_REF = /&#(x(?:00)?[\dA-F]{2}|0?\d{1,3});?/gi;
|
|
19
|
-
export const REG_MIME_DOM =
|
|
20
|
-
/^(?:application\/(?:[\w#&.;-]+\+)?x|image\/svg\+x|text\/(?:ht|x))ml;?/;
|
|
21
|
-
export const REG_MIME_TEXT = /^text\/[\w#&.;-]+/;
|
|
22
|
-
export const REG_SCHEME = /^[a-z][\da-z+.-]*$/;
|
|
23
|
-
export const REG_SCHEME_EXT = /^(?:ext|web)\+[a-z]+$/;
|
|
24
|
-
export const REG_SCRIPT = /(?:java|vb)script/;
|
|
25
|
-
export const REG_SCRIPT_BLOB = /(?:java|vb)script|blob/;
|
|
26
|
-
export const REG_TAG_QUOT = /%(?:22|27|3C|3E)|[\x22\x27<>]/;
|
|
27
|
-
export const REG_URL_ENC = /^%[\dA-F]{2}$/i;
|
|
42
|
+
/**
|
|
43
|
+
* The end index for extracting the core type name from an object.
|
|
44
|
+
* (e.g., excludes the trailing "]" in "[object String]")
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
export const TYPE_TO = -1;
|
package/src/mjs/dompurify.js
CHANGED
|
@@ -6,4 +6,10 @@ import DOMPurify from 'dompurify';
|
|
|
6
6
|
import { JSDOM } from 'jsdom';
|
|
7
7
|
|
|
8
8
|
const { window } = new JSDOM('');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An instance of DOMPurify initialized with a JSDOM window context.
|
|
12
|
+
* Used to sanitize HTML/SVG strings.
|
|
13
|
+
* @type {import('dompurify')}
|
|
14
|
+
*/
|
|
9
15
|
export const domPurify = DOMPurify(window);
|
package/src/mjs/file-reader.js
CHANGED
|
@@ -4,18 +4,31 @@
|
|
|
4
4
|
|
|
5
5
|
/* shared */
|
|
6
6
|
import { getType, isString } from './common.js';
|
|
7
|
-
import { textChars } from './lib-util.js';
|
|
8
7
|
|
|
9
8
|
/* constants */
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
} from './
|
|
9
|
+
import { CHUNK_SIZE } from './constant.js';
|
|
10
|
+
import { REG_CHARSET, REG_MIME_DOM, REG_MIME_TEXT } from './regexp.js';
|
|
11
|
+
import { CTRL_CHAR_CODES } from './text-chars.js';
|
|
13
12
|
const DONE = 2;
|
|
14
13
|
const EMPTY = 0;
|
|
15
14
|
const LOADING = 1;
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
17
|
+
* Converts a Uint8Array to a binary string in chunks to prevent stack overflow.
|
|
18
|
+
* @private
|
|
19
|
+
* @param {Uint8Array} uint8arr - The array to convert.
|
|
20
|
+
* @returns {string} The resulting binary string.
|
|
21
|
+
*/
|
|
22
|
+
const getBinaryString = uint8arr => {
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for (let i = 0; i < uint8arr.length; i += CHUNK_SIZE) {
|
|
25
|
+
chunks.push(String.fromCharCode(...uint8arr.subarray(i, i + CHUNK_SIZE)));
|
|
26
|
+
}
|
|
27
|
+
return chunks.join('');
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Represents a progress event.
|
|
19
32
|
* @see {@link https://xhr.spec.whatwg.org/#interface-progressevent}
|
|
20
33
|
*/
|
|
21
34
|
export class ProgressEvent extends Event {
|
|
@@ -25,15 +38,15 @@ export class ProgressEvent extends Event {
|
|
|
25
38
|
#total;
|
|
26
39
|
|
|
27
40
|
/**
|
|
28
|
-
*
|
|
29
|
-
* @param {string} type - type
|
|
30
|
-
* @param {object} [opt] -
|
|
31
|
-
* @param {boolean} [opt.bubbles] - bubbles
|
|
32
|
-
* @param {boolean} [opt.cancelable] - cancelable
|
|
33
|
-
* @param {boolean} [opt.composed] - composed
|
|
34
|
-
* @param {boolean} [opt.lengthComputable] -
|
|
35
|
-
* @param {number} [opt.loaded] -
|
|
36
|
-
* @param {number} [opt.total] - total
|
|
41
|
+
* Creates a new ProgressEvent instance.
|
|
42
|
+
* @param {string} type - The event type.
|
|
43
|
+
* @param {object} [opt] - Initialization options.
|
|
44
|
+
* @param {boolean} [opt.bubbles] - Whether the event bubbles.
|
|
45
|
+
* @param {boolean} [opt.cancelable] - Whether the event is cancelable.
|
|
46
|
+
* @param {boolean} [opt.composed] - Whether the event is composed.
|
|
47
|
+
* @param {boolean} [opt.lengthComputable] - Whether the length is computable.
|
|
48
|
+
* @param {number} [opt.loaded] - The amount of work already performed.
|
|
49
|
+
* @param {number} [opt.total] - The total amount of work to be performed.
|
|
37
50
|
*/
|
|
38
51
|
constructor(type, opt = {}) {
|
|
39
52
|
const {
|
|
@@ -49,23 +62,37 @@ export class ProgressEvent extends Event {
|
|
|
49
62
|
this.#total = Number.isInteger(total) && total > 0 ? total : 0;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Indicates whether the total size of the transfer is computable.
|
|
67
|
+
* @type {boolean}
|
|
68
|
+
*/
|
|
53
69
|
get lengthComputable() {
|
|
54
70
|
return this.#lengthComputable;
|
|
55
71
|
}
|
|
56
72
|
|
|
73
|
+
/**
|
|
74
|
+
* The number of bytes already loaded.
|
|
75
|
+
* @type {number}
|
|
76
|
+
*/
|
|
57
77
|
get loaded() {
|
|
58
78
|
return this.#loaded;
|
|
59
79
|
}
|
|
60
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The total number of bytes to be loaded.
|
|
83
|
+
* @type {number}
|
|
84
|
+
*/
|
|
61
85
|
get total() {
|
|
62
86
|
return this.#total;
|
|
63
87
|
}
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
/**
|
|
67
|
-
*
|
|
91
|
+
* Asynchronously reads the contents of files (or raw data buffers).
|
|
68
92
|
* @see {@link https://w3c.github.io/FileAPI/#APIASynch}
|
|
93
|
+
* @property {number} EMPTY - The read operation has not started yet.
|
|
94
|
+
* @property {number} LOADING - The data is currently being read.
|
|
95
|
+
* @property {number} DONE - The read operation has completed successfully or failed.
|
|
69
96
|
*/
|
|
70
97
|
export class FileReader extends EventTarget {
|
|
71
98
|
/* private fields */
|
|
@@ -75,7 +102,7 @@ export class FileReader extends EventTarget {
|
|
|
75
102
|
#terminate = false;
|
|
76
103
|
|
|
77
104
|
/**
|
|
78
|
-
*
|
|
105
|
+
* Creates a new FileReader instance.
|
|
79
106
|
*/
|
|
80
107
|
constructor() {
|
|
81
108
|
super();
|
|
@@ -84,38 +111,48 @@ export class FileReader extends EventTarget {
|
|
|
84
111
|
this.DONE = DONE;
|
|
85
112
|
}
|
|
86
113
|
|
|
87
|
-
|
|
114
|
+
/**
|
|
115
|
+
* The error that occurred while reading the file.
|
|
116
|
+
* @type {Error|DOMException|null}
|
|
117
|
+
*/
|
|
88
118
|
get error() {
|
|
89
119
|
return this.#error;
|
|
90
120
|
}
|
|
91
121
|
|
|
122
|
+
/**
|
|
123
|
+
* The current state of the FileReader operation.
|
|
124
|
+
* @type {number}
|
|
125
|
+
*/
|
|
92
126
|
get readyState() {
|
|
93
127
|
return this.#state;
|
|
94
128
|
}
|
|
95
129
|
|
|
130
|
+
/**
|
|
131
|
+
* The file's contents. Only valid after the read operation is complete.
|
|
132
|
+
* @type {string|ArrayBuffer|null}
|
|
133
|
+
*/
|
|
96
134
|
get result() {
|
|
97
135
|
return this.#result;
|
|
98
136
|
}
|
|
99
137
|
|
|
100
138
|
/**
|
|
101
|
-
*
|
|
102
|
-
* @
|
|
103
|
-
* @
|
|
139
|
+
* Dispatches a progress event.
|
|
140
|
+
* @private
|
|
141
|
+
* @param {string} type - The type of the event to dispatch.
|
|
142
|
+
* @returns {boolean} True if dispatched and not canceled, false otherwise.
|
|
104
143
|
*/
|
|
105
144
|
_dispatchProgressEvent(type) {
|
|
106
|
-
if (isString(type)) {
|
|
107
|
-
type = type.trim();
|
|
108
|
-
if (!/abort|error|load(?:end|start)?|progress/.test(type)) {
|
|
109
|
-
this.#error = new DOMException('Invalid state.', 'InvalidStateError');
|
|
110
|
-
this._dispatchProgressEvent('error');
|
|
111
|
-
throw this.#error;
|
|
112
|
-
}
|
|
113
|
-
} else {
|
|
145
|
+
if (!isString(type)) {
|
|
114
146
|
this.#error = new TypeError(`Expected String but got ${getType(type)}.`);
|
|
115
147
|
this._dispatchProgressEvent('error');
|
|
116
148
|
throw this.#error;
|
|
117
149
|
}
|
|
118
|
-
|
|
150
|
+
type = type.trim();
|
|
151
|
+
if (!/abort|error|load(?:end|start)?|progress/.test(type)) {
|
|
152
|
+
this.#error = new DOMException('Invalid state.', 'InvalidStateError');
|
|
153
|
+
this._dispatchProgressEvent('error');
|
|
154
|
+
throw this.#error;
|
|
155
|
+
} else if (type === 'error' && !this.#error) {
|
|
119
156
|
this.#error = new Error('Unknown error.');
|
|
120
157
|
}
|
|
121
158
|
const evt = new ProgressEvent(type, {
|
|
@@ -128,27 +165,40 @@ export class FileReader extends EventTarget {
|
|
|
128
165
|
}
|
|
129
166
|
|
|
130
167
|
/**
|
|
131
|
-
*
|
|
168
|
+
* Aborts the ongoing read operation.
|
|
132
169
|
* @returns {void}
|
|
133
170
|
*/
|
|
134
171
|
abort() {
|
|
172
|
+
if (this.#state === this.EMPTY || this.#state === this.DONE) {
|
|
173
|
+
this.#result = null;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
135
176
|
this.#state = this.DONE;
|
|
136
177
|
this.#result = null;
|
|
137
178
|
this.#terminate = true;
|
|
179
|
+
this.#error =
|
|
180
|
+
new DOMException('The read operation was aborted.', 'AbortError');
|
|
138
181
|
this._dispatchProgressEvent('abort');
|
|
139
182
|
this._dispatchProgressEvent('loadend');
|
|
140
183
|
}
|
|
141
184
|
|
|
142
185
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @
|
|
145
|
-
* @param {
|
|
146
|
-
* @param {string}
|
|
147
|
-
* @
|
|
186
|
+
* Reads the given blob based on the specified format.
|
|
187
|
+
* @private
|
|
188
|
+
* @param {Blob} blob - The target Blob object.
|
|
189
|
+
* @param {string} format - The format in which to read the blob data.
|
|
190
|
+
* @param {string} [encoding] - The character encoding for text formats.
|
|
191
|
+
* @returns {Promise<void>}
|
|
148
192
|
*/
|
|
149
193
|
async _read(blob, format, encoding = '') {
|
|
150
|
-
if (!(blob instanceof Blob
|
|
151
|
-
|
|
194
|
+
if (!(blob instanceof Blob)) {
|
|
195
|
+
throw new TypeError(`Expected Blob but got ${getType(blob)}.`);
|
|
196
|
+
}
|
|
197
|
+
if (!isString(format)) {
|
|
198
|
+
throw new TypeError(`Expected String but got ${getType(format)}.`);
|
|
199
|
+
}
|
|
200
|
+
if (!isString(encoding)) {
|
|
201
|
+
throw new TypeError(`Expected String but got ${getType(encoding)}.`);
|
|
152
202
|
}
|
|
153
203
|
if (!this.#terminate) {
|
|
154
204
|
if (this.#state === this.LOADING) {
|
|
@@ -165,32 +215,39 @@ export class FileReader extends EventTarget {
|
|
|
165
215
|
const mediaTypes = type ? type.split(';') : [];
|
|
166
216
|
const buffer = await blob.arrayBuffer();
|
|
167
217
|
const uint8arr = new Uint8Array(buffer);
|
|
168
|
-
const chunkSize = CHUNK_SIZE;
|
|
169
|
-
let binary = '';
|
|
170
|
-
for (let i = 0; i < uint8arr.length; i += chunkSize) {
|
|
171
|
-
binary += String.fromCharCode(...uint8arr.subarray(i, i + chunkSize));
|
|
172
|
-
}
|
|
173
218
|
this._dispatchProgressEvent('loadstart');
|
|
174
219
|
switch (format) {
|
|
175
|
-
case 'arrayBuffer':
|
|
220
|
+
case 'arrayBuffer': {
|
|
176
221
|
res = buffer;
|
|
177
222
|
this._dispatchProgressEvent('progress');
|
|
178
223
|
break;
|
|
179
|
-
|
|
180
|
-
|
|
224
|
+
}
|
|
225
|
+
case 'binaryString': {
|
|
226
|
+
res = getBinaryString(uint8arr);
|
|
181
227
|
this._dispatchProgressEvent('progress');
|
|
182
228
|
break;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
229
|
+
}
|
|
230
|
+
case 'dataURL': {
|
|
231
|
+
const mime = mediaTypes.length > 0 ? mediaTypes.join(';') : '';
|
|
232
|
+
const mimeStr = mime ? `${mime};base64` : 'base64';
|
|
233
|
+
if (typeof globalThis.Buffer !== 'undefined') {
|
|
234
|
+
const base64 = globalThis.Buffer.from(buffer).toString('base64');
|
|
235
|
+
res = `data:${mimeStr},${base64}`;
|
|
236
|
+
} else {
|
|
237
|
+
res = `data:${mimeStr},${btoa(getBinaryString(uint8arr))}`;
|
|
187
238
|
}
|
|
188
|
-
res = `data:${mediaTypes.join(';')},${btoa(binary)}`;
|
|
189
239
|
this._dispatchProgressEvent('progress');
|
|
190
240
|
break;
|
|
241
|
+
}
|
|
191
242
|
case 'text': {
|
|
192
|
-
|
|
193
|
-
|
|
243
|
+
let isSafeText = true;
|
|
244
|
+
for (const i of uint8arr) {
|
|
245
|
+
if (CTRL_CHAR_CODES.has(i)) {
|
|
246
|
+
isSafeText = false;
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if (isSafeText) {
|
|
194
251
|
let charset;
|
|
195
252
|
for (const media of mediaTypes) {
|
|
196
253
|
if (REG_CHARSET.test(media)) {
|
|
@@ -216,14 +273,14 @@ export class FileReader extends EventTarget {
|
|
|
216
273
|
}
|
|
217
274
|
if (REG_MIME_DOM.test(type)) {
|
|
218
275
|
if (encoding === charset || (encoding === 'utf8' && !charset)) {
|
|
219
|
-
res =
|
|
276
|
+
res = getBinaryString(uint8arr);
|
|
220
277
|
this._dispatchProgressEvent('progress');
|
|
221
278
|
}
|
|
222
279
|
} else if (REG_MIME_TEXT.test(type)) {
|
|
223
280
|
if (encoding === charset ||
|
|
224
281
|
(encoding === 'utf8' &&
|
|
225
282
|
(!charset || charset === 'us-ascii'))) {
|
|
226
|
-
res =
|
|
283
|
+
res = getBinaryString(uint8arr);
|
|
227
284
|
this._dispatchProgressEvent('progress');
|
|
228
285
|
}
|
|
229
286
|
}
|
|
@@ -254,37 +311,37 @@ export class FileReader extends EventTarget {
|
|
|
254
311
|
}
|
|
255
312
|
|
|
256
313
|
/**
|
|
257
|
-
*
|
|
258
|
-
* @param {
|
|
259
|
-
* @returns {Promise
|
|
314
|
+
* Reads the contents of the specified Blob as an ArrayBuffer.
|
|
315
|
+
* @param {Blob} blob - The target Blob object.
|
|
316
|
+
* @returns {Promise<void>}
|
|
260
317
|
*/
|
|
261
318
|
async readAsArrayBuffer(blob) {
|
|
262
319
|
await this._read(blob, 'arrayBuffer');
|
|
263
320
|
}
|
|
264
321
|
|
|
265
322
|
/**
|
|
266
|
-
*
|
|
267
|
-
* @param {
|
|
268
|
-
* @returns {Promise
|
|
323
|
+
* Reads the contents of the specified Blob as a binary string.
|
|
324
|
+
* @param {Blob} blob - The target Blob object.
|
|
325
|
+
* @returns {Promise<void>}
|
|
269
326
|
*/
|
|
270
327
|
async readAsBinaryString(blob) {
|
|
271
328
|
await this._read(blob, 'binaryString');
|
|
272
329
|
}
|
|
273
330
|
|
|
274
331
|
/**
|
|
275
|
-
*
|
|
276
|
-
* @param {
|
|
277
|
-
* @returns {Promise
|
|
332
|
+
* Reads the contents of the specified Blob as a base64-encoded data URL.
|
|
333
|
+
* @param {Blob} blob - The target Blob object.
|
|
334
|
+
* @returns {Promise<void>}
|
|
278
335
|
*/
|
|
279
336
|
async readAsDataURL(blob) {
|
|
280
337
|
await this._read(blob, 'dataURL');
|
|
281
338
|
}
|
|
282
339
|
|
|
283
340
|
/**
|
|
284
|
-
*
|
|
285
|
-
* @param {
|
|
286
|
-
* @param {string} [encoding] - encoding
|
|
287
|
-
* @returns {Promise
|
|
341
|
+
* Reads the contents of the specified Blob as text.
|
|
342
|
+
* @param {Blob} blob - The target Blob object.
|
|
343
|
+
* @param {string} [encoding] - The character encoding to use.
|
|
344
|
+
* @returns {Promise<void>}
|
|
288
345
|
*/
|
|
289
346
|
async readAsText(blob, encoding) {
|
|
290
347
|
await this._read(blob, 'text', encoding);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* regexp.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Matches and captures the charset parameter in a media type string.
|
|
7
|
+
* @type {RegExp}
|
|
8
|
+
*/
|
|
9
|
+
export const REG_CHARSET = /^charset=([\w#&.;-]+)$/;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Matches a trailing empty fragment identifier (#).
|
|
13
|
+
* @type {RegExp}
|
|
14
|
+
*/
|
|
15
|
+
export const REG_HASH = /(?:#|%23)$/;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Matches MIME types that contain DOM-parsable content (HTML, XML, SVG).
|
|
19
|
+
* @type {RegExp}
|
|
20
|
+
*/
|
|
21
|
+
export const REG_MIME_DOM =
|
|
22
|
+
/^(?:application\/(?:[\w#&.;-]+\+)?x|image\/svg\+x|text\/(?:ht|x))ml;?/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Matches general text MIME types.
|
|
26
|
+
* @type {RegExp}
|
|
27
|
+
*/
|
|
28
|
+
export const REG_MIME_TEXT = /^text\/[\w#&.;-]+/;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Matches a trailing empty query string (?), ensuring it is not part of a hash.
|
|
32
|
+
* @type {RegExp}
|
|
33
|
+
*/
|
|
34
|
+
export const REG_QUERY = /(?<!(?:#|%23).*)(?:\?|%3F)$/;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validates a string against standard IANA URI scheme syntax.
|
|
38
|
+
* @type {RegExp}
|
|
39
|
+
*/
|
|
40
|
+
export const REG_SCHEME = /^[a-z][\da-z+.-]*$/;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Matches web custom schemes (`web+`) or application-specific schemes (`ext+`).
|
|
44
|
+
* - `web+` schemes comply with the HTML Standard.
|
|
45
|
+
* - `ext+` schemes comply with RFC 3986 but disallow `+` in the suffix.
|
|
46
|
+
* @type {RegExp}
|
|
47
|
+
*/
|
|
48
|
+
export const REG_SCHEME_EXT = /^(?:web\+[a-z]+|ext\+[a-z][\da-z.-]*)$/;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Matches executable script schemes (javascript or vbscript), case-insensitive.
|
|
52
|
+
* @type {RegExp}
|
|
53
|
+
*/
|
|
54
|
+
export const REG_SCRIPT = /^(?:java|vb)script$/i;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Matches script-executing or data-fetching schemes.
|
|
58
|
+
* @type {RegExp}
|
|
59
|
+
*/
|
|
60
|
+
export const REG_SCRIPT_BLOB = /^(?:(?:java|vb)script|blob)$/i;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Matches HTML tags, quotes, and their encoded or character reference variants.
|
|
64
|
+
* @type {RegExp}
|
|
65
|
+
*/
|
|
66
|
+
export const REG_TAG_QUOT =
|
|
67
|
+
/["'<>]|%(?:22|27|3C|3E)|&(?:apos|lt|gt|quot|#0*(?:34|39|60|62)|#x0*(?:22|27|3C|3E));/i;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Matches a valid percent-encoded character format (%XX).
|
|
71
|
+
* @type {RegExp}
|
|
72
|
+
*/
|
|
73
|
+
export const REG_URL_ENC = /^%[\dA-F]{2}$/i;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Validates relative URLs and flags invalid patterns.
|
|
77
|
+
* @type {RegExp}
|
|
78
|
+
*/
|
|
79
|
+
export const REG_VERIFY_RELATIVE = /^(?:\/{2,}|\\|[a-z][a-z\d+\-.]*:[^/])/i;
|