Een Excel-werkboek kan EMF- en WMF-afbeeldingen meedragen, en de conventionele manier om er één te tekenen is de bytestream aan de metafile-speler van het besturingssysteem overhandigen. Dat is een beslissing om rechtstreeks naar te kijken: een metafile is een geserialiseerde commandostroom voor een graphics-API, en er één herafspelen betekent een bestand dat via e-mail binnenkwam de grafische driver laten besturen. HotXLS neemt de andere route. XLSDecodeVectorScene parseert de metafile zelf, valideert de header, elke recordgrootte, het gedeclareerde recordtotaal en de exacte plaatsing van het end-of-file-record, verwerpt escape-records ronduit en geeft een TXLSVectorScene van primitieve tekencommandos terug die de Canvas- en SVG-backends via hun eigen code herafspelen. Op geen enkel moment is driver-afspeel betrokken
De ruil is dekking tegen insluiting. Een op rechthoeken gerichte commandowhitelist zal niet elke metafile reproduceren die een ontwerper kan maken, dus de scène meldt hoeveel tekenrecords die niet kon representeren en de aanroeper beslist wat ermee te doen. Voor een serverproces dat documenten rendert die het niet zelf maakte, is die ruil de juiste kant op
Waarom past metafile-afspeel slecht bij onbetrouwbare input?
Omdat het formaat geen afbeelding is, het is een programma. Een EMF-recordstroom manipuleert een statestack van de device context, wijst objecten toe uit een handle-tabel en selecteert ze, en kan escape-records meedragen waarvan de payload wordt doorgegeven aan een apparaatstuurprogramma. Het herafspelen ervan oefent paden uit in de graphics-stack van het platform die zijn geschreven onder de aanname dat de metafile afkomstig was van een meewerkende applicatie op dezelfde machine. Wanneer de input een spreadsheetbijlage is, is die aanname weg, en geen enkele zorgvuldigheid binnen de spreadsheetbibliotheek helpt omdat de bibliotheek niet het onderdeel is dat parseert
Dit is dezelfde redenering die de containerlaag stuurt. Een werkboek is een ZIP-archief, en HotXLS valideert zijn central directory in plaats van gedeclareerde offsets te vertrouwen, zoals beschreven in het artikel over ZIP end-of-central-directory-validatie. Metafile-payloads zijn de volgende laag van hetzelfde probleem
Wat de decoder controleert voordat die iets tekent
De validatie is structureel en vindt vooraf plaats, want een parser die begint te tekenen en onderweg valideert heeft al gehandeld naar gegevens die niet waren geverifieerd. De header moet strikt overeenkomen, niet plausibel. Elke record moet een grootte declareren die binnen de resterende buffer past en groot genoeg is voor zijn eigen vaste velden. Het recordaantal dat de header declareert moet overeenkomen met de werkelijk aanwezige records. Het end-of-file-record moet exact liggen waar de stream eindigt, niet slechts ergens in de buurt, wat de trailing-garbage-truc afsluit die een tweede payload achter een geldige afbeelding verbergt
Voorbij de structuur is de decoder fail-closed op semantiek. Escape-records worden geweigerd, niet overgeslagen. Een toestandsveranderend record dat de decoder niet modelleert laat de decode falen in plaats van genegeerd te worden, want een toestandswissel negeren betekent dat elk daaropvolgend tekencommando wordt uitgevoerd in een toestand die het bestand niet vroeg, en het resultaat is een afbeelding die fout is op een manier die niemand kan voorspellen. Tekenrecords buiten de ondersteunde commandoset zijn een andere zaak: die worden geteld en overgeslagen, want een ontbrekende vorm is een zichtbaar, rapporteerbaar gat in plaats van een geruisloze corruptie
Budgets horen bij het formaatcontract
Vectorformaten hebben hun eigen versie van de decompressiebom. Een paar kilobyte aan records kan polylines met honderden miljoenen punten declareren, of een afbeelding waarvan de gedeclareerde afmetingen vermenigvuldigen naar terabytes. Grenzen moeten dus expliciete constanten zijn in plaats van wat de machine toevallig overleeft
// Uit lxVectorScene: het decode-budget, uitgesproken in plaats van geïmpliceerd
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;
Twee daarvan verdienen een notitie. De dieptecap van 32 op de device context bestaat omdat SaveDC- en RestoreDC-records nesten, en een uit balans zijnde stream kan oneindig pushen; 32 is royaal voor echte metafiles en goedkoop af te dwingen. De coördinaatcap bestaat omdat coördinaten een transformatie voeden, en een waarde nabij de grenzen van het integerbereik levert een getransformeerd resultaat op dat oneindig is of omslaat, waarna elke bounding-boxberekening stroomafwaarts onzin is. Coördinaten bij parsen begrenzen is veel makkelijker te redeneren dan elke verbruiker van de geometrie verdedigen
De scène gebruiken
De decoder geeft een object terug dat van u is, een commandotelling, een nominale grootte en een telling van tekenrecords die die niet wilde representeren
uses
lxVectorScene;
var
Scene: TXLSVectorScene;
Error: WideString;
I: Integer;
begin
// Data bevat de ruwe afbeeldingspayload uit het werkboek
if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
begin
// Geweigerd: header, grenzen, totalen, EOF-plaatsing of een 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;
Het commandorecord draagt alles mee dat een backend nodig heeft en niets dat een apparaat vereist: pen-aanwezigheid, kleur, breedte en stijl; brush-aanwezigheid en kleur; de geometrie; en bij tekst de string, fontnaam, grootte, stijlen en uitlijning. Dat maakt dezelfde scène bruikbaar voor zowel de on-screen canvas-renderer als de SVG-schrijver, en dat is waarom het vectorpad niet uiteenloopt tussen voorvertoning en export. Schermrendering van werkbladinhoud in het algemeen wordt behandeld in het artikel over custom VCL grid-rendering
Een afbeelding weigeren beschadigt het werkboek niet
Een belangrijke eigenschap van dit ontwerp is dat een geweigerde decode alleen het renderen raakt. De oorspronkelijke payload blijft in het model, dus een werkboek dat wordt geopend en opnieuw opgeslagen voert zijn metafile-afbeeldingen byte voor byte mee, of de veilige decoder ze nu kon tekenen of niet. Het bestaande begrensde rasterpad blijft ook als fallback beschikbaar. Anders gezegd: de strenge parser begrenst wat wordt uitgevoerd, niet wat wordt bewaard, en dat is het onderscheid dat een uit beveiligingsoogpunt gemotiveerde wijziging laat verschepen zonder in een gegevensverlies-wijziging te veranderen
De afhandeling van tekenobjecten in het algemeen, inclusief de delen van het objectmodel die round-trips onaangetast overleven, wordt behandeld in het artikel over grafieken, afbeeldingen en tekeningen
Waar dit een serveruitrol laat staan
Rendert u in een dienst werkboeken die gebruikers uploaden, dan is de praktische positie nu verdedigbaar: metafile-afbeeldingen worden geparseerd door code die u kunt auditen, begrensd door constanten die u kunt lezen, en nooit aan een grafische driver overhandigd. De eerlijke kanttekening is dekking. Complexe metafiles uit tekenhulpmiddelen zullen de teller van overgeslagen records raken, en het antwoord daarop is de teller zichtbaar te maken in plaats van de whitelist geruisloos te verbreden. Een afbeelding die deels rendert en dat zegt is een supportgesprek; een afbeelding die verkeerd rendert en niets zegt is een bugrapport van een klant
HotXLS verwerkt XLS, XLSX, ODS en CSV natively in Delphi en C++Builder zonder geïnstalleerde Excel, en dezelfde begrensd-parsen-filosofie loopt door zijn container-, formule- en tekenlagen. Formaat- en beveiligingsdetails staan op de productpagina van de HotXLS Delphi spreadsheet component