@@ -9,20 +9,29 @@ const $A = (selector, parent = document) => parent?.querySelectorAll(selector);
99
1010function toFilteredUTFText ( s ) {
1111 if ( ! s ) return s
12- return safeFilteredText ( s . normalize ( 'NFC ' ) . replace ( / [ \u200B - \u200C \u200E - \u200F \uFEFF \u2060 - \u206F \uE000 - \uF8FF \u007F - \u009F \u2028 \u2029 ] / g, '' ) ) ;
12+ return safeFilteredText ( s . normalize ( 'NFKC ' ) . replace ( / [ \u200B - \u200C \u200E - \u200F \uFEFF \u2060 - \u206F \uE000 - \uF8FF \u007F - \u009F \u2028 \u2029 \uFE00 – \uFE0D \u202A - \u202E \u2066 - \u2069 \u00AD \u034F \u180E \u202F \u0080 - \u009F \u115F \u1160 \u17B4 \uFDD0 - \uFDEF \u0300 - \u036F ] / g, '' ) ) ;
1313 // \u200D - kept for unicode character joining
1414 // \u0000-\u001F (C0), \u007F-\u009F (C1)
1515 // \u2028 - line sep, \u2029 - par sep.
1616 // \u0000-\u001F ... kept unsolved because of browser loading stooped due to this!
17+ // \uFE00–\uFE0F - think about this - fall unicode icons to no color forms, but improves security
1718}
1819
1920function safeFilteredText ( s ) {
2021 if ( ! s ) return s ;
21- return s . split ( '' ) . filter ( ( c , i ) => {
22- const code = c . charCodeAt ( 0 ) ;
23- return code >= 32 || [ 9 , 10 , 13 ] . includes ( code ) ;
24- } )
25- . join ( '' ) ;
22+ let out = '' ;
23+ for ( const ch of s ) {
24+ const cp = ch . codePointAt ( 0 ) ;
25+
26+ // skip: SMP variation selectors
27+ if ( cp >= 0xE0100 && cp <= 0xE01EF ) continue ;
28+
29+ // keep: TAB (9), LF (10), CR (13), 32 and higher
30+ if ( cp >= 32 || cp === 9 || cp === 10 || cp === 13 ) {
31+ out += ch ;
32+ }
33+ }
34+ return out ;
2635}
2736
2837function newUID ( length = 8 ) {
@@ -240,7 +249,9 @@ var _Storage = (() => {
240249 if ( ! storagesC . has ( key ) )
241250 return null ;
242251
243- return await storagesC . get ( key ) . searchImage ( filePath ) ;
252+ let rawData = await storagesC . get ( key ) . searchImage ( filePath ) ;
253+ rawData = await doSteganographyCorrectionForImage ( rawData || filePath ) ;
254+ return rawData ;
244255 }
245256
246257 function getKey ( key ) {
@@ -259,6 +270,56 @@ var _Storage = (() => {
259270 } ;
260271} ) ( ) ;
261272
273+ async function doSteganographyCorrectionForImage ( data ) {
274+ const img = new Image ( ) ;
275+ if ( data . startsWith ( 'data:image/' ) )
276+ img . src = data ;
277+ else {
278+ const response = await fetchDataOrZero ( data ) ;
279+ if ( response . byteLength == 0 )
280+ return null ;
281+
282+ const content = btoa ( String . fromCharCode ( ...new Uint8Array ( response ) ) ) ;
283+ img . src = `data:image/${ data . split ( '.' ) . pop ( ) . toLowerCase ( ) } ;base64,${ content } ` ;
284+ }
285+
286+ await new Promise ( ( resolve , reject ) => {
287+ img . onload = resolve ;
288+ img . onerror = reject ;
289+ } ) ;
290+
291+ const canvas = document . createElement ( 'canvas' ) ;
292+ const ctx = canvas . getContext ( '2d' ) ;
293+ canvas . width = img . width ;
294+ canvas . height = img . height ;
295+ ctx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
296+
297+ if ( canvas . width > 16 || canvas . height > 16 ) {
298+ // exception : prevent blur of edges and lines in image
299+ const tempCanvas = document . createElement ( 'canvas' ) ;
300+ const tempCtx = tempCanvas . getContext ( '2d' ) ;
301+ tempCanvas . width = Math . ceil ( img . width * 1.005 ) ;
302+ tempCanvas . height = Math . ceil ( img . height * 1.005 ) ;
303+ tempCtx . drawImage ( img , 0 , 0 , tempCanvas . width , tempCanvas . height ) ;
304+ ctx . drawImage ( tempCanvas , 0 , 0 , canvas . width , canvas . height ) ;
305+ } else {
306+ ctx . drawImage ( img , 0 , 0 ) ;
307+ }
308+
309+ const imageData = ctx . getImageData ( 0 , 0 , canvas . width , canvas . height ) ;
310+ const pixels = imageData . data ;
311+ for ( let i = 0 ; i < pixels . length ; i += 4 ) {
312+ pixels [ i ] = ( pixels [ i ] & ~ 1 ) | ( Math . random ( ) < 0.5 ? 1 : 0 ) ;
313+ pixels [ i + 1 ] = ( pixels [ i + 1 ] & ~ 1 ) | ( Math . random ( ) < 0.5 ? 1 : 0 ) ;
314+ pixels [ i + 2 ] = ( pixels [ i + 2 ] & ~ 1 ) | ( Math . random ( ) < 0.5 ? 1 : 0 ) ;
315+ pixels [ i + 3 ] = ( pixels [ i + 3 ] & ~ 1 ) | ( Math . random ( ) < 0.5 ? 1 : 0 ) ;
316+ }
317+ ctx . putImageData ( imageData , 0 , 0 ) ;
318+
319+ const canvasReply = canvas . toDataURL ( 'image/png' ) ;
320+ return canvasReply ;
321+ }
322+
262323/**
263324 * @interface
264325 */
@@ -270,24 +331,43 @@ class IStorage {
270331}
271332
272333class StorageZip extends IStorage {
334+ #storageO;
335+
273336 constructor ( ) {
274337 super ( ) ;
275- this . storageO = null ;
338+ this . # storageO = null ;
276339 }
277340
278341 async init ( path ) {
279- this . storageO = await ZIPHelpers . loadZipFromUrl ( path ) ;
342+ this . #storageO = await ZIPHelpers . loadZipFromUrl ( path ) ;
343+ let hasSlip = false ;
344+ const paths = Object . keys ( this . #storageO. files ) ;
345+ for ( const path of paths ) {
346+ if ( path . includes ( '..' ) ) {
347+ delete this . #storageO. files [ path ] ;
348+ hasSlip = true ;
349+ log ( 'E Zip Slip:' , path ) ;
350+ }
351+ }
352+ if ( hasSlip ) {
353+ const msg = `Invalid paths found in given archive <b>${ path } </b>. Loading stopped for ensuring security.` ;
354+ log ( `E ${ msg } ` ) ;
355+ let pane = $O ( '#cors-error' ) || $O ( '#content' ) || document . body ;
356+ pane . innerHTML = `⚠ ${ msg } ` ;
357+ throw new Error ( msg ) ;
358+ }
359+ Object . freeze ( this . #storageO. files ) ;
280360 return this ;
281361 }
282362
283363 async search ( filePath , format = STOF_TEXT ) {
284- return await ZIPHelpers . searchArchiveForFile ( filePath , this . storageO , format ) ;
364+ return await ZIPHelpers . searchArchiveForFile ( filePath , this . # storageO, format ) ;
285365 }
286366
287367 async getSubdirs ( parentPath ) {
288368 const subdirs = new Set ( ) ;
289369
290- this . storageO ?. forEach ( ( relativePath , file ) => {
370+ this . # storageO?. forEach ( ( relativePath , file ) => {
291371 if ( relativePath . startsWith ( parentPath ) && relativePath !== parentPath )
292372 {
293373 const subPath = relativePath . slice ( parentPath . length ) ;
0 commit comments