Teknisk artikel

Spela upp obetrodda EMF och WMF säkert i HotXLS

En Excel-arbetsbok kan bära EMF- och WMF-bilder, och det konventionella sättet att rita en är att lämna bytesströmmen till operativsystemets metafilespelare. Det är ett beslut värt att titta rakt på: en metafile är en serialiserad kommandostream för ett grafik-API, och att spela upp en betyder att låta en fil som anlänt via e-post köra grafikdrivrutinen. HotXLS tar den andra vägen. XLSDecodeVectorScene parsar metafilen själv, validerar huvudet, varenda poststorlek, det deklarerade posttotalen och den exakta placeringen av end-of-file-posten, avvisar escape-poster blankt och returnerar en TXLSVectorScene av primitiva ritkommandon som Canvas- och SVG-backenderna spelar upp genom sin egen kod. Ingen drivrutinsuppspelning är inblandad vid någon punkt

HotXLS parsar obetrodda EMF- och WMF-kalkylbladsbyte med XLSDecodeVectorScene till en TXLSVectorScene-kommandolista i stället för GDI-metafileuppspelning
HotXLS parsar metafilen själv och returnerar primitiva kommandon för Canvas- och SVG-uppspelning; den konventionella vägen exekverar bytesströmmen på grafikstacken

Bytet är täckning för inneslutning. En rektangelorienterad kommandovitlista kommer inte återskapa varenda metafile en designer kan skapa, så scenen rapporterar hur många ritposter den inte kunde representera och anroparen bestämmer vad som ska göras åt det. För en serverprocess som renderar dokument den inte skapade är det bytet rätt väg

Varför är metafileuppspelning en dålig passform för obetrodd indata?

För formatet är inte en bild, det är ett program. En EMF-postström manipulerar en tillståndsstack för device context, allokerar och väljer objekt ur en handletabell och kan bära escape-poster vars nyttolast passeras vidare till en enhetsdrivrutin. Att spela upp den trär vägar i plattformens grafikstack som skrevs under antagandet att metafilen kom från ett samarbetande program på samma maskin. När indatan är en kalkylbladsbilaga är det antagandet borta, och ingen mängd omsorg inuti kalkylbladsbiblioteket hjälper eftersom biblioteket inte är den komponent som parsnar

Det är samma resonemang som styr containerlagret. En arbetsbok är ett ZIP-arkiv, och HotXLS validerar dess central directory i stället för att lita på deklarerade offset, som beskrivs i artikeln om ZIP end-of-central-directory-validering. Metabildsnyttolaster är nästa lager av samma problem

Vad avkodaren kontrollerar innan den ritar någonting

Valideringen är strukturell och den händer på förhand, eftersom en parser som börjar rita och validerar allt eftersom redan har agerat på data den inte verifierat. Huvudet måste matcha strikt i stället för plausibelt. Varenda post måste deklarera en storlek som ryms i den återstående bufferten och är tillräckligt stor för sina egna fasta fält. Postantalet som huvudet deklarerar måste matcha posterna som faktiskt finns. End-of-file-posten måste sitta exakt där strömmen slutar, inte bara någonstans nära den, vilket stänger av efterföljande skräp-tricket som gömmer en andra nyttolast bakom en giltig bild

Bortom strukturen är avkodaren fail-closed på semantik. Escape-poster vägras, inte hoppas över. En tillståndsändrande post avkodaren inte modellerar får avkodningen att misslyckas i stället för att ignoreras, eftersom att ignorera en tillståndsändring betyder att vartdera följande ritkommando exekveras i ett tillstånd filen inte bad om, och resultatet är en bild som är fel på ett sätt ingen kan förutsäga. Ritposter utanför den stödda kommandouppsättningen är en annan sak: de räknas och hoppas över, för en saknad form är ett synligt, rapporterbart gap i stället för en tyst korrumpering

XLSDecodeVectorScene kontrollerar huvud, poststorlekar, totaler och EOF-placering på förhand och vägrar sedan escape-poster samt räknar ostödda ritposter
Strukturella kontroller körs på förhand och fail-closed-semantik vägrar escape-poster, medan ostödda ritposter bara räknas och hoppas över

Budgetar är en del av formatkontraktet

Vektorformat har sin egen version av dekomprimeringsbomben. Några kilobyte poster kan deklarera polylinjer med hundratals miljoner punkter, eller en bild vars deklarerade dimensioner multipliceras in i terabyte. Gränser måste därför vara uttryckliga konstanter i stället för vad maskinen råkar överleva

// Ur lxVectorScene: avkodningsbudgeten, uttalad snarare än underförstådd
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;

Två av dem förtjänar en notis. Djuptaket på 32 för device context finns för att SaveDC- och RestoreDC-poster nästlar sig, och en obalanserad ström kan push:a i all evighet; 32 är generöst för riktiga metafiler och billigt att tillämpa. Koordinattaket finns för att koordinater matar en transform, och ett värde nära heltalsområdets gränser ger ett transformerat resultat som antingen är oändligt eller wrap:ar, varefter varje begränsningsruteberäkning nedströms är nonsense. Att klamma koordinater vid parstiden är mycket lättare att resonera om än att försvara varje konsument av geometrin

Avkodningsbudgetkonstanter i HotXLS lxVectorScene för poster, handles, DC-djup, kommandon, punkter, text, bildstorlek och koordinatklamring
Varje gräns är en namngiven konstant som tillämpas under parsningen; DC-djuptaket och koordinatklamringen förtjänar mest uppmärksamhet

Att använda scenen

Avkodaren räcker tillbaka ett objekt du äger, ett kommandoantal, en nominell storlek och ett antal ritposter den valde att inte representera

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data innehåller den råa bildnyttolasten tagen ur arbetsboken
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Vägrad: huvud, gränser, totaler, EOF-placering eller en 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;

Kommandoposten bär allt en backend behöver och inget som kräver en enhet: pennans närvaro, färg, bredd och stil; penselns närvaro och färg; geometrin; och för text strängen, teckensnittsnamnet, storleken, stilarna och inriktningen. Det är det som gör samma scen användbar av både skärmscanvas-renderaren och SVG-skrivaren, och det är därför vektorspåret inte divergerar mellan förhandsvisning och export. Skärmrendering av kalkylbladsinnehåll i allmänhet tas upp i artikeln om anpassad VCL-grid-rendering

Att avvisa en bild skadar inte arbetsboken

En viktig egenskap hos denna design är att en vägrad avkodning påverkar bara renderingen. Den ursprungliga nyttolasten stannar i modellen, så en arbetsbok som öppnas och sparas igen bär sina metabildsbilder ut byte för byte, oavsett om den säkra avkodaren kunde rita dem eller inte. Det befintliga avgränsade rasterspåret förblir också tillgängligt som reservväg. Med andra ord, den strikta parsaren spärrar vad som exekveras, inte vad som bevaras, vilket är distinktionen som låter en säkerhetsmotiverad ändring skeppas utan att bli en dataförluständring

Hantering av ritarobjekt i allmänhet, inklusive delarna av objektmodellen som överlever rundturer orörda, tas upp i artikeln om diagram, bilder och ritningar

Var detta lämnar en serverdistribution

Om du renderar användaruppladdade arbetsböcker i en tjänst är den praktiska positionen nu försvarbar: metabildsbilder parsas av kod du kan granska, avgränsas av konstanter du kan läsa och lämnas aldrig till en grafikdrivrutin. Den ärliga brasklappen är täckning. Komplexa metafiler producerade av ritverktyg kommer träffa räknaren för hoppade poster, och svaret på det är att lyfta fram räknaren i stället för att tyst bredda vitlistan. En bild som renderas delvis och säger det är ett supportsamtal; en bild som renderas fel och säger inget är en buggrapport från en kund

HotXLS hanterar XLS, XLSX, ODS och CSV nativt i Delphi och C++Builder utan Excel installerat, och samma avgränsade-parsningsfilosofi löper genom dess container-, formel- och ritlager. Format- och säkerhetsdetaljer listas på produktsidan för HotXLS Delphi spreadsheet component