Artikel Teknis

Callback Formula Tidak Aman HotXLS: CALL dan WEBSERVICE

HotXLS menolak mengarahkan 20 nama formula berbahaya — termasuk CALL, REGISTER.ID, WEBSERVICE, dan DDE — ke callback user-function Delphi Anda kecuali Anda ikut serta. Properti workbook AllowUnsafeFormulaCallbacks default-nya False, pemeriksaan berjalan sebelum argumen mana pun dievaluasi, dan panggilan yang ditolak melaporkan xlfeUnsafeFunctionDenied tanpa memanggil satu handler pun

Skenario yang memaksa fitur ini berbaham biasa saja. Sebuah layanan menerima berkas XLS atau XLSX unggahan, menghitung ulangnya di sisi server, dan membaca beberapa total kembali. Aplikasi host mendaftarkan handler OnUserFunction bertahun-tahun lalu untuk segelintir fungsi bisnis, dan di suatu titik handler itu tumbuh cabang catch-all yang meneruskan apa pun yang tidak ia kenali ke tabel plugin. Tak seorang pun di tim pernah mengetik =WEBSERVICE(...) ke dalam sel. Pengunggahnya iya. Menjaga formula itu tetap utuh melewati buka, recalc, dan simpan adalah fitur kesetiaan berkas. Membiarkannya mencapai kode host yang bisa membuka socket atau berkas adalah keputusan otorisasi, dan selama HotXLS belum memisahkan keduanya, library-lah yang diam-diam mengambil keputusan itu atas nama Anda

Mengapa mempertahankan formula berubah jadi izin untuk menjalankannya?

Akar masalahnya satu jalur fallback tunggal. HotXLS mengurai setiap nama fungsi Excel yang dikenalnya, tapi tidak semua nama yang dikenal punya implementasi di engine kalkulasi. Built-in yang dikenali tapi tak terimplementasi dulu jatuh ke fallback user-defined function yang sama dengan nama benar-benar kustom, sehingga CALL dan REGISTER.ID berbagi jalur dispatch dengan DISCOUNT atau REGIONRATE Anda. Nama tak dikenal seperti WEBSERVICE atau DDE juga bisa saja cocok dengan entri bernama sama di registry workbook, registry selebar proses, atau event handler. Mekanika fallback itu dibahas di cara HotXLS me-resolve fungsi kustom lewat OnUserFunction; masalahnya, tak ada apa pun di jalur itu yang bertanya apakah nama itu sendiri termasuk yang layak dieksekusi host waras

Urutan dispatch menentukan arti "tak dikenal" di sini. Panggilan yang tak mampu dievaluasi native oleh engine ditawarkan bergiliran ke binding leksikal LAMBDA dan LET — yang di-resolve lebih dulu oleh dukungan closure di engine formula HotXLS — lalu ke fungsi lokal workbook yang didaftarkan dengan RegisterUserFunction, lalu ke fungsi selebar proses dari TXLSWorkbook.RegisterGlobalUserFunction, dan akhirnya ke event OnUserFunction dan OnUserFunctionEx. Hanya ketika semuanya menolak, fungsi yang benar-benar tak dikenal menjadi #NAME?. Setiap tahap setelah lookup lambda menyerahkan kendali ke kode yang Anda tulis, dan justru itulah alasan pemeriksaan keamanan harus duduk di depan seluruh rantai, bukan di dalam salah satu handler

Cara HotXLS menjaga callback formula berbahaya: GetValueItemUserFunction memeriksa nama sebelum array argumen dibangun atau resolver mana pun berjalan, sehingga =WEBSERVICE(AUDIT_TOKEN()) bersarang keluar dengan lxErrorUnsafeFunctionDenied dan log audit tetap kosong, sementara panggilan aman berjalan menyusuri rantai dari binding LAMBDA dan LET turun ke OnUserFunction
Hanya ketika setiap tahap menolak, fungsi yang benar-benar tak dikenal menjadi #NAME?, dan itulah mengapa pemeriksaan keamanan duduk di depan seluruh rantai alih-alih di dalam salah satu handler

Nama fungsi apa saja yang diblokir HotXLS secara default?

XLSFormulaCallbackIsUnsafe di lxCalc.pas menampung deny set tetap berisi 20 nama: 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, dan FILE.DELETE. Itu semua adalah nama yang, di Excel atau bahasa makro-nya, memuat kode native, menjangkau jaringan, berbicara dengan proses lain, atau menyentuh file system. Sebelum perbandingan, fungsi ini memangkas whitespace di sekelilingnya, mengkapitalkan nama, dan melepas satu prefiks _XLFN. atau _XLWS., sehingga _xlfn.webservice yang ditulis build Excel lebih baru tertangkap persis seperti ejaan polosnya. Daftar ini tinggal di boundary calculator alih-alih di parser Classic, XLSX, dan ODS, sehingga satu AST, satu token stream BIFF, dan satu workbook hasil konversi berperilaku identik

Cara HotXLS menormalisasi nama fungsi sebelum perbandingan unsafe-callback: whitespace dipangkas, nama dikapitalkan, dan satu prefiks _XLFN. atau _XLWS. dilepas sehingga _xlfn.webservice tertangkap seperti ejaan polosnya, lalu hasilnya dicocokkan persis terhadap deny set tetap berisi 20 nama di lxCalc.pas
Daftarnya mencakup nama-nama yang memuat kode native, menjangkau jaringan, berbicara dengan proses lain, atau menyentuh file system, dari DDE, CALL, dan WEBSERVICE sampai FWRITE dan FILE.DELETE

Dua pinggiran layak diketahui sebelum Anda mengandalkannya. Cocoknya persis, jadi handler yang Anda daftarkan sebagai MYWEBSERVICE tidak terpengaruh, dan sebaliknya UDF internal resmi yang kebetulan bernama OPEN atau RUN kini ditolak secara default. Deny set ini juga bukan sandbox untuk handler Anda sendiri. Kalau cabang catch-all Anda mengeksekusi nama plugin apa pun, gerbang ini hanya menghentikan yang berbahaya dan terkenal itu saja; perbaikan yang tahan lama tetaplah handler yang mencocokkan allowlist eksplisit dengan SameText dan membiarkan Handled tetap False untuk semua yang bukan miliknya

Mengapa gerbang harus berjalan sebelum evaluasi argumen?

Gerbang yang menyala setelah argumen selesai dihitung sudah terlambat, karena argumennya sendiri bisa memanggil kode Anda. GetValueItemUserFunction memeriksa nama lebih dulu dan keluar dengan lxErrorUnsafeFunctionDenied sebelum membangun array argumen, sebelum berkonsultasi ke resolver atau registry mana pun, dan bahkan sebelum menyadari bahwa tidak ada handler yang terpasang sama sekali. Urutan itulah yang mengalahkan kasus bersarang di bawah, di mana panggilan luar memang akan ditolak juga, tapi UDF dalam yang tampak tak berbahaya bakal menyala lebih dulu dan meninggalkan side effect-nya

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 di kode 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, dan FAuditLog masih kosong

Default workbook versus TXLSFormulaEvaluationOptions per-panggilan

Flag workbook adalah default dan opsi per-panggilan adalah kata final. TXLSWorkbook.AllowUnsafeFormulaCallbacks dan TXLSXWorkbook.AllowUnsafeFormulaCallbacks mengatur recalculation biasa, Calculate, EvaluateFormulaAt dua argumen, template evaluasi, view read-only, dan di XLSX, setiap worker di pool recalculation paralel. Entry point mana pun yang menerima record TXLSFormulaEvaluationOptions eksplisit mengambil Options.AllowUnsafeFormulaCallbacks sebagai vonis untuk panggilan itu dan tidak meng-OR-nya dengan properti workbook. Asimetri ini disengaja: job internal terpercaya bisa mengotorisasi satu lookup RTD tanpa membalik seluruh workbook, dan workbook yang globally ikut serta tetap bisa memaksa satu evaluasi sensitif kembali ke tolak

var
  Options: TXLSFormulaEvaluationOptions;
  Eval: TXLSFormulaEvaluationResult;
begin
  // workbook tetap terkunci, satu panggilan terpercaya diizinkan lewat
  Book.AllowUnsafeFormulaCallbacks := False;
  Options := XLSDefaultFormulaEvaluationOptions;
  Options.AllowUnsafeFormulaCallbacks := True;
  Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);

  // workbook ikut serta, tapi evaluasi teks unggahan ini tidak
  Book.AllowUnsafeFormulaCallbacks := True;
  Options := XLSDefaultFormulaEvaluationOptions;   // flag kembali False
  Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
  if Eval.Status = xlfeUnsafeFunctionDenied then
    LogRejected(Eval.Issue.Message);
end;

Membolak-balikkan properti workbook juga menandai graf dependensi dirty di kedua engine. Tanpa langkah itu, hasil cache yang dihitung saat callback masih diizinkan bisa tersaji setelah izinnya dicabut, atau hasil cache xlfeUnsafeFunctionDenied bisa bertahan lebih lama dari opt-in. Status baru ditambahkan ke TXLSFormulaEvaluationStatus setelah xlfeFailed, sehingga ordinalnya 10 dan setiap ordinal yang ada mempertahankan nilainya; aturan tambah-di-ekor yang sama berlaku untuk field record opsi serta getter dan setter IXLSWorkbook, meski consumer yang dibangun terhadap rilis lama tetap butuh recompile

Apa yang terjadi pada teks formula tak dikenal dan berbahaya saat disimpan?

Mempertahankan formula dan menjalankannya kini dua pertanyaan terpisah, dan kebijakan entri hanya menjawab yang pertama. FormulaEntryPolicy di kedua kelas workbook membawa UnknownFunctionMode dan UnknownNameMode, keduanya default xlfusmReject, sehingga pemberian formula berisi panggilan tak dikenal lewat properti Formula normal ditolak sebelum nilai sel, cache formula, atau dependensi berubah. ValidateFormulaEntry melaporkan keputusan yang sama tanpa side effect. Jalur terpercaya seperti pemuatan berkas, penyalinan, dan konversi format melewati kebijakan entri-pengguna itu, karena default yang ketat tidak boleh menolak simbol yang sudah ada di berkas yang Anda sekadar buka

var
  Policy: TXLSFormulaEntryPolicy;
begin
  Policy := Book.FormulaEntryPolicy;
  Policy.UnknownFunctionMode := xlfusmPreserve;   // entri demi kompatibilitas
  Book.FormulaEntryPolicy := Policy;
  Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)';  // tersimpan, tidak terotorisasi
  Book.SaveAs('rates.xls');
end;

Di BIFF8 Classic, panggilan tak dikenal tidak punya token sendiri, jadi HotXLS menulisnya seperti Excel menulis fungsi add-in. Formula mendapat token PtgNameX ($59) yang entri XTI-nya menunjuk ke SUPBOOK add-in dengan kedua indeks sheet diset $FFFE, diikuti token argumen dan PtgFuncVar yang membawa nomor fungsi 255 dan jumlah argumen yang termasuk slot nama. Body ExternName di baliknya berupa enam byte nol, satu byte panjang dan flag Unicode, nama fungsi UTF-16, lalu formula dua byte $1C $17, sebuah PtgErr yang menampung #REF!. Writer menolak nama lebih panjang dari 255 karakter, argumen lebih dari 29, dan target BIFF5. Cara HotXLS mengklasifikasikan entri SUPBOOK add-in ini di samping tautan workbook eksternal dijelaskan di aturan klasifikasi SUPBOOK dan XTI untuk tautan eksternal BIFF. XLSX mempertahankan teks fungsi mentah dan ODS mempertahankan formula msoxl:-nya, dan di semua format, berkas yang menyimpan =WEBSERVICE(...) terbuka kembali dengan teks utuh dan tetap terevaluasi ke xlfeUnsafeFunctionDenied secara default

Cara HotXLS menulis panggilan formula tak dikenal ke BIFF8 klasik: formula membawa token PtgNameX yang entri XTI-nya menunjuk ke SUPBOOK add-in dengan kedua indeks sheet $FFFE, lalu token argumen dan PtgFuncVar dengan nomor fungsi 255, ditopang body ExternName yang berakhir pada PtgErr dua byte $1C $17 yang menampung #REF!
XLSX mempertahankan teks fungsi mentah dan ODS mempertahankan formula msoxl:-nya, sehingga berkas yang menyimpan =WEBSERVICE(...) terbuka kembali dengan teks utuh dan tetap terevaluasi ke xlfeUnsafeFunctionDenied secara default

Kalau pipeline Anda mengevaluasi workbook yang bukan buatannya sendiri, biarkan AllowUnsafeFormulaCallbacks di False, jaga handler pada allowlist eksplisit, dan berikan opsi per-panggilan hanya di tempat sumber formula adalah milik Anda. API callback, kebijakan entri, dan evaluasi lengkap terdokumentasi bersama komponen spreadsheet Delphi HotXLS