HotXLS từ chối điều 20 tên công thức nguy hiểm, trong đó có CALL, REGISTER.ID, WEBSERVICE và DDE, vào các callback user-function Delphi của bạn trừ khi bạn chủ động bật. Thuộc tính workbook AllowUnsafeFormulaCallbacks mặc định là False, phép kiểm tra chạy trước khi bất kỳ đối số nào được đánh giá, và một lời gọi bị từ chối báo xlfeUnsafeFunctionDenied mà không gọi một handler nào
Tình huống buộc tính năng này ra đời thì tầm thường thôi. Một service nhận các file XLS hay XLSX được upload, recalc chúng phía server và đọc lại vài tổng số. Ứng dụng host đăng ký một handler OnUserFunction từ nhiều năm trước cho vài hàm nghiệp vụ, và đâu đó trên đường đi handler đó mọc ra một nhánh catch-all chuyển tiếp mọi thứ nó không nhận diện sang một bảng plugin. Chẳng ai trong team từng gõ =WEBSERVICE(...) vào một ô. Người upload thì có. Giữ nguyên công thức đó qua open, recalc và save là một tính năng trung thực file. Để nó chạm tới host code có thể mở socket hay file là một quyết định ủy quyền, và trước khi HotXLS tách hai chuyện đó ra, thư viện đang lặng lẽ quyết định thay bạn
Vì sao việc bảo toàn một công thức lại biến thành quyền chạy nó?
Nguyên nhân gốc là một đường fallback duy nhất. HotXLS parse mọi tên hàm Excel nó biết, nhưng không phải tên đã biết nào cũng có một bản cài đặt trong engine tính toán. Các built-in được nhận diện nhưng chưa cài đặt trước đây rơi vào cùng fallback user-defined function với các tên thực sự tùy chỉnh, nên CALL và REGISTER.ID dùng chung đường dispatch với DISCOUNT hay REGIONRATE của bạn. Các tên lạ như WEBSERVICE hay DDE cũng có thể khớp một entry trùng tên trong registry của workbook, registry toàn process hay một event handler. Cơ chế của fallback đó được trình bày trong cách HotXLS phân giải custom function qua OnUserFunction; vấn đề là chẳng có gì trên đường đó hỏi xem bản thân cái tên có phải thứ một host bình thường nên thực thi hay không
Thứ tự dispatch quyết định “unknown” ở đây nghĩa là gì. Một lời gọi mà engine không đánh giá nổi bằng native lần lượt được dâng cho các binding từ vựng LAMBDA và LET, thứ mà phần hỗ trợ closure trong formula engine HotXLS phân giải trước, rồi tới các hàm cấp workbook đăng ký bằng RegisterUserFunction, rồi tới các hàm toàn process từ TXLSWorkbook.RegisterGlobalUserFunction, và cuối cùng là các sự kiện OnUserFunction và OnUserFunctionEx. Chỉ khi tất cả từ chối thì một hàm thực sự lạ mới thành #NAME?. Mỗi chặng sau bước tra lambda đều trao quyền điều khiển cho code bạn viết, đó chính là lý do phép kiểm an toàn phải ngồi trước cả chuỗi thay vì nằm trong bất kỳ handler nào
HotXLS chặn mặc định những tên hàm nào?
XLSFormulaCallbackIsUnsafe trong lxCalc.pas giữ một tập deny cố định 20 tên: 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 và FILE.DELETE. Đó là những tên mà, trong Excel hay ngôn ngữ macro của nó, nạp code native, chạm tới mạng, nói chuyện với tiến trình khác hay đụng vào file system. Trước khi so sánh, hàm cắt khoảng trắng hai đầu, viết hoa tên và gỡ một tiền tố _XLFN. hay _XLWS. duy nhất, nên _xlfn.webservice do một bản build Excel mới hơn ghi ra cũng bị tóm y như cách viết trần. Danh sách nằm ở biên giới calculator thay vì trong các parser Classic, XLSX và ODS, để một AST, một token stream BIFF và một workbook chuyển đổi hành xử như nhau
Hai cạnh góc đáng biết trước khi bạn dựa vào nó. Phép khớp là chính xác, nên một handler bạn đăng ký tên MYWEBSERVICE không bị ảnh hưởng, và ngược lại một UDF nội bộ chính đáng tình cờ mang tên OPEN hay RUN giờ bị từ chối theo mặc định. Tập deny cũng không phải một sandbox cho các handler của bạn. Nếu nhánh catch-all của bạn thực thi tên plugin tùy ý, cánh cổng chỉ chặn mấy tên nguy hiểm nổi tiếng và chẳng hơn; thứ chữa tận gốc vẫn là một handler chỉ khớp một allowlist tường minh bằng SameText và để Handled ở False với mọi thứ nó không sở hữu
Vì sao cánh cổng phải chạy trước khi đánh giá đối số?
Một cánh cổng chỉ kịp đóng sau khi đối số được tính thì đã muộn, vì bản thân các đối số có thể gọi code của bạn. GetValueItemUserFunction kiểm tên trước và thoát với lxErrorUnsafeFunctionDenied trước khi dựng mảng đối số, trước khi hỏi một resolver hay bất kỳ registry nào, và thậm chí trước khi nhận ra chẳng có handler nào được gán. Chính thứ tự đó hạ gục ca lồng nhau bên dưới, nơi lời gọi ngoài dù sao cũng bị từ chối nhưng một UDF bên trong trông vô hại lẽ ra đã kích trước và để lại side effect của nó
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 effect trong host code
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, và FAuditLog vẫn rỗng
Mặc định workbook so với TXLSFormulaEvaluationOptions từng lần gọi
Cờ workbook là mặc định và option từng lần gọi là lời nói cuối. TXLSWorkbook.AllowUnsafeFormulaCallbacks và TXLSXWorkbook.AllowUnsafeFormulaCallbacks điều hành recalc thường, Calculate, EvaluateFormulaAt hai đối số, evaluation template, view chỉ đọc và, trên XLSX, mọi worker trong pool recalc song song. Bất kỳ entry point nào nhận một record TXLSFormulaEvaluationOptions tường minh đều lấy Options.AllowUnsafeFormulaCallbacks làm phán quyết cho lời gọi đó và không OR nó với thuộc tính workbook. Sự bất đối xứng đó là chủ ý: một job nội bộ đáng tin có thể ủy quyền một phép tra RTD mà không lật cả workbook, và một workbook đã opt in toàn cục vẫn có thể ép một phép đánh giá nhạy cảm về deny
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// workbook vẫn bị khóa chặt, một lời gọi đáng tin được cho qua
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// workbook đã opt in, nhưng lần đánh giá text được upload này thì không
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // cờ lại là False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Bật tắt thuộc tính workbook còn đánh dấu dependency graph dirty trên cả hai engine. Thiếu bước đó, một kết quả được cache tính trong lúc callback còn được phép có thể được phục vụ sau khi chúng bị thu hồi, hay một kết quả xlfeUnsafeFunctionDenied đã cache có thể sống dai hơn một lần opt in. Trạng thái mới được nối vào TXLSFormulaEvaluationStatus sau xlfeFailed, nên nó có ordinal 10 và mọi ordinal hiện có giữ nguyên giá trị; luật nối đuôi tương tự áp cho trường record option cùng getter và setter của IXLSWorkbook, dù một consumer dựng trên bản phát hành cũ vẫn cần biên dịch lại
Chuyện gì xảy ra với text công thức lạ và không an toàn khi lưu?
Giữ một công thức và chạy nó giờ là hai câu hỏi tách rời, và chính sách entry chỉ trả lời câu đầu. FormulaEntryPolicy trên cả hai lớp workbook mang UnknownFunctionMode và UnknownNameMode, cùng mặc định xlfusmReject, nên việc gán một công thức chứa lời gọi lạ qua thuộc tính Formula thường bị từ chối trước khi giá trị ô, formula cache hay dependency đổi thay. ValidateFormulaEntry báo cùng quyết định đó mà không có side effect. Các đường đáng tin như nạp file, sao chép và chuyển đổi định dạng đi vòng qua chính sách entry của người dùng đó, vì một mặc định nghiêm ngặt không bao giờ được phép từ chối các ký hiệu đã có sẵn trong một file bạn chỉ đang mở
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // entry kiểu tương thích
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // được lưu, chưa được ủy quyền
Book.SaveAs('rates.xls');
end;
Trong BIFF8 cổ điển, một lời gọi lạ không có token riêng, nên HotXLS ghi nó theo cách Excel ghi các add-in function. Công thức nhận một token PtgNameX ($59) mà entry XTI của nó trỏ vào SUPBOOK add-in với cả hai chỉ mục sheet đặt $FFFE, theo sau là các token đối số và một PtgFuncVar mang số hàm 255 cùng một số đếm đối số có tính cả slot tên. Body ExternName đừng đầu là sáu byte 0, một byte chiều dài cùng cờ Unicode, tên hàm UTF-16, rồi một công thức hai byte $1C $17, một PtgErr giữ #REF!. Writer từ chối tên dài hơn 255 ký tự, quá 29 đối số, và đích BIFF5. HotXLS phân loại các entry SUPBOOK add-in này cạnh các liên kết workbook ngoài ra sao được giải thích trong các luật phân loại SUPBOOK và XTI cho liên kết ngoài BIFF. XLSX giữ nguyên text hàm thô và ODS giữ công thức msoxl: của nó, và với mọi định dạng, một file từng lưu =WEBSERVICE(...) mở lại với text nguyên vẹn và vẫn đánh giá ra xlfeUnsafeFunctionDenied theo mặc định
Nếu pipeline của bạn đánh giá những workbook không phải nó soạn, hãy để AllowUnsafeFormulaCallbacks ở False, giữ các handler trên một allowlist tường minh, và cấp option từng lần gọi chỉ ở nơi nguồn công thức là của bạn. Toàn bộ API callback, entry policy và đánh giá được tài liệu hóa cùng HotXLS Delphi spreadsheet component