@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
- // Use iframe se react-pdf non è disponibile O se è desktop
137
- if (!isReactPdfAvailable || (!isMobile && pdfUrl)) {
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
- // Usa react-pdf solo per mobile e solo se disponibile
147
- return _jsxs(PDFViewerContainer, { children: [loadedPagesNumber === 0 && totalPagesNumber > 0 && _jsx(LoadingOverlay, { children: _jsxs("div", { style: {
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: { display: loadedPagesNumber > 0 && totalPagesNumber > 0 ? 'block' : 'none' }, children: _jsx(Document, { file: pdfBlob, onLoadSuccess: ({ numPages }) => {
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.20.0-dev1.52",
3
+ "version": "6.20.0-dev1.53",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",