HotXLS refuse d'acheminer 20 noms de formules dangereux, dont CALL, REGISTER.ID, WEBSERVICE et DDE, vers vos callbacks de fonctions utilisateur Delphi, sauf si vous l'acceptez explicitement. La propriété de classeur AllowUnsafeFormulaCallbacks vaut False par défaut, le contrôle s'exécute avant toute évaluation d'argument, et un appel refusé signale xlfeUnsafeFunctionDenied sans invoquer un seul handler
Le scénario qui a rendu cela nécessaire est banal. Un service accepte des fichiers XLS ou XLSX téléversés, les recalcule côté serveur et relit quelques totaux. L'application hôte avait enregistré un handler OnUserFunction des années plus tôt pour quelques fonctions métier, et quelque part en chemin ce handler s'était doté d'une branche fourre-tout qui transmet à une table de plugins tout ce qu'il ne reconnaît pas. Personne dans l'équipe n'a jamais tapé =WEBSERVICE(...) dans une cellule. Le téléversement, si. Préserver cette formule à travers ouverture, recalcul et sauvegarde est une fonctionnalité de fidélité de fichier. La laisser atteindre du code hôte capable d'ouvrir des sockets ou des fichiers est une décision d'autorisation, et tant que HotXLS n'avait pas séparé les deux, la bibliothèque prenait discrètement cette décision à votre place
Pourquoi préserver une formule est-il devenu la permission de l'exécuter ?
La cause racine était un unique chemin de repli. HotXLS analyse chaque nom de fonction Excel qu'il connaît, mais tout nom connu n'a pas d'implémentation dans le moteur de calcul. Les fonctions intégrées reconnues mais non implémentées tombaient auparavant dans le même repli de fonctions définies par l'utilisateur que les noms réellement personnalisés, si bien que CALL et REGISTER.ID partageaient un chemin de dispatch avec votre DISCOUNT ou REGIONRATE. Les noms inconnus comme WEBSERVICE ou DDE pouvaient de même correspondre à une entrée homonyme dans le registre du classeur, le registre global du processus ou un handler d'événement. La mécanique de ce repli est couverte dans comment HotXLS résout les fonctions personnalisées via OnUserFunction ; le problème était que rien sur ce chemin ne demandait si le nom lui-même était du genre qu'un hôte sain devrait exécuter
L'ordre de dispatch compte pour savoir ce que le mot inconnu veut dire ici. Un appel que le moteur ne peut pas évaluer nativement est offert tour à tour aux liaisons lexicales LAMBDA et LET, que le support des closures dans le moteur de formules HotXLS résout en premier, puis aux fonctions locales au classeur enregistrées avec RegisterUserFunction, puis aux fonctions globales au processus venant de TXLSWorkbook.RegisterGlobalUserFunction, et enfin aux événements OnUserFunction et OnUserFunctionEx. Ce n'est que lorsque tous déclinent qu'une fonction vraiment inconnue devient #NAME?. Chaque étape après la recherche lambda remet le contrôle à du code que vous avez écrit, et c'est exactement pourquoi le contrôle de sécurité doit se placer devant toute la chaîne plutôt qu'à l'intérieur d'un handler quelconque
Quels noms de fonctions HotXLS bloque-t-il par défaut ?
XLSFormulaCallbackIsUnsafe dans lxCalc.pas porte un ensemble de refus fixe de 20 noms : 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 et FILE.DELETE. Ce sont les noms qui, dans Excel ou son langage macro, chargent du code natif, atteignent le réseau, parlent à d'autres processus ou touchent au système de fichiers. Avant comparaison, la fonction retire les espaces environnants, met le nom en majuscules et retire un préfixe unique _XLFN. ou _XLWS., si bien que _xlfn.webservice écrit par une version plus récente d'Excel est attrapé comme l'orthographe nue. La liste vit à la frontière de la calculatrice plutôt que dans les analyseurs Classic, XLSX et ODS, ce qui garde un AST, un flux de jetons BIFF et un classeur converti au comportement identique
Deux bords méritent d'être connus avant de vous y fier. La correspondance est exacte, donc un handler que vous enregistrez sous MYWEBSERVICE n'est pas affecté, et réciproquement une UDF maison légitime qui s'appellerait OPEN ou RUN est désormais refusée par défaut. L'ensemble de refus n'est pas non plus un bac à sable pour vos propres handlers. Si votre branche fourre-tout exécute des noms de plugins arbitraires, le verrou arrête les plus célèbres des dangereux et rien d'autre ; le correctif durable reste un handler qui confronte une liste blanche explicite avec SameText et laisse Handled à False pour tout ce qu'il ne possède pas
Pourquoi le verrou doit-il passer avant l'évaluation des arguments ?
Un verrou qui se déclenche après le calcul des arguments arrive trop tard, parce que les arguments eux-mêmes peuvent appeler votre code. GetValueItemUserFunction contrôle le nom en premier et sort avec lxErrorUnsafeFunctionDenied avant de construire le tableau d'arguments, avant de consulter un résolveur ou l'un des registres, et même avant de remarquer qu'aucun handler n'est affecté du tout. Cet ordonnancement est ce qui défait le cas imbriqué ci-dessous, où l'appel extérieur serait de toute façon refusé mais où une UDF intérieure d'apparence anodine partirait sinon en premier et laisserait son effet de bord derrière elle
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'); // effet de bord dans le code hôte
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, et FAuditLog est toujours vide
Défaut du classeur contre TXLSFormulaEvaluationOptions par appel
Le drapeau du classeur est le défaut et l'option par appel est le dernier mot. TXLSWorkbook.AllowUnsafeFormulaCallbacks et TXLSXWorkbook.AllowUnsafeFormulaCallbacks gouvernent le recalcul ordinaire, Calculate, le EvaluateFormulaAt à deux arguments, les modèles d'évaluation, les vues en lecture seule et, sur XLSX, chaque worker du pool de recalcul parallèle. Tout point d'entrée qui accepte un enregistrement TXLSFormulaEvaluationOptions explicite prend Options.AllowUnsafeFormulaCallbacks comme verdict pour cet appel et ne le combine pas en OU avec la propriété du classeur. Cette asymétrie est délibérée : un traitement interne de confiance peut autoriser une recherche RTD isolée sans basculer tout le classeur, et un classeur globalement ouvert peut quand même forcer une évaluation sensible vers le refus
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// le classeur reste verrouillé, un seul appel de confiance passe
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// classeur ouvert, mais cette évaluation de texte téléversé ne l'est pas
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // le drapeau repasse à False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Basculer la propriété du classeur marque aussi le graphe de dépendances comme sale sur les deux moteurs. Sans cette étape, un résultat en cache calculé pendant que les callbacks étaient autorisés pourrait être servi après leur révocation, ou un résultat en cache xlfeUnsafeFunctionDenied pourrait survivre à une ouverture. Le nouveau statut a été ajouté en fin de TXLSFormulaEvaluationStatus après xlfeFailed, si bien qu'il a l'ordinal 10 et que chaque ordinal existant garde sa valeur ; la même règle d'ajout en queue vaut pour le champ de l'enregistrement d'options et pour le getter et le setter IXLSWorkbook, bien qu'un consommateur construit contre une version plus ancienne ait quand même besoin d'une recompilation
Que devient le texte des formules inconnues et à risque à la sauvegarde ?
Conserver une formule et l'exécuter sont désormais deux questions séparées, et la politique d'entrée ne répond qu'à la première. FormulaEntryPolicy sur chaque classe de classeur porte UnknownFunctionMode et UnknownNameMode, tous deux valant xlfusmReject par défaut, si bien qu'affecter une formule avec un appel inconnu via la propriété Formula normale est rejeté avant que la valeur de cellule, le cache de formules ou les dépendances ne changent. ValidateFormulaEntry rapporte la même décision sans effets de bord. Les chemins de confiance comme le chargement de fichiers, la copie et la conversion de format contournent cette politique d'entrée utilisateur, parce qu'un défaut strict ne doit jamais rejeter des symboles déjà présents dans un fichier que vous vous contentez d'ouvrir
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // entrée de compatibilité
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // stockée, pas autorisée
Book.SaveAs('rates.xls');
end;
En BIFF8 classique, un appel inconnu n'a pas de jeton propre, donc HotXLS l'écrit comme Excel écrit les fonctions de complément. La formule reçoit un jeton PtgNameX ($59) dont l'entrée XTI pointe vers le SUPBOOK de complément avec les deux index de feuilles à $FFFE, suivie des jetons d'arguments et d'un PtgFuncVar portant le numéro de fonction 255 et un compte d'arguments qui inclut l'emplacement du nom. Le corps ExternName sous-jacent est six octets nuls, un octet de longueur et un drapeau Unicode, le nom de fonction UTF-16, puis une formule de deux octets $1C $17, un PtgErr portant #REF!. L'écrivain refuse les noms de plus de 255 caractères, plus de 29 arguments, et la cible BIFF5. La manière dont HotXLS classe ces entrées SUPBOOK de complément à côté des liens externes vers des classeurs est expliquée dans les règles de classification SUPBOOK et XTI des liens externes BIFF. XLSX garde le texte brut de la fonction et ODS garde sa formule msoxl:, et dans chaque format un fichier qui a sauvegardé =WEBSERVICE(...) se rouvre avec le texte intact et s'évalue toujours en xlfeUnsafeFunctionDenied par défaut
Si votre pipeline évalue des classeurs qu'il n'a pas écrits, laissez AllowUnsafeFormulaCallbacks à False, gardez les handlers sur une liste blanche explicite, et n'accordez des options par appel que là où la source de la formule est la vôtre. L'API complète de callbacks, de politique d'entrée et d'évaluation est documentée avec le composant tableur HotXLS pour Delphi