HotXLS отказва да пренасочи 20 опасни formula имена, сред които CALL, REGISTER.ID, WEBSERVICE и DDE, към вашите Delphi user-function callbacks, освен ако не го разрешите изрично. Свойството на работната книга AllowUnsafeFormulaCallbacks по подразбиране е False, проверката се пуска преди да е изчислен хоть един аргумент, а отхвърлено извикване докладва xlfeUnsafeFunctionDenied без да вика нито един handler
Сценарият, който направи това необходимо, е всекидневен. Един service приема качени XLS или XLSX файлове, преизчислява ги server-side и чете обратно няколко сборни числа. Host приложението е регистрирало OnUserFunction handler преди години за няколко бизнес функции, а по някое време този handler е отгледал catch-all разклонение, което препраща всичко, което не разпознава, към таблица с плъгини. Никой от екипа никога не е писал =WEBSERVICE(...) в клетка. Качващият е написал. Да запазиш тази формула непокътната през open, recalc и save е функция за файлова съвестност. Да ѝ позволиш да стигне до host код, който може да отваря sockets или файлове, е решение за оторизация, и докато HotXLS не раздели двете, библиотеката тихо вземаше това решение вместо вас
Защо запазването на формула се превърна в разрешение да я изпълниш?
Основната причина беше един-единствен fallback път. HotXLS парсва всяко Excel function име, което познава, но не всяко познато име има имплементация в calculation engine. Built-in-и, разпознати но неимплементирани, падаха в същия user-defined function fallback като истински custom имена, така че CALL и REGISTER.ID споделяха dispatch път с вашите DISCOUNT или REGIONRATE. Непознати имена като WEBSERVICE или DDE също можеха да намерят едноименен запис в workbook регистъра, process-wide регистъра или event handler. Механиката на този fallback е разгледана в как HotXLS resolve-ва custom функции чрез OnUserFunction; проблемът беше, че нищо по този път не питаше дали самото име е от типа, които разумен host изобщо би изпълнил
Редът на dispatch-ване има значение за това какво значи „непознат“ тук. Извикване, което engine-ът не може да изчисли нативно, се предлага последователно на лексикалните LAMBDA и LET binding-и, които closure поддръжката в formula engine-а на HotXLS resolve-ва първа, после на workbook-локални функции, регистрирани с RegisterUserFunction, после на process-wide функции от TXLSWorkbook.RegisterGlobalUserFunction и най-накрая на събитията OnUserFunction и OnUserFunctionEx. Само когато всички откажат, истински непозната функция става #NAME?. Всяка стъпка след lambda търсенето подава контрола на код, написан от вас, което е точно причината safety проверката да седи пред цялата верига, а не вътре в някой един handler
Кои function имена спира HotXLS по подразбиране?
XLSFormulaCallbackIsUnsafe в lxCalc.pas държи фиксиран deny набор от 20 имена: 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 и FILE.DELETE. Това са имената, които в Excel или неговия macro език зареждат native код, стигат до мрежата, разговарят с други процеси или пипат файловата система. Преди сравнението функцията реже околните интервали, качва името в главни букви и съблича единичен префикс _XLFN. или _XLWS., така че _xlfn.webservice, написан от по-нов Excel build, се хваща точно като голото изписване. Списъкът живее на границата на калкулатора, а не в Classic, XLSX и ODS parser-ите, което пази един AST, един BIFF token поток и една конвертирана работна книга да се държат еднакво
Два детайла си струва да знаете, преди да разчитате на това. Съвпадението е точно, така че handler, който сте регистрирали като MYWEBSERVICE, не се засяга, и обратно — легитимен домашен UDF, случайно кръстен OPEN или RUN, вече се отхвърля по подразбиране. Deny наборът освен това не е sandbox за вашите собствени handler-и. Ако вашето catch-all разклонение изпълнява произволни имена на плъгини, гейтът спира известните опасни и нищо друго; траен fix е пак handler, който мачка изричен allowlist с SameText и оставя Handled на False за всичко, което не е негово
Защо гейтът трябва да се пусне преди изчислението на аргументите?
Гейт, който палва след изчисляване на аргументите, е твърде късно, защото самите аргументи могат да викат вашия код. GetValueItemUserFunction проверява първо името и излиза с lxErrorUnsafeFunctionDenied, преди да е изградил аргументния масив, преди да е попитал resolver или някой от регистрите, и дори преди да е забелязал, че изобщо не е зададен handler. Именно този ред неутрализира вложения случай по-долу, където външното извикване и без това щеше да бъде отказано, но безобидно изглеждащ вътрешен UDF иначе щеше да палне пръв и да остави side ефекта си след себе си
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'); // side ефект в host кода
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, а FAuditLog все още е празен
Workbook default срещу per-call TXLSFormulaEvaluationOptions
Workbook флагът е default, а per-call опцията има последната дума. TXLSWorkbook.AllowUnsafeFormulaCallbacks и TXLSXWorkbook.AllowUnsafeFormulaCallbacks управляват обикновеното преизчисляване, Calculate, двуаргументният EvaluateFormulaAt, evaluation шаблоните, read-only изгледите и, на XLSX, всеки worker в паралелния recalculation pool. Всеки входен пункт, който приема изричен record TXLSFormulaEvaluationOptions, приема Options.AllowUnsafeFormulaCallbacks като присъда за това извикване и не го OR-ва със свойството на работната книга. Тази асиметрия е нарочна: доверен вътрешен job може да оторизира едно RTD търсене, без да преобръща цялата работна книга, а глобално разреширена работна книга все пак може да върне чувствително изчисление към отказ
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// работната книга остава заключена, едно доверено извикване минава
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// работната книга е разреширена, но това изчисление на качен текст не е
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // флагът пак е False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Превключването на workbook свойството също маркира dependency графата dirty и в двата engine-а. Без тази стъпка кеширан резултат, изчислен докато callbacks бяха разрешени, би могъл да се сервира след като са отнети, или кеширан резултат xlfeUnsafeFunctionDenied би могъл да надживее opt-in. Новият статус е добавен в края на TXLSFormulaEvaluationStatus след xlfeFailed, така че има ordinal 10 и всеки съществуващ ordinal пази стойността си; същото правило за добавяне в опашката важи за полето в options record-а и за getter и setter на IXLSWorkbook, макар че потребител, изграден срещу по-стара версия, все пак се нуждае от recompile
Какво става с непознат и unsafe formula текст при запис?
Да запазиш формула и да я изпълниш сега са два отделни въпроса, а entry политиката отговаря само на първия. FormulaEntryPolicy и на двата класа работни книги носи UnknownFunctionMode и UnknownNameMode, и двете по подразбиране xlfusmReject, така че присвояване на формула с непознато извикване през нормалното свойство Formula се отхвърля, преди клетъчната стойност, formula кешът или зависимостите да са се променили. ValidateFormulaEntry докладва същото решение без side ефекти. Доверени пътища като зареждане на файл, копиране и конверсия на формат заобикалят тази user-entry политика, защото строг default никога не бива да отхвърля символи, които вече са във файл, който просто отваряте
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // съвместим вход
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // съхранена, не оторизирана
Book.SaveAs('rates.xls');
end;
В Classic BIFF8 непознато извикване няма собствен token, така че HotXLS го записва по начина, по който Excel записва add-in функции. Формулата получава token PtgNameX ($59), чийто XTI запис сочи към add-in SUPBOOK с двата sheet индекса зададени на $FFFE, следвано от аргументните токени и PtgFuncVar с function номер 255 и брой аргументи, включващ name слота. Гарантиращият ExternName body е шест нулеви байта, байт дължина и Unicode флаг, UTF-16 името на функцията, после двубайтова формула $1C $17, PtgErr, носещ #REF!. Writer-ът отказва имена по-дълги от 255 символа, повече от 29 аргумента и BIFF5 целта. Как HotXLS класифицира тези add-in SUPBOOK записи до външните workbook линкове е обяснено в правилата за SUPBOOK и XTI класификация на BIFF външни линкове. XLSX пази суровия function текст, ODS пази своята msoxl: формула, и във всеки формат файл, записал =WEBSERVICE(...), се отваря наново с текста непокътнат и продължава да се изчислява на xlfeUnsafeFunctionDenied по подразбиране
Ако вашият pipeline преизчислява работни книги, които не сте написали сам, оставете AllowUnsafeFormulaCallbacks на False, дръжте handler-ите на изричен allowlist и давайте per-call опции само там, където източникът на формулата е ваш. Пълният callback, entry-policy и evaluation API е документиран при HotXLS Delphi spreadsheet компонента