HotXLS kieltäytyy reitittämästä 20 vaarallista kaavanimeä, mukaan lukien CALL, REGISTER.ID, WEBSERVICE ja DDE, Delphin käyttäjäfunktiocallbackeihisi ellet valitse toiminnon käyttöön. Työkirjan ominaisuuden AllowUnsafeFormulaCallbacks oletus on False, tarkistus ajetaan ennen kuin yhtään argumenttia evaluoidaan, ja hylätty kutsu raportoi xlfeUnsafeFunctionDeniedin kutsumatta yhtään käsittelijää
Skenaario joka teki tästä tarpeelliseksi on arkinen. Palvelu ottaa vastaan ladattuja XLS- tai XLSX-tiedostoja, laskee ne uudelleen palvelinpuolella ja lukee pari summaa takaisin. Isäntäsovellus rekisteröi OnUserFunction-käsittelijän vuosia sitten paria liiketoimintafunktiota varten, ja jossakin matkan varrella käsittelijä kasvatti haarautuman joka napkaa kaiken tunnistamattoman ja välittää sen liitännäistaulukkoon. Kukaan tiimiläisistä ei koskaan näpyttänyt =WEBSERVICE(...)ia soluun. Lataaja teki. Sen kaavan pitäminen ennallaan avauksen, uudelleenlaskennan ja tallennuksen läpi on tiedoston uskollisuusominaisuus. Sen päästäminen isäntäkoodiin joka voi avata socketteja tai tiedostoja on valtuutuspäätös, ja ennen kuin HotXLS erotti nuo kaksi, kirjasto teki sen päätöksen hiljaa puolestasi
Miksi kaavan säilyttäminen muuttui luvaksi ajaa se?
Juurisyy oli yksi varapolku. HotXLS jäsentää jokaisen tuntemansa Excel-funktionimen, mutta kaikki tunnetut nimet eivät ole toteutettuja laskentamoottorissa. Tunnistetut mutta toteuttamattomat valmiit funktiot putosivat aiemmin samaan käyttäjän määrittelemien funktioiden varapolkuun kuin aidosti omat nimet, joten CALL ja REGISTER.ID jakoivat dispatch-polun DISCOUNTisi tai REGIONRATEisi kanssa. Tuntemattomat nimet kuten WEBSERVICE tai DDE saattoivat vastaavasti osua samannimiseen merkintään työkirjarekisterissä, prosessinlaajuisessa rekisterissä tai tapahtumakäsittelijässä. Kyseisen varapolun mekaniikat käsitellään artikkelissa miten HotXLS ratkaisee omat funktiot OnUserFunctionin kautta; ongelma oli että mikään polulla ei kysynyt onko nimi itsessään sellainen jonka järkevä isäntä koskaan ajaisi
Dispatch-järjestyksellä on väliä sille mitä ”tuntematon” tässä tarkoittaa. Kutsua jota moottori ei osaa evaluoida natiivisti tarjotaan vuorollaan leksikaalisille LAMBDA- ja LET-sidonnoille, jotka HotXLS:n kaavamoottorin closure-tuki ratkaisee ensin, sitten RegisterUserFunctionilla rekisteröidyille työkirjakohtaisille funktioille, sitten prosessinlaajuisille funktioille TXLSWorkbook.RegisterGlobalUserFunctionista ja viimeksi OnUserFunction- ja OnUserFunctionEx-tapahtumille. Vasta kun kaikki kieltäytyvät, aidosti tuntematon funktio muuttuu arvoksi #NAME?. Jokainen vaihe lambda-haun jälkeen luovuttaa ohjauksen kirjoittamallesi koodille, mikä on juuri syy sille että turvatarkistuksen on istuttava koko ketjun edessä eikä minkään yhden käsittelijän sisällä
Mitkä funktioiden nimet HotXLS estää oletuksena?
XLSFormulaCallbackIsUnsafe tiedostossa lxCalc.pas pitää kiinteää 20 nimen estolistaa: DDE, CALL, REGISTER, REGISTER.ID, WEBSERVICE, RTD, SQL.REQUEST, EXEC, RUN, CREATE.OBJECT, APP.ACTIVATE, SEND.KEYS, OPEN, SAVE, SAVE.AS, FOPEN, FWRITE, FWRITELN, FCLOSE ja FILE.DELETE. Ne ovat nimet jotka Excelissä tai sen makrokielessä lataavat natiivikoodia, ulottuvat verkkoon, puhuvat muille prosesseille tai koskettavat tiedostojärjestelmää. Ennen vertailua funktio trimmaa ympäröivän tyhjän, muuntaa nimen isoiksi kirjaimiksi ja irrottaa yhden _XLFN.- tai _XLWS.-etuliitteen, joten uudemman Excel-version kirjoittama _xlfn.webservice jää kiinni aivan kuten paljas kirjoitusasu. Lista asuu laskurin rajapinnassa eikä Classic-, XLSX- ja ODS-jäsentimissä, mikä pitää yhden AST:n, yhden BIFF-tokenvirran ja yhden konvertoidun työkirjan käyttäytymässä identtisesti
Kaksi reunaehdoa kannattaa tuntea ennen kuin luotat siihen. Vertailu on täsmällinen, joten MYWEBSERVICEiksi rekisteröimäsi käsittelijä ei vaikudu, ja toisaalta laillinen in-house UDF joka sattuu nimiin OPEN tai RUN hylätään nyt oletuksena. Estolista ei myöskään ole hiekkalaatikko omille käsittelijöillesi. Jos kaiken napkaiseva haarautumasi ajaa mielivaltaisia liitännäisnimiä, portti pysäyttää kuuluisat vaaralliset eikä muuta; kestävä korjaus on yhä käsittelijä joka vertaa eksplisiittiseen sallittujen listaan SameTextilla ja jättää Handledin arvoon False kaikelle jota se ei omista
Miksi portin on ajettava ennen argumenttien evaluointia?
Portti joka laukeaa vasta kun argumentit on laskettu on myöhässä, koska argumentit itsessään voivat kutsua koodiasi. GetValueItemUserFunction tarkistaa nimen ensin ja poistuu arvolla lxErrorUnsafeFunctionDenied ennen kuin se rakentaa argumentti-aulokon, ennen kuin se konsultoi resolveria tai kumpaakin rekisteriä, ja jopa ennen kuin se huomaa ettei yhtään käsittelijää ole sijoitettu. Se järjestys on se mikä kaataa alla olevan sisäkkäisen tapauksen, jossa ulompi kutsu hylättäisiin joka tapauksessa mutta harmiton näköinen sisempi UDF muuten lauhtuisi ensin ja jättäisi sivuvaikutuksensa jälkeensä
procedure TImportService.HandleUdf(Sender: TObject;
const FunctionName: WideString; const Args: Variant;
var Value: Variant; var Handled: Boolean);
begin
if SameText(FunctionName, 'AUDIT_TOKEN') then
begin
FAuditLog.Add('AUDIT_TOKEN evaluated'); // sivuvaikutus isäntäkoodissa
Value := 'token-42';
Handled := True;
end;
end;
Book.OnUserFunction := HandleUdf;
Eval := Sheet.EvaluateFormulaAt(1, 1, '=WEBSERVICE(AUDIT_TOKEN())');
// Eval.Status = xlfeUnsafeFunctionDenied, Eval.Value = Null,
// Eval.Issue.NativeCode = -106, ja FAuditLog on yhä tyhjä
Työkirjan oletus vastaan kutsukohtainen TXLSFormulaEvaluationOptions
Työkirjan lippu on oletus ja kutsukohtainen asetus on viimeinen sana. TXLSWorkbook.AllowUnsafeFormulaCallbacks ja TXLSXWorkbook.AllowUnsafeFormulaCallbacks hallitsevat tavallista uudelleenlaskentaa, Calculatea, kaksiparametrista EvaluateFormulaAtia, evaluointipohjia, vain luku -näkymiä ja XLSX:n osalta jokaista työntekijää rinnakkaisessa uudelleenlaskentapoolissa. Jokainen sisääntulopiste joka ottaa vastaan eksplisiittisen TXLSFormulaEvaluationOptions-recordin ottaa Options.AllowUnsafeFormulaCallbacksin tuomiona kyseiselle kutsulle eikä tee sille OR-operaatiota työkirjan ominaisuuden kanssa. Se epäsymmetria on tarkoituksellinen: luotettu sisäinen eräajo voi valtuuttaa yhden RTD-haun kääntämättä koko työkirjaa, ja globaalisti valittu työkirja voi silti pakottaa arkaluontoisen evaluoinnin takaisin hylkäykseen
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// työkirja pysyy lukittuna, yksi luotettu kutsu pääsee läpi
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// työkirja valittu käyttöön, mutta tämä ladatun tekstin evaluointi ei ole
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // lippu on taas False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Työkirjan ominaisuuden vaihtaminen merkitsee myös riippuvuusgraafin likaiseksi molemmissa koneissa. Ilman sitä välimuistissa oleva tulos joka laskettiin kun callbackit olivat sallittuja voitaisiin palvella sen jälkeen kun ne oli peruttu, tai välimuistissa oleva xlfeUnsafeFunctionDenied-lopputulos voi elää opt-inin yli. Uusi tila liitettiin TXLSFormulaEvaluationStatusin xlfeFailedin perään, joten sen ordinaali on 10 ja jokainen olemassa oleva ordinaali säilyttää arvonsa; sama hännälle liittämissääntö pätee options-recordin kenttään ja IXLSWorkbook-getteriin ja -setteriin, vaikka vanhempaa julkaisua vasten rakennettu kuluttaja tarvitsee yhä uudelleenkäännöksen
Mitä tuntemattomalle ja turvattomalle kaavatekstille tapahtuu tallennuksessa?
Kaavan säilyttäminen ja sen ajaminen ovat nyt kaksi eri kysymystä, ja sisääntulokäytäntö vastaa vain ensimmäiseen. FormulaEntryPolicy kummassakin työkirjaluokassa kantaa UnknownFunctionModen ja UnknownNameModen, molemmat oletuksena xlfusmReject, joten tuntemattoman kutsun sisältävän kaavan sijoittaminen tavallisen Formula-ominaisuuden kautta hylätään ennen kuin solun arvo, kaavavälimuisti tai riippuvuudet muuttuvat. ValidateFormulaEntry raportoi saman päätöksen ilman sivuvaikutuksia. Luotetut polut kuten tiedoston lataaminen, kopioiminen ja muotojen konversio ohittavat kyseisen käyttäjän sisääntulokäytännön, koska tiukka oletus ei saa koskaan hylätä symboleita jotka ovat jo tiedostossa jota olet vain avaamassa
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // yhteensopivuusmerkintä
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // tallennettu, ei valtuutettu
Book.SaveAs('rates.xls');
end;
Klassisessa BIFF8:ssa tuntemattomalla kutsulla ei ole omaa tokenia, joten HotXLS kirjoittaa sen sillä tavalla kuin Excel kirjoittaa add-in-funktiot. Kaava saa PtgNameX-tokenin ($59) jonka XTI-merkintä osoittaa add-in-SUPBOOKiin molempien sheet-indeksien ollessa $FFFE, jota seuraavat argumenttitokenit ja PtgFuncVar joka kantaa funktionumeroa 255 ja argumenttien määrää joka sisältää nimipaikan. Taustalla oleva ExternName-runko on kuusi nollatavua, pituustavu ja Unicode-lippu, UTF-16-funktionimi, sitten kaksitavuinen kaava $1C $17, PtgErr joka pitää sisällään arvon #REF!. Kirjoittaja kieltäytyy yli 255 merkin nimistä, yli 29 argumentista ja BIFF5-kohteesta. Miten HotXLS luokittelee nämä add-in-SUPBOOK-merkinnät ulkoisten työkirjalinkkien rinnalla selittää SUPBOOK- ja XTI-luokittelusäännöt BIFF-ulkoisille linkeille. XLSX pitää raakan funktion tekstin ja ODS pitää msoxl:-kaavansa, ja joka formaatissa tiedosto joka tallensi =WEBSERVICE(...)in avautuu uudelleen tekstin ollessa ennallaan ja evaluoituu yhä oletuksena arvoon xlfeUnsafeFunctionDenied
Jos putkesi evaluoi työkirjoja joita se ei itse kirjoittanut, jätä AllowUnsafeFormulaCallbacks arvoon False, pidä käsittelijät eksplisiittisellä sallittujen listalla ja myönnä kutsukohtaiset asetukset vain siellä missä kaavalähde on sinun. Koko callback-, sisääntulokäytäntö- ja evaluointi-API on dokumentoitu HotXLS Delphi spreadsheet component -komponentin yhteydessä