PDF Library for Delphi повторює неправильний пароль зашифрованого PDF, відкидаючи TPDFDocument, що щойно провалився, і створюючи цілком новий для наступної спроби, керований колбеком OnPassword (TPDFlibPasswordEvent), що виконується до шістнадцяти разів, перш ніж здатися. Це навмисне відхилення від інстинкту, до якого більшість розробників Delphi звертаються першим: тримати об'єкт документа, що вже сидить у пам'яті, подати йому виправлений пароль і завантажити знову на місці замість того, щоб починати з нуля. Цикл повторних спроб PDF Library for Delphi, доданий у v3.245.0, займає протилежну позицію, з причин, специфічних для того, що залишає невдала спроба пароля. Сценарій за цим достатньо звичайний, що більшість застосунків, насичених документами, на Delphi врешті-решт натрапляють на нього: екран прийому приймає PDF, зашифрований трейлер змушує з'явитися діалог пароля, оператор помиляється пальцем у рядку, і діалог з'являється знову для другої спроби. Ніщо в цьому досвіді користувача не незвичайне, тож код за ним мусить приймати більше однієї кандидатки пароля для того самого файлу, і мусить робити це безпечно, не витікаючи стан відхиленої спроби в наступну
Чому не можна просто повторити спробу на тому самому об'єкті документа?
Повторне використання TPDFDocument через спроби пароля не працює, бо невдала спроба вже зруйнувала цей об'єкт внутрішньо, а не залишила його в якомусь призупиненому, відновному стані. Відкриття зашифрованого PDF означає розбір таблиці перехресних посилань, побудову читача над базовим джерелом та конструювання обробника шифрування з будь-якого поданого пароля, і все це ще до того, як PDF Library for Delphi взагалі може перевірити, чи цей пароль правильний. Коли пароль виявляється неправильним, внутрішня процедура завантаження документа очищає читач, таблицю перехресних посилань та обробник шифрування як частину провалу, точно так, як має бути, а це означає, що не залишається напівпобудованого парсера, що чекав би на виправлений пароль при другому виклику. Проведіть той самий об'єкт через ще одну спробу завантаження все одно, і режим збою точно того типу, що жалюгідно налагоджувати: помилка проявляється з внутрішнього стану, побудованого для іншого, уже провального розбору, з нічим у ній, що очевидно вказувало б назад на пароль трьома викликами вище за течією. PDF Library for Delphi уникає всього цього класу проблем, ніколи не намагаючись відновити об'єкт документа, щойно той провалив відкриття; кожна спроба отримує документ, що ніколи не бачив неправильного пароля, включно з читачем і таблицею перехресних посилань
Як колбек 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 відхиленої спроби негайно звільняється, забираючи з собою свій читач, таблицю перехресних посилань та обробник шифрування, і наступна спроба починається заново з об'єктом, що взагалі не має історії
// Спрощений витяг зсередини 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; // читач, таблиця перехресних посилань відхиленої спроби
End; // та обробник шифрування руйнуються саме тут
if Success or (LoadResult <> lrWrongPassword) then
Break; // успіх або помилка, що не пов'язана з паролем: зупинка
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
Той рядок Doc := nil прямо перед блоком Finally — весь контракт життєвого циклу об'єкта в одному операторі. Документ, що провалюється, за задумом забирає свій напівпобудований стан парсера в могилу разом із собою, а документ, що вдається, — єдиний, що коли-небудь додається до FDocs, колекції, яку TPDFlib тримає для кожного документа, що викликач має відкритим. Ніщо з відхиленої спроби не видно ззовні циклу повторних спроб: ні напівініціалізований читач, ні застаріла кількість сторінок, ні обробник шифрування, побудований із неправильного ключа
Скільки разів 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 і зупиняється після першої спроби, бо жодна кількість вгадувань пароля не виправить структурно зламаний файл
Чи цикл повторних спроб працює однаково для файлів, потоків та рядків?
Колбек OnPassword та обмеження шістнадцяти спроб поводяться ідентично через LoadFromFile, LoadFromStream та LoadFromString, хоча ці три точки входу тримають своє джерело по-різному між спробами. Шлях файлу дешево переглянути знову, оскільки кожна спроба просто повторно відкриває названий файл, а джерело-рядок уже сидить у пам'яті як власна копія викликача, тож жодному з них не потрібна жодна допомога від викликача між спробами. Потік, наданий викликачем, — той єдиний випадок, вартий паузи: LoadFromStream перемотує цей потік назад у позицію нуль і копіює його внутрішньо перед першою спробою розбору, тож кожна наступна спроба, і щойно сконструйований TPDFDocument за нею, відтворюється з цієї внутрішньої копії, а не з того місця, де провалений розбір залишив позицію потоку. Передайте 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 документа охоплює читання алгоритму, ревізії та бітів дозволів, які відкриває PDF Library for Delphi, щойно такий файл завантажується
Повторна спроба пароля також — вузький приклад ширшої дисципліни, яку PDF Library for Delphi застосовує по всьому своєму шару розбору: файл, що ще не довів себе, не отримує жодної переваги сумніву, чи питання в тому, який пароль його розблоковує, чи в тому, чи поле довжини всередині нього бреше про розмір буфера, який йому потрібен. Загартування парсера PDF на Pascal проти зловмисних файлів охоплює іншу половину цієї дисципліни, декодери, що трактують кожну програму шрифту та потік зображення у вхідному PDF як ворожий вхід, а не коректно сформований документ, що просто забув свій пароль
OnPassword та цикл повторних спроб за ним — частина стандартної бібліотеки PDF PDF Library for Delphi для Delphi та C++Builder, доступної всюди, де вже доступні LoadFromFile, LoadFromStream чи LoadFromString, без потреби в окремому модулі чи рівні ліцензії для документа, якому просто потрібна друга спроба вгадати свій пароль