O HotXLS se recusa a rotear 20 nomes de fórmulas perigosos, incluindo CALL, REGISTER.ID, WEBSERVICE e DDE, para os callbacks de funções do usuário do seu Delphi, a menos que você opte por isso. A propriedade AllowUnsafeFormulaCallbacks da pasta de trabalho tem False como padrão, a checagem roda antes de qualquer argumento ser avaliado, e uma chamada negada reporta xlfeUnsafeFunctionDenied sem invocar handler nenhum
O cenário que tornou isso necessário é banal. Um serviço aceita arquivos XLS ou XLSX enviados por upload, recalcula no servidor e lê alguns totais de volta. A aplicação host registrou um handler OnUserFunction anos atrás para um par de funções de negócio, e em algum momento esse handler ganhou um ramo catch-all que encaminha tudo que não reconhece para uma tabela de plugins. Ninguém do time jamais digitou =WEBSERVICE(...) numa célula. Quem digitou foi quem fez o upload. Manter essa fórmula intacta através de abertura, recálculo e save é um recurso de fidelidade de arquivo. Deixá-la alcançar código host que pode abrir sockets ou arquivos é uma decisão de autorização, e até o HotXLS separar as duas coisas, a biblioteca tomava essa decisão silenciosamente por você
Por que preservar uma fórmula virou permissão para executá-la?
A causa raiz era um único caminho de fallback. O HotXLS analisa todo nome de função do Excel que conhece, mas nem todo nome conhecido tem implementação no motor de cálculo. Built-ins reconhecidos mas não implementados costumavam cair no mesmo fallback de função definida pelo usuário que nomes genuinamente customizados, então CALL e REGISTER.ID compartilhavam um caminho de despacho com o seu DISCOUNT ou REGIONRATE. Nomes desconhecidos como WEBSERVICE ou DDE podiam do mesmo jeito casar com uma entrada de mesmo nome no registro da pasta de trabalho, no registro do processo ou num event handler. A mecânica desse fallback está coberta em como o HotXLS resolve funções customizadas por meio do OnUserFunction; o problema era que nada naquele caminho perguntava se o nome em si era um que um host são deveria executar algum dia
A ordem de despacho importa para o que "desconhecido" significa aqui. Uma chamada que o motor não consegue avaliar nativamente é oferecida, nesta ordem, a bindings lexicais de LAMBDA e LET, que o suporte a closures no motor de fórmulas do HotXLS resolve primeiro, depois a funções locais da pasta de trabalho registradas com RegisterUserFunction, depois a funções de processo vindas de TXLSWorkbook.RegisterGlobalUserFunction, e por fim aos eventos OnUserFunction e OnUserFunctionEx. Só quando todos declinam é que uma função verdadeiramente desconhecida vira #NAME?. Todo estágio depois da busca de lambda entrega o controle a código que você escreveu, e é exatamente por isso que a checagem de segurança precisa ficar na frente da cadeia inteira em vez de dentro de qualquer handler
Quais nomes de função o HotXLS bloqueia por padrão?
A XLSFormulaCallbackIsUnsafe em lxCalc.pas guarda um conjunto fixo de negação com 20 nomes: 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 e FILE.DELETE. São os nomes que, no Excel ou na linguagem de macro dele, carregam código nativo, alcançam a rede, conversam com outros processos ou tocam o sistema de arquivos. Antes da comparação a função corta espaços em branco nas pontas, converte o nome para maiúsculas e remove um único prefixo _XLFN. ou _XLWS., então um _xlfn.webservice escrito por uma build mais nova do Excel é pego igual à grafia nua. A lista vive na fronteira da calculadora em vez de nos parsers Classic, XLSX e ODS, o que mantém um AST, um stream de tokens BIFF e uma pasta de trabalho convertida se comportando de forma idêntica
Duas arestas valem conhecer antes de confiar nisso. O casamento é exato, então um handler que você registra como MYWEBSERVICE não é afetado, e inversamente uma UDF interna legítima que por acaso se chama OPEN ou RUN agora é negada por padrão. O conjunto de negação também não é uma sandbox para os seus próprios handlers. Se o seu ramo catch-all executa nomes de plugin arbitrários, o gate para os famosos perigosos e nada mais; a correção duradoura continua sendo um handler que casa com uma allowlist explícita usando SameText e deixa Handled em False para tudo que não é dele
Por que o gate precisa rodar antes da avaliação dos argumentos?
Um gate que dispara depois de os argumentos serem calculados é tarde demais, porque os próprios argumentos podem chamar o seu código. O GetValueItemUserFunction checa o nome primeiro e sai com lxErrorUnsafeFunctionDenied antes de montar o array de argumentos, antes de consultar um resolver ou qualquer um dos registros, e até antes de notar que nenhum handler está atribuído. Essa ordenação é o que derrota o caso aninhado abaixo, em que a chamada externa seria recusada de qualquer jeito, mas uma UDF interna de aparência inofensiva dispararia primeiro e deixaria o efeito colateral dela para trás
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'); // efeito colateral em código 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, e a FAuditLog continua vazia
Padrão da pasta de trabalho versus TXLSFormulaEvaluationOptions por chamada
A flag da pasta de trabalho é o padrão e a opção por chamada tem a palavra final. TXLSWorkbook.AllowUnsafeFormulaCallbacks e TXLSXWorkbook.AllowUnsafeFormulaCallbacks governam recálculo comum, Calculate, o EvaluateFormulaAt de dois argumentos, templates de avaliação, views somente leitura e, no XLSX, todo worker do pool de recálculo paralelo. Qualquer ponto de entrada que aceita um record TXLSFormulaEvaluationOptions explícito toma Options.AllowUnsafeFormulaCallbacks como o veredito daquela chamada e não faz OR com a propriedade da pasta de trabalho. Essa assimetria é deliberada: um job interno confiável pode autorizar um lookup RTD sem virar a pasta de trabalho inteira, e uma pasta de trabalho globalmente autorizada ainda pode forçar uma avaliação sensível de volta para negado
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// a pasta de trabalho segue travada, uma chamada confiável passa
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// a pasta de trabalho autorizou, mas esta avaliação de texto enviado não
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // a flag volta para False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Alternar a propriedade da pasta de trabalho também marca o grafo de dependências como sujo nos dois motores. Sem esse passo, um resultado em cache calculado enquanto os callbacks eram permitidos poderia ser servido depois de revogados, ou um resultado xlfeUnsafeFunctionDenied em cache poderia sobreviver a uma autorização. O novo status foi anexado ao TXLSFormulaEvaluationStatus depois de xlfeFailed, então tem ordinal 10 e todo ordinal existente mantém o valor; a mesma regra de anexar no fim vale para o campo do record de opções e para o getter e setter do IXLSWorkbook, embora um consumidor compilado contra uma versão antiga ainda precise recompilar
O que acontece com texto de fórmulas desconhecidas e inseguras no save?
Manter uma fórmula e executá-la agora são duas perguntas separadas, e a política de entrada só responde a primeira. A FormulaEntryPolicy em qualquer classe de pasta de trabalho carrega UnknownFunctionMode e UnknownNameMode, ambos com xlfusmReject como padrão, então atribuir uma fórmula com uma chamada desconhecida pela propriedade Formula normal é rejeitada antes de o valor da célula, o cache de fórmulas ou as dependências mudarem. O ValidateFormulaEntry reporta a mesma decisão sem efeitos colaterais. Caminhos confiáveis como carga de arquivo, cópia e conversão de formato contornam essa política de entrada do usuário, porque um padrão estrito nunca deve rejeitar símbolos já presentes num arquivo que você está apenas abrindo
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // entrada por compatibilidade
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // armazenada, não autorizada
Book.SaveAs('rates.xls');
end;
No Classic BIFF8 uma chamada desconhecida não tem token próprio, então o HotXLS a grava do jeito que o Excel grava funções de add-in. A fórmula recebe um token PtgNameX ($59) cuja entrada XTI aponta para o SUPBOOK de add-in com os dois índices de planilha em $FFFE, seguido dos tokens de argumento e um PtgFuncVar carregando número de função 255 e uma contagem de argumentos que inclui o slot de nome. O corpo ExternName de apoio são seis bytes zero, um byte de comprimento e flag Unicode, o nome da função em UTF-16, e então uma fórmula de dois bytes $1C $17, um PtgErr guardando #REF!. O writer recusa nomes com mais de 255 caracteres, mais de 29 argumentos, e o alvo BIFF5. Como o HotXLS classifica essas entradas de SUPBOOK de add-in ao lado de links externos de pasta de trabalho está explicado em as regras de classificação de SUPBOOK e XTI para links externos do BIFF. O XLSX mantém o texto cru da função e o ODS mantém sua fórmula msoxl:, e em todo formato um arquivo que salvou =WEBSERVICE(...) reabre com o texto intacto e ainda avalia para xlfeUnsafeFunctionDenied por padrão
Se o seu pipeline avalia pastas de trabalho que ele não escreveu, deixe AllowUnsafeFormulaCallbacks em False, mantenha handlers numa allowlist explícita, e conceda opções por chamada só onde a fonte da fórmula é sua. A API completa de callbacks, política de entrada e avaliação está documentada com o componente de planilha HotXLS para Delphi