Repository navigation
Expand file tree
/
Copy pathServiceWorker.js
More file actions
2908 lines (2627 loc) · 135 KB
/
Copy pathServiceWorker.js
File metadata and controls
2908 lines (2627 loc) · 135 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
127
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* 📄 [ DISCIPLINARE TECNICO DI CONFORMITÀ ]
*
* ⚙️ CORE: 🪖 PANZER v7.9.7+ ( v2.x SDK ⛓️)
*
* 🛡️ REQUISITI OPERATIVI DI SISTEMA :
*
* 1. 🧱🔀 ISOLAMENTO DEI FLUSSI :
* Profilazione fisica del canale nello Scope isolato e protetto del Service Worker.
*
* 2. 🔀🎭 INSTRADAMENTO POLIMORFO :
* Flusso Fluido d'ufficio basato su scansione ciclica 'for...in' e telemetria hardware.
*
* 3. 🩻🪨 RESILIENZA STRUTTURALE :
* Meccanismo Smart Sync con campionamento RTT e interruzione atomica via AbortController.
*
* 4. 🪨🔬🧬 SW FORENSICS ( DNA CHECK ) :
* Ispezione biometrica sequenziale del payload su tre (3) scomparti stagni:
* - FASE 1 ( TESTA 🤯 ): Validazione Strict dei Magic Numbers contro attacchi di MIME-sniffing.
* - FASE 2 ( CODA 🚓🚓🚓 ): Verifica dei marcatori strutturali ( Footer 👣 ) contro attacchi di tipo Append.
* - FASE 3 ( CORPO 🪵🧬 ): Analisi euristica stringente anti-script per l'intercettazione di vettori malevoli nei PDF.
*
* 5. 🌡️🛡 ️CPU THERMAL SHIELD :
* Ottimizzatore adattivo del respiro dell'Event Loop via 'waitTillIdle' per la prevenzione del logoramento hardware.
*
* ⚙️️ NUCLEO: [ SERVICE WORKER ] - ( SW ) " 🪖 PANZER V7.x+ " :
* 🎖 MILITARY EDITION ( 🔐 CRYPTOGRAPHIC 🗄️ VAULT 🔑 AES-GCM 256-BIT ).
*
* Licenza di Distribuzione :
* 🇪🇺📜 EUPL v1.2 ( Conforme alle direttive CAD della PA 🏛 ️)
*
* Sviluppo Software e Ingegnerizzazione del Protocollo a cura di :
* 👩💻🇮🇹 Valentino Aglianò - Perito Industriale Informatico ( 2013 )
* [ 🏛️ Istruttore Informatico - Idoneo Nazionale MaxConcorso ASMEL 2025 ]
*
*
* 🛡 ️DIRECTIVE DI SICUREZZA ATTIVA E BONIFICA FORENSE ( 🔐 ZERO-TRUST POLICY 🚨 ) :
*
* [ 🔑 MASTER KEY ]:
* Istanza crittografica non esportabile (CryptoKey) generata a runtime e isolata in RAM volatile.
*
* [ 🗄️ PWA_VAUL T]:
* Persistenza protetta della chiave opaca in IndexedDB ➿ tramite algoritmo di clonazione strutturata (extractable: false).
*
* [ 🫙 SANDBOX CONTENIMENTO ]:
* Iniezione perentoria di header CSP restrittivi per l'isolamento dei contenuti erogati a schermo.
*
* [ 🐺 WATCHDOG LOOPBACK ]:
* Test atomico di cifratura/decrittazione simmetrica a runtime (🐦 vaultCanaryText) per la validazione della memoria.
*
* [ 🚨🧼 EMERGENCY WIPE ]:
* Tabula rasa immediata, distruzione totale e bonifica delle cache in caso di fallimento strutturale del canarino.
*
* [ 🚧🕵 ️ANTI-MEMORY INSPECTION ]:
* Sovrascrittura fisica e azzeramento dei buffer binari di transito (headBuffer, tailBuffer, fullBuffer), tramite metodo nativo 'Uint8Array.prototype.fill(0)' immediatamente post-elaborazione.
*
*/
let encryptionKey = null;
let isLogicEnabled = false;
let syncAbortController = null;
const BASE_PATH = self.location.pathname.replace(/[^\/]+$/, "").replace(/\/+/g, '/');
/**
* 📊 CONFIGURAZIONE GLOBALE ( Dizionario dei Vincoli Operativi )
* Definisce i parametri strutturali per la resilienza di rete, crittografia e tolleranza ai guasti.
*/
let globalAbortController = new AbortController();
const CONFIG = {
ROOT: BASE_PATH,
cacheName: 'PA_PANZER_ENGINE_v7.9.7',
userCacheName: 'user_PA_PANZER_ENGINE_v7.9.7',
vaultCanaryText: 'KANARY_OK_PANZER_KEY',
MAX_PATH_LENGTH: 400,
ALLOWED_SCHEMES: [
'http:',
'https:'
],
ALLOWED_METHODS: [
'GET',
'HEAD',
'POST'
],
ALLOWED_IPC_TYPES: [
'INIT_DB'
],
userCacheTTL: 7,
networkResilient: {
maxRetries: 5,
timeWaitSec: 3,
profiles: {
'Ultrafast': {
limit: 12, timeout: 15
},
'Fast': {
limit: 8, timeout: 20
},
'Medium': {
limit: 4, timeout: 45
},
'Low': {
limit: 2, timeout: 90
},
'Verylow': {
limit: 1, timeout: 120
}
}
},
/**
* 🖼️ SEZIONE IMAGE: Soglie e controlli per formati grafici Raster & SVG
*/
minSizeMap: {
'image': {
// Dimensione minima accettabile (byte) per estensione per prevenire asset vuoti o corrotti
'webp': 500,
'jpeg': 5000,
'jpg': 5000,
'png': 500,
'avif': 500,
'svg': 100,
// Limiti dimensionali dei buffer di analisi
'maxHeaderBytes': 4096, // Byte massimi di testa da ispezionare per Magic Numbers
'maxFooterBytes': 4096, // Byte massimi di coda per verificare i marcatori di chiusura
'maxSvgBytes': 65536, // Tetto massimo di lettura per la scansione dei tag SVG
'maxRasterBytes': 65536, // Tetto massimo per la scansione anti-poliglotta nei raster
'magicNumbers': {
// Firme esadecimali ufficiali per la verifica d'integrità di testa e coda
'header': [
'52494646',
'FFD8FF',
'89504E47',
'3C737667'
], // WEBP/RIFF, JPEG, PNG, SVG (<svg)
'footer': [
'57454250',
'49454E44AE426082',
'FFD9',
'3B',
'3C2F7376673E'
] // WEBP, PNG (IEND), JPEG, SVG (; / </svg>)
},
'svgPatterns': {
'forbiddenTags': /<(script|foreignobject|embed|object|iframe)[\s>]/i,
'forbiddenEvents': /\son\w+\s*=/i,
'forbiddenUri': /(href|xlink:href)\s*=\s*["']?\s*(javascript:|data:text\/html)/i
},
'rasterPatterns': {
'executableSignatures': /<script|javascript:|eval\(|function\s*\(/i
},
'useHeadProbe': true,
'tolerance': 0.30,
'defaultMin': 500,
'timingNoiseMs': 50
},
'pdf': {
'firmato': 5000,
'default': 10000,
'maxHeaderBytes': 4096,
'maxFooterBytes': 8192,
'magicNumbers': {
'header': [
'25504446',
'5044462D',
'3082',
'3081'
],
'footer': [
'2525454F46',
'3082',
'3081'
]
},
'pdfMaliciousPatterns': [
'/javascript',
'/js',
'/launch',
'/embeddedfile',
'/openaction',
'/submitform',
'/importdata',
'/richmedia'
],
'compressedStreamPatterns': [
'/flatedecode',
'/lzwdecode',
'/objstm',
'/crypt',
'/asciihexdecode',
'/asci85decode',
'/runlengthdecode'
],
'supportedSignatures': [
'adbe.pkcs7.detached',
'adbe.pkcs7.sha1',
'etsi.cades.detached',
'application/pkcs7-mime',
'smime.p7m'
],
'chunkSize': 1048576,
'overlapSize': 4096,
'streamContextWindow': 300,
'idleMinMs': 200,
'idleTimeoutMs': 8000,
'useHeadProbe': false,
'tolerance': 0.30,
'defaultMin': 2048,
'timingNoiseMs': 50
},
'code': {
'html': 100,
'css': 100,
'js': 100,
'json': 10,
'magicNumbers': {
'header': [],
'footer': []
},
'useHeadProbe': true,
'tolerance': 0.90,
'defaultMin': 10,
'timingNoiseMs': 50
},
'universal': {
'tolerance': 0.5,
'minAbsoluteByte': 64,
'fallbackToGet': true,
'maxHeaderBytes': 4096,
'maxFooterBytes': 8192,
'timingNoiseMs': 50
}
},
coreAssets: [ BASE_PATH ],
manifestPath: ``,
mappingLogic: {
idKeys: [],
contexts: {},
parentSpecials: {}
},
defPlaceHolderLogo: ``,
defaultExt: '.webp',
extensions: [
'jpg', 'jpeg', 'png', 'webp',
'pdf'
],
extExlPHr: [
'pdf', 'zip', 'doc', 'docx'
],
get syncRegex() {
return new RegExp(`\\.(${this.extensions.join('|')})$`, 'i');
},
get allVariants() {
const variants = [];
this.extensions.forEach(ext => {
variants.push('.' + ext.toLowerCase(), '.' + ext.toUpperCase());
});
return [...new Set(variants)];
}
};
/*
* 🚧 SBARRAMENTO LOGICO IN CASCATA 🚧
* ❄️ DEEP FREEZE:
* Ciclo ricorsivo di congelamento profondo.
* Blocca CONFIG e qualsiasi sotto-oggetto presente o futuro.
*
* anti-Prototype Pollution
* Hijacking a runtime
*/
const deepFreeze = (obj) => {
if (obj && typeof obj === 'object') {
Object.freeze(obj);
for (const key of Object.getOwnPropertyNames(obj)) {
const prop = obj[key];
if (prop !== null && typeof prop === 'object' && !Object.isFrozen(prop)) {
deepFreeze(prop);
}
}
}
};
Object.freeze(deepFreeze);
deepFreeze(CONFIG);
/**
* 🛡🏴☠️ ANTI-PROFILING: Generatore di oggetti Error sterili privi di stack trace esposto.
* @param {string} name - Nome formale dell'errore.
* @param {string} message - Descrizione sintetica della violazione o del blocco.
* @returns {Readonly<Object>} Errore sigillato ed enucleato da tracciamenti interni.
*/
const createCleanError = (name, message) => {
const cleanErr = {};
Object.defineProperties(cleanErr, {
name: { value: String(name), enumerable: true },
message: { value: String(message), enumerable: true },
stack: { value: undefined, configurable: false, writable: false, enumerable: false }
});
return Object.freeze(cleanErr);
};
/**
* 🔒 CONTEXTUAL HTML ENCODING: Sanitizzatore di caratteri speciali per prevenire iniezioni DOM/XSS.
* @param {string} str - Stringa grezza proveniente da input o URI telematici.
* @returns {string} Stringa con caratteri riservati convertiti in entità HTML sicure.
*/
const escapeHtml = (str) => {
if (typeof str !== 'string') return '';
return str
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/\//g, '/');
};
/**
* 🛡⏱️ SHIELD TEMPORALE (ANTI-TIMING ATTACK)
* Livella il tempo di computazione percepito dall'esterno su una baseline fissa,
* 🎲 aggiungendo un jitter stocastico per distruggere i profili statistici del 🔴 Red Team.
* * PARAMETRI OPERATIVI DI INGRESSO:
* @param {number} startTime - Marcatore temporale ad alta precisione (generato tramite performance.now())
* acquisito all'inizio della sessione di scansione o transito di rete.
* @param {number} targetBaselineMs - Finestra temporale minima nominale (espressa in millisecondi)
* garantita per l'uniformazione della latenza verso l'esterno.
*/
const injectTimingNoise = async (startTime, targetBaselineMs = 45) => {
try {
const elapsed = performance.now() - startTime;
if (elapsed < targetBaselineMs) {
const padding = targetBaselineMs - elapsed;
const jitter = Math.random() * (25 - 5) + 5;
const totalShieldTime = Math.ceil(padding + jitter);
await sleep(totalShieldTime);
} else {
const microJitter = Math.random() * (5 - 1) + 1;
await sleep(Math.ceil(microJitter));
}
} catch (e) {
// Fail-safe asettico...
}
};
Object.freeze(injectTimingNoise);
/**
* ⏳ Generatore di ritardo temporizzato isolato.
* Blocca l'esecuzione sequenziale asincrona del ciclo per i secondi di resilienza configurati.
* @param {number} ms - Tempo di sospensione espresso in millisecondi.
* @returns {Promise<void>} Promessa risolta al termine del timeout.
*/
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
Object.freeze(sleep);
/**
* ⏳💤 Ottimizzatore adattivo del respiro della CPU per Service Worker.
* Misura lo stress attuale dell'Event Loop e calcola la pausa ideale.
* @param {number} [minPauseMs=50] - Pausa minima richiesta (Base assoluta: 50ms).
* @param {number} [timeoutMs=8000] - Tempo limite massimo (guardia) di sicurezza.
* @returns {Promise<void>}
*/
const waitTillIdle = (minPauseMs = 50, timeoutMs = 8000) => {
return new Promise((resolve) => {
const pausaBaseMinima = Math.max(30, minPauseMs);
const inizioTest = performance.now();
const ATTESA_TEST_MS = 20; // Micro-timer
setTimeout(() => {
const fineTest = performance.now();
const tempoEffettivo = fineTest - inizioTest;
// Calcola lo sfasamento reale dell'Event Loop (drift)
const driftEventLoop = Math.max(0, tempoEffettivo - ATTESA_TEST_MS);
const coefficienteSensibilita = 25;
let tempoRespiroCalcolato = pausaBaseMinima + (driftEventLoop * coefficienteSensibilita);
tempoRespiroCalcolato = Math.min(tempoRespiroCalcolato, timeoutMs);
console.log(`⏳ SW Drift: ${driftEventLoop.toFixed(1)}ms -> Pausa applicata: ${Math.round(tempoRespiroCalcolato)}ms`);
setTimeout(resolve, tempoRespiroCalcolato);
}, ATTESA_TEST_MS);
});
};
Object.freeze(waitTillIdle);
/**
* 🔬🧬 SW Forensics & DNA Check - v4.6
* ( 🪨 Hardened 🪖 v7.9.x+ ).
*
* @param {Response|Blob} input - Il flusso dati grezzo intercettato dal network o dal cache layer.
* @param {string} contentType - Intestazione MIME-Type ufficiale dichiarata dal server.
* @param {number} [expectedSize=0] - Dimensione nominale attesa (Content-Length) per verifica tolleranza.
* @param {boolean} [isEncrypted=false] - Flag di bypass firme per i flussi già cifrati nel Bunker.
* @param {AbortSignal} [signals=globalAbortController?.signal] - Segnale Globale di interruzione atomica per processi pendenti.
* @returns {Promise<{valid: boolean, blob: Blob|null, response: Response|null}>}
*/
const isValidBlob = async (input, contentType, expectedSize = 0, isEncrypted = false, signals = globalAbortController?.signal) => {
const startForensicTime = performance.now();
let signal = signals || null;
let blob = null;
let encoding = null;
let finalContentType = contentType;
let originalResponse = null;
let isResponseInput = false;
const result = { valid: false, blob: null, response: null };
let headerBuffer = null;
let tailBuffer = null;
const activeBuffers = [];
const wipeRAM = () => {
if (headerBuffer) {
try { new Uint8Array(headerBuffer).fill(0); } catch (_) {}
headerBuffer = null;
}
if (tailBuffer) {
try { new Uint8Array(tailBuffer).fill(0); } catch (_) {}
tailBuffer = null;
}
while (activeBuffers.length > 0) {
const buf = activeBuffers.pop();
if (buf) {
try { new Uint8Array(buf).fill(0); } catch (_) {}
}
}
};
try {
let mainType = (contentType || '').split('/')[0]?.toLowerCase() || '';
let subType = (contentType || '').split('/')[1]?.split(';')[0]?.toLowerCase() || '';
const minMap = CONFIG?.minSizeMap || {};
const pdfConfig = minMap?.pdf || {};
const universal = minMap.universal || { minAbsoluteByte: 64, tolerance: 0.05, timingNoiseMs: 50 };
let isPdfContext = subType === 'pdf' || subType.includes('pkcs7') || subType.includes('p7m') ||
(contentType || '').toLowerCase().includes('pdf') ||
(contentType || '').toLowerCase().includes('pkcs7') ||
(contentType || '').toLowerCase().includes('p7m');
const section = isPdfContext ? pdfConfig : (minMap[mainType] || minMap[subType] || minMap['code'] || universal);
const timingNoiseMs = section?.timingNoiseMs || universal?.timingNoiseMs || 50;
if (input instanceof Response) {
if (signal?.aborted) return result;
originalResponse = input;
isResponseInput = true;
encoding = input.headers.get('Content-Encoding');
finalContentType = contentType || input.headers.get('Content-Type') || '';
try {
blob = await input.clone().blob();
} catch (e) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
return result;
}
} else if (input instanceof Blob) {
if (signal?.aborted) return result;
isResponseInput = false;
finalContentType = contentType || input.type || '';
blob = input.slice(0, input.size, finalContentType);
} else {
await injectTimingNoise(startForensicTime, timingNoiseMs);
return result;
}
if (!blob || !(blob instanceof Blob)) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
return result;
}
mainType = finalContentType.split('/')[0]?.toLowerCase() || '';
subType = finalContentType.split('/')[1]?.split(';')[0]?.toLowerCase() || '';
// 🐛 Controllo robusto sull'encoding per evitare che undefined attivi falsamente il bypass
const isTransformed = encoding !== null && encoding !== undefined && encoding !== '' && encoding.toLowerCase() !== 'identity';
let minSize = section?.defaultMin || universal.minAbsoluteByte || 64;
if (section) {
const tolerance = section.tolerance || universal.tolerance || 0.1;
const baseMin = section[subType] || section.defaultMin || section.default || universal.minAbsoluteByte || 64;
minSize = (expectedSize > 0) ? (expectedSize * (1 - tolerance)) : baseMin;
}
if (isEncrypted || isTransformed) minSize = universal.minAbsoluteByte || 64;
if (blob.size < minSize) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Forensics: Asset scartato per dimensione insufficiente", createCleanError("SizeCheckFailed", "Dimensione inferiore alla soglia minima"));
return result;
}
// 🛡️ FASE 1: ANALISI MAGIC NUMBERS & SIGNATURES...
if (!isEncrypted && !isTransformed) {
const maxHeaderBytes = section?.maxHeaderBytes || universal?.maxHeaderBytes || 4096;
const headerSliceSize = Math.min(blob.size, maxHeaderBytes);
headerBuffer = await blob.slice(0, headerSliceSize).arrayBuffer();
activeBuffers.push(headerBuffer);
const headerBytes = new Uint8Array(headerBuffer);
const headerText = new TextDecoder('iso-8859-1', { fatal: false }).decode(headerBytes);
const headerHex = Array.from(headerBytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('').toUpperCase();
// Auto-rilevamento robusto: se il file inizia con la firma PDF (%PDF- / 255044462D), forza il contesto PDF
if (headerHex.startsWith('255044462D') || headerText.startsWith('%PDF-')) {
isPdfContext = true;
}
const activeSection = isPdfContext ? pdfConfig : section;
const isSvg = subType === 'svg' || finalContentType.includes('svg');
const isRasterImage = ['image/png', 'image/webp', 'image/jpeg'].some(t => finalContentType.includes(t)) || ['png', 'webp', 'jpg', 'jpeg'].includes(subType);
if (isSvg) {
const svgSliceSize = Math.min(blob.size, activeSection?.maxSvgBytes || 65536);
const svgBuffer = await blob.slice(0, svgSliceSize).arrayBuffer();
activeBuffers.push(svgBuffer);
const svgArray = new Uint8Array(svgBuffer);
const svgText = new TextDecoder('utf-8', { fatal: false }).decode(svgArray).toLowerCase();
const svgPats = activeSection?.svgPatterns || {};
const forbiddenTags = svgPats.forbiddenTags || /<(script|foreignobject|embed|object|iframe)[\s>]/i;
const forbiddenEvents = svgPats.forbiddenEvents || /\son\w+\s*=/i;
const forbiddenUri = svgPats.forbiddenUri || /(href|xlink:href)\s*=\s*["']?\s*(javascript:|data:text\/html)/i;
if (forbiddenTags.test(svgText) || forbiddenEvents.test(svgText) || forbiddenUri.test(svgText)) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Security: Blocco SVG con elementi/handler non sicuri", createCleanError("SvgMalwareDetected", "Tag o evento ostile rilevato nell'SVG"));
return result;
}
}
if (isRasterImage) {
const sampleSize = Math.min(blob.size, activeSection?.maxRasterBytes || 65536);
const sampleBuffer = await blob.slice(0, sampleSize).arrayBuffer();
activeBuffers.push(sampleBuffer);
const sampleArray = new Uint8Array(sampleBuffer);
const bodyText = new TextDecoder('utf-8').decode(sampleArray);
const rasterPats = activeSection?.rasterPatterns || {};
const executableSignatures = rasterPats.executableSignatures || /<script|javascript:|eval\(|function\s*\(/i;
if (executableSignatures.test(bodyText)) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Security: Blocco file poliglotta", createCleanError("PolyglotDetected", "Signature di codice eseguibile nei dati immagine"));
return result;
}
}
// 🐛️ File di testo, codice o senza magic numbers espliciti non devono mai essere controllati con le firme binarie o i PDF
const isTextOrCode = finalContentType.includes('text/') ||
finalContentType.includes('json') ||
finalContentType.includes('javascript') ||
finalContentType.includes('manifest') ||
!activeSection?.magicNumbers?.header ||
activeSection.magicNumbers.header.length === 0;
const magicSource = isTextOrCode ? { header: [] } : (activeSection?.magicNumbers || {});
const supportedSignatures = isTextOrCode ? [] : (activeSection?.supportedSignatures || []);
if (activeSection && (magicSource?.header?.length > 0 || supportedSignatures.length > 0)) {
if (signal?.aborted) return result;
let hasValidSig = magicSource?.header?.some(sig => {
const cleanSig = sig.toUpperCase();
if (headerHex.startsWith(cleanSig)) return true;
try {
const literalStr = cleanSig.match(/.{1,2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join('');
return headerText.startsWith(literalStr);
} catch (_) {
return false;
}
}) || false;
if (!hasValidSig && isPdfContext) {
hasValidSig = headerHex.startsWith('25504446') || headerText.startsWith('%PDF');
}
if (!hasValidSig && supportedSignatures.length > 0) {
hasValidSig = supportedSignatures.some(sigPattern => {
const patternLower = sigPattern.toLowerCase();
return headerText.toLowerCase().includes(patternLower) || finalContentType.toLowerCase().includes(patternLower);
});
}
if (!hasValidSig && !isPdfContext && (magicSource?.header?.length > 0 || supportedSignatures.length > 0)) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Security: Firma header non valida", createCleanError("HeaderCheckFailed", "Mismatch firma binaria di testa"));
return result;
}
}
}
// 🛡️ FASE 2: ANALISI MARCATORI DI CODA...
const activeSectionFooter = isPdfContext ? pdfConfig : section;
const magicSourceFooter = activeSectionFooter?.magicNumbers;
if (!isEncrypted && !isTransformed && (magicSourceFooter?.footer?.length > 0 || isPdfContext)) {
if (signal?.aborted) return result;
const maxFooterBytes = activeSectionFooter?.maxFooterBytes || universal?.maxFooterBytes || 8192;
const fetchSize = Math.min(blob.size, maxFooterBytes);
tailBuffer = await blob.slice(-fetchSize).arrayBuffer();
activeBuffers.push(tailBuffer);
const tailBytes = new Uint8Array(tailBuffer);
const tailHex = Array.from(tailBytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('').toUpperCase();
const tailText = new TextDecoder('iso-8859-1', { fatal: false }).decode(tailBytes);
let hasValidFooter = false;
if (magicSourceFooter?.footer?.length > 0) {
hasValidFooter = magicSourceFooter.footer.some(foot => {
const cleanFoot = foot.toUpperCase();
if (tailHex.includes(cleanFoot)) return true;
try {
const literalFoot = cleanFoot.match(/.{1,2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join('');
return tailText.includes(literalFoot);
} catch (_) {
return false;
}
});
}
if (!hasValidFooter && isPdfContext) {
hasValidFooter = tailText.includes('%%EOF') || tailHex.includes('2525454F46');
}
const headerBytesCheck = headerBuffer ? new Uint8Array(headerBuffer) : new Uint8Array(0);
const headerTextCheck = new TextDecoder('iso-8859-1', { fatal: false }).decode(headerBytesCheck);
const headerHexCheck = Array.from(headerBytesCheck).map(b => b.toString(16).padStart(2, '0')).join('').toUpperCase();
const isCadesWrapper = headerTextCheck.includes('smime.p7m') || headerHexCheck.startsWith('3082') || headerHexCheck.startsWith('3081');
if (!hasValidFooter && !isCadesWrapper) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Security: Firma footer fallita", createCleanError("FooterCheckFailed", "Mismatch marcatore di coda %%EOF"));
return result;
}
}
// 🛡️ FASE 3: ANALISI PROFONDA PDF...
if (!isEncrypted && !isTransformed && isPdfContext) {
if (signal?.aborted) return result;
const configPdfPatterns = (pdfConfig.pdfMaliciousPatterns || []).map(p => p.toLowerCase());
if (typeof waitTillIdle === 'function') {
const idleMin = pdfConfig.idleMinMs || 200;
const idleTimeout = pdfConfig.idleTimeoutMs || 8000;
await waitTillIdle(idleMin, idleTimeout);
}
const CHUNK_SIZE = pdfConfig.chunkSize || (1024 * 1024);
const OVERLAP_SIZE = pdfConfig.overlapSize || 4096;
const streamContextWindow = pdfConfig.streamContextWindow || 300;
let offset = 0;
let inStream = false;
while (offset < blob.size) {
if (signal?.aborted) return result;
let chunkBuffer = null;
const chunkBuffersToClean = [];
try {
const end = Math.min(offset + CHUNK_SIZE, blob.size);
chunkBuffer = await blob.slice(offset, end).arrayBuffer();
const chunkArray = new Uint8Array(chunkBuffer);
const rawChunkText = new TextDecoder('iso-8859-1', { fatal: false }).decode(chunkArray);
let pdfStructureOnly = "";
let searchPos = 0;
while (searchPos < rawChunkText.length) {
if (inStream) {
const endStreamIdx = rawChunkText.toLowerCase().indexOf('endstream', searchPos);
if (endStreamIdx !== -1) {
inStream = false;
searchPos = endStreamIdx + 9;
} else {
break;
}
} else {
const streamIdx = rawChunkText.toLowerCase().indexOf('stream', searchPos);
if (streamIdx !== -1) {
const nextChar = rawChunkText[streamIdx + 6];
if (nextChar === '\r' || nextChar === '\n' || nextChar === ' ' || streamIdx + 6 === rawChunkText.length) {
pdfStructureOnly += rawChunkText.substring(searchPos, streamIdx);
inStream = true;
searchPos = streamIdx + 6;
} else {
pdfStructureOnly += rawChunkText.substring(searchPos, streamIdx + 6);
searchPos = streamIdx + 6;
}
} else {
pdfStructureOnly += rawChunkText.substring(searchPos);
break;
}
}
}
pdfStructureOnly = pdfStructureOnly.toLowerCase();
pdfStructureOnly = pdfStructureOnly.replace(/#([0-9a-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)).toLowerCase());
pdfStructureOnly = pdfStructureOnly.replace(/\/\*[\s\S]*?\*\//g, '');
pdfStructureOnly = pdfStructureOnly.replace(/\s+/g, ' ');
for (const pattern of configPdfPatterns) {
if (pdfStructureOnly.includes(pattern)) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Security: Pattern ostile nella struttura del PDF", createCleanError("PdfPatternDetected", `Pattern: ${pattern}`));
return result;
}
}
// Ricerca binaria nativa per la decompressione degli stream
let pos = 0;
while (pos < chunkArray.length) {
const streamMarkerIdx = rawChunkText.toLowerCase().indexOf('stream', pos);
if (streamMarkerIdx === -1) break;
const nextChar = rawChunkText[streamMarkerIdx + 6];
if (nextChar !== '\r' && nextChar !== '\n' && nextChar !== ' ' && streamMarkerIdx + 6 < rawChunkText.length) {
pos = streamMarkerIdx + 6;
continue;
}
let streamStartByte = streamMarkerIdx + 6;
if (rawChunkText[streamStartByte] === '\r') streamStartByte++;
if (rawChunkText[streamStartByte] === '\n') streamStartByte++;
const endStreamIdx = rawChunkText.toLowerCase().indexOf('endstream', streamStartByte);
if (endStreamIdx === -1) {
pos = streamMarkerIdx + 6;
continue;
}
let streamEndByte = endStreamIdx;
if (rawChunkText[streamEndByte - 1] === '\n') streamEndByte--;
if (rawChunkText[streamEndByte - 1] === '\r') streamEndByte--;
const streamContext = rawChunkText.substring(Math.max(0, streamMarkerIdx - streamContextWindow), streamMarkerIdx).toLowerCase();
const isCompressedStream = pdfConfig.compressedStreamPatterns ?
pdfConfig.compressedStreamPatterns.some(p => streamContext.includes(p.toLowerCase())) :
streamContext.includes('/flatedecode');
if (isCompressedStream && typeof DecompressionStream !== 'undefined' && streamEndByte > streamStartByte) {
try {
const streamBytes = chunkArray.subarray(streamStartByte, streamEndByte);
if (streamBytes.length > 0) {
const format = ((streamBytes[0] & 0x0F) === 8) ? 'deflate' : 'deflate-raw';
const decompressedStream = new Blob([streamBytes]).stream().pipeThrough(new DecompressionStream(format));
const decompressedBuffer = await new Response(decompressedStream).arrayBuffer();
chunkBuffersToClean.push(decompressedBuffer);
let decompText = new TextDecoder('iso-8859-1', { fatal: false }).decode(new Uint8Array(decompressedBuffer)).toLowerCase();
decompText = decompText.replace(/#([0-9a-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)).toLowerCase());
decompText = decompText.replace(/\s+/g, ' ');
for (const pattern of configPdfPatterns) {
if (decompText.includes(pattern)) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
console.warn("⚠️ SW Security: Payload malevolo in stream decompresso", createCleanError("PdfPatternDetected", `Pattern: ${pattern}`));
return result;
}
}
}
} catch (_) {}
}
pos = endStreamIdx + 9;
}
if (end >= blob.size) break;
offset = end - OVERLAP_SIZE;
} finally {
if (chunkBuffer) {
try { new Uint8Array(chunkBuffer).fill(0); } catch (_) {}
}
while (chunkBuffersToClean.length > 0) {
const buf = chunkBuffersToClean.pop();
if (buf) {
try { new Uint8Array(buf).fill(0); } catch (_) {}
}
}
}
}
}
if (signal?.aborted) return result;
result.valid = true;
result.blob = blob;
if (isResponseInput) {
result.response = originalResponse ? new Response(blob, {
status: originalResponse.status,
statusText: originalResponse.statusText,
headers: new Headers(originalResponse.headers)
}) : new Response(blob, {
status: 200,
statusText: 'OK',
headers: { 'Content-Type': finalContentType || 'application/pdf' }
});
} else {
result.response = null;
}
Object.freeze(result);
return result;
} catch (e) {
await injectTimingNoise(startForensicTime, timingNoiseMs);
return { valid: false, blob: null, response: null };
} finally {
wipeRAM();
}
};
Object.freeze(isValidBlob);
/** v2.0
* 🧲 SONDA TECNICA HEAD: Pre-ispezione preventiva delle risorse (Anti-MIME Sniffing).
* Interroga l'endpoint remoto tramite metodo HEAD leggero per estrarre la dimensione nominale (Content-Length) ed il tipo MIME dichiarato dal server.
* Interrompe l'esecuzione via AbortController in caso di superamento della soglia massima di latenza di 2.5 secondi.
* @param {string} url - URI telematico della risorsa remota da scansionare.
* @param {Object|null} rules - Configurazione dei vincoli e tolleranze estratti da CONFIG.minSizeMap.
* @param {boolean} rules.useHeadProbe - Flag di abilitazione per l'attivazione della sonda HEAD.
* @returns {Promise<{ok: boolean, size: number, type: string}>} Struttura di audit contenente lo stato del server, la dimensione in byte (-1 se assente) e il Content-Type.
*/
const performProbe = async (url, rules) => {
const cleanUrl = url.split('?')[0];
// Elenco delle chiavi di configurazione strutturale da ignorare
const codeConfigKeys = ['magicNumbers', 'useHeadProbe', 'tolerance', 'defaultMin', 'timingNoiseMs'];
// Estrae dinamicamente le estensioni valide direttamente dal CONFIG.minSizeMap.code
const codeExtensions = CONFIG.minSizeMap.code ?
Object.keys(CONFIG.minSizeMap.code).filter(k => !codeConfigKeys.includes(k)) : [];
// Verifica se l'URL è la root o appartiene a un'estensione configurata nel blocco 'code'
const isCodeOrCore = cleanUrl.endsWith('/') ||
codeExtensions.some(ext => cleanUrl.endsWith('.' + ext));
// Salta la sonda HEAD per la root e per qualsiasi file di codice configurato
if (!rules || rules.useHeadProbe === false || isCodeOrCore) {
return { ok: true, size: 0, type: '' };
}
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 2500);
try {
const probe = await fetch(url, {
method: 'HEAD',
cache: 'no-cache',
mode: 'cors',
signal: syncAbortController ?
AbortSignal.any([syncAbortController.signal, controller.signal]) :
controller.signal
});
clearTimeout(timeoutId);
if (probe.ok && probe.status === 200) {
const contentLength = probe.headers.get('Content-Length');
const contentType = (probe.headers.get('Content-Type') || '').toLowerCase();
const size = contentLength ? parseInt(contentLength, 10) : -1;
return {
ok: true,
size: size,
type: contentType
};
}
} catch (e) {
if (e.name === 'AbortError') {
console.info(`🧲⚠️ SW: Sonda HEAD in timeout >= 2.5s per ${url}. Procedo con download.`);
} else {
console.info(`🧲❌ SW: Sonda HEAD fallita (CORS o Rete) per ${url}.`);
}
} finally {
clearTimeout(timeoutId);
}
return {
ok: CONFIG.minSizeMap.universal?.fallbackToGet ?? true,
size: 0,
type: ''
};
};
Object.freeze(performProbe);
/**
* 🔗 FLUSSI: Normalizzatore Strict degli URI Telematici.
* Converte e depura qualsiasi stringa di percorso o URL in un cammino assoluto sterile privo di parametri di query superflui o doppi slash (//).
* Reinstalla l'instradamento d'ufficio verso l'entrypoint index.html in caso di corrispondenza esatta con la radice (ROOT).
* @param {string} url - Stringa grezza o percorso telematico intercettato dal network o dal database.
* @returns {string} Percorso pulito e standardizzato per le chiavi di indicizzazione del database delle cache.
*/
const normalize = (url) => {
if (!url || typeof url !== 'string' || url.startsWith('data:')) return url;
try {
const baseSafe = new URL(CONFIG.ROOT, self.location.origin).href;
const resolved = new URL(url, baseSafe);
let cleanPath = resolved.pathname.replace(/\/+/g, '/');
const rootNormalized = CONFIG.ROOT.replace(/\/+/g, '/');
if (cleanPath === rootNormalized || cleanPath === rootNormalized + '/') {
const mainEntry = activeCoreAssets.find(asset => asset.toLowerCase().includes('.html'));
cleanPath = normalize(mainEntry);
}
return cleanPath;
} catch (e) {
return url.split('?')[0].replace(/\/+/g, '/');
}
};
Object.freeze(normalize);
/**
* 📥🚀 Smart Download Differito con Profilazione Dinamica.
* Gestisce l'allineamento degli asset core e dinamici applicando le barriere di controllo
* del network profile. Integra la crittografia asincrona nativa prima dello storage finale.
* @param {string} url - Endpoint telematico della risorsa da storicizzare.
* @param {Cache} cache - Riferimento all'istanza di destinazione (Bunker o Magazzino).
* @param {boolean} [isCore=false] - Flag di marcatura per file critici di infrastruttura.
* @param {string} [version=''] - Marcatore di versione telematico per l'audit di tracciabilità.
* @param {number} [probeSize=0] - Dimensione preventiva ricavata da sonda HEAD.
* @returns {Promise<string|boolean>} Stato finale dell'operazione ("DOWNLOADED", "ALREADY_OK", "variant_new" o false).
*/
const smartDownload = async (url, cache, isCore = false, version = '', probeSize = 0) => {
// ⏱️ Marcatore di rete.
const startNetworkTime = performance.now();
const cleanKey = normalize(url);
let isEncrypted = false;
const isVaultReady = await verifyVaultIntegrity();
if (!isVaultReady || !encryptionKey) {
throw new Error("VAULT_LOCKED_NO_KEY");
}
const existing = await cache.match(cleanKey);
if (existing) {
const cachedVersion = existing.headers.get('X-PWA-Version');
isEncrypted = existing.headers.get('X-PWA-Encrypted') === 'true';
if (!isCore) {
try {
const blob = await existing.blob();
if (await isValidBlob(blob, existing.headers.get('Content-Type'), 0, isEncrypted, syncAbortController?.signal)) {
return "ALREADY_OK";
}
} catch (e) {
// 🛡️⏱️ INNESTO: 🪝
await injectTimingNoise(startNetworkTime, 50);
}
}
if (isCore) {
if (version && cachedVersion === version.toString()) {
return "ALREADY_OK";
}
try {
const profile = getNetworkProfile(self.navigator);
// 🛡️ ADATTAMENTO DINAMICO: Il timeout segue il profilo...
const dynamicTimeout = (profile.timeout * 1000);
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), dynamicTimeout);
const fetchSignal = syncAbortController
? AbortSignal.any([syncAbortController?.signal, controller?.signal])
: controller?.signal;
const head = await fetch(url, {
method: 'HEAD',
cache: 'no-store',
headers: { 'Cache-Control': 'no-cache' },
mode: 'cors',
signal: fetchSignal
});
const serverLastMod = head.headers.get('Last-Modified');
const localLastMod = existing.headers.get('X-PWA-LastMod');
if (serverLastMod && localLastMod && serverLastMod === localLastMod) {
return "ALREADY_OK";
}
} catch (e) {
// 🛡️⏱️ INNESTO: 🪝
await injectTimingNoise(startNetworkTime, 50);
return "ALREADY_OK";
}
}
await cache.delete(cleanKey);
}
// Se l'URL termina con '/' o non ha punti (es. la root '/demo/'), è l'index HTML principale
const cleanUrlForExt = url.split('?')[0];
const isRootOrHtml = cleanUrlForExt.endsWith('/') || !cleanUrlForExt.includes('.');
const ext = isRootOrHtml ? 'html' : cleanUrlForExt.split('.').pop().toLowerCase();