Το HotXLS αρνείται να δρομολογήσει 20 επικίνδυνα ονόματα τύπων, μεταξύ των οποίων τα CALL, REGISTER.ID, WEBSERVICE και DDE, στα callbacks συναρτήσεων χρήστη του Delphi σας, εκτός αν το ζητήσετε ρητά. Η ιδιότητα AllowUnsafeFormulaCallbacks του workbook έχει προεπιλογή το False, ο έλεγχος τρέχει πριν αξιολογηθεί οποιοδήποτε όρισμα, και μια απορριφθείσα κλήση αναφέρει xlfeUnsafeFunctionDenied χωρίς να καλέσει ούτε έναν handler
Το σενάριο που το έκανε αναγκαίο είναι πεζό. Μια υπηρεσία δέχεται ανεβασμένα αρχεία XLS ή XLSX, τα επανυπολογίζει server-side και διαβάζει λίγα σύνολα πίσω. Η host εφαρμογή είχε κατοχυρώσει έναν handler OnUserFunction χρόνια πριν για μερικές επιχειρησιακές συναρτήσεις, και κάπου στην πορεία εκείνος ο handler απέκτησε έναν κλάδο catch-all που προωθεί ό,τι δεν αναγνωρίζει σε έναν πίνακα plugin. Κανείς στην ομάδα δεν πληκτρολόγησε ποτέ =WEBSERVICE(...) σε ένα κελί. Το αρχείο που ανέβηκε, όμως, ναι. Το να κρατηθεί ο τύπος ανέπαφος μέσα από open, recalc και save είναι ένα feature πιστότητας αρχείου. Το να φτάσει σε κώδικα host που μπορεί να ανοίξει sockets ή αρχεία είναι μια απόφαση εξουσιοδότησης, και μέχρι το HotXLS να χωρίσει τα δύο, η βιβλιοθήκη την έπαιρνε σιωπηλά για λογαριασμό σας
Γιατί η διατήρηση ενός τύπου μετατράπηκε σε άδεια να τον τρέξεις;
Η ρίζα ήταν ένα μοναδικό μονοπάτι fallback. Το HotXLS κάνει parse κάθε όνομα συνάρτησης Excel που γνωρίζει, αλλά δεν έχει κάθε γνωστό όνομα υλοποίηση στη μηχανή υπολογισμού. Τα built-in που αναγνωρίζονται αλλά δεν υλοποιούνται κατέληγαν στο ίδιο fallback συναρτήσεων χρήστη με τα πραγματικά custom ονόματα, οπότε τα CALL και REGISTER.ID μοιράζονταν μονοπάτι δρομολόγησης με τα δικά σας DISCOUNT ή REGIONRATE. Άγνωστα ονόματα όπως WEBSERVICE ή DDE μπορούσαν εξίσου να ταιριάξουν μια ομώνυμη εγγραφή στο registry του workbook, στο registry της διεργασίας ή σε έναν event handler. Η μηχανική εκείνου του fallback καλύπτεται στο πώς το HotXLS επιλύει custom συναρτήσεις μέσω OnUserFunction· το πρόβλημα ήταν ότι τίποτα σε εκείνο το μονοπάτι δεν ρωτούσε αν το ίδιο το όνομα ήταν από αυτά που ένα υγιές host θα έπρεπε ποτέ να εκτελέσει
Η σειρά δρομολόγησης μετράει για το τι σημαίνει «άγνωστο» εδώ. Μια κλήση που η μηχανή δεν μπορεί να αξιολογήσει εγγενώς προσφέρεται με τη σειρά σε λεξικά bindings LAMBDA και LET, που επιλύει πρώτα η υποστήριξη closures στη μηχανή τύπων του HotXLS, μετά σε συναρτήσεις τοπικές στο workbook καταχωρισμένες με RegisterUserFunction, μετά σε συναρτήσεις ολόκληρης της διεργασίας από το TXLSWorkbook.RegisterGlobalUserFunction, και τέλος στα events OnUserFunction και OnUserFunctionEx. Μόνο όταν όλα αυτά αρνηθούν γίνεται μια πραγματικά άγνωστη συνάρτηση #NAME?. Κάθε στάδιο μετά την αναζήτηση lambda παραχωρεί τον έλεγχο σε κώδικα που γράψατε εσείς, και αυτό είναι ακριβώς ο λόγος που ο έλεγχος ασφαλείας πρέπει να κάθεται μπροστά από ολόκληρη την αλυσίδα αντί μέσα σε οποιονδήποτε handler
Ποια ονόματα συναρτήσεων μπλοκάρει το HotXLS εξ ορισμού;
Το XLSFormulaCallbackIsUnsafe στο lxCalc.pas κρατάει ένα σταθερό σύνολο άρνησης 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 ή στη μακρογλώσσα του, φορτώνουν native κώδικα, φτάνουν στο δίκτυο, μιλάνε σε άλλες διεργασίες ή αγγίζουν το σύστημα αρχείων. Πριν τη σύγκριση η συνάρτηση κόβει τα κενά γύρω-γύρω, κάνει το όνομα κεφαλαία και αφαιρεί ένα μόνο πρόθεμα _XLFN. ή _XLWS., ώστε το _xlfn.webservice γραμμένο από νεότερο build του Excel να πιάνεται όπως η σκέτη γραφή. Η λίστα ζει στο όριο της αριθμομηχανής αντί στους parsers Classic, XLSX και ODS, που κρατάει ένα AST, ένα ρεύμα tokens BIFF και ένα μετατραπέν workbook να συμπεριφέρονται πανομοιότυπα
Δύο ακραίες περιπτώσεις αξίζει να ξέρετε πριν βασιστείτε σε αυτό. Η αντιστοίχιση είναι ακριβής, οπότε ένας handler που καταχωρίζετε ως MYWEBSERVICE δεν επηρεάζεται, και αντίστροφα μια νόμιμη εσωτερική UDF που τυχαίνει να λέγεται OPEN ή RUN απορρίπτεται πλέον εξ ορισμού. Το σύνολο άρνησης δεν είναι ούτε sandbox για τους δικούς σας handlers. Αν ο κλάδος catch-all σας εκτελεί αυθαίρετα ονόματα plugin, το φράγμα σταματά τις διάσημες επικίνδυνες και τίποτα άλλο· η ανθεκτική διόρθωση εξακολουθεί να είναι ένας handler που ταυτίζει με ρητή allowlist μέσω SameText και αφήνει το Handled σε False για ό,τι δεν κατέχει
Γιατί το φράγμα πρέπει να τρέχει πριν την αξιολόγηση των ορισμάτων;
Ένα φράγμα που πυροδοτείται αφού υπολογιστούν τα ορίσματα έρχεται αργά, επειδή τα ίδια τα ορίσματα μπορούν να καλέσουν τον κώδικά σας. Το GetValueItemUserFunction ελέγχει πρώτα το όνομα και εξέρχεται με lxErrorUnsafeFunctionDenied πριν χτίσει το array ορισμάτων, πριν συμβουλευτεί resolver ή όποιο registry, και ακόμη πριν προσέξει ότι δεν έχει ανατεθεί καθόλου handler. Εκείνη η σειρά είναι που νικά την εμφωλευμένη περίπτωση παρακάτω, όπου η εξωτερική κλήση θα απορρίπτονταν ούτως ή άλλως, αλλά μια αθώα στην όψη εσωτερική UDF θα πυροδοτούνταν πρώτη και θα άφηνε πίσω της την παρενέργειά της
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'); // παρενέργεια σε κώδικα 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 απέναντι σε TXLSFormulaEvaluationOptions ανά κλήση
Η σημαία του workbook είναι η προεπιλογή και η επιλογή ανά κλήση έχει τον τελευταίο λόγο. Τα TXLSWorkbook.AllowUnsafeFormulaCallbacks και TXLSXWorkbook.AllowUnsafeFormulaCallbacks κυβερνούν τον συνηθισμένο επανυπολογισμό, το Calculate, το EvaluateFormulaAt με τα δύο ορίσματα, τα πρότυπα αξιολόγησης, τις προβολές μόνο για ανάγνωση και, σε XLSX, κάθε worker στη παράλληλη δεξαμενή επανυπολογισμού. Κάθε σημείο εισόδου που δέχεται ρητό record TXLSFormulaEvaluationOptions παίρνει το Options.AllowUnsafeFormulaCallbacks ως ετυμηγορία για εκείνη την κλήση και δεν το συνδυάζει με OR με την ιδιότητα του workbook. Εκείνη η ασυμμετρία είναι σκόπιμη: μια αξιόπιστη εσωτερική εργασία μπορεί να εξουσιοδοτήσει μία αναζήτηση RTD χωρίς να αναποδογυρίσει όλο το workbook, και ένα workbook που έχει δεχτεί συνολικά μπορεί εξακολουθεί να αναγκάζει μια ευαίσθητη αξιολόγηση πίσω σε άρνηση
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// το workbook μένει κλειδωμένο, μία αξιόπιστη κλήση περνά
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// το workbook έχει δεχτεί, αλλά αυτή η αξιολόγηση ανεβασμένου κειμένου όχι
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 σημειώνει επίσης τον γράφο εξαρτήσεων ως βρώμικο και στις δύο μηχανές. Χωρίς εκείνο το βήμα ένα cached αποτέλεσμα υπολογισμένο όσο επιτρέπονταν τα callbacks θα μπορούσε να σερβιριστεί αφού ανακλήθηκαν, ή ένα cached εκβιβασμένο xlfeUnsafeFunctionDenied θα μπορούσε να επιβιώσει από μια συνολική αποδοχή. Η νέα κατάσταση προσαρτήθηκε στο TXLSFormulaEvaluationStatus μετά το xlfeFailed, οπότε έχει ordinal 10 και κάθε υπάρχων ordinal κρατάει την τιμή του· ο ίδιος κανόνας προσάρτησης στο τέλος ισχύει για το πεδίο του record επιλογών και για τα getter και setter του IXLSWorkbook, αν και ένας καταναλωτής χτισμένος πάνω σε παλαιότερη έκδοση εξακολουθεί να χρειάζεται επαναμεταγλώττιση
Τι γίνεται με άγνωστο και ανασφαλή κείμενο τύπου στο save;
Το να διατηρηθεί ένας τύπος και το να τρέξει είναι πλέον δύο ξεχωριστά ερωτήματα, και η πολιτική εισόδου απαντά μόνο στο πρώτο. Το FormulaEntryPolicy σε όποια κλάση workbook κουβαλάει UnknownFunctionMode και UnknownNameMode, και τα δύο με προεπιλογή xlfusmReject, ώστε η ανάθεση τύπου με άγνωστη κλήση μέσω της συνηθισμένης ιδιότητας Formula απορρίπτεται πριν αλλάξει η τιμή του κελιού, η cache τύπων ή οι εξαρτήσεις. Το ValidateFormulaEntry αναφέρει την ίδια απόφαση χωρίς παρενέργειες. Οι αξιόπιστες διαδρομές όπως το φόρτωμα αρχείου, η αντιγραφή και η μετατροπή μορφής παρακάμπτουν εκείνη την πολιτική εισόδου χρήστη, επειδή μια αυστηρή προεπιλογή δεν επιτρέπεται να απορρίψει ποτέ σύμβολα που υπάρχουν ήδη σε ένα αρχείο που απλώς ανοίγετε
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;
Στο κλασικό BIFF8 μια άγνωστη κλήση δεν έχει token δικό της, οπότε το HotXLS τη γράφει όπως το Excel γράφει τις συναρτήσεις add-in. Ο τύπος παίρνει ένα token PtgNameX ($59) του οποίου η εγγραφή XTI δείχνει στο SUPBOOK add-in με τους δύο δείκτες sheet τεθειμένους σε $FFFE, ακολουθούμενο από τα tokens ορισμάτων και ένα PtgFuncVar με αριθμό συνάρτησης 255 και πλήθος ορισμάτων που περιλαμβάνει τη θέση του ονόματος. Το body του υποστηρικτικού ExternName είναι έξι μηδενικά bytes, ένα byte μήκους και σημαία Unicode, το όνομα συνάρτησης σε UTF-16, και μετά ένας τύπος δύο bytes $1C $17, ένα PtgErr που κρατάει #REF!. Ο writer αρνείται ονόματα πάνω από 255 χαρακτήρες, πάνω από 29 ορίσματα, και τον προορισμό BIFF5. Πώς ταξινομεί το HotXLS αυτές τις εγγραφές SUPBOOK add-in δίπλα στους εξωτερικούς συνδέσμους workbook εξηγείται στους κανόνες ταξινόμησης SUPBOOK και XTI για εξωτερικούς συνδέσμους BIFF. Το XLSX κρατάει το ακατέργαστο κείμενο συνάρτησης και το ODS τον τύπο msoxl: του, και σε κάθε μορφή ένα αρχείο που αποθήκευσε =WEBSERVICE(...) ξανανοίγει με το κείμενο ανέπαφο και εξακολουθεί να αξιολογείται σε xlfeUnsafeFunctionDenied εξ ορισμού
Αν ο σωλήνας σας αξιολογεί workbooks που δεν έγραψε ο ίδιος, αφήστε το AllowUnsafeFormulaCallbacks σε False, κρατήστε τους handlers σε ρητή allowlist, και χορηγήστε επιλογές ανά κλήση μόνο όπου η πηγή των τύπων είναι δική σας. Το πλήρες API callbacks, πολιτικής εισόδου και αξιολόγησης τεκμηριώνεται με το HotXLS Delphi spreadsheet component