PDF Library for Delphi ponovi napačno geslo za šifrirani PDF tako, da zavrže TPDFDocument, pri katerem je pravkar prišlo do napake, in za naslednji poskus ustvari povsem novega, pri čemer ga usmerja povratni klic OnPassword (TPDFlibPasswordEvent), ki se izvede za največ šestnajst poskusov, preden odneha. To je premišljen odmik od pristopa, po katerem večina razvijalcev Delphija najprej poseže: obdržati dokumentni objekt, ki je že v pomnilniku, mu podati popravljeno geslo in ga znova naložiti na mestu, namesto da bi začeli znova. Zanka ponovnih poskusov v PDF Library for Delphi, dodana v v3.245.0, zavzame nasprotno stališče zaradi posebnosti tega, kar neuspeli poskus gesla pusti za seboj. Scenarij je dovolj vsakdanji, da ga prej ali slej doživi večina aplikacij Delphi, ki veliko delajo z dokumenti: vnosni zaslon sprejme PDF, šifrirani trailer zahteva pogovorno okno za geslo, operater se zatipka, pogovorno okno pa se znova prikaže za drugi poskus. Pri takšni uporabniški izkušnji ni nič nenavadnega, zato mora koda za isto datoteko varno sprejeti več kandidatnih gesel, ne da bi stanje zavrnjenega poskusa ušlo v naslednjega
Zakaj ni mogoče preprosto ponoviti poskusa z istim dokumentnim objektom?
Uporaba istega TPDFDocument pri več poskusih gesla ne deluje, ker je neuspešen poskus ta objekt interno že razgradil, namesto da bi ga pustil v nekakšnem zaustavljenem stanju, ki ga je mogoče nadaljevati. Odpiranje šifriranega PDF-ja pomeni razčlenitev navzkrižne referenčne tabele, izdelavo bralnika nad osnovnim virom in sestavo upravljavca šifriranja iz podanega gesla, vse še preden lahko PDF Library for Delphi sploh preveri, ali je geslo pravilno. Ko se izkaže, da je geslo napačno, interna rutina za nalaganje dokumenta pri neuspehu počisti bralnik, navzkrižno referenčno tabelo in upravljavca šifriranja, natanko tako, kot mora, kar pomeni, da tam ne sedi napol izdelan razčlenjevalnik, ki bi čakal na popravljeno geslo pri drugem klicu. Če isti objekt vseeno poženete skozi nov poskus nalaganja, dobite napako, ki jo je zelo neprijetno odpravljati: napaka izvira iz internega stanja, izdelanega za drug, že neuspešen razčlenjevalni poskus, pri čemer nič očitno ne kaže nazaj na geslo tri klice prej. PDF Library for Delphi se celotnemu razredu težav izogne tako, da dokumentnega objekta po neuspešnem odpiranju nikoli ne poskuša obnoviti; vsak poskus dobi dokument, ki še nikoli ni videl napačnega gesla, skupaj z bralnikom in navzkrižno referenčno tabelo
Kako povratni klic OnPassword zahteva naslednje geslo?
TPDFlibPasswordEvent je vrsta povratnega klica, ki jo PDF Library for Delphi prikliče prek TPDFlib.LoadFromFile, LoadFromStream in LoadFromString, kadar se izkaže, da je bilo pravkar preizkušeno geslo napačno, upravljavcu pa posreduje tri stvari: številko poskusa, ki se bo pravkar izvedel, parameter Password, ki ga je treba prepisati z naslednjim kandidatom, in zastavico Retry, katere privzeta vrednost je false
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Geslo, podano v prvotnem klicu LoadFromFile, šteje kot prvi poskus, zato je ob prvem sprožitvi OnPassword vrednost AttemptNumber enaka 2. Če Retry pustite nespremenjen, se nalaganje čisto konča z LastErrorCode 404; če ga nastavite na true, PDF Library for Delphi poskusi znova z vrednostjo, ki jo je upravljavec pravkar zapisal v Password
V zanki ponovnih poskusov: nov TPDFDocument za vsak poskus
Interno PDF Library for Delphi na vprašanje o življenjskem ciklu objekta za LoadFromFile, LoadFromStream in LoadFromString odgovori enako: vsak poskus, vključno s prvim, ustvari nov TPDFDocument, ga z geslom tega poskusa izvede skozi celotno zaporedje odpiranja in obdrži objekt samo, če je geslo preverjeno. TPDFDocument zavrnjenega poskusa se takoj sprosti, skupaj z njim pa se odstranijo bralnik, navzkrižna referenčna tabela in upravljavec šifriranja, naslednji poskus pa se začne z objektom, ki nima prav nobene zgodovine
// Poenostavljen izsek iz LoadFromFile: vsak poskus dobi
// dokument, ki še ni videl prej zavrnjenega gesla. FileName,
// AttemptNumber in AttemptPassword prihajata iz zunanje metode
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); // preverjeni dokument predaj
Doc := nil; // zbirki klicatelja; spodnji Free preskoči
end;
Finally
Doc.Free; // bralnik zavrnjenega poskusa, tabela xref
End; // in upravljalnik crypt se razgradita prav tukaj
if Success or (LoadResult <> lrWrongPassword) then
Break; // success, or a non-password failure: stop
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
Vrstica Doc := nil tik pred blokom Finally je celotna pogodba življenjskega cikla objekta v enem stavku. Dokument, ki odpove, odnese svoje napol izdelano stanje razčlenjevalnika s seboj v grob, namerno, uspešen dokument pa je edini, ki se doda v FDocs, zbirko, ki jo TPDFlib ohranja za vsak dokument, ki ga ima klicatelj odprt. Zunaj zanke ponovnih poskusov ni vidno nič iz zavrnjenega poskusa: ne napol inicializiran bralnik, ne zastarelo število strani in ne upravljavec šifriranja, izdelan iz napačnega ključa
Kolikokrat bo PDF Library for Delphi ponovil napačno geslo?
PDF Library for Delphi dovoljuje skupno šestnajst poskusov za en klic LoadFromFile, LoadFromStream ali LoadFromString, pri čemer geslo, podano v samem klicu, šteje kot prvi poskus. OnPassword se sproži samo za poskuse od drugega do šestnajstega, zato je povratni klic omejen na petnajst sprožitev; če zahtevate sedemnajsti poskus, ga PDF Library for Delphi zavrne, ne da bi sploh priklical upravljavca. Če kadar koli pustite Retry na privzeti vrednosti false ali izčrpate vseh šestnajst poskusov brez pravilnega gesla, LoadFromFile vrne 0, LastErrorCode pa nastavi na 404, kodo PDF Library for Delphi za zavrnjeno geslo. Omejitev obstaja iz več razlogov kot le zaradi urejenosti: neomejena zanka ponovnih poskusov je preprost način, da se eno zatipkano geslo spremeni v nenamerno zavrnitev storitve nad nitjo, ki izvaja nalaganje, zlasti ko je upravljavec povezan z nečim avtomatiziranim, kot je seznam že videnih gesel, namesto s človekom, ki klika skozi pogovorno okno. PDF Library for Delphi upošteva tudi klic Abort na primerku TPDFlib iz upravljavca, saj Sender prispe kot isti objekt, kar je uporabno za gumb Prekliči v pogovornem oknu za geslo, zanko ponovnih poskusov pa ustavi pri naslednjem preverjanju ne glede na vrednost Retry. Nalaganje, ki odpove iz razloga, ki ni napačno geslo, na primer zaradi poškodovane navzkrižne referenčne tabele, v zanko ponovnih poskusov sploh ne vstopi: PDF Library for Delphi sporoči LastErrorCode 401 in se ustavi po prvem poskusu, ker nobeno število ugibanj gesla ne more popraviti strukturno poškodovane datoteke
Ali zanka ponovnih poskusov deluje enako za datoteke, tokove in nize?
Povratni klic OnPassword in omejitev šestnajstih poskusov se vedeta enako pri LoadFromFile, LoadFromStream in LoadFromString, čeprav tri vstopne točke med poskusi svoje vire ohranjajo različno. Pot do datoteke je mogoče poceni znova uporabiti, saj vsak poskus preprosto znova odpre imenovano datoteko, vir niza pa je že v pomnilniku kot lastna kopija klicatelja, zato nobeden med poskusi ne potrebuje pomoči klicatelja. Primer, pri katerem je vredno biti previden, je tok, ki ga poda klicatelj: LoadFromStream ta tok pomakne nazaj na položaj nič in ga interno kopira pred prvim poskusom razčlenjevanja, zato vsak naslednji poskus in sveže ustvarjeni TPDFDocument za njim znova predvajata interno kopijo, ne pa mesta, kjer je neuspešno razčlenjevanje pustilo položaj toka. Če PDF Library for Delphi-u za dokument, zaščiten z geslom, posredujete TFileStream ali TMemoryStream, ga med ponovnimi poskusi ni treba previjati; PDF Library for Delphi že upošteva položaj, ki ga je prvi, neuspešni poskus morda spremenil
Vključitev ponovnega poskusa gesla v zaslon za vnos dokumenta
Potek vnosa dokumenta je naravno mesto za ta povratni klic, saj je to natančno oblika težave, za reševanje katere je bil OnPassword ustvarjen: datoteka prispe od zunaj aplikacije, njeno geslo vnaprej ni zanesljivo znano, oseba, ki vnaša kandidate, pa potrebuje več kot en poskus, ne da bi okoli LoadFromFile pisala lastno zanko ponovnih poskusov
procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean);
var
Typed: string;
begin
// AttemptNumber šteje od 2: geslo, ki je bilo že preizkušeno, je bil poskus 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, ko upravljavec prekliče, kar pusti
// LastErrorCode na 404, da ga klicatelj sporoči.
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 pride le preverjen dokument
else
LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
finally
Lib.Free;
end;
end;
RegisterIntakeDocument prejme Lib šele, ko LoadFromFile vrne 1, kar pomeni, da je bilo neko geslo v tem izmenjavanju dejansko preverjeno proti upravljavcu šifriranja datoteke; zavrnjen poskus nikoli ne doseže te vrstice, prav tako ne napol odprt dokument. Ko je tak dokument potrjeno odprt, je naslednji korak vreden ponovnega pregleda njegovih zaščitnih nastavitev, namesto predpostavke, da je uspešno geslo celotna varnostna zgodba: pregled tega, kar dejansko določa slovar /Encrypt dokumenta, zajema algoritem, revizijo in bite dovoljenj, ki jih PDF Library for Delphi izpostavi, ko se takšna datoteka naloži
Ponovni poskus gesla je tudi ozek primer širše discipline, ki jo PDF Library for Delphi uporablja v celotni plasti za razčlenjevanje: datoteka, ki se še ni dokazala, ne dobi nobenega privilegija dvoma, ne glede na to, ali je vprašanje, katero geslo jo odklene, ali pa dolžinsko polje v njej laže o velikosti medpomnilnika, ki ga potrebuje. Zaščita razčlenjevalnika PDF v Pascalu pred zlonamernimi datotekami obravnava drugo polovico te discipline, dekoderje, ki vsak program pisave in slikovni tok v vhodnem PDF-ju obravnavajo kot nasproten vhod, ne pa kot pravilno oblikovan dokument, ki je le pozabil svoje geslo
OnPassword in zanka ponovnih poskusov za njim sta del standardne knjižnice PDF Library for Delphi PDF za Delphi in C++Builder, na voljo povsod, kjer so že na voljo LoadFromFile, LoadFromStream ali LoadFromString, brez ločenega modula ali licenčnega paketa za dokument, ki potrebuje le še en poskus gesla