Articol tehnic

Redarea sigură a fișierelor EMF și WMF nesigure în HotXLS

Un registru de lucru Excel poate purta imagini EMF și WMF, iar modalitatea convențională de a desena una este să înmânați fluxul de octeți playerului de metafișiere al sistemului de operare. Aceasta este o decizie care merită privită direct: un metafișier este un flux de comenzi serializat pentru un API grafic, iar redarea lui înseamnă a lăsa un fișier sosit prin e-mail să conducă driverul grafic. HotXLS ia cealaltă rută. XLSDecodeVectorScene parsează el însuși metafișierul, validează header-ul, fiecare mărime de înregistrare, totalul declarat de înregistrări și plasarea exactă a înregistrării de sfârșit de fișier, respinge categoric înregistrările escape și returnează un TXLSVectorScene de comenzi de desenare primitive pe care backend-urile Canvas și SVG le redau prin propriul lor cod. Nicio redare de driver nu este implicată în vreun moment

HotXLS parsează octeții EMF și WMF nesiguri ai foii de lucru cu XLSDecodeVectorScene într-o listă de comenzi TXLSVectorScene în loc de redare GDI a metafișierului
HotXLS parsează el însuși metafișierul și returnează comenzi primitive pentru redarea Canvas și SVG; ruta convențională execută fluxul de octeți pe stiva grafică

Schimbul este acoperire pentru izolare. O listă albă de comenzi orientată pe dreptunghiuri nu va reproduce fiecare metafișier pe care un designer îl poate crea, deci scena raportează câte înregistrări de desenare nu a putut reprezenta, iar apelantul decide ce face cu asta. Pentru un proces de server care randează documente pe care nu le-a creat, acest schimb este orientat corect

De ce este redarea de metafișiere o alegere slabă pentru date de intrare nesigure?

Pentru că formatul nu este o imagine, este un program. Un flux de înregistrări EMF manipulează o stivă de stări de context de dispozitiv, alocă și selectează obiecte dintr-o tabelă de handle și poate purta înregistrări escape a căror sarcină utilă este transmisă unui driver de dispozitiv. Redarea lui exercită căi din stiva grafică a platformei scrise cu presupunerea că metafișierul a venit de la o aplicație cooperantă pe aceeași mașină. Când datele de intrare sunt un atașament de tabelă, presupunerea aceea a dispărut, și niciun grad de grijă în interiorul bibliotecii de tabele nu ajută, deoarece biblioteca nu este componenta care parsează

Acesta este același raționament care guvernează stratul de containere. Un registru de lucru este o arhivă ZIP, iar HotXLS validează directorul central în loc să aibă încredere în offset-urile declarate, după cum este descris în articolul despre validarea end-of-central-directory ZIP. Sarcinile utile de metafișier sunt următorul strat al aceleiași probleme

Ce verifică decoderul înainte să deseneze ceva

Validarea este structurală și are loc din față, deoarece un parser care începe să deseneze și validează pe parcurs a acționat deja pe date pe care nu le-a verificat. Header-ul trebuie să se potrivească strict, nu plauzibil. Fiecare înregistrare trebuie să declare o mărime care încape în bufferul rămas și este suficient de mare pentru propriile ei câmpuri fixe. Numărul de înregistrări pe care îl declară header-ul trebuie să corespundă înregistrărilor efectiv prezente. Înregistrarea de sfârșit de fișier trebuie să stea exact unde se termină fluxul, nu doar undeva aproape de el, ceea ce închide trucul gunoiului din coadă care ascunde o a doua sarcină utilă în spatele unei imagini valide

Dincolo de structură, decoderul eșuează închis la semantică. Înregistrările escape sunt refuzate, nu sărite. O înregistrare care schimbă starea și pe care decoderul nu o modelează face decodarea să eșueze în loc să fie ignorată, deoarece ignorarea unei schimbări de stare înseamnă că fiecare comandă de desenare ulterioară este executată într-o stare pe care fișierul nu a cerut-o, iar rezultatul este o imagine greșită într-un mod pe care nimeni nu îl poate prezice. Înregistrările de desenare din afara setului de comenzi suportat sunt altă poveste: acelea sunt numărate și sărite, deoarece o formă lipsă este o lacună vizibilă, raportabilă, nu o corupere tăcută

XLSDecodeVectorScene verifică din față header-ul, mărimile de înregistrare, totalurile și plasarea EOF, apoi refuză înregistrările escape și numără înregistrările de desenare nesuportate
Verificările structurale rulează din față, iar semantic fail-closed refuză înregistrările escape, în timp ce înregistrările de desenare nesuportate sunt doar numărate și sărite

Bugetele fac parte din contractul de format

Formatele vectoriale au propria lor versiune a bombei de decomprimare. Câțiva kiloocteți de înregistrări pot declara polilinii cu sute de milioane de puncte, sau o imagine ale cărei dimensiuni declarate se înmulțesc în teraocteți. Limitele trebuie prin urmare să fie constante explicite, nu orice ar supraviețui mașinii întâmplător

// Din lxVectorScene: bugetul de decodare, enunțat, nu implicit
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;

Două dintre acestea merită o notă. Limita de adâncime a contextului de dispozitiv de 32 există deoarece înregistrările SaveDC și RestoreDC se imbrică, iar un flux dezechilibrat poate împinge la infinit; 32 este generos pentru metafișiere reale și ieftin de aplicat. Limita de coordonate există deoarece coordonatele hrănesc o transformare, iar o valoare aproape de limitele intervalului de întregi produce un rezultat transformat care este infinit sau se suprascrie, după care orice calcul de bounding-box din aval este nonsense. Limitarea coordonatelor la momentul parsării este mult mai ușor de raționat decât apărarea fiecărui consumator al geometriei

Constantele de buget de decodare în lxVectorScene din HotXLS pentru înregistrări, handle-uri, adâncime DC, comenzi, puncte, text, mărime de imagine și limitarea coordonatelor
Fiecare limită este o constantă numită aplicată în timpul parsării; limita de adâncime DC și limitarea coordonatelor merită cea mai multă atenție

Folosirea scenei

Decoderul predă înapoi un obiect pe care îl dețineți, un număr de comenzi, o mărime nominală și un număr de înregistrări de desenare pe care a ales să nu le reprezinte

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data deține sarcina utilă brută a imaginii luată din registrul de lucru
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Refuzat: header, limite, totaluri, plasare EOF sau un buget
    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;

Înregistrarea de comandă poartă tot ce are nevoie un backend și nimic ce cere un dispozitiv: prezența peniței, culoarea, lățimea și stilul; prezența și culoarea pensulei; geometria; și pentru text șirul, numele fontului, mărimea, stilurile și alinierea. Aceasta face ca aceeași scenă să fie folosibilă atât de randatorul de canvas pe ecran, cât și de scriitorul SVG, și este motivul pentru care calea vectorială nu diverge între previzualizare și export. Randarea pe ecran a conținutului de tabelă în general este tratată în articolul despre randarea grilelor VCL personalizate

Respingerea unei imagini nu deteriorează registrul de lucru

O proprietate importantă a acestui design este că o decodare refuzată afectează doar randarea. Sarcina utilă originală rămâne în model, astfel încât un registru de lucru deschis și salvat din nou își duce imaginile de metafișier mai departe octet cu octet, fie că decoderul sigur le putea desena, fie nu. Calea raster mărginită existentă rămâne de asemenea disponibilă ca rezervă. Cu alte cuvinte, parserul strict filtrează ceea ce se execută, nu ceea ce se păstrează, iar aceasta este distincția care lasă o schimbare motivată de securitate să se livreze fără a se transforma într-o schimbare de pierdere de date

Gestionarea obiectelor de desenare în general, inclusiv părțile modelului de obiecte care supraviețuiesc neatinse călătoriilor dus-întors, este tratată în articolul despre grafice, imagini și desenări

Unde lasă aceasta o instalare de server

Dacă randați registre de lucru încărcate de utilizatori într-un serviciu, poziția practică este acum apărabilă: imaginile de metafișier sunt parseate de cod pe care îl puteți audita, mărginite de constante pe care le puteți citi și niciodată înmânate unui driver grafic. Rezerva onestă este acoperirea. Metafișierele complexe produse de unelte de desenare vor lovi contorul de înregistrări sărite, iar răspunsul este să aduceți contorul la suprafață, nu să lărgiți lista albă în tăcere. O imagine randată parțial care spune acest lucru este o discuție cu suportul; o imagine randată greșit care nu spune nimic este un raport de bug de la un client

HotXLS gestionează XLS, XLSX, ODS și CSV nativ în Delphi și C++Builder fără Excel instalat, iar aceeași filozofie de parsare mărginită traversează straturile lui de containere, formule și desenări. Detaliile de format și securitate sunt listate pe pagina de produs HotXLS Delphi spreadsheet component