@topconsultnpm/sdkui-react 6.20.0-dev1.52 → 6.20.0-dev1.53
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.
|
@@ -492,6 +492,8 @@ export declare class SDKUI_Localizator {
|
|
|
492
492
|
static get PhysDelete(): "Physische Stornierung" | "Physical delete" | "Cancelación física" | "Supression" | "Cancelamento física" | "Cancellazione fisica";
|
|
493
493
|
static get PhysicalHistoryDeletion(): string;
|
|
494
494
|
static get Postponed(): "Verschoben" | "Postponed" | "Aplazada" | "Reportée" | "Adiadas" | "Rinviata";
|
|
495
|
+
static get PotentiallyUnsafeContent(): "Potenziell unsichere Inhalte" | "Potentially Unsafe Content" | "Contenido potencialmente no seguro" | "Contenu potentiellement non sécurisé" | "Conteúdo potencialmente não seguro" | "Contenuti potenzialmente non sicuri";
|
|
496
|
+
static get PotentiallyUnsafeCodePatternsDetected(): "{{0}} potenziell unsicheres Code-Muster in diesem Dokument erkannt." | "{{0}} potentially unsafe code pattern detected in this document." | "Se han detectado {{0}} patrones de código potencialmente no seguro en este documento." | "{{0}} motifs de code potentiellement non sécurisé détectés dans ce document." | "{{0}} padrões de código potencialmente não seguro detectados neste documento." | "Sono stati rilevati {{0}} pattern di codice potenzialmente non sicuro in questo documento.";
|
|
495
497
|
static get PreparingFileForArchive(): "Datei für Archivierung vorbereiten..." | "Preparing file for archive..." | "Preparando archivo para archivar..." | "Préparation du fichier pour l'archive..." | "Preparando arquivo para arquivar..." | "Preparazione del file per l'archivio...";
|
|
496
498
|
static get Preview(): "Vorschau" | "Preview" | "Vista previa" | "Aperçu" | "Pré-visualização" | "Anteprima";
|
|
497
499
|
static get PreviewDocument(): "Vorschau-Dokument" | "Preview document" | "Documento de vista previa" | "Document d'aperçu" | "Documento de pré-visualização" | "Anteprima documento";
|
|
@@ -4878,6 +4878,32 @@ export class SDKUI_Localizator {
|
|
|
4878
4878
|
default: return "Rinviata";
|
|
4879
4879
|
}
|
|
4880
4880
|
}
|
|
4881
|
+
static get PotentiallyUnsafeContent() {
|
|
4882
|
+
switch (this._cultureID) {
|
|
4883
|
+
case CultureIDs.De_DE: return "Potenziell unsichere Inhalte";
|
|
4884
|
+
case CultureIDs.En_US: return "Potentially Unsafe Content";
|
|
4885
|
+
case CultureIDs.Es_ES: return "Contenido potencialmente no seguro";
|
|
4886
|
+
case CultureIDs.Fr_FR: return "Contenu potentiellement non sécurisé";
|
|
4887
|
+
case CultureIDs.Pt_PT: return "Conteúdo potencialmente não seguro";
|
|
4888
|
+
default: return "Contenuti potenzialmente non sicuri";
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
static get PotentiallyUnsafeCodePatternsDetected() {
|
|
4892
|
+
switch (this._cultureID) {
|
|
4893
|
+
case CultureIDs.De_DE:
|
|
4894
|
+
return "{{0}} potenziell unsicheres Code-Muster in diesem Dokument erkannt.";
|
|
4895
|
+
case CultureIDs.En_US:
|
|
4896
|
+
return "{{0}} potentially unsafe code pattern detected in this document.";
|
|
4897
|
+
case CultureIDs.Es_ES:
|
|
4898
|
+
return "Se han detectado {{0}} patrones de código potencialmente no seguro en este documento.";
|
|
4899
|
+
case CultureIDs.Fr_FR:
|
|
4900
|
+
return "{{0}} motifs de code potentiellement non sécurisé détectés dans ce document.";
|
|
4901
|
+
case CultureIDs.Pt_PT:
|
|
4902
|
+
return "{{0}} padrões de código potencialmente não seguro detectados neste documento.";
|
|
4903
|
+
default:
|
|
4904
|
+
return "Sono stati rilevati {{0}} pattern di codice potenzialmente non sicuro in questo documento.";
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4881
4907
|
static get PreparingFileForArchive() {
|
|
4882
4908
|
switch (this._cultureID) {
|
|
4883
4909
|
case CultureIDs.De_DE: return "Datei für Archivierung vorbereiten...";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState, useRef, useCallback } from "react";
|
|
3
3
|
import styled from "styled-components";
|
|
4
4
|
import { LoadIndicator } from 'devextreme-react/load-indicator';
|
|
5
5
|
import { IconCloseOutline } from "./TMIcons";
|
|
6
6
|
import { SDKUI_Localizator } from "./SDKUI_Localizator";
|
|
7
7
|
import { TMColors } from "../utils/theme";
|
|
8
|
+
import { TMMessageBoxManager, ButtonNames } from "../components/base/TMPopUp";
|
|
8
9
|
// Dynamic imports for optional dependencies
|
|
9
10
|
let pdfjs;
|
|
10
11
|
let Document;
|
|
@@ -68,6 +69,9 @@ const TMPdfViewer = (props) => {
|
|
|
68
69
|
const [loadedPagesNumber, setLoadedPagesNumber] = useState(0);
|
|
69
70
|
const [visiblePages, setVisiblePages] = useState(new Set([1]));
|
|
70
71
|
const [pdfUrl, setPdfUrl] = useState("");
|
|
72
|
+
const [hasUnsafeContent, setHasUnsafeContent] = useState(false);
|
|
73
|
+
const [isCheckingPdf, setIsCheckingPdf] = useState(true);
|
|
74
|
+
const [jsMatches, setJsMatches] = useState([]);
|
|
71
75
|
const observerRef = useRef(null);
|
|
72
76
|
useEffect(() => {
|
|
73
77
|
const checkIsMobile = () => {
|
|
@@ -82,7 +86,60 @@ const TMPdfViewer = (props) => {
|
|
|
82
86
|
(navigator.maxTouchPoints > 1 && !/Win|Mac|Linux x86_64/i.test(userAgent));
|
|
83
87
|
setIsMobile(isMobileDevice);
|
|
84
88
|
};
|
|
89
|
+
const checkPdfForJavaScript = async () => {
|
|
90
|
+
setIsCheckingPdf(true);
|
|
91
|
+
try {
|
|
92
|
+
// Legge il PDF come testo per cercare pattern di JavaScript
|
|
93
|
+
const arrayBuffer = await pdfBlob.arrayBuffer();
|
|
94
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
95
|
+
const text = new TextDecoder('latin1').decode(uint8Array);
|
|
96
|
+
// Pattern specifici per rilevare JavaScript effettivo nelle strutture PDF
|
|
97
|
+
const jsPatterns = [
|
|
98
|
+
// Esempio: /JavaScript [ (app.alert('Hello');) ]
|
|
99
|
+
{ name: 'JavaScript Dictionary Entry', pattern: /\/JavaScript\s*[(\[<][\s\S]*?[)\]>]/i },
|
|
100
|
+
// Esempio: /JS 15 0 R (riferimento a un oggetto JavaScript)
|
|
101
|
+
{ name: 'JavaScript Object Reference', pattern: /\/JS\s+\d+\s+\d+\s+R/i },
|
|
102
|
+
// Esempio: /JS (app.alert('Click');) o /JS <hexstring>
|
|
103
|
+
{ name: 'Inline JavaScript Code', pattern: /\/JS\s*[(<][\s\S]*?[)>]/i },
|
|
104
|
+
// Esempio: /AA << /O << /S /JavaScript /JS (app.alert('Open');) >> >>
|
|
105
|
+
{ name: 'Additional Actions (AA) with JavaScript', pattern: /\/AA\s*<<[\s\S]*?\/JS[\s\S]*?>>/is },
|
|
106
|
+
// Esempio: /OpenAction << /S /JavaScript /JS (this.print();) >>
|
|
107
|
+
{ name: 'Document Open Action with JavaScript', pattern: /\/OpenAction\s*<<[\s\S]*?\/JS[\s\S]*?>>/is },
|
|
108
|
+
// Esempio: /Names << /JavaScript [ (MyScript) 12 0 R ] >>
|
|
109
|
+
{ name: 'Named JavaScript Functions', pattern: /\/Names\s*<<[\s\S]*?\/JavaScript[\s\S]*?>>/is },
|
|
110
|
+
];
|
|
111
|
+
let foundJS = false;
|
|
112
|
+
const matches = [];
|
|
113
|
+
jsPatterns.forEach(({ name, pattern }) => {
|
|
114
|
+
const match = text.match(pattern);
|
|
115
|
+
if (match) {
|
|
116
|
+
foundJS = true;
|
|
117
|
+
const matchIndex = match.index || 0;
|
|
118
|
+
const contextStart = Math.max(0, matchIndex - 50);
|
|
119
|
+
const contextEnd = Math.min(text.length, matchIndex + match[0].length + 50);
|
|
120
|
+
const context = text.substring(contextStart, contextEnd);
|
|
121
|
+
matches.push({
|
|
122
|
+
name,
|
|
123
|
+
pattern: pattern.toString(),
|
|
124
|
+
match: match[0],
|
|
125
|
+
context: context.replace(/[\r\n]+/g, ' ').trim()
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
setHasUnsafeContent(foundJS);
|
|
130
|
+
setJsMatches(matches);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
// console.error('Errore nella validazione del PDF:', error);
|
|
134
|
+
// In caso di errore, permetti la visualizzazione
|
|
135
|
+
setHasUnsafeContent(false);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setIsCheckingPdf(false);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
85
141
|
checkIsMobile();
|
|
142
|
+
checkPdfForJavaScript();
|
|
86
143
|
// Create URL for iframe
|
|
87
144
|
const url = URL.createObjectURL(pdfBlob);
|
|
88
145
|
setPdfUrl(url);
|
|
@@ -133,8 +190,100 @@ const TMPdfViewer = (props) => {
|
|
|
133
190
|
observerRef.current?.disconnect();
|
|
134
191
|
};
|
|
135
192
|
}, [pageObserverCallback]);
|
|
136
|
-
|
|
137
|
-
|
|
193
|
+
const showMatchDetails = () => {
|
|
194
|
+
const highlightMatch = (context, matchText) => {
|
|
195
|
+
const matchIndex = context.indexOf(matchText);
|
|
196
|
+
if (matchIndex === -1) {
|
|
197
|
+
// Se non trova il match esatto, prova con una versione normalizzata
|
|
198
|
+
const normalizedContext = context.replace(/\s+/g, ' ');
|
|
199
|
+
const normalizedMatch = matchText.replace(/\s+/g, ' ');
|
|
200
|
+
const normalizedIndex = normalizedContext.indexOf(normalizedMatch);
|
|
201
|
+
if (normalizedIndex === -1) {
|
|
202
|
+
// Se ancora non trova, mostra tutto in grassetto rosso
|
|
203
|
+
return _jsx("strong", { style: { color: '#d32f2f', fontWeight: 'bold' }, children: context });
|
|
204
|
+
}
|
|
205
|
+
return (_jsxs(_Fragment, { children: [normalizedContext.substring(0, normalizedIndex), _jsx("strong", { style: { color: '#d32f2f', fontWeight: 'bold', background: '#ffebee' }, children: normalizedContext.substring(normalizedIndex, normalizedIndex + normalizedMatch.length) }), normalizedContext.substring(normalizedIndex + normalizedMatch.length)] }));
|
|
206
|
+
}
|
|
207
|
+
return (_jsxs(_Fragment, { children: [context.substring(0, matchIndex), _jsx("strong", { style: { color: '#d32f2f', fontWeight: 'bold', background: '#ffebee' }, children: context.substring(matchIndex, matchIndex + matchText.length) }), context.substring(matchIndex + matchText.length)] }));
|
|
208
|
+
};
|
|
209
|
+
TMMessageBoxManager.show({
|
|
210
|
+
title: `${SDKUI_Localizator.Attention}: ${SDKUI_Localizator.PotentiallyUnsafeContent}`,
|
|
211
|
+
buttons: [ButtonNames.OK],
|
|
212
|
+
showToppy: false,
|
|
213
|
+
resizable: true,
|
|
214
|
+
initialWidth: !isMobile ? '800px' : undefined,
|
|
215
|
+
message: (_jsxs("div", { style: { maxHeight: '500px', overflowY: 'auto', padding: '10px', lineHeight: '1.6' }, children: [_jsxs("div", { style: {
|
|
216
|
+
marginBottom: '20px',
|
|
217
|
+
padding: '12px',
|
|
218
|
+
background: '#fff3cd',
|
|
219
|
+
border: '1px solid #ffc107',
|
|
220
|
+
borderRadius: '6px',
|
|
221
|
+
fontSize: '14px',
|
|
222
|
+
wordBreak: 'normal',
|
|
223
|
+
hyphens: 'none'
|
|
224
|
+
}, children: [_jsxs("strong", { children: [SDKUI_Localizator.Attention, ":"] }), " ", SDKUI_Localizator.PotentiallyUnsafeCodePatternsDetected.replaceParams(jsMatches.length.toString())] }), jsMatches.length > 0 ? (jsMatches.map((match, index) => (_jsxs("div", { style: {
|
|
225
|
+
marginBottom: '16px',
|
|
226
|
+
padding: '16px',
|
|
227
|
+
border: '1px solid #ffcdd2',
|
|
228
|
+
borderRadius: '8px',
|
|
229
|
+
background: '#fff',
|
|
230
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.1)'
|
|
231
|
+
}, children: [_jsx("div", { style: {
|
|
232
|
+
marginBottom: '12px',
|
|
233
|
+
paddingBottom: '8px',
|
|
234
|
+
borderBottom: '2px solid #f44336'
|
|
235
|
+
}, children: _jsxs("strong", { style: {
|
|
236
|
+
color: '#d32f2f',
|
|
237
|
+
fontSize: '15px',
|
|
238
|
+
display: 'flex',
|
|
239
|
+
alignItems: 'center',
|
|
240
|
+
gap: '8px'
|
|
241
|
+
}, children: [_jsx("span", { style: {
|
|
242
|
+
background: '#f44336',
|
|
243
|
+
color: '#fff',
|
|
244
|
+
borderRadius: '50%',
|
|
245
|
+
width: '24px',
|
|
246
|
+
height: '24px',
|
|
247
|
+
display: 'inline-flex',
|
|
248
|
+
alignItems: 'center',
|
|
249
|
+
justifyContent: 'center',
|
|
250
|
+
fontSize: '13px',
|
|
251
|
+
fontWeight: 'bold'
|
|
252
|
+
}, children: index + 1 }), match.name] }) }), _jsx("div", { style: { fontSize: '13px' }, children: _jsx("div", { style: {
|
|
253
|
+
background: '#f5f5f5',
|
|
254
|
+
padding: '10px',
|
|
255
|
+
borderRadius: '4px',
|
|
256
|
+
borderLeft: '3px solid #f44336',
|
|
257
|
+
fontFamily: 'Consolas, Monaco, monospace',
|
|
258
|
+
fontSize: '12px',
|
|
259
|
+
wordBreak: 'break-all',
|
|
260
|
+
color: '#333',
|
|
261
|
+
maxHeight: '200px',
|
|
262
|
+
overflowY: 'auto',
|
|
263
|
+
userSelect: 'text'
|
|
264
|
+
}, children: highlightMatch(match.context, match.match) }) })] }, index)))) : (_jsx("div", { style: { textAlign: 'center', padding: '20px', color: '#999' }, children: "Nessun dettaglio disponibile" }))] }))
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
// Mostra loading durante la validazione
|
|
268
|
+
if (isCheckingPdf) {
|
|
269
|
+
return (_jsx(PDFViewerContainer, { children: _jsxs("div", { style: {
|
|
270
|
+
display: 'flex',
|
|
271
|
+
justifyContent: 'center',
|
|
272
|
+
alignItems: 'center',
|
|
273
|
+
height: '100%',
|
|
274
|
+
flexDirection: 'column',
|
|
275
|
+
gap: '10px'
|
|
276
|
+
}, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsx("div", { children: "Validazione PDF in corso..." })] }) }));
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Usa <iframe> nei seguenti casi:
|
|
280
|
+
* 1. react-pdf non è disponibile (libreria non installata)
|
|
281
|
+
* 2. Desktop E nessun contenuto JavaScript rilevato (visualizzazione nativa del browser più performante)
|
|
282
|
+
*
|
|
283
|
+
* L'iframe sfrutta il visualizzatore PDF nativo del browser, ma non può prevenire
|
|
284
|
+
* l'esecuzione di JavaScript embedded nel PDF.
|
|
285
|
+
*/
|
|
286
|
+
if (!isReactPdfAvailable || (!isMobile && !hasUnsafeContent && pdfUrl)) {
|
|
138
287
|
return (_jsx(PDFViewerContainer, { children: _jsx("iframe", { src: `${pdfUrl}#${enableFitToWidth ? 'view=FitH&' : ''}scrollbar=1`, title: title, style: {
|
|
139
288
|
width: '100%',
|
|
140
289
|
height: '100%',
|
|
@@ -143,14 +292,25 @@ const TMPdfViewer = (props) => {
|
|
|
143
292
|
pointerEvents: isResizingActive === true ? "none" : "auto"
|
|
144
293
|
} }, pdfUrl) }));
|
|
145
294
|
}
|
|
146
|
-
|
|
147
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Usa react-pdf nei seguenti casi:
|
|
297
|
+
* 1. Dispositivo mobile (migliore esperienza utente con lazy loading)
|
|
298
|
+
* 2. Desktop con contenuto JavaScript rilevato (rendering sicuro senza esecuzione di script)
|
|
299
|
+
*
|
|
300
|
+
* react-pdf renderizza il PDF come canvas, prevenendo l'esecuzione di JavaScript embedded,
|
|
301
|
+
* ma è meno performante dell'iframe nativo su desktop.
|
|
302
|
+
*/
|
|
303
|
+
return _jsxs(PDFViewerContainer, { style: { display: 'flex', flexDirection: 'column' }, children: [loadedPagesNumber === 0 && totalPagesNumber > 0 && _jsx(LoadingOverlay, { children: _jsxs("div", { style: {
|
|
148
304
|
display: 'flex',
|
|
149
305
|
justifyContent: 'center',
|
|
150
306
|
alignItems: 'center',
|
|
151
307
|
flexDirection: 'column',
|
|
152
308
|
gap: '10px'
|
|
153
|
-
}, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }) }), _jsx("div", { style: {
|
|
309
|
+
}, children: [_jsx(LoadIndicator, { height: 60, width: 60 }), _jsxs("div", { children: [SDKUI_Localizator.Loading, "..."] })] }) }), _jsx("div", { style: {
|
|
310
|
+
display: loadedPagesNumber > 0 && totalPagesNumber > 0 ? 'block' : 'none',
|
|
311
|
+
flex: 1,
|
|
312
|
+
overflow: 'auto'
|
|
313
|
+
}, children: _jsx(Document, { file: pdfBlob, onLoadSuccess: ({ numPages }) => {
|
|
154
314
|
setTotalPagesNumber(numPages);
|
|
155
315
|
setLoadedPagesNumber(0);
|
|
156
316
|
}, loading: _jsxs("div", { style: {
|
|
@@ -182,6 +342,27 @@ const TMPdfViewer = (props) => {
|
|
|
182
342
|
}, children: shouldRender && (_jsx(Page, { pageNumber: pageNumber, renderTextLayer: false, renderAnnotationLayer: false, width: Math.min(window.innerWidth - 40, 1200), loading: _jsx("div", { style: { padding: '20px' }, children: _jsx(LoadIndicator, { height: 40, width: 40 }) }), onLoadSuccess: () => {
|
|
183
343
|
setLoadedPagesNumber(prev => prev + 1);
|
|
184
344
|
} })) }, `page_${pageNumber}`));
|
|
185
|
-
}) }) })
|
|
345
|
+
}) }) }), hasUnsafeContent && (_jsxs("div", { style: {
|
|
346
|
+
display: 'flex',
|
|
347
|
+
justifyContent: 'center',
|
|
348
|
+
alignItems: 'center',
|
|
349
|
+
padding: '12px 20px',
|
|
350
|
+
background: '#fff3cd',
|
|
351
|
+
borderTop: '2px solid #ffc107',
|
|
352
|
+
gap: '8px',
|
|
353
|
+
flexShrink: 0
|
|
354
|
+
}, children: [_jsxs("span", { style: {
|
|
355
|
+
color: '#856404',
|
|
356
|
+
whiteSpace: 'nowrap',
|
|
357
|
+
overflow: 'hidden',
|
|
358
|
+
textOverflow: 'ellipsis',
|
|
359
|
+
flex: 1
|
|
360
|
+
}, children: [_jsx("strong", { children: "Attenzione:" }), " Questo documento contiene contenuti potenzialmente non sicuri."] }), jsMatches.length > 0 && (_jsx("span", { className: "dx-icon-info", style: {
|
|
361
|
+
fontSize: '20px',
|
|
362
|
+
color: '#d32f2f',
|
|
363
|
+
cursor: 'pointer',
|
|
364
|
+
transition: 'color 0.2s',
|
|
365
|
+
marginLeft: '4px'
|
|
366
|
+
}, onClick: showMatchDetails, title: "Clicca per vedere i dettagli", onMouseEnter: (e) => e.currentTarget.style.color = '#b71c1c', onMouseLeave: (e) => e.currentTarget.style.color = '#d32f2f' }))] }))] });
|
|
186
367
|
};
|
|
187
368
|
export default TMPdfViewer;
|