PDFlibPas thử lại một mật khẩu sai trên một PDF đã mã hóa bằng cách loại bỏ TPDFDocument vừa thất bại và tạo một cái hoàn toàn mới cho lần thử tiếp theo, điều khiển bởi một callback OnPassword (TPDFlibPasswordEvent) chạy tối đa mười sáu lần trước khi bỏ cuộc. Đó là một sự khác biệt có chủ đích so với bản năng mà hầu hết lập trình viên Delphi tìm đến đầu tiên: giữ đối tượng tài liệu đã nằm sẵn trong bộ nhớ, đưa cho nó một mật khẩu đã sửa, và nạp lại tại chỗ thay vì bắt đầu lại từ hư vô. Vòng lặp thử lại của PDFlibPas, thêm vào trong v3.245.0, đứng ở vị trí ngược lại, vì các lý do cụ thể với những gì một lần thử mật khẩu thất bại để lại. Kịch bản đứng sau nó đủ thông thường đến mức hầu hết các ứng dụng Delphi nặng tài liệu gặp phải nó cuối cùng: một màn hình tiếp nhận chấp nhận một PDF, một trailer đã mã hóa buộc một hộp thoại mật khẩu, người vận hành gõ nhầm chuỗi, và hộp thoại xuất hiện lại cho lần thử thứ hai. Không có gì về trải nghiệm người dùng đó bất thường, nên code đứng sau nó phải chấp nhận nhiều hơn một mật khẩu ứng viên cho cùng một file, và nó phải làm điều đó an toàn, không rò rỉ trạng thái từ lần thử bị từ chối vào lần thử theo sau
Vì sao bạn không thể chỉ thử lại trên cùng một đối tượng tài liệu?
Việc tái sử dụng một TPDFDocument qua các lần thử mật khẩu không hoạt động, vì một lần thử thất bại đã phá dỡ đối tượng đó nội bộ thay vì để nó ở một trạng thái tạm dừng, có thể tiếp tục nào đó. Việc mở một PDF đã mã hóa có nghĩa là phân tích bảng cross-reference, dựng một reader trên nguồn bên dưới, và xây dựng một crypt handler từ bất cứ mật khẩu nào được cung cấp, tất cả trước khi PDFlibPas thậm chí có thể test liệu mật khẩu đó có đúng hay không. Khi mật khẩu hóa ra sai, routine nạp nội bộ của tài liệu dọn dẹp reader, bảng cross-reference, và crypt handler như một phần của việc thất bại, đúng như nó nên làm, nghĩa là không có trình phân tích xây dựng dở dang nào nằm đó chờ một mật khẩu đã sửa cho một lệnh gọi thứ hai. Đưa chính đối tượng đó qua một lần thử nạp khác dù sao đi nữa và chế độ lỗi chính xác là loại khốn khổ để debug: một lỗi lộ ra từ trạng thái nội bộ được xây dựng cho một lần phân tích khác, đã thất bại, mà không có gì rõ ràng trỏ ngược lại mật khẩu ba lệnh gọi ở thượng nguồn. PDFlibPas tránh toàn bộ nhóm vấn đề đó bằng cách không bao giờ cố phục hồi một đối tượng tài liệu một khi nó đã thất bại trong việc mở; mỗi lần thử nhận được một tài liệu chưa bao giờ thấy một mật khẩu sai, gồm cả reader và bảng cross-reference
Callback OnPassword yêu cầu mật khẩu tiếp theo như thế nào?
TPDFlibPasswordEvent là kiểu callback mà PDFlibPas gọi thông qua TPDFlib.LoadFromFile, LoadFromStream, và LoadFromString bất cứ khi nào mật khẩu vừa thử hóa ra sai, và nó đưa cho handler ba thứ: lần thử nào sắp chạy, một tham số Password để ghi đè bằng ứng viên tiếp theo, và một cờ Retry mặc định là false
TPDFlibPasswordEvent = procedure(Sender: TObject; AttemptNumber: Integer;
var Password: WideString; var Retry: Boolean) of object;
property OnPassword: TPDFlibPasswordEvent read FOnPassword write FOnPassword;
Mật khẩu được truyền vào lệnh gọi LoadFromFile gốc tính là lần thử một, nên lần đầu tiên OnPassword phát ra, AttemptNumber đến là 2. Để Retry chưa đặt và lượt nạp thất bại sạch sẽ với LastErrorCode 404; đặt nó thành true và PDFlibPas thử lại với bất cứ thứ gì handler vừa ghi vào Password
Bên trong vòng lặp thử lại: một TPDFDocument mới cho mỗi lần thử
Nội bộ, PDFlibPas trả lời câu hỏi vòng đời đối tượng theo cùng cách cho LoadFromFile, LoadFromStream, và LoadFromString: mỗi lần thử, kể cả lần đầu, dựng một TPDFDocument mới, chạy nó qua toàn bộ chuỗi mở với bất cứ mật khẩu nào lần thử đó đang dùng, và chỉ giữ đối tượng nếu mật khẩu xác minh được. TPDFDocument của một lần thử bị từ chối được giải phóng ngay lập tức, kéo theo reader, bảng cross-reference, và crypt handler của nó, và lần thử tiếp theo bắt đầu lại với một đối tượng hoàn toàn không có lịch sử
// 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;
Dòng Doc := nil ngay trước khối Finally đó là toàn bộ hợp đồng vòng đời đối tượng trong một câu lệnh. Một tài liệu thất bại mang theo trạng thái trình phân tích xây dựng dở dang của nó xuống mồ, theo thiết kế, và một tài liệu thành công là tài liệu duy nhất từng được thêm vào FDocs, tập hợp mà TPDFlib giữ cho mỗi tài liệu caller đang mở. Không có gì về một lần thử bị từ chối hiển thị từ bên ngoài vòng lặp thử lại: không một reader khởi tạo dở dang, không một số trang cũ, không một crypt handler xây dựng từ sai khóa
PDFlibPas sẽ thử lại một mật khẩu sai bao nhiêu lần?
PDFlibPas cho phép tổng cộng mười sáu lần thử trên một lệnh gọi LoadFromFile, LoadFromStream, hay LoadFromString duy nhất, tính cả mật khẩu được truyền vào chính lệnh gọi đó là lần thử một. OnPassword chỉ bao giờ phát ra cho các lần thử hai đến mười sáu, giới hạn callback ở mười lăm lần gọi; yêu cầu một lần thử thứ mười bảy và PDFlibPas từ chối mà thậm chí không gọi handler. Để Retry ở mặc định false tại bất kỳ điểm nào, hay dùng hết cả mười sáu lần thử mà không có mật khẩu đúng, và LoadFromFile trả về 0 với LastErrorCode đặt thành 404, mã lỗi của PDFlibPas cho một mật khẩu bị từ chối. Trần đó tồn tại vì các lý do vượt ra ngoài sự gọn gàng: một vòng lặp thử lại không giới hạn là một cách dễ dàng để biến một mật khẩu gõ nhầm thành một cuộc từ chối dịch vụ vô tình trên bất cứ luồng nào đang chạy việc nạp, đặc biệt một khi một handler được nối với thứ gì đó tự động, như một danh sách các mật khẩu đã thấy trước đó, thay vì một con người nhấp qua một hộp thoại. PDFlibPas cũng tôn trọng Abort được gọi trên instance TPDFlib từ bên trong handler, vì Sender đến là chính đối tượng đó, hữu ích đằng sau một nút Cancel trên một hộp thoại mật khẩu, và dừng vòng lặp thử lại ở lượt kiểm tra tiếp theo bất kể Retry đã được đặt là gì. Một lượt nạp thất bại vì một lý do khác ngoài mật khẩu sai, một bảng cross-reference bị hỏng chẳng hạn, không bao giờ vào vòng lặp thử lại: PDFlibPas báo cáo LastErrorCode 401 và dừng sau lần thử đầu tiên, vì không số lượng đoán mật khẩu nào sửa được một file hỏng về mặt cấu trúc
Vòng lặp thử lại có hoạt động giống nhau cho file, stream, và chuỗi không?
Callback OnPassword và trần mười sáu lần thử hành xử giống hệt nhau qua LoadFromFile, LoadFromStream, và LoadFromString, dù ba điểm vào giữ nguồn của chúng khác nhau giữa các lần thử. Một đường dẫn file rẻ để thăm lại, vì mỗi lần thử đơn giản mở lại file có tên, và một nguồn chuỗi đã nằm sẵn trong bộ nhớ như bản sao riêng của caller, nên không cái nào cần trợ giúp gì từ caller giữa các lần thử. Một stream do caller cung cấp là trường hợp duy nhất đáng dừng lại: LoadFromStream tìm stream đó trở lại vị trí không và copy nó nội bộ trước lần thử phân tích đầu tiên, nên mỗi lần thử tiếp theo, và TPDFDocument vừa được dựng đằng sau nó, phát lại từ bản copy nội bộ đó thay vì từ bất cứ đâu một lần phân tích thất bại đã để lại vị trí của stream. Đưa cho PDFlibPas một TFileStream hay TMemoryStream cho một tài liệu được bảo vệ mật khẩu và không cần tua lại nó giữa các lần thử lại; PDFlibPas đã tính đến một vị trí mà một lần thử đầu tiên, thất bại có thể đã di chuyển
Đưa việc thử lại mật khẩu vào một màn hình tiếp nhận tài liệu
Một luồng công việc tiếp nhận tài liệu là nhà tự nhiên cho callback này, vì nó chính xác là hình dạng vấn đề mà OnPassword được xây dựng để giải quyết: một file đến từ bên ngoài ứng dụng, mật khẩu của nó không được biết chắc chắn trước, và người cung cấp ứng viên cần nhiều hơn một lần đoán mà không cần code xung quanh tự viết vòng lặp thử lại của riêng nó quanh 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 chỉ bao giờ nhận Lib một khi LoadFromFile đã trả về 1, nghĩa là một mật khẩu nào đó trong cuộc trao đổi đó thực sự xác minh được với crypt handler của file; một lần thử bị từ chối không bao giờ đến được dòng đó, và một tài liệu mở dở dang cũng vậy. Điều tiếp theo, một khi một tài liệu như thế này được xác nhận mở, đáng để nhìn lại lần thứ hai vào các thiết lập bảo vệ của nó thay vì một giả định rằng mật khẩu đã hoạt động là toàn bộ câu chuyện bảo mật: kiểm toán những gì từ điển /Encrypt của một tài liệu thực sự khai báo nói đến việc đọc thuật toán, revision, và các bit quyền mà PDFlibPas phơi bày một khi một file như thế này nạp
Việc thử lại mật khẩu cũng là một trường hợp hẹp của một kỷ luật rộng hơn mà PDFlibPas áp dụng xuyên suốt lớp phân tích của nó: một file chưa tự chứng minh được không nhận được lợi ích của sự nghi ngờ, bất kể câu hỏi là mật khẩu nào mở khóa nó hay liệu một trường độ dài bên trong nó có đang nói dối về kích thước bộ đệm nó cần hay không. Gia cố một trình phân tích PDF Pascal chống lại các file độc hại nói đến nửa còn lại của kỷ luật đó, các bộ giải mã coi mỗi chương trình font và luồng ảnh trong một PDF đến như đầu vào đối kháng thay vì một tài liệu được định dạng tốt chỉ đơn giản quên mật khẩu của nó
OnPassword và vòng lặp thử lại đứng sau nó là một phần của thư viện PDF PDFlibPas tiêu chuẩn dành cho Delphi và C++Builder, có sẵn ở bất cứ đâu LoadFromFile, LoadFromStream, hay LoadFromString đã có sẵn, không cần module riêng hay cấp giấy phép riêng nào cho một tài liệu chỉ cần một lần đoán thứ hai với mật khẩu của nó