PDF Library for Delphi opakuje špatné heslo na šifrovaném PDF tak, že zahodí TPDFDocument, který právě selhal, a vytvoří úplně nový pro další pokus, řízený zpětným voláním OnPassword (TPDFlibPasswordEvent), které běží až šestnáctkrát, než se vzdá. Je to záměrný odklon od instinktu, po kterém sáhne většina vývojářů v Delphi jako první: podržet objekt dokumentu, který už sedí v paměti, podat mu opravené heslo, a znovu načíst na místě místo začínání od nuly. Smyčka opakování PDF Library for Delphi, přidaná v v3.245.0, zaujímá opačnou pozici, z důvodů specifických pro to, co pokus se špatným heslem zanechá za sebou. Scénář za tím je dost obyčejný na to, aby na něj většina aplikací v Delphi náročných na dokumenty nakonec narazila: obrazovka příjmu přijme PDF, šifrovaný trailer vynutí dialog hesla, operátor se překlepne, a dialog se objeví znovu na druhý pokus. Nic na tomto zážitku uživatele není neobvyklé, takže kód za ním musí přijmout víc než jedno kandidátní heslo pro stejný soubor, a musí to udělat bezpečně, bez úniku stavu z odmítnutého pokusu do toho, který následuje
Proč nemůžete prostě opakovat na stejném objektu dokumentu?
Znovupoužití TPDFDocument napříč pokusy o heslo nefunguje, protože selhaný pokus už tento objekt interně strhl místo toho, aby jej ponechal v nějakém pozastaveném, obnovitelném stavu. Otevření šifrovaného PDF znamená parsování tabulky křížového odkazu, sestavení čtenáře nad podkladovým zdrojem, a konstrukci obsluhy šifrování z jakéhokoli podaného hesla, to vše dřív, než PDF Library for Delphi vůbec dokáže otestovat, zda je toto heslo správné. Když se heslo ukáže být špatné, interní rutina načítání dokumentu vyčistí čtenáře, tabulku křížového odkazu, a obsluhu šifrování jako součást selhání, přesně jak by měla, což znamená, že tam nesedí žádný napůl postavený parser čekající na opravené heslo při druhém volání. Prožeňte přesto stejný objekt dalším pokusem o načtení a režim selhání je přesně ten druh, který je bídný na ladění: chyba se vynoří z interního stavu postaveného pro jiný, už selhaný parsing, s ničím na ní zjevně ukazujícím zpátky na heslo o tři volání proti proudu. PDF Library for Delphi se celé této třídě problému vyhýbá tím, že se nikdy nesnaží zotavit objekt dokumentu, jakmile jednou selže při otevření; každý pokus dostane dokument, který nikdy neviděl špatné heslo, čtenáře a tabulku křížového odkazu nevyjímaje
Jak zpětné volání OnPassword žádá o další heslo?
TPDFlibPasswordEvent je typ zpětného volání, který PDF Library for Delphi vyvolá přes TPDFlib.LoadFromFile, LoadFromStream, a LoadFromString kdykoli se heslo, které bylo právě zkusené, ukáže být špatné, a podá obsluze tři věci: který pokus se právě chystá běžet, parametr Password k přepsání dalším kandidátem, a příznak Retry, který ve výchozím stavu je false
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Heslo předané do původního volání LoadFromFile se počítá jako pokus jedna, takže poprvé, kdy se OnPassword vůbec vyvolá, AttemptNumber dorazí jako 2. Ponechte Retry nenastavené a načtení čistě selže s LastErrorCode 404; nastavte jej na true a PDF Library for Delphi to zkusí znovu s tím, co obsluha právě zapsala do Password
Uvnitř smyčky opakování: nový TPDFDocument pro každý pokus
Interně PDF Library for Delphi odpovídá na otázku životního cyklu objektu stejným způsobem pro LoadFromFile, LoadFromStream, a LoadFromString: každý pokus, včetně prvního, konstruuje čerstvý TPDFDocument, prožene jej kompletní sekvencí otevření s jakýmkoli heslem, které tento pokus používá, a objekt si podrží jen tehdy, když se heslo ověří. TPDFDocument odmítnutého pokusu se okamžitě uvolní, strhne s sebou čtenáře, tabulku křížového odkazu, a obsluhu šifrování, a další pokus začíná znovu s objektem, který nemá žádnou historii vůbec
// Zjednodušený úryvek zevnitř LoadFromFile: každý pokus dostane
// dokument, který nikdy neviděl dříve odmítnuté heslo. FileName,
// AttemptNumber a AttemptPassword pocházejí z obklopující metody.
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); // předá ověřený dokument do
Doc := nil; // kolekce volajícího; Free níže se přeskočí
end;
Finally
Doc.Free; // čtenář, tabulka křížového odkazu odmítnutého pokusu
End; // a obsluha šifrování se strhnou přesně zde
if Success or (LoadResult <> lrWrongPassword) then
Break; // úspěch, nebo chyba nesouvisející s heslem: zastavit
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
Ten řádek Doc := nil těsně před blokem Finally je celá smlouva životního cyklu objektu v jednom příkazu. Dokument, který selže, si nese svůj napůl postavený stav parseru do hrobu, záměrně, a dokument, který uspěje, je jediný, který se kdy přidá do FDocs, kolekce, kterou TPDFlib drží pro každý dokument, který volající má otevřený. Nic z odmítnutého pokusu není viditelné zvenčí smyčky opakování: žádný napůl inicializovaný čtenář, žádný zastaralý počet stránek, žádná obsluha šifrování postavená ze špatného klíče
Kolikrát bude PDF Library for Delphi opakovat špatné heslo?
PDF Library for Delphi povoluje celkem šestnáct pokusů proti jednomu volání LoadFromFile, LoadFromStream, nebo LoadFromString, počítá heslo předané do samotného volání jako pokus jedna. OnPassword se vyvolá jen pro pokusy dvě až šestnáct, což omezuje zpětné volání na patnáct vyvolání; požádejte o sedmnáctý pokus a PDF Library for Delphi odmítne, aniž by dokonce obsluhu vyvolal. Ponechte Retry na jeho výchozí hodnotě false kdykoli, nebo vyčerpejte všech šestnáct pokusů bez správného hesla, a LoadFromFile vrátí 0 s LastErrorCode nastaveným na 404, kód PDF Library for Delphi pro odmítnuté heslo. Strop existuje z důvodů nad rámec úhlednosti: neomezená smyčka opakování je snadný způsob, jak proměnit jedno překlepnuté heslo na náhodné odepření služby proti jakémukoli vláknu, které spouští načtení, obzvlášť jakmile je obsluha zapojená k něčemu automatizovanému, jako seznam dříve viděných hesel, místo člověka proklikávajícího dialog. PDF Library for Delphi také respektuje Abort zavolané na instanci TPDFlib zevnitř obsluhy, protože Sender dorazí jako tentýž objekt, užitečné za tlačítkem Zrušit na dialogu hesla, a zastaví smyčku opakování na další kontrole bez ohledu na to, na co bylo Retry nastaveno. Načtení, které selže z jiného důvodu než špatného hesla, poškozené tabulky křížového odkazu například, do smyčky opakování vůbec nevstoupí: PDF Library for Delphi nahlásí LastErrorCode 401 a zastaví se po prvním pokusu, protože žádný počet hádaných hesel neopraví strukturálně rozbitý soubor
Funguje smyčka opakování stejně pro soubory, proudy a řetězce?
Zpětné volání OnPassword a strop šestnácti pokusů se chovají identicky napříč LoadFromFile, LoadFromStream, a LoadFromString, přestože tyto tři vstupní body drží svůj zdroj mezi pokusy odlišně. Cesta k souboru je levné navštívit znovu, protože každý pokus prostě znovu otevře pojmenovaný soubor, a zdroj řetězce už sedí v paměti jako vlastní kopie volajícího, takže ani jeden nepotřebuje žádnou pomoc od volajícího mezi pokusy. Proud dodaný volajícím je jediný případ, který stojí za pozastavení: LoadFromStream posune tento proud zpátky na pozici nula a interně jej zkopíruje ještě před prvním pokusem o parsování, takže každý následující pokus, a čerstvě zkonstruovaný TPDFDocument za ním, přehrává z této interní kopie místo z toho, kde selhané parsování ponechalo pozici proudu. Podejte PDF Library for Delphi TFileStream nebo TMemoryStream pro dokument chráněný heslem a není potřeba jej mezi opakováními přetáčet zpátky; PDF Library for Delphi už počítá s pozicí, kterou první, selhaný pokus mohl posunout
Zapojení opakování hesla do obrazovky příjmu dokumentů
Pracovní postup příjmu dokumentů je přirozený domov pro toto zpětné volání, protože je to přesně tvar problému, pro který bylo OnPassword postaveno: soubor dorazí mimo aplikaci, jeho heslo není předem známé s jistotou, a osoba dodávající kandidáty potřebuje víc než jeden hádaný pokus bez toho, aby okolní kód psal vlastní smyčku opakování kolem LoadFromFile
procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean);
var
Typed: string;
begin
// AttemptNumber počítá od 2: heslo, které již bylo vyzkoušeno, byl pokus 1.
Typed := '';
Retry := InputQuery('Password required',
Format('Attempt %d of 16 - enter the document password', [AttemptNumber]), Typed);
if Retry then
Password := Typed;
// Retry je False, když operátor zruší akci, což ponechá
// LastErrorCode na 404, aby to volající mohl nahlásit.
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) // sem se dostane jen ověřený dokument
else
LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
finally
Lib.Free;
end;
end;
RegisterIntakeDocument dostane Lib jen tehdy, když se LoadFromFile vrátilo 1, což znamená, že se nějaké heslo v této výměně skutečně ověřilo proti obsluze šifrování souboru; odmítnutý pokus se na tento řádek nikdy nedostane, a stejně tak ani napůl otevřený dokument. Co následuje, jakmile je takový dokument potvrzen jako otevřený, stojí za druhý pohled na jeho nastavení ochrany místo předpokladu, že heslo, které fungovalo, je celý bezpečnostní příběh: audit toho, co slovník /Encrypt dokumentu skutečně deklaruje popisuje čtení algoritmu, revize, a bitů oprávnění, které PDF Library for Delphi vystavuje, jakmile se takový soubor načte
Opakování hesla je také úzká instance širší disciplíny, kterou PDF Library for Delphi aplikuje napříč svou vrstvou parsování: soubor, který se ještě neosvědčil, nedostává výhodu pochybnosti, ať je otázka, které heslo jej odemyká, nebo zda pole délky uvnitř lže o velikosti bufferu, který potřebuje. Zpevňování parseru PDF v Pascalu proti škodlivým souborům popisuje druhou polovinu této disciplíny, dekodéry, které berou každý program fontu a proud obrázku v příchozím PDF jako nepřátelský vstup místo dobře formovaného dokumentu, který si prostě zapomněl heslo
OnPassword a smyčka opakování za ním jsou součástí standardní knihovny PDF PDF Library for Delphi pro Delphi a C++Builder, dostupné kdekoli, kde už jsou LoadFromFile, LoadFromStream, nebo LoadFromString, bez potřeby samostatného modulu nebo licenční úrovně pro dokument, který jen potřebuje druhý hádaný pokus o své heslo