Una cartella di lavoro Excel può trasportare immagini EMF e WMF, e il modo convenzionale di disegnarne una è consegnare lo stream di byte al player di metafile del sistema operativo. È una decisione che vale la pena guardare direttamente: un metafile è uno stream di comandi serializzato per un'API grafica, e riprodurne uno significa lasciare che un file arrivato via email comandi il driver grafico. HotXLS prende l'altra strada. XLSDecodeVectorScene analizza il metafile da sé, valida l'header, ogni dimensione di record, il totale di record dichiarato e la collocazione esatta del record end-of-file, rifiuta categoricamente i record escape, e restituisce una TXLSVectorScene di comandi di disegno primitivi che i backend Canvas e SVG riproducono attraverso il proprio codice. Nessun playback di driver è coinvolto in alcun punto
Lo scambio è copertura in cambio di contenimento. Una whitelist di comandi orientata ai rettangoli non riprodurrà ogni metafile che un designer può creare, così la scena riporta quanti record di disegno non ha potuto rappresentare e il chiamante decide cosa farne. Per un processo server che renderizza documenti che non ha creato, quello scambio è nel verso giusto
Perché il playback di metafile è inadatto all'input non fidato?
Perché il formato non è un'immagine, è un programma. Uno stream di record EMF manipola uno stack di stato del device context, alloca e seleziona oggetti da una handle table, e può trasportare record escape il cui payload è passato attraverso a un driver di dispositivo. Riprodurlo esercita percorsi dello stack grafico della piattaforma scritti partendo dal presupposto che il metafile venisse da un'applicazione cooperante sulla stessa macchina. Quando l'input è un allegato foglio di calcolo, quel presupposto è sparito, e nessuna cura dentro la libreria foglio di calcolo aiuta perché la libreria non è il componente che fa il parsing
Questo è lo stesso ragionamento che governa lo strato contenitore. Una cartella di lavoro è un archivio ZIP, e HotXLS valida il central directory anziché fidarsi degli offset dichiarati, come descritto nell'articolo sulla validazione dello ZIP end-of-central-directory. I payload metafile sono lo strato successivo dello stesso problema
Cosa verifica il decoder prima di disegnare qualsiasi cosa
La validazione è strutturale e avviene in anticipo, perché un parser che comincia a disegnare e valida man mano ha già agito su dati non verificati. L'header deve corrispondere strettamente anziché plausibilmente. Ogni record deve dichiarare una dimensione che stia dentro il buffer residuo ed sia abbastanza grande per i propri campi fissi. Il numero di record che l'header dichiara deve corrispondere ai record effettivamente presenti. Il record end-of-file deve sedere esattamente dove finisce lo stream, non semplicemente da qualche parte vicino, il che chiude il trucco dei rifiuti finali che nasconde un secondo payload dietro un'immagine valida
Oltre alla struttura, il decoder è fail-closed sulla semantica. I record escape sono rifiutati, non saltati. Un record che cambia stato e che il decoder non modella fa fallire la decodifica anziché essere ignorato, perché ignorare un cambio di stato significa che ogni comando di disegno successivo è eseguito in uno stato che il file non ha chiesto, e il risultato è un'immagine sbagliata in un modo che nessuno può prevedere. I record di disegno fuori dall'insieme di comandi supportato sono un'altra questione: quelli sono contati e saltati, perché una forma mancante è un vuoto visibile e riportabile anziché una corruzione silenziosa
I budget fanno parte del contratto di formato
I formati vettoriali hanno la loro versione della bomba di decompressione. Pochi kilobyte di record possono dichiarare polilinee con centinaia di milioni di punti, o un'immagine le cui dimensioni dichiarate moltiplicate danno terabyte. I limiti quindi devono essere costanti esplicite anziché ciò che la macchina per caso sopravvive
// Da lxVectorScene: il budget di decodifica, dichiarato anziché implicito
XL_VECTOR_MAX_RECORDS = 1000000;
XL_VECTOR_MAX_HANDLES = 4096;
XL_VECTOR_MAX_DC_DEPTH = 32;
XL_VECTOR_MAX_COMMANDS = 100000;
XL_VECTOR_MAX_POINTS_PER_RECORD = 100000;
XL_VECTOR_MAX_TOTAL_POINTS = 2000000;
XL_VECTOR_MAX_TEXT_CHARS = 4096;
XL_VECTOR_MAX_TOTAL_TEXT_CHARS = 1000000;
XL_VECTOR_MAX_IMAGE_SIDE = 8192;
XL_VECTOR_MAX_IMAGE_PIXELS = 32 * 1024 * 1024;
XL_VECTOR_MAX_IMAGE_BYTES = 64 * 1024 * 1024;
XL_VECTOR_MAX_COORD = 1000000000;
Due di queste meritano una nota. Il tetto di profondità del device context di 32 esiste perché i record SaveDC e RestoreDC si annidano, e uno stream sbilanciato può impilare per sempre; 32 è generoso per metafile reali ed economico da far rispettare. Il tetto delle coordinate esiste perché le coordinate alimentano una trasformazione, e un valore vicino ai limiti dell'intervallo intero produce un risultato trasformato infinito o in wrap, dopo il quale ogni calcolo di bounding box a valle è privo di senso. Limitare le coordinate al momento del parsing è molto più facile da ragionare che difendere ogni consumatore della geometria
Usare la scena
Il decoder restituisce un oggetto di proprietà di Lei, un numero di comandi, una dimensione nominale e un conteggio dei record di disegno che ha scelto di non rappresentare
uses
lxVectorScene;
var
Scene: TXLSVectorScene;
Error: WideString;
I: Integer;
begin
// Data contiene il payload immagine grezzo preso dalla cartella di lavoro
if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
begin
// Rifiutato: header, limiti, totali, collocazione EOF o un budget
LogReject('metafile rejected: ' + Error);
Exit;
end;
try
if Scene.SkippedDrawRecords > 0 then
LogWarning(Format('%d drawing records outside the safe subset',
[Scene.SkippedDrawRecords]));
for I := 0 to Scene.Count - 1 do
case Scene.Commands[I].Kind of
xlsvcRectangle: DrawRect(Scene.Commands[I]);
xlsvcEllipse: DrawEllipse(Scene.Commands[I]);
xlsvcPolyline,
xlsvcPolygon,
xlsvcBezier: DrawPath(Scene.Commands[I]);
xlsvcText: DrawText(Scene.Commands[I]);
xlsvcImage: DrawImage(Scene.Commands[I]);
end;
finally
Scene.Free;
end;
end;
Il record di comando trasporta tutto ciò di cui un backend ha bisogno e nulla che richieda un dispositivo: presenza, colore, larghezza e stile della penna; presenza e colore del pennello; la geometria; e per il testo la stringa, il nome del font, la dimensione, gli stili e l'allineamento. È ciò che rende la stessa scena utilizzabile sia dal renderer canvas a schermo sia dal writer SVG, ed è per questo che la via vettoriale non diverge tra anteprima ed esportazione. Il rendering a schermo del contenuto dei fogli in generale è trattato nell'articolo sul rendering della griglia VCL personalizzata
Rifiutare un'immagine non danneggia la cartella di lavoro
Una proprietà importante di questo progetto è che una decodifica rifiutata tocca solo il rendering. Il payload originale resta nel modello, così una cartella di lavoro aperta e risalvata trasporta fuori le proprie immagini metafile byte per byte, che il decoder sicuro le sappia disegnare o no. La via raster limitata esistente resta inoltre disponibile come ripiego. In altre parole, il parser severo condiziona ciò che viene eseguito, non ciò che viene preservato, che è la distinzione che lascia spedire una modifica motivata dalla sicurezza senza che diventi una modifica di perdita di dati
La gestione degli oggetti di disegno in generale, comprese le parti del modello di oggetti che sopravvivono intatte ai round-trip, è trattata nell'articolo su grafici, immagini e disegni
Dove questo lascia un deployment server
Se Lei renderizza cartelle di lavoro caricate dagli utenti in un servizio, la posizione pratica ora è difendibile: le immagini metafile sono analizzate da codice che si può auditare, limitate da costanti che si possono leggere, e mai consegnate a un driver grafico. L'avvertimento onesto è la copertura. I metafile complessi prodotti dai tool di disegno colpiranno il contatore dei record saltati, e la risposta è portare alla luce il contatore anziché allargare la whitelist in silenzio. Un'immagine che si rende parzialmente e lo dice è una conversazione di assistenza; un'immagine che si rende male e non dice nulla è una segnalazione di bug da un cliente
HotXLS gestisce XLS, XLSX, ODS e CSV nativamente in Delphi e C++Builder senza Excel installato, e la stessa filosofia di parsing limitato attraversa i suoi strati contenitore, formule e disegni. I dettagli di formato e sicurezza sono elencati nella pagina di prodotto di HotXLS Delphi spreadsheet component