url-sanitizer 2.1.0 → 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 +11 -1
- 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 +3 -3
- package/dist/url-sanitizer.min.js.map +3 -3
- package/package.json +1 -1
- 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/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
["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"]
|
|
1
|
+
["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"]
|
package/src/mjs/constant.js
CHANGED
package/src/mjs/file-reader.js
CHANGED
|
@@ -7,7 +7,9 @@ import { getType, isString } from './common.js';
|
|
|
7
7
|
import { textChars } from './lib-util.js';
|
|
8
8
|
|
|
9
9
|
/* constants */
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
CHUNK_SIZE, REG_CHARSET, REG_MIME_DOM, REG_MIME_TEXT
|
|
12
|
+
} from './constant.js';
|
|
11
13
|
const DONE = 2;
|
|
12
14
|
const EMPTY = 0;
|
|
13
15
|
const LOADING = 1;
|
|
@@ -163,7 +165,11 @@ export class FileReader extends EventTarget {
|
|
|
163
165
|
const mediaTypes = type ? type.split(';') : [];
|
|
164
166
|
const buffer = await blob.arrayBuffer();
|
|
165
167
|
const uint8arr = new Uint8Array(buffer);
|
|
166
|
-
const
|
|
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
|
+
}
|
|
167
173
|
this._dispatchProgressEvent('loadstart');
|
|
168
174
|
switch (format) {
|
|
169
175
|
case 'arrayBuffer':
|
package/src/mjs/sanitizer.js
CHANGED
|
@@ -10,8 +10,8 @@ import {
|
|
|
10
10
|
parseURLEncodedNumCharRef, URISchemes
|
|
11
11
|
} from './uri-util.js';
|
|
12
12
|
import {
|
|
13
|
-
HEX, REG_DATA_URL, REG_DATA_URL_B64, REG_DATA_URL_G,
|
|
14
|
-
REG_SCRIPT_BLOB, REG_TAG_QUOT
|
|
13
|
+
HEX, MAX_BLOB_SIZE, REG_DATA_URL, REG_DATA_URL_B64, REG_DATA_URL_G,
|
|
14
|
+
REG_MIME_DOM, REG_SCRIPT_BLOB, REG_TAG_QUOT
|
|
15
15
|
} from './constant.js';
|
|
16
16
|
|
|
17
17
|
/* typedef */
|
|
@@ -36,6 +36,18 @@ import {
|
|
|
36
36
|
* @property {string} [hash] - fragment identifier of the sanitized URL
|
|
37
37
|
*/
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Internal debug logger.
|
|
41
|
+
* @param {boolean} isDebug - Debug flag.
|
|
42
|
+
* @param {string} message - Output message.
|
|
43
|
+
* @param {Error} [error] - Caught error object.
|
|
44
|
+
*/
|
|
45
|
+
export const logDebug = (isDebug, message, error) => {
|
|
46
|
+
if (isDebug) {
|
|
47
|
+
console.warn(`[URLSanitizer Debug] ${message}`, error ? error.message : '');
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
39
51
|
/**
|
|
40
52
|
* URL sanitizer
|
|
41
53
|
*/
|
|
@@ -125,7 +137,7 @@ class URLSanitizer extends URISchemes {
|
|
|
125
137
|
this.#nest = 0;
|
|
126
138
|
throw new Error('Data URLs nested too deeply.');
|
|
127
139
|
}
|
|
128
|
-
const { allow, deny, only } = opt ?? {};
|
|
140
|
+
const { allow, deny, only, debug = false } = opt ?? {};
|
|
129
141
|
const schemeMap = new Map([
|
|
130
142
|
['blob', false],
|
|
131
143
|
['data', false],
|
|
@@ -151,7 +163,8 @@ class URLSanitizer extends URISchemes {
|
|
|
151
163
|
try {
|
|
152
164
|
super.add(item);
|
|
153
165
|
} catch (e) {
|
|
154
|
-
|
|
166
|
+
const msg = `Failed to add scheme '${item}' in 'only' list.`;
|
|
167
|
+
logDebug(debug, msg, e);
|
|
155
168
|
}
|
|
156
169
|
if (super.has(item)) {
|
|
157
170
|
schemeMap.set(item, true);
|
|
@@ -177,7 +190,8 @@ class URLSanitizer extends URISchemes {
|
|
|
177
190
|
try {
|
|
178
191
|
super.add(item);
|
|
179
192
|
} catch (e) {
|
|
180
|
-
|
|
193
|
+
const msg = `Failed to add scheme '${item}' in 'allow' list.`;
|
|
194
|
+
logDebug(debug, msg, e);
|
|
181
195
|
}
|
|
182
196
|
if (super.has(item)) {
|
|
183
197
|
schemeMap.set(item, true);
|
|
@@ -237,7 +251,8 @@ class URLSanitizer extends URISchemes {
|
|
|
237
251
|
urlToSanitize = '';
|
|
238
252
|
}
|
|
239
253
|
} catch (e) {
|
|
240
|
-
|
|
254
|
+
const msg = 'Failed to parse inner data URL protocol.';
|
|
255
|
+
logDebug(debug, msg, e);
|
|
241
256
|
}
|
|
242
257
|
const containsDataUrl = REG_DATA_URL.test(parsedData);
|
|
243
258
|
if (parsedData !== data || containsDataUrl) {
|
|
@@ -383,8 +398,14 @@ const urlSanitizer = new URLSanitizer();
|
|
|
383
398
|
export const sanitizeURL = async (url, opt = {
|
|
384
399
|
allow: [],
|
|
385
400
|
deny: [],
|
|
386
|
-
only: []
|
|
401
|
+
only: [],
|
|
402
|
+
debug: false,
|
|
403
|
+
maxBlobSize: MAX_BLOB_SIZE
|
|
387
404
|
}) => {
|
|
405
|
+
const isDebug = !!opt?.debug;
|
|
406
|
+
const maxBlobSize = Number.isInteger(opt?.maxBlobSize) && opt.maxBlobSize > 0
|
|
407
|
+
? opt.maxBlobSize
|
|
408
|
+
: MAX_BLOB_SIZE;
|
|
388
409
|
let res;
|
|
389
410
|
if (url && isString(url)) {
|
|
390
411
|
let scheme;
|
|
@@ -392,7 +413,7 @@ export const sanitizeURL = async (url, opt = {
|
|
|
392
413
|
const { protocol } = new URL(url);
|
|
393
414
|
scheme = protocol.replace(/:$/, '');
|
|
394
415
|
} catch (e) {
|
|
395
|
-
|
|
416
|
+
logDebug(isDebug, `Invalid URL input format: ${url}`, e);
|
|
396
417
|
}
|
|
397
418
|
if (scheme === 'blob') {
|
|
398
419
|
const { allow, deny, only } = opt;
|
|
@@ -401,10 +422,12 @@ export const sanitizeURL = async (url, opt = {
|
|
|
401
422
|
(Array.isArray(only) && only.includes('blob'))) {
|
|
402
423
|
let data;
|
|
403
424
|
try {
|
|
404
|
-
data =
|
|
405
|
-
|
|
425
|
+
data = await fetch(url)
|
|
426
|
+
.then(r => r.blob())
|
|
427
|
+
.then(b => createDataURLFromBlob(b, maxBlobSize));
|
|
406
428
|
} catch (e) {
|
|
407
|
-
|
|
429
|
+
const msg = `Failed to fetch and convert blob URL: ${url}`;
|
|
430
|
+
logDebug(isDebug, msg, e);
|
|
408
431
|
}
|
|
409
432
|
if (data) {
|
|
410
433
|
if (Array.isArray(only)) {
|
|
@@ -450,7 +473,8 @@ export const sanitizeURLSync = (url, opt) => {
|
|
|
450
473
|
const { protocol } = new URL(url);
|
|
451
474
|
scheme = protocol.replace(/:$/, '');
|
|
452
475
|
} catch (e) {
|
|
453
|
-
|
|
476
|
+
const msg = `Failed to parse URL: ${url}`;
|
|
477
|
+
logDebug(opt?.debug, msg, e);
|
|
454
478
|
}
|
|
455
479
|
if (scheme === 'blob') {
|
|
456
480
|
URL.revokeObjectURL(url);
|
package/src/mjs/uri-util.js
CHANGED
|
@@ -8,7 +8,8 @@ import { FileReader } from './file-reader.js';
|
|
|
8
8
|
import { textChars, uriSchemes } from './lib-util.js';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
-
HEX, REG_B64, REG_NUM_REF, REG_SCHEME, REG_SCHEME_EXT,
|
|
11
|
+
HEX, MAX_BLOB_SIZE, REG_B64, REG_NUM_REF, REG_SCHEME, REG_SCHEME_EXT,
|
|
12
|
+
REG_SCRIPT, REG_URL_ENC
|
|
12
13
|
} from './constant.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -128,15 +129,25 @@ export const parseURLEncodedNumCharRef = (str, nest = 0) => {
|
|
|
128
129
|
/**
|
|
129
130
|
* create data URL from blob
|
|
130
131
|
* @param {object} blob - blob
|
|
132
|
+
* @param {number} maxBlobSize - max blob size
|
|
131
133
|
* @returns {Promise.<?string>} - data URL
|
|
132
134
|
*/
|
|
133
|
-
export const createDataURLFromBlob = blob
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
})
|
|
135
|
+
export const createDataURLFromBlob = (blob, maxBlobSize = MAX_BLOB_SIZE) =>
|
|
136
|
+
new Promise((resolve, reject) => {
|
|
137
|
+
if (!Number.isInteger(blob?.size)) {
|
|
138
|
+
return resolve(null);
|
|
139
|
+
} else if (Number.isInteger(maxBlobSize) && blob.size > maxBlobSize) {
|
|
140
|
+
return reject(new DOMException(
|
|
141
|
+
`Blob size (${blob.size} bytes) exceeds the maximum allowed size of ${maxBlobSize} bytes.`,
|
|
142
|
+
'NotReadableError'
|
|
143
|
+
));
|
|
144
|
+
}
|
|
145
|
+
const reader = new FileReader();
|
|
146
|
+
reader.addEventListener('error', () => reject(reader.error));
|
|
147
|
+
reader.addEventListener('abort', () => resolve(reader.result));
|
|
148
|
+
reader.addEventListener('load', () => resolve(reader.result));
|
|
149
|
+
reader.readAsDataURL(blob);
|
|
150
|
+
});
|
|
140
151
|
|
|
141
152
|
/**
|
|
142
153
|
* URI schemes
|
package/types/mjs/constant.d.ts
CHANGED
package/types/mjs/sanitizer.d.ts
CHANGED
package/types/mjs/uri-util.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export function getURLEncodedString(str: string): string;
|
|
|
2
2
|
export function escapeURLEncodedHTMLChars(ch: string): string;
|
|
3
3
|
export function parseBase64(data: string): string;
|
|
4
4
|
export function parseURLEncodedNumCharRef(str: string, nest?: number): string;
|
|
5
|
-
export function createDataURLFromBlob(blob: object): Promise<string | null>;
|
|
5
|
+
export function createDataURLFromBlob(blob: object, maxBlobSize?: number): Promise<string | null>;
|
|
6
6
|
export class URISchemes {
|
|
7
7
|
get(): Array<string>;
|
|
8
8
|
has(scheme: string): boolean;
|