url-sanitizer 5.0.1 → 5.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 +19 -15
- 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 +7 -7
- package/src/index.js +72 -8
- package/src/lib/iana/uri-schemes.json +1 -1
- package/src/mjs/common.js +10 -0
- package/src/mjs/constant.js +6 -0
- package/src/mjs/filter.js +444 -0
- package/src/mjs/regexp.js +24 -18
- package/src/mjs/sanitize.js +509 -0
- package/src/mjs/scheme.js +150 -0
- package/src/mjs/text-chars.js +4 -4
- package/src/mjs/{uri-util.js → utility.js} +98 -132
- package/types/index.d.ts +27 -1
- package/types/mjs/common.d.ts +1 -0
- package/types/mjs/constant.d.ts +1 -0
- package/types/mjs/filter.d.ts +28 -0
- package/types/mjs/regexp.d.ts +4 -3
- package/types/mjs/sanitize.d.ts +57 -0
- package/types/mjs/scheme.d.ts +11 -0
- package/types/mjs/{uri-util.d.ts → utility.d.ts} +10 -11
- package/src/mjs/sanitizer.js +0 -1019
- package/types/mjs/sanitizer.d.ts +0 -90
package/README.md
CHANGED
|
@@ -112,7 +112,7 @@ Sanitizes the given URL asynchronously.
|
|
|
112
112
|
* opt.maxBlobSize **\[number\]** Maximum allowed blob size in bytes. Default is `16_777_216` (16MB).
|
|
113
113
|
* opt.maxLength **\[number\]** Maximum allowed URL length. Default is `65_536` (64KB).
|
|
114
114
|
|
|
115
|
-
**Returns** **Promise<string?>** Sanitized URL,
|
|
115
|
+
**Returns** **Promise<string?>** Sanitized URL, or null.
|
|
116
116
|
|
|
117
117
|
#### Samples
|
|
118
118
|
|
|
@@ -238,31 +238,34 @@ Sanitizes the given URL and returns its parsed components.
|
|
|
238
238
|
#### InspectedURLResult
|
|
239
239
|
|
|
240
240
|
An object extending the standard [URL](https://developer.mozilla.org/ja/docs/Web/API/URL) API with additional properties.
|
|
241
|
-
The properties except for `input` and `
|
|
241
|
+
The properties except for `input`, `valid`, and `href` are omitted from the object for invalid URLs.
|
|
242
242
|
|
|
243
243
|
* `input` — **string** The original URL input.
|
|
244
244
|
* `valid` — **boolean** Indicates whether the URI is valid.
|
|
245
|
-
* `
|
|
245
|
+
* `href` — **string?** The sanitized URL, or null.
|
|
246
|
+
* `relative` — **[boolean]** Indicates whether the input is a valid relative URL.
|
|
246
247
|
* `reason` — **[string]** The reason why the URL is invalid. Omitted if `valid` is `true`.
|
|
247
248
|
* `data` — **[{ mime: string; base64: boolean; data: string; } | null | undefined]** The parsed result of a data URL. Returns `null` if the URL is valid but not a data URL, and is omitted if the URL is invalid.
|
|
248
249
|
* `data.mime` — **string** The MIME type of the data.
|
|
249
250
|
* `data.base64` — **boolean** Indicates whether the data is base64-encoded.
|
|
250
251
|
* `data.data` — **string** The actual data part of the data URL.
|
|
251
|
-
* `
|
|
252
|
+
* `origin`, `protocol`, `username`, `password`, `host`, `hostname`, `port`, `pathname`, `search`, `hash` — **[string | undefined]** Properties identical to the standard URL API (omitted if the URL is invalid).
|
|
252
253
|
|
|
253
254
|
``` javascript
|
|
254
255
|
const res1 = await inspectURL('javascript:alert(1)');
|
|
255
256
|
/* => {
|
|
256
257
|
input: 'javascript:alert(1)',
|
|
257
|
-
valid: false
|
|
258
|
+
valid: false,
|
|
259
|
+
href: null,
|
|
260
|
+
reason: 'Sanitization failed (blocked by allowed schemes or rules).'
|
|
258
261
|
} */
|
|
259
262
|
|
|
260
263
|
const res2 = await inspectURL('https://www.example.com/?foo=bar#baz');
|
|
261
264
|
/* => {
|
|
262
265
|
input: 'https://www.example.com/?foo=bar#baz',
|
|
263
266
|
valid: true,
|
|
264
|
-
data: null,
|
|
265
267
|
href: 'https://www.example.com/?foo=bar#baz',
|
|
268
|
+
data: null,
|
|
266
269
|
origin: 'https://www.example.com',
|
|
267
270
|
protocol: 'https:',
|
|
268
271
|
hostname: 'www.example.com',
|
|
@@ -277,12 +280,12 @@ const res3 = await inspectURL('data:image/svg+xml;base64,PHN2Zz48ZyBvbmNsaWNrPSJ
|
|
|
277
280
|
/* => {
|
|
278
281
|
input: 'data:image/svg+xml;base64,PHN2Zz48ZyBvbmNsaWNrPSJhbGVydCgxKSIvPjwvc3ZnPg==',
|
|
279
282
|
valid: true,
|
|
283
|
+
href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
280
284
|
data: {
|
|
281
285
|
mime: 'image/svg+xml',
|
|
282
286
|
base64: false,
|
|
283
287
|
data: '%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E'
|
|
284
288
|
},
|
|
285
|
-
href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
286
289
|
origin: 'null',
|
|
287
290
|
protocol: 'data:',
|
|
288
291
|
pathname: 'image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
@@ -294,12 +297,12 @@ const res4 = await inspectURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAU
|
|
|
294
297
|
/* => {
|
|
295
298
|
input: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
296
299
|
valid: true,
|
|
300
|
+
href: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
297
301
|
data: {
|
|
298
302
|
mime: 'image/png',
|
|
299
303
|
base64: true,
|
|
300
304
|
data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
|
|
301
305
|
},
|
|
302
|
-
href: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
303
306
|
origin: 'null',
|
|
304
307
|
protocol: 'data:',
|
|
305
308
|
pathname: 'image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==',
|
|
@@ -314,12 +317,12 @@ const res5 = await inspectURL(url5);
|
|
|
314
317
|
/* => {
|
|
315
318
|
input: 'blob:nodedata:82ecc5a4-aea8-48d7-a407-64e2ef0913da',
|
|
316
319
|
valid: true,
|
|
320
|
+
href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
317
321
|
data: {
|
|
318
322
|
mime: 'image/svg+xml',
|
|
319
323
|
base64: false,
|
|
320
324
|
data: '%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E'
|
|
321
325
|
},
|
|
322
|
-
href: 'data:image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
323
326
|
origin: 'null',
|
|
324
327
|
protocol: 'data:',
|
|
325
328
|
pathname: 'image/svg+xml,%3Csvg%3E%3Cg%3E%3C/g%3E%3C/svg%3E',
|
|
@@ -436,12 +439,13 @@ Execution times were measured using [mitata](https://github.com/evanwashere/mita
|
|
|
436
439
|
### Benchmark Results
|
|
437
440
|
|
|
438
441
|
| URL Type | `url-sanitizer` | [@braintree/sanitize-url](https://www.npmjs.com/package/@braintree/sanitize-url) | [strict-url-sanitise](https://www.npmjs.com/package/strict-url-sanitise) |
|
|
439
|
-
| :--- |
|
|
440
|
-
| **Normal
|
|
441
|
-
| **
|
|
442
|
-
| **
|
|
443
|
-
| **
|
|
444
|
-
| **
|
|
442
|
+
| :--- | :--- | :--- | :--- |
|
|
443
|
+
| **Normal HTTPS URL** | ~1.41 µs/iter | ~4.00 µs/iter | ~4.65 µs/iter |
|
|
444
|
+
| **HTTPS URL with XSS** | ~6.44 µs/iter<br>returns sanitized URL | ~4.50 µs/iter<br>returns encoded URL (not fully sanitized against XSS) | ~6.56 µs/iter<br>returns encoded URL (not fully sanitized against XSS) |
|
|
445
|
+
| **XSS Scheme URL** | ~5.78 µs/iter<br>returns `null` | ~1.51 µs/iter<br>returns `about:blank` | ~6.33 µs/iter<br>throws `Error` |
|
|
446
|
+
| **XSS Data URL** | ~393.24 µs/iter<br>returns sanitized data URL | ~2.34 µs/iter<br>returns `about:blank` | ~9.83 µs/iter<br>throws `Error` |
|
|
447
|
+
| **XSS Blob URL** | ~606.15 µs/iter<br>returns sanitized data URL | ~2.14 µs/iter<br>returns blob URL as-is | ~9.98 µs/iter<br>throws `Error` |
|
|
448
|
+
| **Invalid URL** | ~0.22 µs/iter<br>returns `null` | ~1.22 µs/iter<br>returns invalid URL as-is | ~20.50 µs/iter<br>throws `Error` |
|
|
445
449
|
|
|
446
450
|
### Characteristics & Trade-offs
|
|
447
451
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var me=Object.freeze(["addHook","removeHook","removeAllHooks","setConfig"]),b=[],I=new Proxy({},{get(r,t,e){let s=globalThis.DOMPurify||globalThis.window?.DOMPurify;if(s){if(b.length){for(let{prop:o,args:i}of b)typeof s[o]=="function"&&s[o](...i);b.length=0}let n=s[t];return typeof n=="function"?n.bind(s):n}return(...n)=>{if(me.includes(t))return b.push({prop:t,args:n}),e;throw new Error("DOMPurify is not available. Ensure DOMPurify is exposed globally (e.g., window.DOMPurify).")}}});var u=r=>Object.prototype.toString.call(r).slice(8,-1),p=r=>typeof r=="string"||r instanceof String;var M=["aaa","aaas","about","acap","acct","acd","acr","adiumxtra","adt","aet","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","cm","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","ez","facetime","feed","feedready","fido","file","finger","first-run-pen-experience","fish","fm","ftp","fuchsia-pkg","gcx","gdsi","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","mdoc","mdoc-openid4vp","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","musik","mvn","mvrp","mvrps","news","nfs","ni","nih","nntp","nostr","notes","npamp","num","ocf","oid","onenote","onenote-cmd","opaquelocktoken","openid","openpgp4fpr","otpauth","palm","paparazzi","payto","pkcs11","pkg","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","sss","starknet","steam","stun","stuns","submit","svn","swh","swid","swidpath","tag","taler","teamspeak","teapot","teapots","tel","teliaeid","telnet","tftp","things","thismessage","tip","tn3270","tool","tttps","turn","turns","tv","udp","unreal","upn","urn","ust","ut2004","uuaid","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 D=/#$/,G=/^(?:application\/(?:[\w.-]+\+)?xml|image\/svg\+xml|text\/(?:html|xml))(?:\s*;\s*|$)/i;var H=/&#(x0*[\dA-F]{1,2}|0*\d{1,3});?/gi,B=/^((?:[^#%]|%(?!23))*)\?$/,j=/^[a-z][\da-z+.-]*$/,F=/^(?:web\+[a-z]+|ext\+[a-z][\da-z.-]*)$/,x=/^(?:java|vb)script$/i,v=/^(?:(?:java|vb)script|blob)$/i,U=/["'<>]|%(?:22|27|3C|3E)|&(?:apos|lt|gt|quot|#0*(?:34|39|60|62)|#x0*(?:22|27|3C|3E));/i,X=/^%[\dA-F]{2}$/i,V=/(?:%[\dA-F]{2})+/gi,Y=/&/gi,q=/&(?=[#a-z0-9])(?![a-z0-9]+=)/i,Q=/^(?:\/{2,}|\\|[a-z][a-z\d+\-.]*:[^/])/i;var W=[9,10,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];var C=new Set(W),z=new Map([[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]),Ee=()=>{let r=new Map;for(let t=0;t<256;t++)!C.has(t)&&!z.has(t)&&r.set(t,`\\x${t.toString(16).padStart(2,"0").toUpperCase()}`);return r},L=Ee();var[h,ye,be,xe,Re,we]=["&","#","<",">",'"',"'"].map(r=>`%${r.charCodeAt(0).toString(16).toUpperCase()}`),_e={[h]:`${h}amp;`,[be]:`${h}lt;`,[xe]:`${h}gt;`,[Re]:`${h}quot;`,[we]:`${h}${ye}39;`},tt=Array.from({length:256},(r,t)=>`%${t.toString(16).padStart(2,"0").toUpperCase()}`),Se=globalThis.process?.versions?.node!==void 0,K=`[${[...L.values()].join("")}]`,Te=new RegExp(K),ve=new RegExp(K,"g"),st=new TextEncoder,Ue=new TextDecoder("utf-8",{fatal:!0}),_=class{#e=new Set(M);get(){return[...this.#e]}getScheme(t){if(!p(t))return null;let e=this.parse(t);return e?e.protocol.replace(/:$/,""):null}has(t){let e=this.normalize(t,!0);return e?this.#e.has(e):!1}normalize(t,e=!1){if(!p(t))return null;let s=t.normalize("NFKC");return e?s.trim().toLowerCase():s}parse(t,e){if(!p(t))return null;let s=this.normalize(t);return e&&p(e)?URL.parse(s,this.normalize(e)):URL.parse(s)}verify(t,e){if(!p(t))return!1;let s=this.parse(t);return this.verifyParsed(s,e,!0)}verifyParsed(t,e,s=!1){if(!t||!t.protocol)return!1;let n=t.protocol.toLowerCase().replace(/:$/,""),o=n.split("+");return o.some(a=>x.test(a))?!1:(e||(e=this.#e),e.has(n)?!0:o.length>1?s&&F.test(n)||o.every(a=>e.has(a)):!1)}},$=r=>p(r)?r.replace(/:$/,"").split("+"):[];var J=r=>{if(!p(r))return r;let t=X.test(r)?r.toUpperCase():r;return _e[t]??r},g=r=>{let t=p(r)?r:String(r);return t.length>64?`${t.slice(0,64)}...`:t},ee=r=>p(r)?r.replace(D,"").replace(B,"$1"):r,Ce=(r,t)=>{let e=/^x/i.test(t)?Number.parseInt(t.slice(1),16):Number.parseInt(t,10);if(!Number.isInteger(e)||e<0||e>=256)return r;if(L.has(e))return"";if(C.has(e))return String.fromCharCode(e);let s=z.get(e);return String.fromCodePoint(s)},te=(r,t=0)=>{if(!p(r))throw new TypeError(`Expected String but got ${u(r)}.`);if(!Number.isInteger(t))throw new TypeError(`Expected Number but got ${u(t)}.`);let e;try{e=decodeURIComponent(r)}catch{e=r.replace(V,n=>{try{return decodeURIComponent(n)}catch{return n}})}e=e.replace(ve,"");let s=0;for(;s+t<16;s++){let n=e;if(e=e.replace(Y,"&").replace(H,Ce),e===n)break}if(s+t>=16&&/&#/.test(e))throw new Error("Character references nested too deeply.");return e},A=(r,t="",e="")=>{if(!p(r))return{mediaType:"",mediaTypes:[],data:"",isBase64:!1};let s=r.indexOf(",");if(s===-1)return{mediaType:r,mediaTypes:r.split(";"),data:"",isBase64:!1};let n=r.slice(0,s),o=`${r.slice(s+1)}${t}${e}`,i=n.split(";"),a=/^base64$/i.test(i.at(-1));return{mediaType:n,mediaTypes:i,data:o,isBase64:a}},se=r=>{if(!p(r))throw new TypeError(`Expected String but got ${u(r)}.`);let t=r.replace(/\s/g,""),e;try{e=atob(t)}catch{throw new Error(`Invalid base64 data: ${g(r)}`)}let s;if(Se&&globalThis.Buffer)s=globalThis.Buffer.from(e,"latin1");else{let n=e.length;s=new Uint8Array(n);for(let o=0;o<n;o++)s[o]=e.charCodeAt(o)}try{let n=Ue.decode(s);return Te.test(n)?t:n}catch{return t}};var ne="http://dummy.local",Le=globalThis.process?.versions?.node!==void 0,Ne=Object.freeze(["href","origin","protocol","username","password","host","hostname","port","pathname","search","hash"]),S=Object.freeze({allow:Object.freeze([]),deny:Object.freeze([]),only:Object.freeze([]),allowRelative:!1,debug:!1,revokeObjectURL:!1,maxBlobSize:16777216,maxLength:65536}),T=(r,t)=>{console.warn(`[URLSanitizer Debug] ${r}`,t)},O=class{constructor(t,e){this.debug=!!t?.debug,this.domPurify=e,this.nest=0,this.recurse=new Set,this.schemeMap=new Map([["blob",!1],["data",!1],["file",!1],["javascript",!1],["vbscript",!1]])}enter(t){return this.recurse.has(t)?(this.debug&&T(`Circular Data URL detected and skipped: ${g(t)}`),!1):(this.nest++,this.recurse.add(t),!0)}leave(t){this.nest--,this.recurse.delete(t)}},P=class extends _{#e;constructor(){super(),this.#e=new Set(super.get())}#t(t,e){if(!t||!p(t))return null;let{allow:s,allowRelative:n,deny:o,maxLength:i,only:a}=e;if(Number.isInteger(i)&&t.length>i){let d=`URL length ${t.length} exceeds max length ${i}.`;throw new RangeError(d)}if(!(Array.isArray(o)&&o.length||Array.isArray(a)&&a.length||n)&&(t.startsWith("https://")||t.startsWith("http://"))&&!U.test(t)&&!t.includes("data:")){let d=this.parse(t);return d?d.href.replace(/%26/g,J):null}let l=new O(e,I);return this.#s(t,{allow:s,allowRelative:n,deny:o,only:a},l)}#s(t,{allow:e,allowRelative:s,deny:n,only:o},i){if(i.nest>16)throw new Error("Data URLs nested too deeply.");let{allowedSchemes:a,restrictScheme:c,schemeMap:l}=this.#o({allow:e,deny:n,only:o},i),{isDataUrl:d,isRelative:f,isVerified:k,scheme:y,schemeParts:le,urlObj:pe,urlToSanitize:de}=this.#i(t,s,a,i);return!k||!this.#a(y,le,c,l,f)?null:d?this.#c(pe,y,i):this.#d(de)}#o({allow:t,deny:e,only:s},n){let o=this.#e,i=!1,a=new Map(n.schemeMap);if(Array.isArray(s)&&s.length){o=new Set,i=!0;for(let c of s)p(c)&&this.#r(c,"only",o,a,n)}else{if(Array.isArray(t)&&t.length){o=new Set(this.#e);for(let c of t)p(c)&&this.#r(c,"allow",o,a,n)}if(Array.isArray(e)&&e.length){for(let c of e)if(p(c)){let l=this.normalize(c,!0);l&&a.set(l,!1)}}}return{allowedSchemes:o,restrictScheme:i,schemeMap:a}}#r(t,e,s,n,o){let i=this.normalize(t,!0);return this.#n(i)?(n.set(i,!0),s.add(i),!0):!1}#n(t){return v.test(t)?!1:!t.split("+").some(n=>x.test(n))&&j.test(t)}#i(t,e,s,n){let o=this.parse(t),i=this.verifyParsed(o,s),a=!1,c="";if(!i&&e&&!Q.test(t)){let f=this.parse(t,ne);f&&f.protocol==="http:"&&f.hostname==="dummy.local"&&(i=!0,a=!0,c=`${f.pathname}${f.search}${f.hash}`)}if(!i)return{isVerified:!1};if(a)return{isRelative:a,isVerified:i,isDataUrl:!1,scheme:"",schemeParts:[],urlToSanitize:c};let l=o.protocol.replace(/:$/,""),d=l.split("+");return{isRelative:a,isVerified:i,scheme:l,schemeParts:d,urlObj:o,isDataUrl:l==="data",urlToSanitize:o.href}}#a(t,e,s,n,o){if(o)return!0;if(s)return n.has(t)||e.every(i=>n.get(i));for(let[i,a]of n.entries())if(!(a||t!==i&&e.every(l=>l!==i)))return!1;return!0}#c(t,e,s){let{mediaType:n,mediaTypes:o,data:i,isBase64:a}=A(t.pathname,t.search,t.hash),c=i;if(a)try{c=se(i)}catch(l){return s.debug&&T("Failed to parse base64 data.",l),null}try{let l=te(c).trim(),d=this.parse(l,ne);if(!d)return null;let f=d.protocol;if($(f).some(y=>v.test(y)))return null}catch(l){return s.debug&&T("Failed to parse inner data URL protocol.",l),null}return(!n||G.test(n))&&(c=this.#l(c,s)),c?(a&&c!==i&&o.pop(),`${e}:${o.join(";")},${c}`):null}#l(t,e){let s=t;try{s=decodeURIComponent(t)}catch{}let n,o=(i,a)=>this.#p(i,a,e);e.domPurify.addHook("uponSanitizeAttribute",o);try{n=e.domPurify.sanitize(s)}finally{e.domPurify.removeHook("uponSanitizeAttribute")}n=ee(n);try{return encodeURI(n)}catch{return n}}#p(t,e,s){if(!e.attrValue||!/^\s*data:/i.test(e.attrValue))return;let n=this.parse(e.attrValue);if(!n||n.protocol!=="data:")return;let o=e.attrValue;if(!s.enter(o)){e.attrValue="";return}try{let i=this.#s(o,{allow:["data"],deny:[],only:[],allowRelative:!1},s);e.attrValue=i||""}finally{s.leave(o)}}#d(t){let e=t,s=[U,q];for(let n of s){let o=n.exec(e);o&&(e=e.substring(0,o.index).replace(/[?&]$/,""))}return e}sanitize(t,e={}){let s={...S,...e};return this.#t(t,s)}inspect(t){let e={input:t,valid:!1};if(!p(t))return e.reason=`Invalid URL input: ${g(t)}`,e;if(t==="")return e.reason="Invalid URL input: (empty string)",e;let s,n=null;try{s=this.#t(t,{...S,allow:["data"],allowRelative:!0}),s||(n="Sanitization failed (blocked by allowed schemes or rules).")}catch(o){n=o.message}if(s){let o=this.parse(s);if(e.valid=!0,o){if($(o.protocol).includes("data")){let{mediaTypes:c,data:l,isBase64:d}=A(o.pathname,o.search,o.hash);d&&c.pop(),e.data={mime:c.join(";"),base64:d,data:l}}else e.data=null;for(let c of Ne){let l=o[c];p(l)&&(e[c]=l)}}else e.valid=!1,e.relative=!0,e.href=s,e.reason="Invalid as an absolute URL, but valid as a relative URL."}else e.valid=!1,e.reason=n;return e}get(){return[...this.#e]}has(t){let e=this.normalize(t,!0);return e?this.#e.has(e):!1}add(t){if(!p(t))throw new TypeError(`Expected String but got ${u(t)}.`);let e=this.normalize(t,!0);if(!this.#n(e))throw new Error(`Invalid scheme: ${t}`);return this.#e.add(e),[...this.#e]}remove(t){if(!p(t))return!1;let e=this.normalize(t,!0);return this.#e.delete(e)}reset(){this.#e=new Set(super.get())}verify(t,e){return super.verify(t,e??this.#e)}},$e=async r=>{let t=r.type?`${r.type};base64`:"base64",e=await r.arrayBuffer(),s=globalThis.Buffer.from(e).toString("base64");return`data:${t},${s}`},Ae=r=>new Promise((t,e)=>{let s=new globalThis.FileReader;s.addEventListener("error",()=>{let n=s.error||new DOMException("Failed to read Blob via FileReader.","NotReadableError");e(n)}),s.addEventListener("abort",()=>t(null)),s.addEventListener("load",()=>t(s.result)),s.readAsDataURL(r)}),Oe=async r=>{let t=r.type?`${r.type};base64`:"base64",e=await r.arrayBuffer(),s=new Uint8Array(e),n=[];for(let o=0;o<s.length;o+=8192)n.push(String.fromCharCode(...s.subarray(o,o+8192)));return`data:${t},${btoa(n.join(""))}`},ie=async(r,t,e,s=0)=>{let{done:n,value:o}=await r.read();if(n)return;let i=s+o.byteLength;if(i>t){await r.cancel("Size limit exceeded");let a=`Payload (${i} bytes) exceeds max (${t} bytes).`;throw new DOMException(a,"NotReadableError")}return e.push(o),ie(r,t,e,i)},ae=async(r,t)=>{let e=16777216;Number.isInteger(t)&&t>0&&(e=t);let s=await fetch(r);if(!s.ok){let a=`Failed to fetch ${g(r)}`;throw Number.isInteger(s.status)&&(s.statusText?a+=`: ${s.status} ${s.statusText}`:a+=`: ${s.status}`),new Error(a)}let n=s.headers.get("content-length");if(n){let i=Number.parseInt(n,10);if(Number.isInteger(i)&&i>e){let a=`Payload (${i} bytes) exceeds max (${e} bytes).`;throw new DOMException(a,"NotReadableError")}}let o;if(s.body){let i=s.body.getReader(),a=[];try{await ie(i,e,a,0)}finally{i.releaseLock()}let c=s.headers.get("content-type");c?o=new Blob(a,{type:c}):o=new Blob(a)}else if(o=await s.blob(),o.size>e){let i=`Payload (${o.size} bytes) exceeds max (${e} bytes).`;throw new DOMException(i,"NotReadableError")}return Le&&globalThis.Buffer?$e(o):globalThis.FileReader?Ae(o):Oe(o)},m=new P,Pe=async(r,t={})=>{if(!r||!p(r))return null;let e={...S,...t},s=m.getScheme(r);if(s===null)return e.allowRelative?m.sanitize(r,e):null;if(s==="blob"){let{allow:n,deny:o,only:i}=e,a=null;if(n.includes("blob")&&!o.includes("blob")||i.includes("blob")){let c;try{c=await ae(r,e.maxBlobSize)}catch(l){e.debug&&T(`Failed to fetch and convert blob URL: ${g(r)}`,l)}c&&(i.length?i.includes("data")||(e.only=[...i,"data"]):(n.includes("data")||(e.allow=[...n,"data"]),e.deny=e.deny.filter(l=>l!=="data")),a=m.sanitize(c,e))}return e.revokeObjectURL&&URL.revokeObjectURL(r),a}return m.sanitize(r,e)},ke=(r,t={})=>{if(!r||!p(r))return null;let e={...S,...t},s=m.getScheme(r);return s===null?e.allowRelative?m.sanitize(r,e):null:s==="blob"?(e.revokeObjectURL&&URL.revokeObjectURL(r),null):m.sanitize(r,e)},Ie=async r=>{if(p(r)){let t=m.parse(r);if(t?.protocol==="blob:")try{let e=await ae(t.href),s=m.inspect(e);return s.input=r,s}catch(e){return{input:r,valid:!1,reason:e.message}}}return m.inspect(r)},ce=r=>m.verify(r),Me=r=>ce(r),De=m;export{De as default,Ie as inspectURL,Me as isURISync,ce as isValidURI,Pe as sanitizeURL,ke as sanitizeURLSync};
|
|
1
|
+
var M="http://dummy.local";var R=n=>Object.prototype.toString.call(n).slice(8,-1),l=n=>typeof n=="string"||n instanceof String,y=(n,e)=>{console.warn(`[URLSanitizer Debug] ${n}`,e)};var ue=Object.freeze(["addHook","removeHook","removeAllHooks","setConfig"]),S=[],L=new Proxy({},{get(n,e,t){let s=globalThis.DOMPurify||globalThis.window?.DOMPurify;if(s){if(S.length){for(let{prop:i,args:a}of S)typeof s[i]=="function"&&s[i](...a);S.length=0}let r=s[e];return typeof r=="function"?r.bind(s):r}return(...r)=>{if(ue.includes(e))return S.push({prop:e,args:r}),t;throw new Error("DOMPurify is not available. Ensure DOMPurify is exposed globally (e.g., window.DOMPurify).")}}});var H=/&/gi,j=/&(?=[#a-z0-9])(?![a-z0-9]+=)/i;var X=/#$/,F=/^(?:application\/(?:[\w.-]+\+)?xml|image\/svg\+xml|text\/(?:html|xml))(?:\s*;\s*|$)/i;var V=/\P{ASCII}/u,Y=/&#(x0*[\dA-F]{1,2}|0*\d{1,3});?/gi,q=/(?:%[\dA-F]{2})+/gi,Q=/^((?:[^#%]|%(?!23))*)\?$/,T=/^[a-z][\da-z+.-]*$/,W=/^(?:web\+[a-z]+|ext\+[a-z][\da-z.-]*)$/,d=/^(?:java|vb)script$/i,x=/^(?:(?:java|vb)script|blob)$/i,P=/["'<>]|%(?:22|27|3C|3E)|&(?:apos|lt|gt|quot|#0*(?:34|39|60|62)|#x0*(?:22|27|3C|3E));/i,Z=/^%[\dA-F]{2}$/i,K=/^(?:\/{2,}|\\|[a-z][a-z\d+\-.]*:[^/])/i;var J=[9,10,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];var k=new Set(J),G=new Map([[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]),ge=()=>{let n=new Map;for(let e=0;e<256;e++)!k.has(e)&&!G.has(e)&&n.set(e,`\\x${e.toString(16).padStart(2,"0").toUpperCase()}`);return n},D=ge();var[b,Re,ye,be,Ee,xe]=["&","#","<",">",'"',"'"].map(n=>`%${n.charCodeAt(0).toString(16).toUpperCase()}`),we={[b]:`${b}amp;`,[ye]:`${b}lt;`,[be]:`${b}gt;`,[Ee]:`${b}quot;`,[xe]:`${b}${Re}39;`},Fe=Array.from({length:256},(n,e)=>`%${e.toString(16).padStart(2,"0").toUpperCase()}`),se=globalThis.process?.versions?.node!==void 0,re=`[${[...D.values()].join("")}]`,_e=new RegExp(re),Ue=new RegExp(re,"g"),Se=new Uint8Array(65536),Ve=new TextEncoder,Le=new TextDecoder("utf-8",{fatal:!0}),m=(n,e=!1)=>{if(!l(n))return null;let t=V.test(n)?n.normalize("NFKC"):n;return e?t.trim().toLowerCase().replace(/:$/,""):t},f=(n,e,t=!1)=>{if(!l(n)||n==="")return null;let s=n,r=e&&l(e)?e:"";if(t&&(s=m(s),r&&(r=m(r))),typeof URL.parse=="function")return r?URL.parse(s,r):URL.parse(s);try{return r?new URL(s,r):new URL(s)}catch{return null}},ne=n=>l(n)?n.replace(/:$/,"").split("+"):[];var ie=n=>{if(!l(n))return n;let e=Z.test(n)?n.toUpperCase():n;return we[e]??n},u=n=>{let e=l(n)?n:String(n);return e.length>64?`${e.slice(0,64)}...`:e},oe=n=>l(n)?n.replace(X,"").replace(Q,"$1"):n,Te=(n,e)=>{let t=/^x/i.test(e)?Number.parseInt(e.slice(1),16):Number.parseInt(e,10);if(!Number.isInteger(t)||t<0||t>=256)return n;if(D.has(t))return"";if(k.has(t))return String.fromCharCode(t);let s=G.get(t);return String.fromCodePoint(s)},ae=(n,e=0)=>{if(!l(n))throw new TypeError(`Expected String but got ${R(n)}.`);if(!Number.isInteger(e))throw new TypeError(`Expected Number but got ${R(e)}.`);let t;try{t=decodeURIComponent(n)}catch{t=n.replace(q,r=>{try{return decodeURIComponent(r)}catch{return r}})}t=t.replace(Ue,"");let s=0;for(;s+e<16;s++){let r=t;if(t=t.replace(H,"&").replace(Y,Te),t===r)break}if(s+e>=16&&/&#/.test(t))throw new Error("Character references nested too deeply.");return t},N=(n,e="",t="")=>{if(!l(n))return{mediaType:"",mediaTypes:[],data:"",isBase64:!1};let s=n.indexOf(",");if(s===-1)return{mediaType:n,mediaTypes:n.split(";"),data:"",isBase64:!1};let r=n.substring(0,s),i=n.substring(s+1),a=e===""&&t===""?i:i+e+t,o=r.split(";"),c=o.at(-1),p=c!==void 0&&c.length===6&&c.toLowerCase()==="base64";return{mediaType:r,mediaTypes:o,data:a,isBase64:p}},ce=n=>{if(!l(n))throw new TypeError(`Expected String but got ${R(n)}.`);let e,t=n;try{e=atob(n)}catch{t=n.replace(/\s/g,"");try{e=atob(t)}catch{throw new Error(`Invalid base64 data: ${u(n)}`)}}let s;if(se&&globalThis.Buffer)s=globalThis.Buffer.from(e,"latin1");else{let r=e.length;r>65536?s=new Uint8Array(r):s=Se.subarray(0,r);for(let i=0;i<r;i++)s[i]=e.charCodeAt(i)}try{let r=Le.decode(s);return _e.test(r)?t:r}catch{return t}},z=n=>{if(se&&globalThis.Buffer)return globalThis.Buffer.from(n).toString("base64");let e=new Uint8Array(n);if(typeof e.toBase64=="function")return e.toBase64();let t=[];for(let s=0;s<e.length;s+=8192)t.push(String.fromCharCode(...e.subarray(s,s+8192)));return btoa(t.join(""))};var _=class{constructor(e,t){this.domPurify=t,this.nest=0,this.recurse=new Set,this.restrictScheme=!1,this.schemeMap=new Map([["blob",!1],["data",!1],["file",!1],["javascript",!1],["vbscript",!1]]),this.#e(e)}#e(e={}){let{allowRelative:t,debug:s,schemes:r,allow:i,deny:a,only:o}=e;if(this.allowRelative=!!t,this.debug=!!s,Array.isArray(o)&&o.length){this.schemes=new Set,this.restrictScheme=!0;for(let c of o)this.#t(c)}else{if(this.schemes=new Set(r||[]),Array.isArray(i)&&i.length)for(let c of i)this.#t(c);if(Array.isArray(a)&&a.length)for(let c of a){let p=m(c,!0);p&&this.schemeMap.set(p,!1)}}}#t(e){let t=m(e,!0);return!t||x.test(t)||!T.test(t)||t.includes("+")&&t.split("+").some(r=>d.test(r))?!1:(this.schemeMap.set(t,!0),this.schemes.add(t),!0)}enter(e){return this.recurse.has(e)?(this.debug&&y(`Circular Data URL detected and skipped: ${u(e)}`),!1):(this.nest++,this.recurse.add(e),!0)}leave(e){this.nest--,this.recurse.delete(e)}},O=class{#e;constructor(e){this.#e=e}#t(e,t){if(t.nest>16)throw new Error("Data URLs nested too deeply.");let s=f(e,null,!0);if(!s){if(t.allowRelative&&!K.test(e)){let i=f(e,M);if(i){let{protocol:a,hostname:o,pathname:c,search:p,hash:h}=i;if(a==="http:"&&o==="dummy.local")return this.#r(`${c}${p}${h}`)}}return null}let r=m(s.protocol,!0);if(t.restrictScheme&&!t.schemeMap.has(r))return null;if(r.includes("+")){let i=r.split("+");if(t.schemeMap.get(r)===!1||i.some(a=>t.schemeMap.get(a)===!1))return null}else if(t.schemeMap.get(r)===!1)return null;return r==="data"?this.#s(s,t):this.#r(s.href)}#s(e,t){let{mediaType:s,mediaTypes:r,data:i,isBase64:a}=N(e.pathname,e.search,e.hash),o=i;if(a)try{o=ce(i)}catch(c){return t.debug&&y("Failed to parse base64 data.",c),null}try{let c=ae(o).trim(),p=f(c,M);if(!p||ne(p.protocol).some(U=>x.test(U)))return null}catch(c){return t.debug&&y("Failed to parse inner data URL protocol.",c),null}return(!s||F.test(s))&&(o=this.#i(o,t)),o?(a&&o!==i&&r.pop(),`data:${r.join(";")},${o}`):null}#i(e,t){let s=e;try{s=decodeURIComponent(e)}catch{}let r;try{t.domPurify.addHook("uponSanitizeAttribute",(i,a)=>this.#n(i,a,t)),r=t.domPurify.sanitize(s)}finally{t.domPurify.removeHook("uponSanitizeAttribute")}r=oe(r);try{return encodeURI(r)}catch{return r}}#n(e,t,s){if(!t.attrValue||!/^\s*data:/i.test(t.attrValue))return;let r=f(t.attrValue,null,!0);if(!r||r.protocol!=="data:")return;let i=t.attrValue;if(!s.enter(i)){t.attrValue="";return}try{let a=this.#t(i,s);t.attrValue=a||""}finally{s.leave(i)}}#r(e){let t=e.length,s=t,r=P.exec(e);if(r&&(s=r.index),e.indexOf("&")>-1){let a=j.exec(e);if(a){let o=a.index;o<s&&(s=o)}}if(s===t)return e;let i=e.charCodeAt(s-1);return(i===63||i===38)&&s--,e.substring(0,s)}sanitize(e,t=this.#e){if(!e||!l(e))return null;let{deny:s,maxLength:r,only:i,allowRelative:a}=t;if(Number.isInteger(r)&&e.length>r){let p=`URL length ${e.length} exceeds max length ${r}.`;throw new RangeError(p)}if(!(Array.isArray(s)&&s.length>0||Array.isArray(i)&&i.length>0||a)&&(e.startsWith("https://")||e.startsWith("http://"))&&!P.test(e)&&!e.includes("data:")){let p=f(e,null,!0);return p&&t.schemes.has(p.protocol.replace(/:$/,""))?p.href.replace(/%26/g,ie):null}let c=new _(t,L);return this.#t(e,c)}async sanitizeBuffer(e,t,s=this.#e){if(!(e instanceof ArrayBuffer))return null;let r=z(e),i=`data:${t?`${t};base64`:"base64"},${r}`;if(Number.isInteger(s.maxLength)&&i.length>s.maxLength)throw new RangeError(`URL length ${i.length} exceeds max length ${s.maxLength}.`);let a=new _(s,L);if(!a.schemeMap.get("data"))return null;let o=f(i);return o?this.#s(o,a):null}async sanitizeDataURL(e,t=this.#e){if(!e||!l(e))return null;let{maxLength:s}=t;if(Number.isInteger(s)&&e.length>s)throw new RangeError(`URL length ${e.length} exceeds max length ${s}.`);let r=f(e);if(!r)return null;let i=m(r.protocol,!0);if(i!=="data")return null;let a=new _(t,L);return a.schemeMap.get(i)?this.#s(r,a):null}};var le=["aaa","aaas","about","acap","acct","acd","acr","adiumxtra","adt","aet","afp","afs","agtp","aim","amss","android","appdata","apt","ar","ari","ark","ars","at","attachment","aw","barion","beshare","bitcoin","bitcoincash","bl","blob","bluetooth","bolo","brid","browserext","cabal","caip","calculator","callto","cap","cast","casts","chrome","chrome-extension","cid","cm","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","ez","facetime","feed","feedready","fido","file","finger","first-run-pen-experience","fish","fm","ftp","fuchsia-pkg","gcx","gdsi","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","interaction","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","mdoc","mdoc-openid4vp","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","musik","mvn","mvrp","mvrps","news","nfs","ni","nih","nntp","nostr","notes","npamp","num","ocf","oid","onenote","onenote-cmd","opaquelocktoken","openid","openpgp4fpr","otpauth","palm","paparazzi","payto","pkcs11","pkg","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","sss","starknet","steam","stun","stuns","submit","svn","swh","swid","swidpath","tag","taler","teamspeak","teapot","teapots","tel","teliaeid","telnet","tftp","things","thismessage","tip","tn3270","tool","tttps","turn","turns","tv","udp","unreal","upn","urn","ust","ut2004","uuaid","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 I=class{#e=new Set(le);get(){return[...this.#e]}getScheme(e){if(!l(e))return null;let t=this.parse(e);return t?this.normalize(t.protocol,!0):null}has(e){let t=this.normalize(e,!0);return t?this.#e.has(t):!1}normalize(e,t=!1){return m(e,t)}normalizeSchemes(e){if(Array.isArray(e)){let t=[];for(let s of e){let r=m(s,!0);r&&t.push(r)}return t}return[]}parse(e,t,s=!1){return f(e,t,s)}verifyURI(e,t){if(!l(e))return!1;let s=this.parse(e);return s?this.verifyScheme(s.protocol,t,!0):!1}verifyScheme(e,t,s=!1){if(!l(e))return!1;let r=this.normalize(e,!0),i=t||this.#e;if(!r.includes("+"))return d.test(r)?!1:i.has(r);let a=r.split("+");return a.some(c=>d.test(c))?!1:i.has(r)?!0:s&&W.test(r)||a.every(c=>i.has(c))}};var ve=Object.freeze(["href","origin","protocol","username","password","host","hostname","port","pathname","search","hash"]),Ae=Object.freeze({allow:Object.freeze([]),deny:Object.freeze([]),only:Object.freeze([]),allowRelative:!1,debug:!1,revokeObjectURL:!1,maxBlobSize:16777216,maxLength:65536}),Ne=async(n,e)=>{let t=n.body.getReader(),s=[];try{let r=0;for(;r<=e;){let{done:o,value:c}=await t.read();if(o)break;if(r+=c.byteLength,r>e)throw await t.cancel("Size limit exceeded"),new DOMException(`Payload (${r} bytes) exceeds max (${e} bytes).`,"NotReadableError");s.push(c)}let i=new Uint8Array(r),a=0;for(let o of s)i.set(o,a),a+=o.byteLength;return i.buffer}finally{t.releaseLock()}},pe=async(n,e)=>{let t=16777216;Number.isInteger(e)&&e>0&&(t=e);let s=await fetch(n);if(!s.ok){let c=`Failed to fetch ${u(n)}`;throw Number.isInteger(s.status)&&(s.statusText?c+=`: ${s.status} ${s.statusText}`:c+=`: ${s.status}`),new Error(c)}let r=s.headers.get("content-length");if(r){let o=Number.parseInt(r,10);if(Number.isInteger(o)&&o>t)throw new DOMException(`Payload (${o} bytes) exceeds max (${t} bytes).`,"NotReadableError")}let i=s.headers.get("content-type")||"",a;if(s.body)a=await Ne(s,t);else{let o=await s.blob();if(o.size>t)throw new DOMException(`Payload (${o.size} bytes) exceeds max (${t} bytes).`,"NotReadableError");a=await o.arrayBuffer()}return{buffer:a,mimeType:i}},$=class extends I{#e;#t;#s;constructor(){super(),this.#e=new Set(super.get()),this.#t={...Ae,schemes:this.#e},this.#s=new O(this.#t)}#i(e){return!l(e)||x.test(e)||e.includes("+")&&e.split("+").some(s=>d.test(s))?!1:T.test(e)}#n(e,t={}){if(!e||!l(e))return{isValid:!1};let s={...this.#t,...t,allow:this.normalizeSchemes(t.allow),deny:this.normalizeSchemes(t.deny),only:this.normalizeSchemes(t.only),schemes:this.#e},r=this.getScheme(e);return r===null&&!s.allowRelative?{isValid:!1}:{options:s,scheme:r}}#r(e){let t={input:e,href:null,valid:!1};if(!l(e))return t.reason=`Invalid URL input: ${u(e)}`,t;if(e==="")return t.reason="Invalid URL input: (empty string)",t;let s,r=null;try{s=this.#s.sanitize(e,{...this.#t,allow:["data"],allowRelative:!0}),s||(r="Sanitization failed (blocked by allowed schemes or rules).")}catch(i){r=i.message}if(s){let i=f(s);if(t.valid=!0,i){if(m(i.protocol,!0)==="data"){let{mediaTypes:o,data:c,isBase64:p}=N(i.pathname,i.search,i.hash);p&&o.pop(),t.data={mime:o.join(";"),base64:p,data:c}}else t.data=null;for(let o of ve){let c=i[o];l(c)&&(t[o]=c)}}else t.valid=!1,t.relative=!0,t.href=s,t.reason="Invalid as an absolute URL, but valid as a relative URL."}else t.reason=r;return t}get(){return[...this.#e]}has(e){let t=m(e,!0);return t?this.#e.has(t):!1}add(e){if(!l(e))throw new TypeError(`Expected String but got ${R(e)}.`);let t=m(e,!0);if(!this.#i(t))throw new Error(`Invalid scheme: ${e}`);return this.#e.add(t),[...this.#e]}remove(e){if(!l(e))return!1;let t=m(e,!0);return this.#e.delete(t)}reset(){this.#e=new Set(super.get())}async sanitizeURL(e,t){let{isValid:s,options:r,scheme:i}=this.#n(e,t);if(s===!1)return null;if(i==="blob"){let{allow:a,deny:o,only:c}=r,p=null;if(a.includes("blob")&&!o.includes("blob")||c.includes("blob")){let h=null,U="";try{let g=await pe(e,r.maxBlobSize);h=g.buffer,U=g.mimeType}catch(g){r.debug&&y(`Failed to fetch and convert blob URL: ${u(e)}`,g)}h&&(c.length?c.includes("data")||(r.only=[...c,"data"]):(a.includes("data")||(r.allow=[...a,"data"]),r.deny=o.filter(g=>g!=="data")),p=await this.#s.sanitizeBuffer(h,U,r))}return r.revokeObjectURL&&URL.revokeObjectURL(e),p}else if(i==="data")return this.#s.sanitizeDataURL(e,r);return this.#s.sanitize(e,r)}sanitizeURLSync(e,t){let{isValid:s,options:r,scheme:i}=this.#n(e,t);return s===!1?null:i==="blob"?(r.revokeObjectURL&&URL.revokeObjectURL(e),null):this.#s.sanitize(e,r)}async inspectURL(e){if(l(e)){let t=f(e,null,!0);if(t?.protocol==="blob:")try{let{buffer:s,mimeType:r}=await pe(t.href),i=z(s),a=`data:${r?`${r};base64`:"base64"},${i}`,o=this.#r(a);return o.input=e,o}catch(s){return{input:e,valid:!1,reason:s.message}}}return this.#r(e)}isValidURI(e){return super.verifyURI(e,this.#e)}};var E=new $,ut=async(n,e)=>E.sanitizeURL(n,e),ht=(n,e)=>E.sanitizeURLSync(n,e),gt=async n=>E.inspectURL(n),Rt=n=>E.isValidURI(n),yt=n=>E.isValidURI(n),bt=E;export{$ as URLSanitizer,bt as default,gt as inspectURL,yt as isURISync,Rt as isValidURI,ut as sanitizeURL,ht as sanitizeURLSync};
|
|
2
2
|
/*!
|
|
3
3
|
* URL Sanitizer
|
|
4
4
|
*
|