PDFlibPas, şifreli bir PDF'teki yanlış bir parolayı, az önce başarısız olan TPDFDocument'i atarak ve bir sonraki deneme için tamamen yenisini oluşturarak yeniden dener; bu, vazgeçmeden önce en fazla on altı deneme için çalışan bir OnPassword geri çağırması (TPDFlibPasswordEvent) tarafından yönlendirilir. Bu, çoğu Delphi geliştiricisinin önce başvurduğu içgüdüden kasıtlı bir sapmadır: belleğe zaten oturan belge nesnesini tutmak, ona düzeltilmiş bir parola vermek ve sıfırdan başlamak yerine yerinde tekrar yüklemek. PDFlibPas'ın v3.245.0'da eklenen yeniden deneme döngüsü, başarısız bir parola denemesinin arkasında ne bıraktığına özgü nedenlerle tersi konumu alır. Bunun arkasındaki senaryo yeterince sıradandır ki çoğu belge-yoğun Delphi uygulaması sonunda buna çarpar: bir giriş ekranı bir PDF kabul eder, şifreli bir arka plan bir parola diyaloğunu zorlar, operatör dizeyi yanlış yazar ve diyalog ikinci bir deneme için yeniden görünür. Bu kullanıcı deneyimi hakkında alışılmadık hiçbir şey yoktur; bu yüzden onun arkasındaki kod, aynı dosya için birden fazla aday parolayı kabul etmek zorundadır ve bunu güvenli bir şekilde, reddedilen denemeden durumu izleyen denemeye sızdırmadan yapmak zorundadır
Neden aynı belge nesnesinde yeniden deneyemezsiniz?
Parola denemeleri boyunca bir TPDFDocument'i yeniden kullanmak işe yaramaz, çünkü başarısız bir deneme o nesneyi bir tür durakladırılmış, devam ettirilebilir durumda bırakmak yerine dahili olarak zaten yıkmıştır. Şifreli bir PDF'i açmak, çapraz referans tablosunu ayrıştırmak, altta yatan kaynak üzerinde bir okuyucu kurmak ve sağlanan her ne parola varsa ondan bir şifreleme işleyicisi kurmak anlamına gelir, hepsi PDFlibPas o parolanın doğru olup olmadığını test edebilmeden önce. Parola yanlış çıktığında, belgenin dahili yükleme rutini, tam olarak yapması gerektiği gibi, başarısız olmanın bir parçası olarak okuyucuyu, çapraz referans tablosunu ve şifreleme işleyicisini temizler; bu, ikinci bir çağrıda düzeltilmiş bir parolayı bekleyen yarım kurulmuş bir ayrıştırıcının orada oturmadığı anlamına gelir. Aynı nesneyi yine de başka bir yükleme denemesinden geçirin ve başarısızlık modu tam olarak hata ayıklaması berbat türden olur: farklı, zaten başarısız olmuş bir ayrıştırma için kurulmuş dahili durumdan bir hata yüzeye çıkar; hiçbir şeyi belirgin şekilde üç çağrı akış yukarısındaki parolaya geri işaret etmez. PDFlibPas, açılmayı başaramamış bir belge nesnesini asla kurtarmaya çalışmayarak sorunun tüm sınıfından kaçınır; her deneme, okuyucu ve çapraz referans tablosu dahil, hiç yanlış bir parola görmemiş bir belge alır
OnPassword geri çağırması bir sonraki parolayı nasıl ister?
TPDFlibPasswordEvent, PDFlibPas'ın az önce denenen parola yanlış çıktığında TPDFlib.LoadFromFile, LoadFromStream ve LoadFromString aracılığıyla çağırdığı geri çağırma türüdür ve işleyiciye üç şey verir: hangi denemenin çalışmak üzere olduğu, bir sonraki adayla üzerine yazılacak bir Password parametresi ve varsayılan olarak false olan bir Retry bayrağı
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Orijinal LoadFromFile çağrısına geçirilen parola, birinci deneme olarak sayılır; bu yüzden OnPassword ilk kez tetiklendiğinde, AttemptNumber 2 olarak gelir. Retry'ı ayarlanmamış bırakın ve yükleme LastErrorCode 404 ile temizce başarısız olur; onu true'ya ayarlayın ve PDFlibPas, işleyicinin Password'e az önce yazdığı her neyse onunla tekrar dener
Yeniden deneme döngüsünün içinde: her deneme için yeni bir TPDFDocument
Dahili olarak, PDFlibPas nesne-yaşam-döngüsü sorusunu LoadFromFile, LoadFromStream ve LoadFromString için aynı şekilde yanıtlar: birinci dahil her deneme, taze bir TPDFDocument kurar, o denemenin kullandığı her ne parolaysa onunla tam açma dizisinden geçirir ve parola doğrulanırsa nesneyi yalnızca o zaman tutar. Reddedilen bir denemenin TPDFDocument'i hemen serbest bırakılır, okuyucusunu, çapraz referans tablosunu ve şifreleme işleyicisini de birlikte götürür ve bir sonraki deneme hiç geçmişi olmayan bir nesneyle yeniden başlar
// Simplified excerpt from inside LoadFromFile: every attempt gets a
// document that has never seen a previously rejected password. FileName,
// AttemptNumber and AttemptPassword come from the enclosing method.
Var
Doc: TPDFDocument;
LoadResult: TPLLoadResult;
Success: Boolean;
Begin
Success := False;
Repeat
Doc := TPDFDocument.Create;
Doc.DecodeMode := FDefaultDecodeMode;
Try
LoadResult := Doc.LoadFromFile(FileName, AttemptPassword);
Success := LoadResult = lrOkay;
if Success then
begin
FDocs.Add(Doc); // hand the verified document to the
Doc := nil; // caller's collection; skip the Free below
end;
Finally
Doc.Free; // a rejected attempt's reader, xref table
End; // and crypt handler are torn down right here
if Success or (LoadResult <> lrWrongPassword) then
Break; // success, or a non-password failure: stop
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
Finally bloğundan hemen önceki o Doc := nil satırı, nesne-yaşam-döngüsü sözleşmesinin tamamıdır, tek bir ifadede. Başarısız olan bir belge, tasarım gereği, yarım kurulmuş ayrıştırıcı durumunu mezara kadar taşır ve başarılı olan bir belge, TPDFlib'in çağıranın açık tuttuğu her belge için sakladığı koleksiyon olan FDocs'a hiç eklenen tek belgedir. Reddedilen bir deneme hakkında hiçbir şey yeniden deneme döngüsünün dışından görünmez: ne yarım başlatılmış bir okuyucu, ne bayat bir sayfa sayısı, ne de yanlış anahtardan kurulmuş bir şifreleme işleyicisi
PDFlibPas yanlış bir parolayı kaç kez yeniden dener?
PDFlibPas, çağrının kendisine geçirilen parolayı birinci deneme olarak sayarak, tek bir LoadFromFile, LoadFromStream veya LoadFromString çağrısına karşı toplam on altı denemeye izin verir. OnPassword yalnızca ikinciden on altıncıya kadar denemeler için tetiklenir ki bu geri çağırmayı on beş çağrıyla sınırlar; on yedinci bir deneme isteyin ve PDFlibPas işleyiciyi hiç çağırmadan reddeder. Retry'ı herhangi bir noktada varsayılanı false'ta bırakın veya doğru bir parola olmadan on altı denemenin tümünü tüketin ve LoadFromFile, LastErrorCode PDFlibPas'ın reddedilen bir parola için kodu olan 404'e ayarlanmış olarak 0 döndürür. Sınır, düzenlilik ötesinde nedenlerle vardır: sınırsız bir yeniden deneme döngüsü, özellikle bir işleyici, bir diyalogda tıklayan bir insan yerine daha önce görülen parolaların bir listesi gibi otomatikleştirilmiş bir şeye bağlandığında, bir yanlış yazılmış parolayı yüklemeyi çalıştıran her ne iş parçacığıysa ona karşı kazara bir hizmet reddine dönüştürmenin kolay bir yoludur. PDFlibPas ayrıca, Sender o aynı nesne olarak geldiğinden, işleyicinin içinden TPDFlib örneği üzerinde Abort'un çağrılmasına saygı gösterir; bir parola diyaloğunda bir İptal düğmesinin arkasında kullanışlıdır ve Retry ne olarak ayarlanmış olursa olsun bir sonraki kontrolde yeniden deneme döngüsünü durdurur. Yanlış bir parola dışında bir nedenle başarısız olan bir yükleme -örneğin hasarlı bir çapraz referans tablosu- hiçbir zaman yeniden deneme döngüsüne girmez: PDFlibPas LastErrorCode 401'i bildirir ve ilk denemeden sonra durur, çünkü hiçbir sayıda parola tahmini yapısal olarak bozuk bir dosyayı düzeltmez
Yeniden deneme döngüsü dosyalar, akışlar ve dizeler için aynı şekilde mi çalışır?
OnPassword geri çağırması ve on altı-deneme sınırı, LoadFromFile, LoadFromStream ve LoadFromString genelinde özdeş davranır, ama üç giriş noktası denemeler arasında kaynaklarını farklı şekilde tutar. Her deneme yalnızca adlandırılmış dosyayı yeniden açtığından, bir dosya yolunu yeniden ziyaret etmek ucuzdur ve bir dize kaynağı zaten çağıranın kendi kopyası olarak bellekte oturur; bu yüzden hiçbiri denemeler arasında çağırandan yardıma ihtiyaç duymaz. Çağıran tarafından sağlanan bir akış, üzerinde durulmaya değer tek durumdur: LoadFromStream, ilk ayrıştırma denemesinden önce o akışı konum sıfıra geri sarar ve dahili olarak kopyalar; bu yüzden sonraki her deneme, ve arkasındaki taze kurulmuş TPDFDocument, başarısız bir ayrıştırmanın akışın konumunu nerede bıraktığından değil, o dahili kopyadan yeniden oynatır. Şifre korumalı bir belge için PDFlibPas'a bir TFileStream veya TMemoryStream verin ve yeniden denemeler arasında onu geri sarmaya gerek yoktur; PDFlibPas, birinci, başarısız bir denemenin taşımış olabileceği bir konumu zaten hesaba katar
Parola yeniden denemesini bir belge giriş ekranına oturtmak
Bir belge giriş iş akışı, bu geri çağırma için doğal bir ev sağlar, çünkü tam olarak OnPassword'ün çözmek için kurulduğu sorun şeklidir: bir dosya uygulamanın dışından gelir, parolası önceden kesinlikle bilinmez ve aday sağlayan kişi, çevredeki kodun LoadFromFile'ın etrafına kendi yeniden deneme döngüsünü yazmasına gerek kalmadan birden fazla tahmine ihtiyaç duyar
procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean);
var
Typed: string;
begin
// AttemptNumber counts from 2: the password already tried was attempt 1.
Typed := '';
Retry := InputQuery('Password required',
Format('Attempt %d of 16 - enter the document password', [AttemptNumber]), Typed);
if Retry then
Password := Typed;
// Retry is False when the operator cancels, which leaves
// LastErrorCode at 404 for the caller to report.
end;
procedure TIntakeForm.LoadInboundDocument;
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.OnPassword := SupplyPassword;
if Lib.LoadFromFile('inbound-invoice.pdf', '') = 1 then
RegisterIntakeDocument(Lib) // only a verified document reaches here
else
LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
finally
Lib.Free;
end;
end;
RegisterIntakeDocument, Lib'i yalnızca LoadFromFile 1 döndürdükten sonra alır, bu o alışverişteki bir parolanın gerçekten dosyanın şifreleme işleyicisine karşı doğrulandığı anlamına gelir; reddedilen bir deneme o satıra hiç ulaşmaz, ne de yarı açık bir belge ulaşır. Böyle bir belge açık olarak onaylandığında, sırada gelen şey, işe yarayan parolanın tüm güvenlik hikayesi olduğunu varsaymak yerine koruma ayarlarına ikinci bir bakışa değer: bir belgenin /Encrypt sözlüğünün gerçekte neyi bildirdiğini denetleme makalesi, böyle bir dosya yüklendiğinde PDFlibPas'ın sunduğu algoritma, revizyon ve izin bitlerini okumayı ele alır
Parola yeniden deneme, aynı zamanda PDFlibPas'ın ayrıştırma katmanı boyunca uyguladığı daha geniş bir disiplinin dar bir örneğidir: kendini henüz kanıtlamamış bir dosya, sorunun hangi parolanın kilidini açtığı mı yoksa içindeki bir uzunluk alanının ihtiyaç duyduğu arabellek boyutu hakkında yalan söyleyip söylemediği mi olduğuna bakılmaksızın, şüphenin faydasını almaz. Bir Pascal PDF ayrıştırıcısını kötü niyetli dosyalara karşı sertleştirmek, o disiplinin diğer yarısını ele alır: gelen bir PDF'teki her yazı tipi programını ve görüntü akışını, yalnızca parolasını unutmuş iyi biçimlendirilmiş bir belge değil, düşman girdisi olarak ele alan kod çözücüler
OnPassword ve arkasındaki yeniden deneme döngüsü, LoadFromFile, LoadFromStream veya LoadFromString'in zaten bulunduğu her yerde mevcut olan, Delphi ve C++Builder için standart PDFlibPas PDF kütüphanesi'nin bir parçasıdır; parolasında yalnızca ikinci bir tahmine ihtiyaç duyan bir belge için ayrı bir modül veya lisans katmanı gerekmez