Teknisk artikkel

Avspille utiltrudt EMF og WMF trygt i HotXLS

En Excel-arbeidsbok kan bære EMF- og WMF-bilder, og den konvensjonelle måten å tegne én på, er å gi bytestrømmen til operativsystemets metafilspiller. Det er en avgjørelse verdt å se rett på: en metafil er en serialisert kommandostrøm for et grafikk-API, og å spille én av, betyr å la en fil som ankom over e-post drive grafikkdriveren. HotXLS tar den andre ruten. XLSDecodeVectorScene parser metafilen selv, validerer headeren, hver poststørrelse, det deklarerte posttotalen og den nøyaktige plasseringen av slutt-på-fil-posten, avviser escape-poster blankt, og returnerer en TXLSVectorScene av primitive tegnekommandoer som Canvas- og SVG-backendene spiller av gjennom sin egen kode. Ingen driveravspilling er involvert på noe tidspunkt

HotXLS parser utiltrudte EMF- og WMF-regnearkbyte med XLSDecodeVectorScene til en TXLSVectorScene-kommandoliste i stedet for GDI-metafilavspilling
HotXLS parser metafilen selv og returnerer primitive kommandoer for Canvas- og SVG-avspilling; den konvensjonelle ruten kjører bytestrømmen på grafikkstakken

Byttet er dekning for innkapsling. En rektangelorientert kommandohviteliste vil ikke reprodusere hver metafil en designer kan skape, så scenen rapporterer hvor mange tegneposter den ikke kunne representere, og kalleren avgjør hva den vil gjøre med det. For en servertjeneste som renderer dokumenter den ikke skapte, er det byttet riktig vei rundt

Hvorfor er metafilavspilling en dårlig passform for utiltrudt input?

Fordi formatet ikke er et bilde, det er et program. En EMF-poststrøm manipulerer en device-context-tilstandsstakk, allokerer og velger objekter fra en håndtakstabell, og kan bære escape-poster hvis payload sendes videre til en enhetsdriver. Å spille den av, trener veier i plattformens grafikkstakk som ble skrevet under antakelsen om at metafilen kom fra et samarbeidende program på samme maskin. Når inputen er et regnearkvedlegg, er den antakelsen borte, og ingen mengde omsorg inne i regnearkbiblioteket hjelper, fordi biblioteket ikke er komponenten som gjør parsingen

Dette er den samme resonneringen som styrer containerlaget. En arbeidsbok er et ZIP-arkiv, og HotXLS validerer dets sentralkatalog snarere enn å stole på deklarerte forskyvninger, som beskrevet i artikkelen om ZIP end-of-central-directory-validering. Metafilpayloads er neste lag av det samme problemet

Hva dekoderen sjekker før den tegner noe

Valideringen er strukturell og skjer på forhånd, fordi en parser som begynner å tegne og validerer underveis, allerede har handlet på data den ikke har verifisert. Headeren må matche strengt snarere enn plausibelt. Hver post må erklære en størrelse som passer innenfor den gjenværende bufferen og er stor nok for sine egne faste felt. Postantallet headeren erklærer, må matche postene faktisk til stede. Slutt-på-fil-posten må sitte nøyaktig der strømmen slutter, ikke bare et sted i nærheten, noe som lukker søppel-på-slutten-trikset som gjemmer en andre payload bak et gyldig bilde

Utover struktur er dekoderen fail-closed på semantikk. Escape-poster avslås, ikke hoppes over. En tilstandsendrende post dekoderen ikke modellerer, får dekodingen til å feile snarere enn å bli ignorert, for å ignorere en tilstandsendring betyr at hver etterfølgende tegnekommando kjøres i en tilstand filen ikke ba om, og resultatet er et bilde som er feil på en måte ingen kan forutsi. Tegneposter utenfor det støttede kommandosettet er en annen sak: de telles og hoppes over, fordi en manglende form er et synlig, rapporterbart gap snarere enn en lydløs korruptering

XLSDecodeVectorScene sjekker header, poststørrelser, totaler og EOF-plassering på forhånd, og avslår deretter escape-poster og teller ustøttede tegneposter
Strukturelle sjekker kjøres på forhånd og fail-closed-semantikk avslår escape-poster, mens ustøttede tegneposter bare telles og hoppes over

Budsjetter er en del av formatkontrakten

Vektorformater har sin egen versjon av dekomprimeringsbomben. Noen få kilobyte med poster kan erklære polylinjer med hundrevis av millioner punkter, eller et bilde hvis deklarerte dimensjoner multipliserer inn i terabyte. Grenser må derfor være eksplisitte konstanter snarere enn det maskinen tilfeldigvis overlever

// Fra lxVectorScene: dekodebudsjettet, slått fast snarere enn underforstått
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;

To av disse fortjener en merknad. Device-context-dybdetaket på 32 finnes fordi SaveDC- og RestoreDC-poster nester, og en ubalansert strøm kan pushe for alltid; 32 er generøst for ekte metafiler og billig å håndheve. Koordinattaket finnes fordi koordinater mater en transform, og en verdi nær grensene av heltallsområdet produserer et transformert resultat som enten er uendelig eller wrapper, hvoretter enhver bbox-beregning nedstrøms er nonsens. Å klemme koordinater ved parsetid er mye enklere å resonnere om enn å forsvare hver konsument av geometrien

Dekodebudsjettkonstanter i HotXLS lxVectorScene for poster, håndtak, DC-dybde, kommandoer, punkter, tekst, bildestørrelse og koordinatklemming
Hver grense er en navngitt konstant håndhevet under parsing; DC-dybdetaket og koordinatklemmen fortjener mest oppmerksomhet

Å bruke scenen

Dekoderen gir tilbake et objekt du eier, et kommandoantall, en nominell størrelse, og et antall tegneposter den valgte å ikke representere

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data holder rått bildepayload tatt fra arbeidsboken
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Avslått: header, grenser, totaler, EOF-plassering eller et budsjett
    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ærer alt en backend trenger og ingenting som krever en enhet: pen til stede, farge, bredde og stil; børste til stede og farge; geometrien; og for tekst strengen, fontnavnet, størrelsen, stilene og justeringen. Det er det som gjør den samme scenen brukbar av både skjerm-canvas-rendereren og SVG-skriveren, og det er hvorfor vektorveien ikke divergerer mellom forhåndsvisning og eksport. Skjermrendering av regnearkinnhold generelt er dekket i artikkelen om tilpasset VCL grid-rendering

Å avvise et bilde skader ikke arbeidsboken

En viktig egenskap ved dette designet er at en avslått dekoding påvirker bare rendering. Den opprinnelige payloaden blir i modellen, så en arbeidsbok som åpnes og lagres igjen, bærer sine metafilbilder ut byte for byte, enten den trygge dekoderen kunne tegne dem eller ikke. Den eksisterende avgrensede rasterveien forblir også tilgjengelig som fallback. Med andre ord, den strenge parseren gater hva som blir kjørt, ikke hva som blir bevart, noe som er skillet som lar en sikkerhetsmotivert endring sendes uten å bli en datatapsendring

Håndtering av tegneobjekter generelt, inkludert delene av objektmodellen som overlever rundturer urørt, er dekket i artikkelen om diagrammer, bilder og tegninger

Hvor dette etterlater en serverutrulling

Hvis du renderer brukeropplastede arbeidsbøker i en tjeneste, er den praktiske posisjonen nå forsvarlig: metafilbilder parses av kode du kan revidere, avgrenset av konstanter du kan lese, og aldri gitt til en grafikkdriver. Det ærlige forbeholdet er dekning. Komplekse metafiler produsert av tegneverktøy vil treffe den hoppede-post-telleren, og svaret på det er å synliggjøre telleren snarere enn å utvide hvitelisten lydløst. Et bilde som renderer delvis og sier det, er en supportsamtale; et bilde som renderer feil og sier ingenting, er en feilrapport fra en kunde

HotXLS håndterer XLS, XLSX, ODS og CSV nativt i Delphi og C++Builder uten Excel installert, og den samme avgrenset-parse-filosofien går gjennom dets container-, formel- og tegnelag. Format- og sikkerhetsdetaljer er oppført på produktsiden for HotXLS Delphi spreadsheet component