Το PDF Library for Delphi ξαναδοκιμάζει έναν λανθασμένο κωδικό σε ένα κρυπτογραφημένο PDF απορρίπτοντας το TPDFDocument που μόλις απέτυχε και δημιουργώντας ένα εντελώς νέο για την επόμενη προσπάθεια, οδηγούμενο από μια επιστροφή κλήσης OnPassword (TPDFlibPasswordEvent) που τρέχει έως δεκαέξι φορές πριν παραιτηθεί. Αυτή είναι μια σκόπιμη απόκλιση από το ένστικτο που έχουν οι περισσότεροι προγραμματιστές Delphi: κρατήστε το αντικείμενο εγγράφου που ήδη κάθεται στη μνήμη, τροφοδοτήστε του έναν διορθωμένο κωδικό, και φορτώστε ξανά επί τόπου αντί να ξεκινήσετε από το μηδέν. Ο βρόχος επανάληψης του PDF Library for Delphi, που προστέθηκε στην v3.245.0, παίρνει την αντίθετη θέση, για λόγους ειδικούς σχετικά με το τι αφήνει πίσω μια αποτυχημένη προσπάθεια κωδικού. Το σενάριο πίσω από αυτό είναι αρκετά συνηθισμένο ώστε οι περισσότερες εφαρμογές Delphi με πολλά έγγραφα να το συναντούν τελικά: μια οθόνη εισαγωγής δέχεται ένα PDF, ένα κρυπτογραφημένο trailer αναγκάζει έναν διάλογο κωδικού, ο χειριστής πληκτρολογεί λάθος τη συμβολοσειρά, και ο διάλογος εμφανίζεται ξανά για δεύτερη προσπάθεια. Τίποτα σε αυτή την εμπειρία χρήστη δεν είναι ασυνήθιστο, οπότε ο κώδικας πίσω της πρέπει να δέχεται περισσότερους από έναν υποψήφιους κωδικούς για το ίδιο αρχείο, και πρέπει να το κάνει με ασφάλεια, χωρίς να διαρρέει κατάσταση από την απορριφθείσα προσπάθεια σε αυτή που ακολουθεί
Γιατί δεν μπορείτε απλώς να ξαναδοκιμάσετε στο ίδιο αντικείμενο εγγράφου;
Η επαναχρησιμοποίηση ενός TPDFDocument σε πολλές προσπάθειες κωδικού δεν λειτουργεί, γιατί μια αποτυχημένη προσπάθεια έχει ήδη κατεδαφίσει εσωτερικά εκείνο το αντικείμενο αντί να το αφήσει σε κάποια παυμένη, επαναλήψιμη κατάσταση. Το άνοιγμα ενός κρυπτογραφημένου PDF σημαίνει ανάλυση του πίνακα διαγώνιων αναφορών, χτίσιμο ενός reader πάνω από την υποκείμενη πηγή, και κατασκευή ενός χειριστή κρυπτογράφησης από ό,τι κωδικό δόθηκε, όλα πριν το PDF Library for Delphi μπορέσει έστω να ελέγξει αν εκείνος ο κωδικός είναι σωστός. Όταν ο κωδικός αποδειχθεί λάθος, η εσωτερική ρουτίνα φόρτωσης του εγγράφου καθαρίζει τον reader, τον πίνακα διαγώνιων αναφορών, και τον χειριστή κρυπτογράφησης ως μέρος της αποτυχίας, ακριβώς όπως πρέπει, κάτι που σημαίνει ότι δεν υπάρχει κανένας μισοχτισμένος parser να περιμένει έναν διορθωμένο κωδικό σε δεύτερη κλήση. Οδηγήστε ούτως ή άλλως το ίδιο αντικείμενο μέσα από άλλη προσπάθεια φόρτωσης και ο τρόπος αποτυχίας είναι ακριβώς το είδος που είναι άθλιο να γίνει debug: ένα σφάλμα εμφανίζεται από εσωτερική κατάσταση χτισμένη για μια διαφορετική, ήδη αποτυχημένη ανάλυση, χωρίς τίποτα σε αυτό να δείχνει προφανώς πίσω στον κωδικό τρεις κλήσεις πιο πάνω. Το PDF Library for Delphi αποφεύγει ολόκληρη αυτή την κατηγορία προβλήματος μη προσπαθώντας ποτέ να ανακτήσει ένα αντικείμενο εγγράφου μόλις αποτύχει να ανοίξει· κάθε προσπάθεια αποκτά ένα έγγραφο που ποτέ δεν έχει δει λάθος κωδικό, reader και πίνακα διαγώνιων αναφορών συμπεριλαμβανομένων
Πώς ζητά η επιστροφή κλήσης OnPassword τον επόμενο κωδικό;
Το TPDFlibPasswordEvent είναι ο τύπος επιστροφής κλήσης που καλεί το PDF Library for Delphi μέσω των TPDFlib.LoadFromFile, LoadFromStream και LoadFromString όποτε ο κωδικός που μόλις δοκιμάστηκε αποδεικνύεται λάθος, και παραδίδει στον χειριστή τρία πράγματα: ποια προσπάθεια πρόκειται να τρέξει, μια παράμετρο Password προς αντικατάσταση με τον επόμενο υποψήφιο, και μια σημαία Retry που έχει προεπιλογή false
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Ο κωδικός που περνιέται στην αρχική κλήση LoadFromFile μετράει ως πρώτη προσπάθεια, οπότε την πρώτη φορά που πυροδοτείται καν το OnPassword, το AttemptNumber φτάνει ως 2. Αφήστε το Retry ανεπηρέαστο και η φόρτωση αποτυγχάνει καθαρά με LastErrorCode 404· θέστε το true και το PDF Library for Delphi ξαναδοκιμάζει με ό,τι μόλις έγραψε ο χειριστής στο Password
Μέσα στον Βρόχο Επανάληψης: Ένα Νέο TPDFDocument για Κάθε Προσπάθεια
Εσωτερικά, το PDF Library for Delphi απαντά στο ερώτημα κύκλου ζωής αντικειμένου με τον ίδιο τρόπο για τις LoadFromFile, LoadFromStream και LoadFromString: κάθε προσπάθεια, συμπεριλαμβανομένης της πρώτης, κατασκευάζει ένα φρέσκο TPDFDocument, το τρέχει μέσα από την πλήρη ακολουθία ανοίγματος με όποιον κωδικό χρησιμοποιεί εκείνη η προσπάθεια, και κρατά το αντικείμενο μόνο αν ο κωδικός επαληθευτεί. Το TPDFDocument μιας απορριφθείσας προσπάθειας απελευθερώνεται αμέσως, παρασύροντας μαζί του τον reader, τον πίνακα διαγώνιων αναφορών, και τον χειριστή κρυπτογράφησης, και η επόμενη προσπάθεια ξεκινά από την αρχή με ένα αντικείμενο που δεν έχει καθόλου ιστορικό
// Απλοποιημένο απόσπασμα από μέσα στη LoadFromFile: κάθε προσπάθεια παίρνει
// ένα έγγραφο που δεν έχει δει ποτέ έναν προηγουμένως απορριφθέντα κωδικό. Τα FileName,
// AttemptNumber και AttemptPassword προέρχονται από τη μέθοδο που περιέχει τον κώδικα.
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); // παραδώστε το επαληθευμένο έγγραφο στη
Doc := nil; // συλλογή του καλούντος· παραλείψτε το Free παρακάτω
end;
Finally
Doc.Free; // ο reader, ο πίνακας διαγώνιων αναφορών
End; // και ο χειριστής κρυπτογράφησης μιας απορριφθείσας προσπάθειας καταστρέφονται ακριβώς εδώ
if Success or (LoadResult <> lrWrongPassword) then
Break; // επιτυχία, ή αποτυχία που δεν οφείλεται σε κωδικό: διακοπή
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
Εκείνη η γραμμή Doc := nil ακριβώς πριν το μπλοκ Finally είναι ολόκληρο το συμβόλαιο κύκλου ζωής αντικειμένου σε μία εντολή. Ένα έγγραφο που αποτυγχάνει παίρνει την μισοχτισμένη κατάσταση του parser του στον τάφο μαζί του, εκ σχεδιασμού, και ένα έγγραφο που πετυχαίνει είναι το μόνο που προστίθεται ποτέ στο FDocs, τη συλλογή που κρατά η TPDFlib για κάθε έγγραφο που έχει ανοιχτό ο καλών. Τίποτα από μια απορριφθείσα προσπάθεια δεν είναι ορατό από έξω από τον βρόχο επανάληψης: ούτε ένας μισό-αρχικοποιημένος reader, ούτε ένας παλιωμένος μετρητής σελίδων, ούτε ένας χειριστής κρυπτογράφησης χτισμένος από λάθος κλειδί
Πόσες Φορές θα Ξαναδοκιμάσει το PDF Library for Delphi έναν Λάθος Κωδικό;
Το PDF Library for Delphi επιτρέπει δεκαέξι συνολικές προσπάθειες ενάντια σε μία μόνη κλήση LoadFromFile, LoadFromStream, ή LoadFromString, μετρώντας τον κωδικό που περάστηκε στην ίδια την κλήση ως πρώτη προσπάθεια. Το OnPassword πυροδοτείται μόνο για τις προσπάθειες δύο έως δεκαέξι, κάτι που περιορίζει την επιστροφή κλήσης σε δεκαπέντε επικλήσεις· ζητήστε δέκατη έβδομη προσπάθεια και το PDF Library for Delphi αρνείται χωρίς να καλέσει καν τον χειριστή. Αφήστε το Retry στην προεπιλογή του false οποιαδήποτε στιγμή, ή εξαντλήστε και τις δεκαέξι προσπάθειες χωρίς σωστό κωδικό, και η LoadFromFile επιστρέφει 0 με το LastErrorCode να τίθεται σε 404, τον κωδικό του PDF Library for Delphi για απορριφθέντα κωδικό. Το ανώτατο όριο υπάρχει για λόγους πέρα από την τακτοποίηση: ένας απεριόριστος βρόχος επανάληψης είναι ένας εύκολος τρόπος να μετατραπεί ένας λάθος πληκτρολογημένος κωδικός σε τυχαία άρνηση εξυπηρέτησης ενάντια σε όποιο νήμα τρέχει τη φόρτωση, ειδικά μόλις ένας χειριστής συνδεθεί με κάτι αυτοματοποιημένο, όπως μια λίστα προηγουμένως δοκιμασμένων κωδικών, αντί για έναν άνθρωπο να κάνει κλικ σε έναν διάλογο. Το PDF Library for Delphi τιμά επίσης την Abort που καλείται στην παρουσία TPDFlib από μέσα από τον χειριστή, αφού το Sender φτάνει ως το ίδιο αντικείμενο, χρήσιμο πίσω από ένα κουμπί Ακύρωση σε έναν διάλογο κωδικού, και σταματά τον βρόχο επανάληψης στον επόμενο έλεγχο ανεξάρτητα από το τι είχε τεθεί το Retry. Μια φόρτωση που αποτυγχάνει για λόγο διαφορετικό από λάθος κωδικό, έναν κατεστραμμένο πίνακα διαγώνιων αναφορών για παράδειγμα, ποτέ δεν εισέρχεται καν στον βρόχο επανάληψης: το PDF Library for Delphi αναφέρει LastErrorCode 401 και σταματά μετά την πρώτη προσπάθεια, γιατί κανένας αριθμός μαντεψιών κωδικού δεν διορθώνει ένα δομικά κατεστραμμένο αρχείο
Δουλεύει ο Βρόχος Επανάληψης το Ίδιο για Αρχεία, Streams και Συμβολοσειρές;
Η επιστροφή κλήσης OnPassword και το ανώτατο όριο δεκαέξι προσπαθειών συμπεριφέρονται πανομοιότυπα στις LoadFromFile, LoadFromStream και LoadFromString, αν και τα τρία σημεία εισόδου κρατούν την πηγή τους διαφορετικά μεταξύ προσπαθειών. Μια διαδρομή αρχείου είναι φθηνή να ξαναεπισκεφθεί, αφού κάθε προσπάθεια απλώς ξανανοίγει το ονομασμένο αρχείο, και μια πηγή συμβολοσειράς ήδη κάθεται στη μνήμη ως δικό του αντίγραφο του καλούντος, οπότε καμία από τις δύο δεν χρειάζεται καμία βοήθεια από τον καλούντα μεταξύ προσπαθειών. Ένα stream παρεχόμενο από τον καλούντα είναι η μία περίπτωση που αξίζει να σταθούμε: η LoadFromStream επαναφέρει εκείνο το stream στη θέση μηδέν και το αντιγράφει εσωτερικά πριν την πρώτη προσπάθεια ανάλυσης, οπότε κάθε επόμενη προσπάθεια, και το φρεσκοκατασκευασμένο TPDFDocument πίσω της, αναπαράγεται από εκείνο το εσωτερικό αντίγραφο αντί από όπου μια αποτυχημένη ανάλυση άφησε τη θέση του stream. Δώστε στο PDF Library for Delphi ένα TFileStream ή TMemoryStream για ένα έγγραφο προστατευμένο με κωδικό και δεν χρειάζεται να το επαναφέρετε μεταξύ επαναλήψεων· το PDF Library for Delphi ήδη λογαριάζει μια θέση που μια πρώτη, αποτυχημένη προσπάθεια μπορεί να έχει μετακινήσει
Ενσωματώνοντας την Επανάληψη Κωδικού σε μια Οθόνη Εισαγωγής Εγγράφων
Μια ροή εργασίας εισαγωγής εγγράφων είναι το φυσικό σπίτι για αυτή την επιστροφή κλήσης, γιατί είναι ακριβώς το σχήμα προβλήματος για το οποίο χτίστηκε το OnPassword να λύσει: ένα αρχείο φτάνει από έξω από την εφαρμογή, ο κωδικός του δεν είναι γνωστός με βεβαιότητα εκ των προτέρων, και το άτομο που παρέχει υποψηφίους χρειάζεται περισσότερες από μία μαντεψιές χωρίς ο περιβάλλων κώδικας να γράφει τον δικό του βρόχο επανάληψης γύρω από την LoadFromFile
procedure TIntakeForm.SupplyPassword(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean);
var
Typed: string;
begin
// Το AttemptNumber μετράει από το 2: ο κωδικός που είχε ήδη δοκιμαστεί ήταν η προσπάθεια 1.
Typed := '';
Retry := InputQuery('Password required',
Format('Attempt %d of 16 - enter the document password', [AttemptNumber]), Typed);
if Retry then
Password := Typed;
// Το Retry είναι False όταν ο χειριστής ακυρώνει, κάτι που αφήνει
// το LastErrorCode στο 404 για να το αναφέρει ο καλών.
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) // μόνο ένα επαληθευμένο έγγραφο φτάνει εδώ
else
LogRejectedIntake('inbound-invoice.pdf', Lib.LastErrorCode);
finally
Lib.Free;
end;
end;
Η RegisterIntakeDocument λαμβάνει ποτέ το Lib μόνο αφού η LoadFromFile έχει επιστρέψει 1, που σημαίνει ότι κάποιος κωδικός σε εκείνη την ανταλλαγή πράγματι επαληθεύτηκε ενάντια στον χειριστή κρυπτογράφησης του αρχείου· μια απορριφθείσα προσπάθεια ποτέ δεν φτάνει σε εκείνη τη γραμμή, ούτε φτάνει ένα μισό-ανοιχτό έγγραφο. Αυτό που ακολουθεί στη συνέχεια, μόλις ένα τέτοιο έγγραφο επιβεβαιωθεί ανοιχτό, αξίζει μια δεύτερη ματιά στις ρυθμίσεις προστασίας του παρά μια υπόθεση ότι ο κωδικός που δούλεψε είναι ολόκληρη η ιστορία ασφάλειας: ο έλεγχος του τι πράγματι δηλώνει το λεξικό /Encrypt ενός εγγράφου καλύπτει την ανάγνωση του αλγορίθμου, της αναθεώρησης, και των bits δικαιωμάτων που εκθέτει το PDF Library for Delphi μόλις φορτωθεί ένα τέτοιο αρχείο
Η επανάληψη κωδικού είναι επίσης μια στενή περίπτωση μιας ευρύτερης πειθαρχίας που εφαρμόζει το PDF Library for Delphi σε όλο το επίπεδο ανάλυσής του: ένα αρχείο που δεν έχει ακόμα αποδείξει τον εαυτό του δεν παίρνει το ευεργέτημα της αμφιβολίας, είτε το ερώτημα είναι ποιος κωδικός το ξεκλειδώνει είτε αν ένα πεδίο μήκους μέσα του ψεύδεται για το μέγεθος buffer που χρειάζεται. Το σκλήρυνση ενός parser PDF σε Pascal ενάντια σε κακόβουλα αρχεία καλύπτει το άλλο μισό αυτής της πειθαρχίας, τους αποκωδικοποιητές που αντιμετωπίζουν κάθε πρόγραμμα γραμματοσειράς και ροή εικόνας σε ένα εισερχόμενο PDF ως εχθρική είσοδο αντί για ένα καλοδιατυπωμένο έγγραφο που απλώς ξέχασε τον κωδικό του
Το OnPassword και ο βρόχος επανάληψης πίσω του είναι μέρος της τυπικής βιβλιοθήκης PDF PDF Library for Delphi για Delphi και C++Builder, διαθέσιμος οπουδήποτε είναι ήδη διαθέσιμες οι LoadFromFile, LoadFromStream, ή LoadFromString, χωρίς να απαιτείται ξεχωριστή μονάδα ή επίπεδο άδειας για ένα έγγραφο που απλώς χρειάζεται μια δεύτερη μαντεψιά στον κωδικό του