PDFlibPas prøver et feil passord på en kryptert PDF på nytt ved å forkaste den TPDFDocument-en som nettopp feilet og opprette en helt ny en for neste forsøk, drevet av en OnPassword-callback (TPDFlibPasswordEvent) som kjører for opptil seksten forsøk før den gir opp. Det er et bevisst avvik fra instinktet de fleste Delphi-utviklere griper til først: behold dokumentobjektet som allerede sitter i minnet, mat det med et korrigert passord, og last inn på nytt på stedet i stedet for å starte på nytt fra ingenting. PDFlibPas' retry-løkke, lagt til i v3.245.0, tar den motsatte posisjonen, av grunner spesifikke for hva et mislykket passordforsøk etterlater. Scenarioet bak den er vanlig nok til at de fleste dokument-tunge Delphi-applikasjoner treffer den til slutt: en inntaksskjerm aksepterer en PDF, en kryptert trailer tvinger frem en passorddialog, operatøren skriver feil i strengen, og dialogen dukker opp igjen for et andre forsøk. Ingenting ved den brukeropplevelsen er uvanlig, så koden bak den må akseptere mer enn ett kandidatpassord for den samme filen, og den må gjøre det trygt, uten å lekke tilstand fra det avviste forsøket inn i det som følger
Hvorfor kan man ikke bare prøve på nytt på det samme dokumentobjektet?
Å gjenbruke en TPDFDocument på tvers av passordforsøk fungerer ikke, fordi et mislykket forsøk allerede har revet det objektet ned internt i stedet for å la det stå i en pauset, gjenopptakbar tilstand. Å åpne en kryptert PDF betyr å parse kryssreferansetabellen, bygge en leser over den underliggende kilden, og konstruere en krypto-håndterer fra hvilket som helst passord som ble levert, alt før PDFlibPas i det hele tatt kan teste om det passordet er korrekt. Når passordet viser seg å være feil, rydder dokumentets interne last-rutine opp leseren, kryssreferansetabellen, og krypto-håndtereren som en del av å feile ut, nøyaktig slik den skal, noe som betyr at det ikke finnes noen halvbygget parser som sitter der og venter på et korrigert passord ved et andre kall. Kjør det samme objektet gjennom et annet lastforsøk uansett, og feilmodusen er nøyaktig den typen som er elendig å feilsøke: en feil dukker opp fra intern tilstand bygget for en annen, allerede mislykket parsing, uten at noe ved den åpenbart peker tilbake til passordet tre kall oppstrøms. PDFlibPas unngår hele den klassen av problem ved aldri å prøve å gjenopprette et dokumentobjekt når det først har feilet å åpne; hvert forsøk får et dokument som aldri har sett et feil passord, leser og kryssreferansetabell inkludert
Hvordan spør OnPassword-callbacken om neste passord?
TPDFlibPasswordEvent er callback-typen PDFlibPas påkaller gjennom TPDFlib.LoadFromFile, LoadFromStream, og LoadFromString hver gang passordet som nettopp ble prøvd, viser seg å være feil, og den gir håndtereren tre ting: hvilket forsøk som er i ferd med å kjøre, en Password-parameter å overskrive med neste kandidat, og et Retry-flagg som som standard er false
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Passordet sendt inn i det opprinnelige LoadFromFile-kallet teller som forsøk én, så første gang OnPassword i det hele tatt utløses, ankommer AttemptNumber som 2. La Retry stå usatt, og lastingen feiler rent med LastErrorCode 404; sett den til true, og PDFlibPas prøver igjen med hva som enn håndtereren nettopp skrev inn i Password
Inne i retry-løkken: en ny TPDFDocument for hvert forsøk
Internt svarer PDFlibPas på objekt-livssyklus-spørsmålet på samme måte for LoadFromFile, LoadFromStream, og LoadFromString: hvert forsøk, inkludert det første, konstruerer en fersk TPDFDocument, kjører den gjennom hele åpne-sekvensen med hvilket som helst passord det forsøket bruker, og beholder objektet bare hvis passordet verifiseres. Et avvist forsøks TPDFDocument frigjøres umiddelbart, og river ned leseren, kryssreferansetabellen, og krypto-håndtereren med seg, og det neste forsøket starter på nytt med et objekt som ikke har noen historie i det hele tatt
// 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;
Den Doc := nil-linjen rett før Finally-blokken, er hele objekt-livssyklus-kontrakten i én setning. Et dokument som feiler, bærer sin halvbygde parser-tilstand med seg i graven, med hensikt, og et dokument som lykkes, er det eneste som noensinne legges til i FDocs, samlingen TPDFlib holder for hvert dokument kalleren har åpent. Ingenting ved et avvist forsøk er synlig utenfra retry-løkken: ingen halvinitialisert leser, ingen utdatert sideantall, ingen krypto-håndterer bygget fra feil nøkkel
Hvor mange ganger vil PDFlibPas prøve et feil passord på nytt?
PDFlibPas tillater seksten totale forsøk mot ett enkelt LoadFromFile-, LoadFromStream-, eller LoadFromString-kall, og teller passordet sendt inn i selve kallet som forsøk én. OnPassword utløses bare noensinne for forsøk to til seksten, noe som gir callbacken et tak på femten påkallinger; be om et syttende forsøk, og PDFlibPas avslår uten engang å påkalle håndtereren. La Retry stå på sin standard false på noe tidspunkt, eller uttøm alle seksten forsøk uten et korrekt passord, og LoadFromFile returnerer 0 med LastErrorCode satt til 404, PDFlibPas' kode for et avvist passord. Taket finnes av grunner utover ryddighet: en ubegrenset retry-løkke er en lett måte å gjøre ett feiltastet passord om til en utilsiktet denial-of-service mot hvilken som helst tråd som kjører lastingen, spesielt når en håndterer er koblet til noe automatisert, som en liste over tidligere sette passord, snarere enn et menneske som klikker gjennom en dialog. PDFlibPas respekterer også Abort kalt på TPDFlib-instansen fra inne i håndtereren, ettersom Sender ankommer som det samme objektet, nyttig bak en Avbryt-knapp på en passorddialog, og stopper retry-løkken ved neste sjekk uansett hva Retry var satt til. En lasting som feiler av en annen grunn enn et feil passord, en skadet kryssreferansetabell for eksempel, går aldri inn i retry-løkken i det hele tatt: PDFlibPas rapporterer LastErrorCode 401 og stopper etter det første forsøket, fordi ingen mengde passordgjettinger fikser en strukturelt ødelagt fil
Fungerer retry-løkken likt for filer, strømmer, og strenger?
OnPassword-callbacken og seksten-forsøks-taket oppfører seg identisk på tvers av LoadFromFile, LoadFromStream, og LoadFromString, selv om de tre inngangspunktene holder på kilden sin forskjellig mellom forsøk. En filsti er billig å besøke på nytt, ettersom hvert forsøk ganske enkelt åpner den navngitte filen på nytt, og en strengkilde sitter allerede i minnet som kallerens egen kopi, så ingen av dem trenger noen hjelp fra kalleren mellom forsøk. En kaller-levert strøm er det ene tilfellet verdt å stoppe ved: LoadFromStream søker den strømmen tilbake til posisjon null og kopierer den internt før det første parse-forsøket, så hvert etterfølgende forsøk, og den ferskt konstruerte TPDFDocument-en bak det, spiller av på nytt fra den interne kopien i stedet for fra hvor enn et mislykket parse-forsøk etterlot strømmens posisjon. Overlever PDFlibPas en TFileStream eller TMemoryStream for et passordbeskyttet dokument, og det er ikke nødvendig å spole den tilbake mellom forsøk; PDFlibPas tar allerede høyde for en posisjon et første, mislykket forsøk kan ha flyttet
Å tilpasse passord-retry inn i en dokument-inntaksskjerm
En dokument-inntaks-arbeidsflyt er det naturlige hjemmet for denne callbacken, fordi det er nøyaktig den formen problem OnPassword ble bygget for å løse: en fil ankommer utenfra applikasjonen, passordet dets er ikke kjent med sikkerhet på forhånd, og personen som leverer kandidater, trenger mer enn ett gjettforsøk uten at den omkringliggende koden skriver sin egen retry-løkke rundt LoadFromFile
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 mottar bare noensinne Lib når LoadFromFile har returnert 1, noe som betyr at et passord i den utvekslingen faktisk verifiserte mot filens krypto-håndterer; et avvist forsøk når aldri den linjen, og det gjør heller ikke et halvåpent dokument. Hva som kommer neste, når et dokument som dette er bekreftet åpent, er verdt et andre blikk på beskyttelsesinnstillingene dets, snarere enn en antakelse om at passordet som virket, er hele sikkerhetshistorien: å revidere hva et dokuments /Encrypt-ordbok faktisk deklarerer dekker å lese algoritmen, revisjonen, og tillatelsesbitene PDFlibPas eksponerer når en fil som denne lastes inn
Passord-retry er også en snever instans av en bredere disiplin PDFlibPas anvender gjennom hele parsingslaget sitt: en fil som ennå ikke har bevist seg selv, får ingen tvilens fordel, uansett om spørsmålet er hvilket passord som låser den opp, eller om et lengdefelt inni den lyver om størrelsen på bufferen den trenger. Å herde en Pascal-PDF-parser mot ondsinnede filer dekker den andre halvparten av den disiplinen, dekoderne som behandler hvert skrifttypeprogram og bildestrøm i en innkommende PDF som fiendtlig inndata snarere enn et velformet dokument som bare glemte passordet sitt
OnPassword og retry-løkken bak den er en del av den standard PDFlibPas PDF-biblioteket for Delphi og C++Builder, tilgjengelig overalt LoadFromFile, LoadFromStream, eller LoadFromString allerede er, uten at noen separat modul eller lisensklasse kreves for et dokument som bare trenger et andre gjettforsøk på passordet sitt