En Excel-projektmappe kan bære EMF- og WMF-billeder, og den konventionelle måde at tegne et på er at give bytestrømmen til operativsystemets metafilafspiller. Det er en beslutning værd at se direkte på: en metafil er en serialiseret kommandostream til et grafik-API, og at afspille en betyder at lade en fil, der ankom over e-mail, drive grafikdriveren. HotXLS tager den anden vej. XLSDecodeVectorScene parser metafilen selv, validerer headeren, hver record-størrelse, det erklærede record-antal og den eksakte placering af end-of-file-recorden, afviser escape-records blankt og returnerer en TXLSVectorScene af primitive tegnekommandoer, som Canvas- og SVG-backends genafspiller gennem deres egen kode. Ingen driverafspilning er involveret på noget tidspunkt
Byttet er dækning for indeslutning. En rektangelorienteret kommando-whitelist vil ikke gengive enhver metafil, en designer kan skabe, så scenen rapporterer, hvor mange tegnerecords den ikke kunne repræsentere, og kalderen beslutter, hvad der skal gøres ved det. For en serverproces, der renderer dokumenter, den ikke selv skabte, er det bytte den rigtige vej rundt
Hvorfor er metafilafspilning en dårlig pasform til upålideligt input?
Fordi formatet ikke er et billede, det er et program. En EMF-recordstream manipulerer en device context-tilstandsstak, allokerer og vælger objekter fra en handle-tabel og kan bære escape-records, hvis payload sendes videre til en enhedsdriver. At genafspille den afprøver veje i platformens grafikstak, der var skrevet under antagelsen om, at metafilen kom fra et samarbejdsvilligt program på samme maskine. Når inputtet er et regnearksbilag, er den antagelse væk, og al omhu i regnearksbiblioteket hjælper ikke, for biblioteket er ikke den komponent, der parser
Dette er samme ræsonnement, der styrer containerlaget. En projektmappe er et ZIP-arkiv, og HotXLS validerer dets central directory frem for at stole på erklærede offsets, som beskrevet i artiklen om ZIP end-of-central-directory-validering. Metafil-payloads er næste lag af samme problem
Hvad dekoderen tjekker, før den tegner noget
Valideringen er strukturel og sker på forhånd, for en parser, der begynder at tegne og validerer undervejs, har allerede handlet på data, den ikke har verificeret. Headeren skal matche strengt snarere end plausibelt. Hver record skal erklære en størrelse, der passer ind i den resterende buffer og er stor nok til dens egne faste felter. Record-antallet, som headeren erklærer, skal matche de records, der reelt er til stede. End-of-file-recorden skal ligge præcis der, hvor strømmen slutter, ikke blot et sted i nærheden, hvilket lukker af for trailing-garbage-tricket, der skjuler en anden payload bag et gyldigt billede
Ud over strukturen er dekoderen fail-closed på semantik. Escape-records afvises, ikke springes over. En tilstandsændrende record, som dekoderen ikke modellerer, får dekodningen til at fejle frem for at blive ignoreret, for at ignorere en tilstandsændring betyder, at hver efterfølgende tegnekommando udføres i en tilstand, filen ikke bad om, og resultatet er et billede, der er forkert på en måde, ingen kan forudsige. Tegnerecords uden for det understøttede kommandosæt er en anden sag: de tælles og springes over, for en manglende form er et synligt, rapporterbart hul frem for en stille korruption
Budgetter er en del af formatkontrakten
Vektorformater har deres egen version af dekomprimeringsbomben. Et par kilobytes records kan erklære polylines med hundredvis af millioner af punkter eller et billede, hvis erklærede dimensioner multiplicerer sig til terabytes. Grænser må derfor være eksplicitte konstanter frem for hvad end maskinen tilfældigvis overlever
// Fra lxVectorScene: dekodningsbudgettet, slået fast frem for underforstået
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 af dem fortjener en note. Device context-dybdetaget på 32 findes, for SaveDC- og RestoreDC-records næster, og en ubalanceret strøm kan pushe for altid; 32 er generøst for rigtige metafiler og billigt at håndhæve. Koordinattagfindes, for koordinater føder en transform, og en værdi nær heltalsintervallets grænser giver et transformeret resultat, der enten er uendeligt eller wrappes, hvorefter enhver bounding-box-beregning længere nede er nonsens. At clamp-e koordinater på parsetidspunktet er langt lettere at ræsonnere om end at forsvare hver forbruger af geometrien
At bruge scenen
Dekoderen giver tilbage et objekt, du ejer, et kommandoantal, en nominel størrelse og et antal tegnerecords, den valgte ikke at repræsentere
uses
lxVectorScene;
var
Scene: TXLSVectorScene;
Error: WideString;
I: Integer;
begin
// Data indeholder rå billedpayload taget fra projektmappen
if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
begin
// Afvist: header, grænser, totaler, EOF-placering eller et 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;
Kommandorecordren bærer alt, hvad en backend behøver, og intet, der kræver en enhed: pen-tilstedeværelse, farve, bredde og stil; brush-tilstedeværelse og farve; geometrien; og til tekst strengen, fontnavnet, størrelsen, stile og justering. Det er det, der gør samme scene brugbar af både on-screen canvas-rendereren og SVG-writeren, og det er grunden til, at vektorvejen ikke divergerer mellem preview og eksport. Skærmrendering af regnearksindhold generelt er dækket i artiklen om custom VCL grid-rendering
At afvise et billede skader ikke projektmappen
En vigtig egenskab ved dette design er, at en afvist dekodning kun påvirker rendering. Den oprindelige payload forbliver i modellen, så en projektmappe, der åbnes og gemmes igen, bærer sine metafilbilleder ud byte for byte, uanset om den sikre dekoder kunne tegne dem. Den eksisterende afgrænsede rastervej forbliver også tilgængelig som fallback. Med andre ord gater den strenge parser, hvad der bliver udført, ikke hvad der bliver bevaret, hvilket er den skelnen, der gør, at en sikkerhedsmotiveret ændring kan udsendes uden at blive til en datatab-ændring
Håndtering af tegneobjekter generelt, inklusive de dele af objektmodellen, der overlever round-trips urørt, er dækket i artiklen om diagrammer, billeder og tegninger
Hvor dette efterlader en serverudrulning
Hvis du renderer bruger-uploadede projektmapper i en tjeneste, er den praktiske position nu forsvarlig: metafilbilleder parses af kode, du kan revidere, afgrænset af konstanter, du kan læse, og gives aldrig til en grafikdriver. Det ærlige forbehold er dækning. Komplekse metafiler skabt af tegneværktøjer vil ramme den sprunget-record-tæller, og svaret på det er at bringe tælleren frem frem for stille at udvide whitelisten. Et billede, der renderer delvist og siger det, er en support-samtale; et billede, der renderer forkert og ikke siger noget, er en fejlrapport fra en kunde
HotXLS håndterer XLS, XLSX, ODS og CSV nativt i Delphi og C++Builder uden Excel installeret, og samme afgrænsede parse-filosofi løber gennem dets container-, formel- og tegnelag. Format- og sikkerhedsdetaljer er opført på produktsiden HotXLS Delphi spreadsheet component