url-sanitizer 2.0.13 → 2.2.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 +128 -175
- 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 -3
- package/dist/url-sanitizer.min.js.map +3 -3
- package/package.json +5 -4
- package/src/lib/iana/uri-schemes.json +1 -1
- package/src/mjs/constant.js +2 -0
- package/src/mjs/file-reader.js +8 -2
- package/src/mjs/sanitizer.js +36 -12
- package/src/mjs/uri-util.js +19 -8
- package/types/mjs/constant.d.ts +2 -0
- package/types/mjs/sanitizer.d.ts +1 -0
- package/types/mjs/uri-util.d.ts +1 -1
package/README.md
CHANGED
|
@@ -5,77 +5,121 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/url-sanitizer)
|
|
6
6
|
[](https://github.com/asamuzaK/urlSanitizer/releases)
|
|
7
7
|
|
|
8
|
-
URL sanitizer for Node.js, browsers and
|
|
9
|
-
|
|
10
|
-
It also
|
|
11
|
-
|
|
8
|
+
A robust URL sanitizer for Node.js, browsers, and websites.
|
|
9
|
+
It sanitizes not only regular URLs but also deeply inspects `data` URLs and `blob` URLs.
|
|
10
|
+
It also provides built-in utilities to parse URLs and verify URI schemes.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
* **Secure by Default**: Automatically denies `javascript:` and `vbscript:` schemes.
|
|
15
|
+
* **Deep Data URL Inspection**: Parses, decodes (including base64), and sanitizes nested data URLs.
|
|
16
|
+
* **Blob URL Support**: Converts and sanitizes blob URLs into safe data URLs (async).
|
|
17
|
+
* **DOMPurify Integration**: Safely purifies HTML/SVG content embedded within data URLs.
|
|
18
|
+
* **Pure ESM with TypeScript Support**: Works seamlessly across modern environments like Node.js, Deno, websites, and browsers (including WebExtensions).
|
|
19
|
+
|
|
20
|
+
## Table of Contents
|
|
21
|
+
* [Install](#install)
|
|
22
|
+
* [Usage](#usage)
|
|
23
|
+
* [API Reference](#api-reference)
|
|
24
|
+
* [sanitizeURL(url, opt)](#sanitizeurlurl-opt)
|
|
25
|
+
* [sanitizeURLSync(url, opt)](#sanitizeurlsyncurl-opt)
|
|
26
|
+
* [parseURL(url)](#parseurlurl)
|
|
27
|
+
* [parseURLSync(url)](#parseurlsyncurl)
|
|
28
|
+
* [isURI(uri)](#isuriuri)
|
|
29
|
+
* [isURISync(uri)](#isurisyncuri)
|
|
30
|
+
* [urlSanitizer Instance](#urlsanitizer)
|
|
31
|
+
* [Acknowledgments](#acknowledgments)
|
|
12
32
|
|
|
13
33
|
## Install
|
|
14
34
|
|
|
15
|
-
|
|
35
|
+
### Node.js
|
|
36
|
+
|
|
37
|
+
``` console
|
|
16
38
|
npm i url-sanitizer
|
|
17
39
|
```
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
### Deno
|
|
42
|
+
|
|
43
|
+
You can import directly in your code without installing:
|
|
44
|
+
``` javascript
|
|
45
|
+
import urlSanitizer from "npm:url-sanitizer";
|
|
46
|
+
```
|
|
22
47
|
|
|
23
|
-
|
|
48
|
+
### Browsers and Websites
|
|
24
49
|
|
|
25
|
-
|
|
26
|
-
If you use it, make sure DOMPurify is exposed globally, e.g. `window.DOMPurify`.
|
|
50
|
+
Standalone ESM builds are available in the `dist/` directory:
|
|
27
51
|
|
|
52
|
+
* node_modules/url-sanitizer/dist/url-sanitizer.min.js
|
|
53
|
+
* node_modules/url-sanitizer/dist/url-sanitizer-wo-dompurify.min.js
|
|
54
|
+
|
|
55
|
+
Alternatively, download them from [Releases](https://github.com/asamuzaK/urlSanitizer/releases).
|
|
56
|
+
**NOTE:** url-sanitizer-wo-dompurify.min.js is a lightweight build without [DOMPurify](https://www.npmjs.com/package/dompurify) bundled.
|
|
57
|
+
If you use this build, ensure DOMPurify is exposed globally (e.g., `window.DOMPurify`), otherwise the sanitizer will throw an error at runtime.
|
|
28
58
|
|
|
29
59
|
## Usage
|
|
30
60
|
|
|
31
|
-
```javascript
|
|
61
|
+
``` javascript
|
|
32
62
|
import urlSanitizer, {
|
|
33
63
|
isURI, isURISync, parseURL, parseURLSync, sanitizeURL, sanitizeURLSync
|
|
34
64
|
} from 'url-sanitizer';
|
|
35
65
|
```
|
|
36
66
|
|
|
67
|
+
## API Reference
|
|
68
|
+
|
|
69
|
+
### sanitizeURL(url, opt)
|
|
37
70
|
|
|
38
|
-
|
|
71
|
+
Sanitizes the given URL asynchronously.
|
|
39
72
|
|
|
40
|
-
|
|
41
|
-
*
|
|
42
|
-
* Given a `blob` URL, returns a sanitized `data` URL.
|
|
73
|
+
* `blob`, `data`, and `file` schemes must be explicitly allowed.
|
|
74
|
+
* Given a blob URL, it returns a sanitized **data** URL.
|
|
43
75
|
|
|
44
|
-
|
|
76
|
+
#### Parameters
|
|
77
|
+
|
|
78
|
+
* url **string** URL input.
|
|
79
|
+
* opt **object?** Options.
|
|
80
|
+
* opt.allow **Array<string>?** Array of allowed schemes, e.g., ['data'].
|
|
81
|
+
* opt.deny **Array<string>?** Array of denied schemes, e.g., ['web+foo'].
|
|
82
|
+
* opt.only **Array<string>?** Array of specific schemes to allow, e.g., ['git', 'https'].
|
|
83
|
+
`only` takes precedence over `allow` and `deny`.
|
|
84
|
+
#### Parameters
|
|
45
85
|
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
86
|
+
* url **string** URL input.
|
|
87
|
+
* opt **object?** Options.
|
|
88
|
+
* opt.allow **Array<string>?** Array of allowed schemes, e.g., ['data'].
|
|
89
|
+
* opt.deny **Array<string>?** Array of denied schemes, e.g., ['web+foo'].
|
|
90
|
+
* opt.only **Array<string>?** Array of specific schemes to allow, e.g., ['git', 'https'].
|
|
51
91
|
`only` takes precedence over `allow` and `deny`.
|
|
92
|
+
* opt.debug **boolean?** If `true`, outputs internal error/warning logs to the console. Default is `false`.
|
|
93
|
+
* opt.maxBlobSize **number?** Maximum allowed blob size in bytes. Default is `33554432` (32MB). Exceeding this limit will result in parsing failure to prevent memory exhaustion.
|
|
52
94
|
|
|
53
|
-
Returns **
|
|
95
|
+
**Returns** **Promise<string?>** Sanitized URL, nullable.
|
|
54
96
|
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
#### Samples
|
|
98
|
+
|
|
99
|
+
Sanitize tags and quotes:
|
|
100
|
+
``` javascript
|
|
57
101
|
const res1 = await sanitizeURL('https://example.com/?<script>alert(1)</script>');
|
|
58
102
|
// => 'https://example.com/'
|
|
59
103
|
|
|
60
104
|
const res1_2 = await sanitizeURL('https://example.com/" onclick="alert(1)"');
|
|
61
105
|
// => 'https://example.com/'
|
|
106
|
+
```
|
|
62
107
|
|
|
63
|
-
|
|
64
|
-
|
|
108
|
+
Parse and sanitize data URLs:
|
|
109
|
+
``` javascript
|
|
65
110
|
const res2 = await sanitizeURL('data:text/html,<div><script>alert(1);</script></div><p onclick="alert(2)"></p>', {
|
|
66
111
|
allow: ['data']
|
|
67
|
-
})
|
|
112
|
+
});
|
|
68
113
|
// => 'data:text/html,%3Cdiv%3E%3C/div%3E%3Cp%3E%3C/p%3E'
|
|
69
114
|
|
|
70
115
|
console.log(decodeURIComponent(res2));
|
|
71
116
|
// => 'data:text/html,<div></div><p></p>'
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
// Also can parse and sanitize base64 encoded data
|
|
118
|
+
// Also can parse and sanitize base64 encoded data:
|
|
75
119
|
const base64data3 = btoa('<div><script>alert(1);</script></div>');
|
|
76
120
|
const res3 = await sanitizeURL(`data:text/html;base64,${base64data3}`, {
|
|
77
121
|
allow: ['data']
|
|
78
|
-
})
|
|
122
|
+
});
|
|
79
123
|
// => 'data:text/html,%3Cdiv%3E%3C/div%3E'
|
|
80
124
|
|
|
81
125
|
console.log(decodeURIComponent(res3));
|
|
@@ -87,9 +131,10 @@ const res3_2 = await sanitizeURL(`data:text/html;base64,${base64data3_2}`);
|
|
|
87
131
|
|
|
88
132
|
console.log(decodeURIComponent(res3_2));
|
|
89
133
|
// => 'data:text/html,<div><img></div>'
|
|
134
|
+
```
|
|
90
135
|
|
|
91
|
-
|
|
92
|
-
|
|
136
|
+
Parse and sanitize blob URLs:
|
|
137
|
+
``` javascript
|
|
93
138
|
const blob4 = new Blob(['<svg><g onload="alert(1)"/></svg>'], {
|
|
94
139
|
type: 'image/svg+xml'
|
|
95
140
|
});
|
|
@@ -101,16 +146,18 @@ const res4 = await sanitizeURL(url4, {
|
|
|
101
146
|
|
|
102
147
|
console.log(decodeURIComponent(res4));
|
|
103
148
|
// => 'data:image/svg+xml,<svg><g></g></svg>'
|
|
149
|
+
```
|
|
104
150
|
|
|
105
|
-
|
|
106
|
-
|
|
151
|
+
Denies if the scheme matches the `deny` list:
|
|
152
|
+
``` javascript
|
|
107
153
|
const res5 = await sanitizeURL('web+foo://example.com', {
|
|
108
154
|
deny: ['web+foo']
|
|
109
155
|
});
|
|
110
156
|
// => null
|
|
157
|
+
```
|
|
111
158
|
|
|
112
|
-
|
|
113
|
-
|
|
159
|
+
Allows only if the scheme matches the `only` list:
|
|
160
|
+
``` javascript
|
|
114
161
|
const res6 = await sanitizeURL('http://example.com', {
|
|
115
162
|
only: ['data', 'git', 'https']
|
|
116
163
|
});
|
|
@@ -121,7 +168,6 @@ const res6_2 = await sanitizeURL('https://example.com/"onmouseover="alert(1)"',
|
|
|
121
168
|
});
|
|
122
169
|
// => 'https://example.com/'
|
|
123
170
|
|
|
124
|
-
|
|
125
171
|
// `only` also allows combination of the schemes in the list
|
|
126
172
|
const res7 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)</script>', {
|
|
127
173
|
only: ['data', 'git', 'https']
|
|
@@ -129,53 +175,38 @@ const res7 = await sanitizeURL('git+https://example.com/foo.git?<script>alert(1)
|
|
|
129
175
|
// => 'git+https://example.com/foo.git'
|
|
130
176
|
```
|
|
131
177
|
|
|
178
|
+
### sanitizeURLSync(url, opt)
|
|
132
179
|
|
|
133
|
-
|
|
180
|
+
Synchronous version of `sanitizeURL()`.
|
|
134
181
|
|
|
135
|
-
Synchronous version of the [sanitizeURL()](#sanitizeurlurl-opt).
|
|
136
182
|
* `data` and `file` schemes must be explicitly allowed.
|
|
137
|
-
* `blob` scheme is not supported
|
|
138
|
-
Use async [sanitizeURL()](#sanitizeurlurl-opt) for `blob`.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
## parseURL(url)
|
|
183
|
+
* `blob` scheme is not supported and will return `null`. Use the async version for `blob`.
|
|
142
184
|
|
|
143
|
-
|
|
144
|
-
* Blob URLs are simply parsed and not yet sanitized.
|
|
185
|
+
### parseURL(url)
|
|
145
186
|
|
|
146
|
-
|
|
187
|
+
Parses the given URL asynchronously.
|
|
147
188
|
|
|
148
|
-
*
|
|
189
|
+
* Blob URLs are simply parsed and not yet sanitized at this stage.
|
|
149
190
|
|
|
150
|
-
|
|
191
|
+
#### Parameters
|
|
151
192
|
|
|
152
|
-
|
|
193
|
+
* url **string** URL input.
|
|
153
194
|
|
|
154
|
-
|
|
195
|
+
**Returns** **Promise<ParsedURL>** Result.
|
|
155
196
|
|
|
156
|
-
|
|
197
|
+
#### ParsedURL
|
|
157
198
|
|
|
158
|
-
|
|
199
|
+
An object with additional properties based on the standard [URL](https://developer.mozilla.org/ja/docs/Web/API/URL) API.
|
|
159
200
|
|
|
160
|
-
* `input` **
|
|
161
|
-
* `valid` **
|
|
162
|
-
* `data` **
|
|
163
|
-
* `data.mime` **
|
|
164
|
-
* `data.base64` **
|
|
165
|
-
* `data.data` **
|
|
166
|
-
* `href` **
|
|
167
|
-
* `origin` **[string][1]?** Scheme, domain and port of the sanitized URL.
|
|
168
|
-
* `protocol` **[string][1]?** Protocol scheme of the sanitized URL.
|
|
169
|
-
* `username` **[string][1]?** Username specified before the domain name.
|
|
170
|
-
* `password` **[string][1]?** Password specified before the domain name.
|
|
171
|
-
* `host` **[string][1]?** Domain and port of the sanitized URL.
|
|
172
|
-
* `hostname` **[string][1]?** Domain of the sanitized URL.
|
|
173
|
-
* `port` **[string][1]?** Port number of the sanitized URL.
|
|
174
|
-
* `pathname` **[string][1]?** Path of the sanitized URL.
|
|
175
|
-
* `search` **[string][1]?** Query string of the sanitized URL.
|
|
176
|
-
* `hash` **[string][1]?** Fragment identifier of the sanitized URL.
|
|
201
|
+
* `input` **string** URL input.
|
|
202
|
+
* `valid` **boolean** Is valid URI.
|
|
203
|
+
* `data` **object?** Parsed result of data URL, nullable.
|
|
204
|
+
* `data.mime` **string?** MIME type.
|
|
205
|
+
* `data.base64` **boolean?** Is base64 encoded.
|
|
206
|
+
* `data.data` **string?** Data part of the data URL.
|
|
207
|
+
* `href`, `origin`, `protocol`, `username`, `password`, `host`, `hostname`, `port`, `pathname`, `search`, `hash` — **string?** Properties same as the standard URL API.
|
|
177
208
|
|
|
178
|
-
```javascript
|
|
209
|
+
``` javascript
|
|
179
210
|
const res1 = await parseURL('javascript:alert(1)');
|
|
180
211
|
/* => {
|
|
181
212
|
input: 'javascript:alert(1)',
|
|
@@ -249,25 +280,24 @@ const res5 = await parseURL(url5);
|
|
|
249
280
|
} */
|
|
250
281
|
```
|
|
251
282
|
|
|
283
|
+
### parseURLSync(url)
|
|
252
284
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
Synchronous version of the [parseURL()](#parseURL).
|
|
285
|
+
Synchronous version of `parseURL()`.
|
|
256
286
|
|
|
287
|
+
### isURI(uri)
|
|
257
288
|
|
|
258
|
-
|
|
289
|
+
Verifies if the given URI is valid and registered.
|
|
259
290
|
|
|
260
|
-
|
|
291
|
+
#### Parameters
|
|
261
292
|
|
|
262
|
-
|
|
293
|
+
* uri **string** URI input.
|
|
263
294
|
|
|
264
|
-
|
|
295
|
+
**Returns** **Promise<boolean>** Result.
|
|
265
296
|
|
|
266
|
-
|
|
267
|
-
* Always `true` for `web+*` and `ext+*` schemes, except `web+javascript`, `web+vbscript`, `ext+javascript`, `ext+vbscript`.
|
|
297
|
+
* Always `true` for `web+*` and `ext+*` schemes (except `web+javascript`, `web+vbscript`, `ext+javascript`, and `ext+vbscript`).
|
|
268
298
|
* `false` for `javascript` and `vbscript` schemes.
|
|
269
299
|
|
|
270
|
-
```javascript
|
|
300
|
+
``` javascript
|
|
271
301
|
const res1 = await isURI('https://example.com/foo');
|
|
272
302
|
// => true
|
|
273
303
|
|
|
@@ -287,122 +317,45 @@ const res6 = await isURI('web+javascript:alert(1)');
|
|
|
287
317
|
// => false
|
|
288
318
|
```
|
|
289
319
|
|
|
320
|
+
### isURISync(uri)
|
|
290
321
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
Synchronous version of the [isURI()](#isuriuri).
|
|
322
|
+
Synchronous version of `isURI()`.
|
|
294
323
|
|
|
295
|
-
|
|
324
|
+
### urlSanitizer
|
|
296
325
|
|
|
297
|
-
|
|
326
|
+
The instance of the sanitizer.
|
|
298
327
|
|
|
299
|
-
|
|
328
|
+
#### urlSanitizer.get()
|
|
300
329
|
|
|
301
|
-
|
|
330
|
+
Get a list of registered URI schemes (includes [IANA registered schemes](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml) by default).
|
|
302
331
|
|
|
303
|
-
|
|
332
|
+
* Historical schemes are omitted.
|
|
333
|
+
* `moz-extension` scheme is added.
|
|
304
334
|
|
|
305
|
-
|
|
306
|
-
* Includes schemes registered at [iana.org](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml "Uniform Resource Identifier (URI) Schemes") by default.
|
|
307
|
-
* Historical schemes omitted.
|
|
308
|
-
* `moz-extension` scheme added.
|
|
309
|
-
* Also includes custom schemes added via [urlSanitizer.add()](#urlsanitizeraddscheme).
|
|
310
|
-
|
|
311
|
-
```javascript
|
|
312
|
-
const schemes = urlSanitizer.get();
|
|
313
|
-
// => ['aaa', 'aaas', 'about', 'acap', 'acct', ...]
|
|
314
|
-
```
|
|
315
|
-
|
|
316
|
-
### urlSanitizer.has(scheme)
|
|
335
|
+
#### urlSanitizer.has(scheme)
|
|
317
336
|
|
|
318
337
|
Check if the given scheme is registered.
|
|
319
338
|
|
|
320
|
-
####
|
|
321
|
-
|
|
322
|
-
* `scheme` **[string][1]** Scheme.
|
|
339
|
+
#### urlSanitizer.add(scheme)
|
|
323
340
|
|
|
324
|
-
|
|
341
|
+
Add a scheme to the list of registered URI schemes (throws if `javascript` or `vbscript`).
|
|
325
342
|
|
|
326
|
-
|
|
327
|
-
const res1 = urlSanitizer.has('https');
|
|
328
|
-
// => true
|
|
329
|
-
|
|
330
|
-
const res2 = urlSanitizer.has('foo');
|
|
331
|
-
// => false
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
### urlSanitizer.add(scheme)
|
|
335
|
-
|
|
336
|
-
Add a scheme to the list of registered URI schemes.
|
|
337
|
-
* `javascript` and `vbscript` schemes can not be registered. It throws.
|
|
338
|
-
|
|
339
|
-
#### Parameters
|
|
340
|
-
|
|
341
|
-
* `scheme` **[string][1]** Scheme.
|
|
342
|
-
|
|
343
|
-
Returns **[Array][4]<[string][1]>** Array of registered URI schemes.
|
|
344
|
-
|
|
345
|
-
```javascript
|
|
346
|
-
console.log(urlSanitizer.has('foo'));
|
|
347
|
-
// => false
|
|
348
|
-
|
|
349
|
-
const res = urlSanitizer.add('foo');
|
|
350
|
-
// => ['aaa', 'aaas', 'about', 'acap', ... 'foo', ...]
|
|
351
|
-
|
|
352
|
-
console.log(urlSanitizer.has('foo'));
|
|
353
|
-
// => true
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### urlSanitizer.remove(scheme)
|
|
343
|
+
#### urlSanitizer.remove(scheme)
|
|
357
344
|
|
|
358
345
|
Remove a scheme from the list of registered URI schemes.
|
|
359
346
|
|
|
360
|
-
####
|
|
361
|
-
|
|
362
|
-
* `scheme` **[string][1]** Scheme.
|
|
363
|
-
|
|
364
|
-
Returns **[boolean][2]** Result.
|
|
365
|
-
* `true` if the scheme is successfully removed, `false` otherwise.
|
|
347
|
+
#### urlSanitizer.reset()
|
|
366
348
|
|
|
367
|
-
|
|
368
|
-
console.log(urlSanitizer.has('aaa'));
|
|
369
|
-
// => true
|
|
370
|
-
|
|
371
|
-
const res1 = urlSanitizer.remove('aaa');
|
|
372
|
-
// => true
|
|
373
|
-
|
|
374
|
-
console.log(urlSanitizer.has('aaa'));
|
|
375
|
-
// => false
|
|
376
|
-
|
|
377
|
-
const res2 = urlSanitizer.remove('foo');
|
|
378
|
-
// => false
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### urlSanitizer.reset()
|
|
382
|
-
|
|
383
|
-
Reset sanitizer.
|
|
384
|
-
|
|
385
|
-
Returns **void**
|
|
386
|
-
|
|
387
|
-
---
|
|
349
|
+
Reset registered schemes to the default list and clear internal cache.
|
|
388
350
|
|
|
389
351
|
## Acknowledgments
|
|
390
352
|
|
|
391
|
-
The following resources have been of great help in the development of the URL Sanitizer
|
|
353
|
+
The following resources have been of great help in the development of the URL Sanitizer:
|
|
392
354
|
|
|
393
355
|
* [DOMPurify](https://www.npmjs.com/package/dompurify)
|
|
394
356
|
* [Uniform Resource Identifier (URI) Schemes - IANA](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml)
|
|
395
357
|
* [Encoding -- determine the character encoding of a text file. - file/file](https://github.com/file/file/blob/master/src/encoding.c)
|
|
396
358
|
|
|
397
359
|
---
|
|
398
|
-
Copyright (c) 2023 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
|
|
399
|
-
|
|
400
|
-
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
|
401
360
|
|
|
402
|
-
[
|
|
403
|
-
|
|
404
|
-
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
|
405
|
-
|
|
406
|
-
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
|
407
|
-
|
|
408
|
-
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
|
361
|
+
Copyright (c) 2023 [asamuzaK (Kazz)](https://github.com/asamuzaK/)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var{DOMPurify:N}=globalThis;var H=/^[\w+/=-]+$/;var S=/data:[\w#&+./;=-]*,/,D=/data:[\w#&+./;=^]*base64,[\w+/=-]+/i,q=/data:[\w#&+./;=-]*,[^\x22]+/g,X=/&#(x(?:00)?[\dA-F]{2}|0?\d{1,3});?/gi,Y=/^(?:application\/(?:[\w#&.;-]+\+)?x|image\/svg\+x|text\/(?:ht|x))ml;?/;var Q=/^[a-z][\da-z+.-]*$/,J=/^(?:ext|web)\+[a-z]+$/,j=/(?:java|vb)script/,A=/(?:java|vb)script|blob/,M=/%(?:22|27|3C|3E)|[\x22\x27<>]/,K=/^%[\dA-F]{2}$/i;var g=t=>Object.prototype.toString.call(t).slice(8,-1),d=t=>typeof t=="string"||t instanceof String;var{FileReader:V}=globalThis;var L=[7,8,9,10,11,12,13,27,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,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,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];var C=["aaa","aaas","about","acap","acct","acd","acr","adiumxtra","adt","afp","afs","agtp","aim","amss","android","appdata","apt","ar","ari","ark","at","attachment","aw","barion","beshare","bitcoin","bitcoincash","bl","blob","bluetooth","bolo","brid","browserext","cabal","caip","calculator","callto","cap","cast","casts","chrome","chrome-extension","cid","coap","coaps","com-eventbrite-attendee","content","content-type","crid","cstr","cvs","dab","dat","data","dav","dhttp","diaspora","dict","did","dilithium3","dis","dlna-playcontainer","dlna-playsingle","dnp","dns","dntp","doi","donau","dpp","drm","dtmi","dtn","dvb","dvx","dweb","ed2k","eid","elsi","embedded","ens","esim","ethereum","example","facetime","feed","feedready","fido","file","finger","first-run-pen-experience","fish","fm","ftp","fuchsia-pkg","geo","gg","git","gitoid","gizmoproject","go","gopher","graph","gtalk","h323","ham","hcap","hcp","hs20","http","https","hxxp","hxxps","hydrazone","hyper","i0","iax","ibi","ibi-","icap","icon","ilstring","im","imap","info","iotdisco","ipfs","ipn","ipns","ipp","ipps","irc","irc6","ircs","iris","iris.beep","iris.lwz","iris.xpc","iris.xpcs","isostore","itms","jabber","jar","jms","keyparc","lastfm","lbry","ldap","ldaps","leaptofrogans","lid","linkid","lorawan","lpa","lvlt","machineProvisioningProgressReporter","magnet","mailto","maps","market","matrix","message","microsoft.windows.camera","microsoft.windows.camera.multipicker","microsoft.windows.camera.picker","mid","mms","mongodb","moz","moz-extension","mqtt","mqtts","ms-access","ms-appinstaller","ms-browser-extension","ms-calculator","ms-drive-to","ms-enrollment","ms-excel","ms-eyecontrolspeech","ms-gamebarservices","ms-gamingoverlay","ms-getoffice","ms-help","ms-infopath","ms-inputapp","ms-launchremotedesktop","ms-lockscreencomponent-config","ms-media-stream-id","ms-meetnow","ms-mixedrealitycapture","ms-mobileplans","ms-newsandinterests","ms-officeapp","ms-people","ms-personacard","ms-powerpoint","ms-project","ms-publisher","ms-recall","ms-remotedesktop","ms-remotedesktop-launch","ms-restoretabcompanion","ms-screenclip","ms-screensketch","ms-search","ms-search-repair","ms-secondary-screen-controller","ms-secondary-screen-setup","ms-settings","ms-settings-airplanemode","ms-settings-bluetooth","ms-settings-camera","ms-settings-cellular","ms-settings-cloudstorage","ms-settings-connectabledevices","ms-settings-displays-topology","ms-settings-emailandaccounts","ms-settings-language","ms-settings-location","ms-settings-lock","ms-settings-nfctransactions","ms-settings-notifications","ms-settings-power","ms-settings-privacy","ms-settings-proximity","ms-settings-screenrotation","ms-settings-wifi","ms-settings-workplace","ms-spd","ms-stickers","ms-sttoverlay","ms-transit-to","ms-useractivityset","ms-uup","ms-virtualtouchpad","ms-visio","ms-walk-to","ms-whiteboard","ms-whiteboard-cmd","ms-widgetboard","ms-widgets","ms-word","msnim","msrp","msrps","mss","mt","mtqp","mtrust","mumble","mupdate","mvn","mvrp","mvrps","news","nfs","ni","nih","nntp","notes","num","ocf","oid","onenote","onenote-cmd","opaquelocktoken","openid","openpgp4fpr","otpauth","palm","paparazzi","payto","pkcs11","platform","pop","pres","proxy","psyc","pttp","pwid","qb","query","quic-transport","redis","rediss","reload","res","resource","rmi","rsync","rtmfp","rtmp","rtsp","rtsps","rtspu","sarif","secondlife","secret-token","service","session","sftp","sgn","shc","shelter","sieve","simpleledger","simplex","sip","sips","skype","smb","smp","sms","smtp","snmp","soap.beep","soap.beeps","soldat","spacify","spiffe","spotify","ssb","ssh","starknet","steam","stun","stuns","submit","svn","swh","swid","swidpath","tag","taler","teamspeak","teapot","teapots","tel","teliaeid","telnet","tftp","things","thismessage","tip","tn3270","tool","turn","turns","tv","udp","unreal","upn","urn","ut2004","uuid-in-package","v-event","vemmi","ventrilo","ves","view-source","vnc","vscode","vscode-insiders","vsls","w3","wasm","wasm-js","wcr","web3","webcal","wifi","ws","wss","wtai","wyciwyg","xcompute","xcon","xcon-userid","xfire","xftp","xmlrpc.beep","xmlrpc.beeps","xmpp","xrcp","xri","ymsgr","z39.50r","z39.50s"];var W=t=>{if(!d(t))throw new TypeError(`Expected String but got ${g(t)}.`);let e=[];for(let s of t)e.push(`%${s.charCodeAt(0).toString(16).toUpperCase()}`);return e.join("")},Z=t=>{d(t)&&K.test(t)&&(t=t.toUpperCase());let[e,s,r,i,o,a]=["&","#","<",">",'"',"'"].map(W),c;return t===e?c=`${e}amp;`:t===r?c=`${e}lt;`:t===i?c=`${e}gt;`:t===o?c=`${e}quot;`:t===a?c=`${e}${s}39;`:c=t,c},ee=t=>{if(d(t)){if(!H.test(t))throw new Error(`Invalid base64 data: ${t}`)}else throw new TypeError(`Expected String but got ${g(t)}.`);let e=atob(t),s=Uint8Array.from([...e].map(o=>o.charCodeAt(0))),r=new Set(L),i;return s.every(o=>r.has(o))?i=e.replace(/\s/g,W):i=t,i},P=(t,e=0)=>{if(!d(t))throw new TypeError(`Expected String but got ${g(t)}.`);if(Number.isInteger(e)){if(e>16)throw new Error("Character references nested too deeply.")}else throw new TypeError(`Expected Number but got ${g(e)}.`);let s=decodeURIComponent(t);if(/&#/.test(s)){let r=new Set(L),i=[...s.matchAll(X)].reverse();for(let o of i){let[a,c]=o,l;if(/^x[\dA-F]+/i.test(c)?l=parseInt(`0${c}`,16):/^\d+/.test(c)&&(l=parseInt(c)),Number.isInteger(l)){let{index:u}=o,[p,n]=[s.substring(0,u),s.substring(u+a.length)];r.has(l)?(s=`${p}${String.fromCharCode(l)}${n}`,(/#x?$/.test(p)||/^#(?:x(?:00)?[2-7]|\d)/.test(n))&&(s=P(s,++e))):l<256&&(s=`${p}${n}`)}}}return s},te=t=>new Promise((e,s)=>{let r=new V;r.addEventListener("error",()=>s(r.error)),r.addEventListener("abort",()=>e(r.result)),r.addEventListener("load",()=>e(r.result)),r.readAsDataURL(t)}),k=class{#e;constructor(){this.#e=new Set(C)}get(){return[...this.#e]}has(e){return this.#e.has(e)}add(e){if(d(e)){if(j.test(e)||!Q.test(e))throw new Error(`Invalid scheme: ${e}`)}else throw new TypeError(`Expected String but got ${g(e)}.`);return this.#e.add(e),[...this.#e]}remove(e){return this.#e.delete(e)}verify(e){let s;if(d(e))try{let{protocol:r}=new URL(e),i=r.replace(/:$/,""),o=i.split("+");s=!j.test(i)&&J.test(i)||o.every(a=>this.#e.has(a))}catch{s=!1}return!!s}reset(){this.#e.clear();for(let e of C)this.#e.add(e)}};var O=class extends k{#e;#t=new Set;constructor(){super(),this.#e=0}replace(e){if(!d(e))throw new TypeError(`Expected String but got ${g(e)}.`);let s=e;if(S.test(s)){let i=[...s.matchAll(q)].reverse();for(let o of i){let[a]=o;D.test(a)&&([a]=D.exec(a)),this.#e++,this.#t.add(a);let c=this.sanitize(a,{allow:["data"]}),{index:l}=o,[u,p]=[s.substring(0,l),s.substring(l+a.length)];c?s=`${u}${c}${p}`:s=`${u}${p}`}}return s}purify(e){if(!d(e))throw new TypeError(`Expected String but got ${g(e)}.`);let s=N.sanitize(decodeURIComponent(e));return s&&S.test(s)&&(s=this.replace(s)),s=s.replace(/(?:#|%23)$/,"").replace(/(?<!(?:#|%23).*)(?:\?|%3F)$/,""),encodeURI(s)}sanitize(e,s){if(this.#e>16)throw this.#e=0,new Error("Data URLs nested too deeply.");let{allow:r,deny:i,only:o}=s??{},a=new Map([["blob",!1],["data",!1],["file",!1],["javascrpt",!1],["vbscript",!1]]),c=new Set,l=!1;if(Array.isArray(o)&&o.length){let p=super.get();for(let m of p)a.set(m,!1);let n=Object.values(o);for(let m of n)if(d(m)&&(m=m.trim(),!A.test(m))){if(super.has(m))a.set(m,!0);else{try{super.add(m)}catch{}super.has(m)&&(a.set(m,!0),c.add(m))}!l&&a.has(m)&&(l=a.get(m))}}else{if(Array.isArray(r)&&r.length){let p=Object.values(r);for(let n of p)if(d(n)&&(n=n.trim(),!A.test(n)))if(super.has(n))a.set(n,!0);else{try{super.add(n)}catch{}super.has(n)&&(a.set(n,!0),c.add(n))}}if(Array.isArray(i)&&i.length){let p=Object.values(i);for(let n of p)d(n)&&(n=n.trim(),n&&a.set(n,!1))}}let u;if(super.verify(e)){let{hash:p,href:n,pathname:m,protocol:U,search:x}=new URL(e),E=U.replace(/:$/,""),G=E.split("+"),_;if(l)_=G.every(y=>a.get(y));else for(let[y,R]of a.entries())if(_=R||E!==y&&G.every(f=>f!==y),!_)break;if(_){let y=G.includes("data"),R,f=n;if(y){let[v,...z]=m.split(","),$=`${z.join(",")}${x}${p}`,T=v.split(";"),I=T[T.length-1]==="base64",h=$;I&&(h=ee($));try{let F=P(h).trim(),{protocol:se}=new URL(F);se.replace(/:$/,"").split("+").some(re=>A.test(re))&&(f="")}catch{}let B=S.test(h);h!==$||B?B?h=this.replace(h):this.#t.has(e)?this.#t.delete(e):R=!0:this.#t.has(e)?this.#t.delete(e):R=!0,(!v||Y.test(v))&&(h=this.purify(h)),f&&h?(I&&h!==$&&T.pop(),f=`${E}:${T.join(";")},${h}`):f=""}else R=!0;if(!y&&M.test(f)){let v=M.exec(f),{index:z}=v;f=f.substring(0,z).replace(/[?&]$/,"")}f?(u=f.replace(/%26/g,Z),R&&(this.#e=0)):this.#e=0}c.size&&c.forEach(y=>{super.remove(y)})}return u||null}parse(e,s){if(!d(e))throw new TypeError(`Expected String but got ${g(e)}.`);let r=new Map([["input",e]]),i;if(this.verify(e)){let{protocol:o}=new URL(e);o==="blob:"?i=e:i=this.sanitize(e,s??{allow:["data","file"]})}if(i){let o=new URL(i),{pathname:a,protocol:c}=o,u=c.replace(/:$/,"").split("+").includes("data");if(r.set("valid",!0),u){let p=new Map,[n,...m]=a.split(","),U=`${m.join(",")}`,x=n.split(";"),E=x[x.length-1]==="base64";E&&x.pop(),p.set("mime",x.join(";")),p.set("base64",E),p.set("data",U),r.set("data",Object.fromEntries(p))}else r.set("data",null);for(let p in o){let n=o[p];d(n)&&r.set(p,n)}}else r.set("valid",!1);return Object.fromEntries(r)}reset(){super.reset(),this.#e=0,this.#t.clear()}};var b=new O,ce=async(t,e={allow:[],deny:[],only:[]})=>{let s;if(t&&d(t)){let r;try{let{protocol:i}=new URL(t);r=i.replace(/:$/,"")}catch{}if(r==="blob"){let{allow:i,deny:o,only:a}=e;if(Array.isArray(i)&&i.includes("blob")&&!(Array.isArray(o)&&o.includes("blob"))||Array.isArray(a)&&a.includes("blob")){let c;try{c=await fetch(t).then(l=>l.blob()).then(te)}catch{}if(c){if(Array.isArray(a))a.includes("data")||a.push("data");else if(Array.isArray(i)&&(i.includes("data")||i.push("data"),Array.isArray(o)&&o.includes("data"))){let l=o.indexOf("data");o.splice(l,1)}s=b.sanitize(c,e)}}URL.revokeObjectURL(t)}else r&&(s=b.sanitize(t,e))}return s||null},pe=(t,e)=>{let s;if(t&&d(t)){let r;try{let{protocol:i}=new URL(t);r=i.replace(/:$/,"")}catch{}r==="blob"?URL.revokeObjectURL(t):r&&(s=b.sanitize(t,e))}return s||null},le=async t=>b.parse(t),me=t=>b.parse(t),de=async t=>b.verify(t),fe=t=>b.verify(t),he=b;export{he as default,de as isURI,fe as isURISync,le as parseURL,me as parseURLSync,ce as sanitizeURL,pe as sanitizeURLSync};
|
|
1
|
+
var Y={sanitize:(...t)=>{if(globalThis?.DOMPurify)return globalThis.DOMPurify.sanitize(...t);let e=["DOMPurify is not available.","Ensure DOMPurify is exposed globally (e.g., window.DOMPurify)."];throw new Error(e.join(" "))}};var Z=/^[\w+/=-]+$/;var T=/data:[\w#&+./;=-]*,/,P=/data:[\w#&+./;=^]*base64,[\w+/=-]+/i,Q=/data:[\w#&+./;=-]*,[^\x22]+/g,K=/&#(x(?:00)?[\dA-F]{2}|0?\d{1,3});?/gi,J=/^(?:application\/(?:[\w#&.;-]+\+)?x|image\/svg\+x|text\/(?:ht|x))ml;?/;var V=/^[a-z][\da-z+.-]*$/,W=/^(?:ext|web)\+[a-z]+$/,j=/(?:java|vb)script/,A=/(?:java|vb)script|blob/,B=/%(?:22|27|3C|3E)|[\x22\x27<>]/,ee=/^%[\dA-F]{2}$/i;var y=t=>Object.prototype.toString.call(t).slice(8,-1),f=t=>typeof t=="string"||t instanceof String;var{FileReader:te}=globalThis;var z=[7,8,9,10,11,12,13,27,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,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,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];var C=["aaa","aaas","about","acap","acct","acd","acr","adiumxtra","adt","afp","afs","agtp","aim","amss","android","appdata","apt","ar","ari","ark","at","attachment","aw","barion","beshare","bitcoin","bitcoincash","bl","blob","bluetooth","bolo","brid","browserext","cabal","caip","calculator","callto","cap","cast","casts","chrome","chrome-extension","cid","coap","coaps","com-eventbrite-attendee","content","content-type","crid","cstr","cttps","cvs","dab","dat","data","dav","dhttp","diaspora","dict","did","dilithium3","dis","dlna-playcontainer","dlna-playsingle","dnp","dns","dntp","doi","donau","dpp","drm","dtmi","dtn","dvb","dvx","dweb","ed2k","eid","elsi","embedded","ens","esim","ethereum","example","facetime","feed","feedready","fido","file","finger","first-run-pen-experience","fish","fm","ftp","fuchsia-pkg","geo","gg","git","gitoid","gizmoproject","go","gopher","graph","gtalk","h323","ham","hcap","hcp","hs20","http","https","hxxp","hxxps","hydrazone","hyper","i0","iax","ibi","ibi-","icap","icon","ilstring","im","imap","info","iotdisco","ipfs","ipn","ipns","ipp","ipps","irc","irc6","ircs","iris","iris.beep","iris.lwz","iris.xpc","iris.xpcs","isostore","itms","jabber","jar","jms","keyparc","lastfm","lbry","ldap","ldaps","leaptofrogans","lid","linkid","lorawan","lpa","lvlt","machineProvisioningProgressReporter","magnet","mailto","maps","market","matrix","message","microsoft.windows.camera","microsoft.windows.camera.multipicker","microsoft.windows.camera.picker","mid","mms","mongodb","moz","moz-extension","mqtt","mqtts","ms-access","ms-appinstaller","ms-browser-extension","ms-calculator","ms-drive-to","ms-enrollment","ms-excel","ms-eyecontrolspeech","ms-gamebarservices","ms-gamingoverlay","ms-getoffice","ms-help","ms-infopath","ms-inputapp","ms-launchremotedesktop","ms-lockscreencomponent-config","ms-media-stream-id","ms-meetnow","ms-mixedrealitycapture","ms-mobileplans","ms-newsandinterests","ms-officeapp","ms-people","ms-personacard","ms-powerpoint","ms-project","ms-publisher","ms-recall","ms-remotedesktop","ms-remotedesktop-launch","ms-restoretabcompanion","ms-screenclip","ms-screensketch","ms-search","ms-search-repair","ms-secondary-screen-controller","ms-secondary-screen-setup","ms-settings","ms-settings-airplanemode","ms-settings-bluetooth","ms-settings-camera","ms-settings-cellular","ms-settings-cloudstorage","ms-settings-connectabledevices","ms-settings-displays-topology","ms-settings-emailandaccounts","ms-settings-language","ms-settings-location","ms-settings-lock","ms-settings-nfctransactions","ms-settings-notifications","ms-settings-power","ms-settings-privacy","ms-settings-proximity","ms-settings-screenrotation","ms-settings-wifi","ms-settings-workplace","ms-spd","ms-stickers","ms-sttoverlay","ms-transit-to","ms-useractivityset","ms-uup","ms-virtualtouchpad","ms-visio","ms-walk-to","ms-whiteboard","ms-whiteboard-cmd","ms-widgetboard","ms-widgets","ms-word","msnim","msrp","msrps","mss","mt","mtqp","mtrust","mumble","mupdate","mvn","mvrp","mvrps","news","nfs","ni","nih","nntp","notes","num","ocf","oid","onenote","onenote-cmd","opaquelocktoken","openid","openpgp4fpr","otpauth","palm","paparazzi","payto","pkcs11","platform","pop","pres","proxy","psyc","pttp","pwid","qb","query","quic-transport","redis","rediss","reload","res","resource","rmi","rsync","rtmfp","rtmp","rtsp","rtsps","rtspu","sarif","secondlife","secret-token","service","session","sftp","sgn","shc","shelter","sieve","simpleledger","simplex","sip","sips","skype","smb","smp","sms","smtp","snmp","soap.beep","soap.beeps","soldat","spacify","spiffe","spotify","ssb","ssh","starknet","steam","stun","stuns","submit","svn","swh","swid","swidpath","tag","taler","teamspeak","teapot","teapots","tel","teliaeid","telnet","tftp","things","thismessage","tip","tn3270","tool","turn","turns","tv","udp","unreal","upn","urn","ut2004","uuid-in-package","v-event","vemmi","ventrilo","ves","view-source","vnc","vscode","vscode-insiders","vsls","w3","wasm","wasm-js","wcr","web3","webcal","wifi","ws","wss","wtai","wyciwyg","xcompute","xcon","xcon-userid","xfire","xftp","xmlrpc.beep","xmlrpc.beeps","xmpp","xrcp","xri","ymsgr","z39.50r","z39.50s","ztdnaid"];var se=t=>{if(!f(t))throw new TypeError(`Expected String but got ${y(t)}.`);let e=[];for(let s of t)e.push(`%${s.charCodeAt(0).toString(16).toUpperCase()}`);return e.join("")},re=t=>{f(t)&&ee.test(t)&&(t=t.toUpperCase());let[e,s,a,r,o,n]=["&","#","<",">",'"',"'"].map(se),i;return t===e?i=`${e}amp;`:t===a?i=`${e}lt;`:t===r?i=`${e}gt;`:t===o?i=`${e}quot;`:t===n?i=`${e}${s}39;`:i=t,i},ie=t=>{if(f(t)){if(!Z.test(t))throw new Error(`Invalid base64 data: ${t}`)}else throw new TypeError(`Expected String but got ${y(t)}.`);let e=atob(t),s=Uint8Array.from([...e].map(o=>o.charCodeAt(0))),a=new Set(z),r;return s.every(o=>a.has(o))?r=e.replace(/\s/g,se):r=t,r},F=(t,e=0)=>{if(!f(t))throw new TypeError(`Expected String but got ${y(t)}.`);if(Number.isInteger(e)){if(e>16)throw new Error("Character references nested too deeply.")}else throw new TypeError(`Expected Number but got ${y(e)}.`);let s=decodeURIComponent(t);if(/&#/.test(s)){let a=new Set(z),r=[...s.matchAll(K)].reverse();for(let o of r){let[n,i]=o,l;if(/^x[\dA-F]+/i.test(i)?l=parseInt(`0${i}`,16):/^\d+/.test(i)&&(l=parseInt(i)),Number.isInteger(l)){let{index:h}=o,[c,d]=[s.substring(0,h),s.substring(h+n.length)];a.has(l)?(s=`${c}${String.fromCharCode(l)}${d}`,(/#x?$/.test(c)||/^#(?:x(?:00)?[2-7]|\d)/.test(d))&&(s=F(s,++e))):l<256&&(s=`${c}${d}`)}}}return s},ae=(t,e=33554432)=>new Promise((s,a)=>{if(Number.isInteger(t?.size)){if(Number.isInteger(e)&&t.size>e)return a(new DOMException(`Blob size (${t.size} bytes) exceeds the maximum allowed size of ${e} bytes.`,"NotReadableError"))}else return s(null);let r=new te;r.addEventListener("error",()=>a(r.error)),r.addEventListener("abort",()=>s(r.result)),r.addEventListener("load",()=>s(r.result)),r.readAsDataURL(t)}),D=class{#e;constructor(){this.#e=new Set(C)}get(){return[...this.#e]}has(e){return this.#e.has(e)}add(e){if(f(e)){if(j.test(e)||!V.test(e))throw new Error(`Invalid scheme: ${e}`)}else throw new TypeError(`Expected String but got ${y(e)}.`);return this.#e.add(e),[...this.#e]}remove(e){return this.#e.delete(e)}verify(e){let s;if(f(e))try{let{protocol:a}=new URL(e),r=a.replace(/:$/,""),o=r.split("+");s=!j.test(r)&&W.test(r)||o.every(n=>this.#e.has(n))}catch{s=!1}return!!s}reset(){this.#e.clear();for(let e of C)this.#e.add(e)}};var U=(t,e,s)=>{t&&console.warn(`[URLSanitizer Debug] ${e}`,s?s.message:"")},N=class extends D{#e;#t=new Set;constructor(){super(),this.#e=0}replace(e){if(!f(e))throw new TypeError(`Expected String but got ${y(e)}.`);let s=e;if(T.test(s)){let r=[...s.matchAll(Q)].reverse();for(let o of r){let[n]=o;P.test(n)&&([n]=P.exec(n)),this.#e++,this.#t.add(n);let i=this.sanitize(n,{allow:["data"]}),{index:l}=o,[h,c]=[s.substring(0,l),s.substring(l+n.length)];i?s=`${h}${i}${c}`:s=`${h}${c}`}}return s}purify(e){if(!f(e))throw new TypeError(`Expected String but got ${y(e)}.`);let s=Y.sanitize(decodeURIComponent(e));return s&&T.test(s)&&(s=this.replace(s)),s=s.replace(/(?:#|%23)$/,"").replace(/(?<!(?:#|%23).*)(?:\?|%3F)$/,""),encodeURI(s)}sanitize(e,s){if(this.#e>16)throw this.#e=0,new Error("Data URLs nested too deeply.");let{allow:a,deny:r,only:o,debug:n=!1}=s??{},i=new Map([["blob",!1],["data",!1],["file",!1],["javascrpt",!1],["vbscript",!1]]),l=new Set,h=!1;if(Array.isArray(o)&&o.length){let d=super.get();for(let m of d)i.set(m,!1);let p=Object.values(o);for(let m of p)if(f(m)&&(m=m.trim(),!A.test(m))){if(super.has(m))i.set(m,!0);else{try{super.add(m)}catch(b){let R=`Failed to add scheme '${m}' in 'only' list.`;U(n,R,b)}super.has(m)&&(i.set(m,!0),l.add(m))}!h&&i.has(m)&&(h=i.get(m))}}else{if(Array.isArray(a)&&a.length){let d=Object.values(a);for(let p of d)if(f(p)&&(p=p.trim(),!A.test(p)))if(super.has(p))i.set(p,!0);else{try{super.add(p)}catch(m){let b=`Failed to add scheme '${p}' in 'allow' list.`;U(n,b,m)}super.has(p)&&(i.set(p,!0),l.add(p))}}if(Array.isArray(r)&&r.length){let d=Object.values(r);for(let p of d)f(p)&&(p=p.trim(),p&&i.set(p,!1))}}let c;if(super.verify(e)){let{hash:d,href:p,pathname:m,protocol:b,search:R}=new URL(e),M=b.replace(/:$/,""),G=M.split("+"),$;if(h)$=G.every(x=>i.get(x));else for(let[x,v]of i.entries())if($=v||M!==x&&G.every(u=>u!==x),!$)break;if($){let x=G.includes("data"),v,u=p;if(x){let[_,...O]=m.split(","),S=`${O.join(",")}${R}${d}`,L=_.split(";"),H=L[L.length-1]==="base64",g=S;H&&(g=ie(S));try{let I=F(g).trim(),{protocol:X}=new URL(I);X.replace(/:$/,"").split("+").some(oe=>A.test(oe))&&(u="")}catch(I){U(n,"Failed to parse inner data URL protocol.",I)}let q=T.test(g);g!==S||q?q?g=this.replace(g):this.#t.has(e)?this.#t.delete(e):v=!0:this.#t.has(e)?this.#t.delete(e):v=!0,(!_||J.test(_))&&(g=this.purify(g)),u&&g?(H&&g!==S&&L.pop(),u=`${M}:${L.join(";")},${g}`):u=""}else v=!0;if(!x&&B.test(u)){let _=B.exec(u),{index:O}=_;u=u.substring(0,O).replace(/[?&]$/,"")}u?(c=u.replace(/%26/g,re),v&&(this.#e=0)):this.#e=0}l.size&&l.forEach(x=>{super.remove(x)})}return c||null}parse(e,s){if(!f(e))throw new TypeError(`Expected String but got ${y(e)}.`);let a=new Map([["input",e]]),r;if(this.verify(e)){let{protocol:o}=new URL(e);o==="blob:"?r=e:r=this.sanitize(e,s??{allow:["data","file"]})}if(r){let o=new URL(r),{pathname:n,protocol:i}=o,h=i.replace(/:$/,"").split("+").includes("data");if(a.set("valid",!0),h){let c=new Map,[d,...p]=n.split(","),m=`${p.join(",")}`,b=d.split(";"),R=b[b.length-1]==="base64";R&&b.pop(),c.set("mime",b.join(";")),c.set("base64",R),c.set("data",m),a.set("data",Object.fromEntries(c))}else a.set("data",null);for(let c in o){let d=o[c];f(d)&&a.set(c,d)}}else a.set("valid",!1);return Object.fromEntries(a)}reset(){super.reset(),this.#e=0,this.#t.clear()}};var w=new N,me=async(t,e={allow:[],deny:[],only:[],debug:!1,maxBlobSize:33554432})=>{let s=!!e?.debug,a=Number.isInteger(e?.maxBlobSize)&&e.maxBlobSize>0?e.maxBlobSize:33554432,r;if(t&&f(t)){let o;try{let{protocol:n}=new URL(t);o=n.replace(/:$/,"")}catch(n){U(s,`Invalid URL input format: ${t}`,n)}if(o==="blob"){let{allow:n,deny:i,only:l}=e;if(Array.isArray(n)&&n.includes("blob")&&!(Array.isArray(i)&&i.includes("blob"))||Array.isArray(l)&&l.includes("blob")){let h;try{h=await fetch(t).then(c=>c.blob()).then(c=>ae(c,a))}catch(c){let d=`Failed to fetch and convert blob URL: ${t}`;U(s,d,c)}if(h){if(Array.isArray(l))l.includes("data")||l.push("data");else if(Array.isArray(n)&&(n.includes("data")||n.push("data"),Array.isArray(i)&&i.includes("data"))){let c=i.indexOf("data");i.splice(c,1)}r=w.sanitize(h,e)}}URL.revokeObjectURL(t)}else o&&(r=w.sanitize(t,e))}return r||null},de=(t,e)=>{let s;if(t&&f(t)){let a;try{let{protocol:r}=new URL(t);a=r.replace(/:$/,"")}catch(r){let o=`Failed to parse URL: ${t}`;U(e?.debug,o,r)}a==="blob"?URL.revokeObjectURL(t):a&&(s=w.sanitize(t,e))}return s||null},fe=async t=>w.parse(t),he=t=>w.parse(t),ue=async t=>w.verify(t),ge=t=>w.verify(t),be=w;export{be as default,ue as isURI,ge as isURISync,fe as parseURL,he as parseURLSync,me as sanitizeURL,de as sanitizeURLSync};
|
|
2
2
|
/*!
|
|
3
3
|
* URL Sanitizer
|
|
4
4
|
*
|