PDFlibPas 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 PDFlibPas, 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 PDFlibPas 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. PDFlibPas 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 PDFlibPas 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, PDFlibPas poskusi znova z vrednostjo, ki jo je upravljavec pravkar zapisal v Password
V zanki ponovnih poskusov: nov TPDFDocument za vsak poskus
Interno PDFlibPas 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
// 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;
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 PDFlibPas ponovil napačno geslo?
PDFlibPas 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 PDFlibPas 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 PDFlibPas 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. PDFlibPas 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: PDFlibPas 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 PDFlibPas-u za dokument, zaščiten z geslom, posredujete TFileStream ali TMemoryStream, ga med ponovnimi poskusi ni treba previjati; PDFlibPas ž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 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 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 PDFlibPas izpostavi, ko se takšna datoteka naloži
Ponovni poskus gesla je tudi ozek primer širše discipline, ki jo PDFlibPas 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 PDFlibPas 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