Teknik Makale

HotXLS'te CALL ve WEBSERVICE Formula Callback Kapısı

HotXLS, CALL, REGISTER.ID, WEBSERVICE ve DDE dahil 20 tehlikeli formula adını, siz açıkça izin vermedikçe Delphi kullanıcı-fonksiyonu callback'lerinize yönlendirmeyi reddediyor. AllowUnsafeFormulaCallbacks çalışma kitabı özelliği varsayılan olarak False gelir, kontrol hiçbir argüman değerlendirilmeden önce çalışır ve reddedilen bir çağrı tek bir handler'ı dahi çalıştırmadan xlfeUnsafeFunctionDenied raporlar

Bunu gerekli kılan senaryo sıradan. Bir servis yüklenen XLS ya da XLSX dosyalarını kabul ediyor, sunucu tarafında yeniden hesaplıyor ve birkaç toplam geri okuyor. Host uygulama yıllar önce birkaç iş fonksiyonu için OnUserFunction handler'ı kaydetti ve o sırada bir yerde handler, tanımadığı her şeyi bir plugin tablosuna yönlendiren catch-all bir dal büyüttü. Ekibin hiçbir üyesi bir hücreye =WEBSERVICE(...) yazmadı. Dosyayı yükleyen yazdı. O formülü open, recalc ve save boyunca olduğu gibi tutmak dosya-sadakati özelliğidir. Onu soket ya da dosya açabilen host koduna ulaştırmak ise bir yetkilendirme kararıdır ve HotXLS ikisini ayırana kadar kütüphane o kararı sessizçe sizin adınıza veriyordu

Bir formülü korumak, onu çalıştırma iznine nasıl dönüştü?

Kök neden tek bir fallback yoluydu. HotXLS bildiği her Excel fonksiyon adını parse eder ama her bilinen adın hesaplama motorunda bir karşılığı yoktur. Tanınan ama uygulanmamış built-in'ler eskiden gerçekten özel adlarla aynı kullanıcı-tanımlı fonksiyon fallback'ine düşüyordu; CALL ve REGISTER.ID sizin DISCOUNT ya da REGIONRATE'inizle aynı dispatch yolunu paylaşıyordu. WEBSERVICE ya da DDE gibi bilinmeyen adlar da aynı şekilde, çalışma kitabı kayıt defterinde, süreç genelindeki kayıt defterinde ya da bir event handler'da aynı adlı bir girdiyle eşleşebiliyordu. O fallback'in mekaniği HotXLS özel fonksiyonları OnUserFunction üzerinden nasıl çözümlüyor yazısında işleniyor; sorun, o yolda hiçbir şeyin adın kendisinin sağduyulu bir hostun asla çalıştırması gereken bir ad olup olmadığını sormamasıydı

Dispatch sırası, "bilinmeyen"in burada ne anlama geldiğini belirler. Motorun doğal olarak değerlendiremediği bir çağrı sırayla, sözcüksel LAMBDA ve LET bağlamalarına — HotXLS formula motorundaki closure desteğinin önce çözdüğü — sonra RegisterUserFunction ile kaydedilen çalışma kitabı-yerel fonksiyonlara, sonra TXLSWorkbook.RegisterGlobalUserFunction'dan gelen süreç-geneli fonksiyonlara ve son olarak OnUserFunction ile OnUserFunctionEx event'lerine sunulur. Hepsi ret ederse, gerçekten bilinmeyen bir fonksiyon #NAME? olur. Lambda aramasından sonraki her aşama kontrolü sizin yazdığınız koda devreder; güvenlik kontrolünün zincirin herhangi bir handler'ının içinde değil, tamamının önünde oturması gereken yer tam olarak budur

HotXLS güvensiz formula callback'lerini nasıl kapılar: GetValueItemUserFunction adı, argüman dizisi kurulmadan ve hiçbir resolver çalıştırılmadan önce kontrol eder; iç içe =WEBSERVICE(AUDIT_TOKEN()) lxErrorUnsafeFunctionDenied ile çıkar ve denetim günlüğü boş kalır, güvenli çağrılar ise zinciri LAMBDA ve LET bağlamalarından OnUserFunction'a dek yürür
Yalnızca her aşama ret ettiğinde gerçekten bilinmeyen bir fonksiyon #NAME? olur; güvenlik kontrolünün zincirin herhangi bir handler'ının içinde değil tamamının önünde oturmasının nedeni budur

HotXLS varsayılan olarak hangi fonksiyon adlarını engelliyor?

lxCalc.pas içindeki XLSFormulaCallbackIsUnsafe, 20 adlı sabit bir engel kümesi tutar: 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 ve FILE.DELETE. Bunlar, Excel'de ya da makro dilinde yerli kod yükleyen, ağa ulaşan, başka süreçlerle konuşan ya da dosya sistemine dokunan adlardır. Karşılaştırmadan önce fonksiyon çevredeki boşlukları kırpar, adı büyük harfe çevirir ve tek bir _XLFN. ya da _XLWS. önekini soyar; böylece daha yeni bir Excel sürümünün yazdığı _xlfn.webservice, çıplak yazımla aynı biçimde yakalanır. Liste Classic, XLSX ve ODS parser'larında değil hesap makinesi sınırında yaşar; tek bir AST, tek bir BIFF token akışı ve tek bir dönüştürülmüş çalışma kitabının özdeş davranmasını sağlayan da budur

HotXLS bir fonksiyon adını güvensiz-callback karşılaştırmasından önce nasıl normalize ediyor: boşluklar kırpılır, ad büyük harfe çevrilir ve tek bir _XLFN. ya da _XLWS. öneki soyulur, böylece _xlfn.webservice çıplak yazımla yakalanır; sonuç sonra lxCalc.pas'taki 20 adlı sabit engel kümesiyle tam eşleşme için karşılaştırılır
Liste, yerli kod yükleyen, ağa ulaşan, başka süreçlerle konuşan ya da dosya sistemine dokunan adları kapsıyor: DDE, CALL ve WEBSERVICE'den FWRITE ve FILE.DELETE'a

Buna güvenmeden önce bilinmeye değer iki uç var. Eşleşme birebirdir; MYWEBSERVICE olarak kaydettiğiniz bir handler etkilenmez ve tersine, tam adı OPEN ya da RUN tesadüf eden meşru bir şirket-içi UDF artık varsayılan olarak reddedilir. Engel kümesi aynı zamanda kendi handler'larınız için bir sandbox değildir. Catch-all dalınız keyfi plugin adları çalıştırıyorsa kapı, ünlü tehlikelileri durdurur ve başka hiçbir şeyi; kalıcı çözüm hâlâ, açık bir allowlist ile SameText üzerinden eşleşen ve sahibi olmadığı her şey için Handled'ı False bırakan bir handler'dır

Kapı neden argüman değerlendirmesinden önce çalışmak zorunda?

Argümanlar hesaplandıktan sonra tetiklenen bir kapı geç kalmıştır, çünkü argümanların kendisi kodunuzu çağırabilir. GetValueItemUserFunction önce adı kontrol eder ve lxErrorUnsafeFunctionDenied ile çıkar; argüman dizisini kurmadan, bir resolver'a ya da iki kayıt defterinden birine başvurmadan, hatta hiçbir handler atanmamış olduğunu fark etmeden bile önce. Bu sıralama, aşağıdaki iç içe vakayı çökerten şeydir: dış çağrı zaten reddedilecekti ama görünüşte zararsız iç UDF aksi hâlde önce ateşlenir ve yan etkisini geride bırakırdı

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 kodunda yan etki
    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, ve FAuditLog hâlâ boş

Çalışma kitabı varsayılanı mı, çağrı başına TXLSFormulaEvaluationOptions mı?

Çalışma kitabı bayrağı varsayılandır, çağrı başına seçenek ise son sözü söyler. TXLSWorkbook.AllowUnsafeFormulaCallbacks ve TXLSXWorkbook.AllowUnsafeFormulaCallbacks, olağan yeniden hesaplamayı, Calculate'i, iki argümanlı EvaluateFormulaAt'ı, değerlendirme şablonlarını, salt-okunur görünümleri ve XLSX'te paralel yeniden hesaplama havuzundaki her worker'ı yönetir. Açık bir TXLSFormulaEvaluationOptions kaydı kabul eden her giriş noktası, o çağrının kararı olarak Options.AllowUnsafeFormulaCallbacks'ı alır ve onu çalışma kitabı özelliğiyle OR'lamaz. Bu asimetri bilinçli: güvenilen bir iç iş, tüm çalışma kitabını devirmeden tek bir RTD aramasına izin verebilir; global olarak izin verilmiş bir çalışma kitabı ise hassas bir değerlendirmeyi yine reddede zorlayabilir

var
  Options: TXLSFormulaEvaluationOptions;
  Eval: TXLSFormulaEvaluationResult;
begin
  // çalışma kitabı kilitli kalır, tek güvenilen çağrı geçer
  Book.AllowUnsafeFormulaCallbacks := False;
  Options := XLSDefaultFormulaEvaluationOptions;
  Options.AllowUnsafeFormulaCallbacks := True;
  Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);

  // çalışma kitabı izin veriyor ama yüklenen metnin bu değerlendirmesi vermiyor
  Book.AllowUnsafeFormulaCallbacks := True;
  Options := XLSDefaultFormulaEvaluationOptions;   // bayrak yeniden False
  Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
  if Eval.Status = xlfeUnsafeFunctionDenied then
    LogRejected(Eval.Issue.Message);
end;

Çalışma kitabı özelliğini değiştirmek her iki motorda da bağımlılık grafiğini dirty işaretler. O adım olmadan, callback'lere izin varken hesaplanmış önbellekteki bir sonuç, izin geri çekildikten sonra servis edilebilirdi ya da önbellekteki bir xlfeUnsafeFunctionDenied sonucu, opt-in'i geçirdi. Yeni durum, TXLSFormulaEvaluationStatus içine xlfeFailed'den sonra eklendi; sıra numarası 10'dur ve mevcut her sıra numarası değerini korur. Aynı sonda-ekleme kuralı options kaydı alanına ve IXLSWorkbook getter ile setter'ına da uygulanır; eski bir sürüme karşı derlenmiş bir tüketicinin yine de yeniden derlemeye ihtiyacı var

Bilinmeyen ve güvensiz formula metnine kaydetme sırasında ne olur?

Bir formülü tutmak ile onu çalıştırmak artık iki ayrı soru ve giriş politikası yalnızca ilkini cevaplıyor. Her iki çalışma kitabı sınıfında da FormulaEntryPolicy, UnknownFunctionMode ve UnknownNameMode taşır; ikisi de varsayılan olarak xlfusmReject gelir, böylece normal Formula özelliği üzerinden bilinmeyen bir çağrı içeren formül atamak, hücre değeri, formula cache ya da bağımlılıklar değişmeden önce reddedilir. ValidateFormulaEntry aynı kararı yan etkisiz raporlar. Dosya yükleme, kopyalama ve format dönüşümü gibi güvenilen yollar o kullanıcı-girişi politikasını aşar; katı bir varsayılan, yalnızca açtığınız bir dosyada zaten bulunan sembolleri asla reddetmemelidir

var
  Policy: TXLSFormulaEntryPolicy;
begin
  Policy := Book.FormulaEntryPolicy;
  Policy.UnknownFunctionMode := xlfusmPreserve;   // uyumluluk girişi
  Book.FormulaEntryPolicy := Policy;
  Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)';  // saklandı, yetkilendirilmedi
  Book.SaveAs('rates.xls');
end;

Classic BIFF8'de bilinmeyen bir çağrının kendi token'ı yoktur; HotXLS onu, Excel'in add-in fonksiyonlarını yazdığı biçimde yazar. Formula, XTI girdisi her iki sheet indeksi de $FFFE'e ayarlı add-in SUPBOOK'una işaret eden bir PtgNameX token'ı ($59) alır; ardından argüman token'ları ve fonksiyon numarası 255, ad yuvasını da kapsayan bir argüman sayısı taşıyan bir PtgFuncVar gelir. Arkasındaki ExternName gövdesi altı sıfır bayt, bir uzunluk baytı ve Unicode bayrağı, UTF-16 fonksiyon adı, sonra $1C $17den oluşan iki baytlık bir formüldür — #REF! taşıyan bir PtgErr. Yazıcı 255 karakterden uzun adları, 29dan fazla argümanı ve BIFF5 hedefini reddeder. HotXLS bu add-in SUPBOOK girdilerini dış çalışma kitabı bağlantılarının yanında nasıl sınıflandırıyor sorusu BIFF dış bağlantıları için SUPBOOK ve XTI sınıflandırma kurallarında açıklanıyor. XLSX ham fonksiyon metnini korur, ODS kendi msoxl: formülünü korur ve her formatta =WEBSERVICE(...) kaydetmiş bir dosya metni sağlam biçimde yeniden açılır ve varsayılan olarak yine xlfeUnsafeFunctionDenied değerlendirilir

HotXLS bilinmeyen bir formula çağrısını classic BIFF8'e nasıl yazar: formula, XTI girdisi her iki sheet indeksi de $FFFE olan add-in SUPBOOK'una işaret eden bir PtgNameX token'ı taşır; ardından argüman token'ları ve fonksiyon numarası 255 olan bir PtgFuncVar gelir, arkasında ise $1C $17 iki baytlık, #REF! taşıyan PtgErr ile biten bir ExternName gövdesi vardır
XLSX ham fonksiyon metnini korur, ODS kendi msoxl: formülünü korur; =WEBSERVICE(...) kaydetmiş bir dosya metni sağlam biçimde yeniden açılır ve varsayılan olarak yine xlfeUnsafeFunctionDenied değerlendirilir

Boruhattınız, yazarı olmadığı çalışma kitaplarını değerlendiriyorsa AllowUnsafeFormulaCallbacks'ı False'ta bırakın, handler'ları açık bir allowlist üzerinde tutun ve çağrı başına seçenekleri yalnızca formula kaynağı size aitken verin. Tam callback, giriş-politikası ve değerlendirme API'si HotXLS Delphi spreadsheet component ile birlikte belgelenir