PDF Library for Delphi یک رمز عبور اشتباه روی یک PDF رمزنگاریشده را با دورریختن TPDFDocumentای که همین الان شکست خورده و ساختن یک عدد کاملاً جدید برای تلاش بعدی، هدایتشده با یک callback به نام OnPassword (TPDFlibPasswordEvent) که تا شانزده تلاش پیش از تسلیمشدن اجرا میشود، تلاشمجدد میکند. این یک انحراف عمدی از غریزهای است که اغلب توسعهدهندگان Delphi ابتدا بهسراغش میروند: شیء سند که از پیش در حافظه نشسته را نگه دارید، یک رمز عبور تصحیحشده به آن بدهید، و دوباره درجا بارگذاری کنید بهجای شروع دوباره از هیچ. حلقهی تلاشمجدد PDF Library for Delphi، افزودهشده در v3.245.0، موضع مخالف را میگیرد، به دلایلی مشخص برای آنچه یک تلاش رمز عبور شکستخورده پشت سر میگذارد. سناریوی پشت آن بهاندازهی کافی معمولی است که اغلب برنامههای سند-محور Delphi در نهایت به آن برمیخورند: یک صفحهی دریافت یک PDF میپذیرد، یک trailer رمزنگاریشده یک دیالوگ رمز عبور را اجبار میکند، اپراتور رشته را اشتباه تایپ میکند، و دیالوگ برای یک تلاش دوم دوباره ظاهر میشود. هیچ چیزی دربارهی آن تجربهی کاربری غیرمعمول نیست، پس کد پشتش باید بیش از یک رمز عبور کاندید برای همان فایل بپذیرد، و باید آن کار را امن انجام دهد، بدون نشتدادن وضعیت از تلاش ردشده به تلاشی که بهدنبالش میآید
چرا نمیتوانید فقط روی همان شیء سند تلاشمجدد کنید؟
دوبارهاستفادهکردن از یک TPDFDocument در سراسر تلاشهای رمز عبور کار نمیکند، چون یک تلاش شکستخورده از پیش آن شیء را داخلاً باز کرده بهجای اینکه آن را در یک وضعیت متوقفشده و قابلازسرگیری رها کند. بازکردن یک PDF رمزنگاریشده یعنی تجزیهی جدول cross-reference، ساخت یک خواننده روی منبع زیرین، و ساخت یک crypt handler از هر رمز عبوری که تأمین شده، همه پیش از اینکه PDF Library for Delphi حتی بتواند بررسی کند آیا آن رمز عبور درست است. وقتی رمز عبور اشتباه درمیآید، روال بارگذاری داخلی سند خواننده، جدول cross-reference، و crypt handler را بهعنوان بخشی از شکستخوردن پاک میکند، دقیقاً همانطور که باید، که یعنی هیچ تجزیهگر نیمهساختهای آنجا منتظر یک رمز عبور تصحیحشده در یک فراخوانی دوم نمینشیند. همان شیء را در هر صورت از یک فراخوانی بارگذاری دیگر عبور دهید و حالت شکست دقیقاً همان نوعی است که برای دیباگکردن بدبختی است: یک خطا از وضعیت داخلی ساختهشده برای یک تجزیهی متفاوت و از پیش شکستخورده نمایان میشود، بدون هیچ چیزی که آشکارا به عقب به رمز عبور سه فراخوانی بالادست اشاره کند. PDF Library for Delphi کل ردهی مسئله را با اینکه هرگز تلاش نکند یک شیء سند را بهمحض شکستخوردن در بازشدن بازیابی کند اجتناب میکند؛ هر تلاش سندی میگیرد که هرگز یک رمز عبور اشتباه ندیده، خواننده و جدول cross-reference شامل
callback به نام OnPassword چطور رمز عبور بعدی را درخواست میکند؟
TPDFlibPasswordEvent نوع callbackای است که PDF Library for Delphi از طریق TPDFlib.LoadFromFile، LoadFromStream، و LoadFromString هر زمان که رمز عبور همین الان امتحانشده اشتباه درمیآید فرا میخواند، و سه چیز را به handler میسپارد: کدام تلاش در شرف اجراست، یک پارامتر 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 بهعنوان ۲ میرسد. Retry را تنظیمنشده رها کنید و بارگذاری تمیز با LastErrorCode برابر 404 شکست میخورد؛ آن را true تنظیم کنید و PDF Library for Delphi دوباره تلاش میکند با هر چیزی که handler همین الان درون Password نوشته
درون حلقهی تلاشمجدد: یک TPDFDocument جدید بهازای هر تلاش
داخلاً، PDF Library for Delphi سؤال چرخهی عمر شیء را به همان روش برای LoadFromFile، LoadFromStream، و LoadFromString پاسخ میدهد: هر تلاش، اولی شامل، یک TPDFDocument تازه میسازد، آن را از طریق دنبالهی کامل بازکردن با هر رمز عبوری که آن تلاش استفاده میکند اجرا میکند، و فقط اگر رمز عبور تأیید شود شیء را نگه میدارد. TPDFDocument یک تلاش ردشده بلافاصله آزاد میشود، خواننده، جدول cross-reference، و crypt handلرش را با خودش پایین میکشد، و تلاش بعدی با شیئی که اصلاً هیچ تاریخچهای ندارد از نو شروع میشود
// گزیدهی سادهشده از درون 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; // خواننده، جدول xref یک تلاش ردشده
End; // و crypt handler دقیقاً همینجا برچیده میشوند
if Success or (LoadResult <> lrWrongPassword) then
Break; // موفقیت، یا شکستی غیرمرتبط با رمز عبور: توقف
Inc(AttemptNumber);
Until not RequestPasswordRetry(AttemptNumber, AttemptPassword);
End;
آن خط Doc := nil دقیقاً پیش از بلوک Finally کل قرارداد چرخهی عمر شیء در یک عبارت است. سندی که شکست میخورد وضعیت تجزیهگر نیمهساختهاش را با خودش به گور میبرد، از نظر طراحی، و سندی که موفق میشود تنها سندی است که هرگز به FDocs، مجموعهای که TPDFlib برای هر سندی که فراخواننده باز دارد نگه میدارد، افزوده میشود. هیچ چیزی دربارهی یک تلاش ردشده از بیرون حلقهی تلاشمجدد قابلمشاهده نیست: نه یک خوانندهی نیمه-مقداردهیشده، نه یک شمار صفحهی کهنه، نه یک crypt handler ساختهشده از کلید اشتباه
PDF Library for Delphi چند بار یک رمز عبور اشتباه را تلاشمجدد میکند؟
PDF Library for Delphi شانزده تلاش کل را در برابر یک فراخوانی تکی LoadFromFile، LoadFromStream، یا LoadFromString مجاز میکند، با شمارش رمز عبوری که به خودِ فراخوانی پاس داده شده بهعنوان تلاش یک. OnPassword فقط برای تلاشهای دو تا شانزده شلیک میشود، که callback را به پانزده فراخوانی محدود میکند؛ یک تلاش هفدهم درخواست کنید و PDF Library for Delphi بدون حتی فراخوانی handler امتناع میکند. Retry را در هر نقطهای در پیشفرضش یعنی false رها کنید، یا هر شانزده تلاش را بدون یک رمز عبور درست خرج کنید، و LoadFromFile مقدار 0 را با LastErrorCode تنظیمشده روی 404، کد PDF Library for Delphi برای یک رمز عبور ردشده، برمیگرداند. سقف به دلایلی فراتر از تمیزی وجود دارد: یک حلقهی تلاشمجدد بدونحد یک راه آسان برای تبدیل یک رمز عبور اشتباهتایپشده به یک انکار سرویس تصادفی در برابر هر رشتهای که بارگذاری را اجرا میکند است، بهخصوص بهمحض اینکه یک handler به چیزی خودکار سیمکشی شود، مثل یک فهرست رمزهای عبور قبلاً دیدهشده، بهجای یک انسان که روی یک دیالوگ کلیک میکند. PDF Library for Delphi همچنین Abort فراخوانیشده روی نمونهی TPDFlib از درون handler را رعایت میکند، چون Sender بهعنوان همان شیء میرسد، مفید پشت یک دکمهی Cancel روی یک دیالوگ رمز عبور، و حلقهی تلاشمجدد را در بررسی بعدی صرفنظر از اینکه Retry به چه چیزی تنظیم شده متوقف میکند. یک بارگذاری که به دلیلی جز یک رمز عبور اشتباه شکست بخورد، مثلاً یک جدول cross-reference آسیبدیده، اصلاً هرگز وارد حلقهی تلاشمجدد نمیشود: PDF Library for Delphi LastErrorCode برابر 401 گزارش میدهد و پس از اولین تلاش متوقف میشود، چون هیچ تعداد حدس رمز عبور یک فایل ساختاری خراب را رفع نمیکند
آیا حلقهی تلاشمجدد برای فایلها، جریانها، و رشتهها یکسان کار میکند؟
callback به نام OnPassword و سقف شانزده-تلاشی در سراسر LoadFromFile، LoadFromStream، و LoadFromString یکسان رفتار میکنند، هرچند سه نقطه ورود منبعشان را بین تلاشها متفاوت نگه میدارند. یک مسیر فایل ارزان برای بازدید دوباره است، چون هر تلاش صرفاً فایل نامدادهشده را دوباره باز میکند، و یک منبع رشتهای از پیش بهعنوان کپی خودِ فراخواننده در حافظه مینشیند، پس هیچکدام به کمکی از فراخواننده بین تلاشها نیاز ندارند. یک جریان تأمینشدهی-فراخواننده همان یک موردی است که ارزش مکث دارد: LoadFromStream آن جریان را به موقعیت صفر برمیگرداند و آن را داخلاً پیش از اولین تلاش تجزیه کپی میکند، پس هر تلاش بعدی، و TPDFDocument تازهساختهشده پشتش، از آن کپی داخلی بازپخش میشود نه از هر جایی که یک تجزیهی شکستخورده موقعیت جریان را رها کرده. PDF Library for Delphi را یک TFileStream یا TMemoryStream برای یک سند محافظتشدهبا-رمزعبور بدهید و هیچ نیازی به rewindکردنش بین تلاشهای مجدد نیست؛ PDF Library for Delphi از پیش موقعیتی را که یک تلاش اول و شکستخورده ممکن است جابهجا کرده باشد در نظر میگیرد
جایدادن تلاشمجدد رمز عبور در یک صفحهی دریافت سند
یک گردشکار دریافت سند خانهی طبیعی این callback است، چون دقیقاً همان شکل مسئلهای است که 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 برگردانده باشد، بهمعنای اینکه یک رمز عبور در آن تبادل واقعاً در برابر crypt handler فایل تأیید شده؛ یک تلاش ردشده هرگز به آن خط نمیرسد، و نه یک سند نیمهباز. آنچه بعداً میآید، بهمحض اینکه یک سند مثل این تأیید-باز شود، ارزش یک نگاه دوم به تنظیمات محافظتش دارد بهجای فرضکردن اینکه رمز عبوری که کار کرد کل داستان امنیت است: ممیزی آنچه دیکشنری /Encrypt یک سند واقعاً اعلان میکند خواندن الگوریتم، revision، و بیتهای مجوز که PDF Library for Delphi بهمحض اینکه یک فایل مثل این بارگذاری شود در معرض دید میگذارد را پوشش میدهد
تلاشمجدد رمز عبور همچنین نمونهای محدود از انضباطی گستردهتر است که PDF Library for Delphi در سراسر لایهی تجزیهاش اعمال میکند: فایلی که هنوز خودش را ثابت نکرده هیچ فایدهای از تردید نمیگیرد، چه سؤال این باشد که کدام رمز عبور آن را باز میکند چه اینکه آیا یک فیلد طول درونش دربارهی اندازهی بافری که نیاز دارد دروغ میگوید. تقویت یک تجزیهگر PDF در Pascal در برابر فایلهای مخرب نیمهی دیگر آن انضباط را پوشش میدهد، رمزگشاهایی که هر برنامهی فونت و جریان تصویری در یک PDF ورودی را بهعنوان ورودی خصمانه در نظر میگیرند نه یک سند درستشکل که صرفاً رمز عبورش را فراموش کرده
OnPassword و حلقهی تلاشمجدد پشتش بخشی از کتابخانهی استاندارد PDF از PDF Library for Delphi برای Delphi و C++Builder هستند، در دسترس هرجایی که LoadFromFile، LoadFromStream، یا LoadFromString از پیش هستند، بدون هیچ ماژول جداگانه یا ردهی مجوز لازم برای سندی که فقط به یک حدس دوم برای رمز عبورش نیاز دارد