النموذج الذي يعاد حساب إجمالياته في Acrobat ولكنه يظل متجمداً في عارض Delphi الخاص بك لا يعتبر أبداً خطأ في العرض. حيث يقوم PDFium بتعطيل جميع نصوص جافا سكريبت الخاصة بالمستند ما لم يقم المضيف بإرفاق منصة JS، ويقوم مكون PDFium بتوصيل تلك المنصة تلقائياً من الإصدار v2.13.2 فصاعداً، لذلك يتم تنفيذ AcroForm JavaScript والنصوص البرمجية على مستوى المستند وحسابات الحقول كلما تم تحميل مكتبة DLL الممكنة لـ V8. ويظل المضيف مسؤولاً من خلال مجموعة من الأحداث التي يمكنها عرض أو إعادة توجيه أو نقض كل ما يحاول النص البرمجي فعله
هذه الجملة الواحدة تحل سؤال دعم رأيناه في عدة أشكال: "يحسب نموذج طلبي مجاميع الأسطر في Acrobat ولكن ليس في تطبيقي"، و"لا يتم تشغيل app.alert أبداً"، و"لا يقوم حقل التاريخ بتنسيق نفسه". وكلها تعود إلى نفس العقد، وفهمه يستحق عشر دقائق لأن نفس العقد هو أيضاً حد الأمان الخاص بك ضد المستندات العدائية
لماذا لا تعمل حسابات نموذج PDF في PDFium؟
يعامل PDFium لغة جافا سكريبت الخاصة بالمستند كخيار اشتراك صارم: إذا كان العضو m_pJsPlatform لبيئة تعبئة النموذج هو NULL، يتم تخطي كل نص برمجي في المستند بصمت. لا خطأ، ولا سطر سجل، ولا تنفيذ جزئي. وتحتفظ الحقول المحسوبة بآخر قيمة مخزنة لها، ويختفي app.alert، ولا تعمل نصوص التنسيق أبداً. ويقوم Acrobat، الذي يشحن دائماً محرك جافا سكريبت الخاص به، بتشغيل نفس الملف بسعادة، ولهذا السبب يبدو هذا التناقض وكأنه خطأ في كودك بينما هو في الواقع افتراضي متعمد في المكتبة الموجودة بالأسفل
كان لمكون PDFium طبقة تاريخية ثانية في هذا الشأن. فخلال الإصدار v2.13.1، قام الربط فقط بإرفاق m_pJsPlatform داخل فرع تهيئة XFA، لذا فإن مستندات AcroForm العادية، التي تمثل الغالبية العظمى من ملفات PDF المكتوبة، لم تحصل على منصة جافا سكريبت على الإطلاق حتى عندما كانت مكتبة DLL الممكنة لـ V8 موجودة. وسحب الإصدار v2.13.2 المرفق خارج قرار XFA: تقوم InitializeFormFill الآن ببناء جدول IPDF_JsPlatform كلما أعادت V8FeaturesAvailable القيمة True، بشكل مستقل عما إذا كان المستند هو XFA. والنتيجة العملية هي أن النصوص البرمجية على مستوى المستند، وسلاسل حساب الحقول، وapp.alert يتم تنفيذها الآن في مستندات AcroForm العادية أيضاً
ما هي مكتبة V8 DLL التي يحتاجها AcroForm JavaScript في دلفي؟
يحتاج AcroForm JavaScript إلى ثنائي PDFium تم تجميعه باستخدام محرك V8، والذي يحمله مكون PDFium كـ pdfium.v8.dll عندما تكون العلامة العالمية EnableV8Engine هي True قبل تحميل المكتبة لأول مرة. وهناك فخ واحد هنا يجدر ذكره بوضوح: يكتشف المكون تلقائياً مستندات XFA عن طريق الفحص المسبق لعلامات /XFA ويقلب EnableV8Engine من أجلك، ولكن مستند AcroForm العادي الذي يحتوي على جافا سكريبت لا يحمل علامة XFA، لذا لا يحدث اكتشاف تلقائي. وإذا كان ينبغي لعارضك تشغيل نصوص النماذج، فاضبط العلامة بنفسك، مرة واحدة، قبل تحميل المستند الأول؛ فبعد تحميل pdfium.dll العادي لا يمكن للعملية تبديل المحركات
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Must run before the singleton PDFium library first loads;
// once plain pdfium.dll is in the process it cannot be swapped
EnableV8Engine := True;
end;
procedure TViewerForm.OpenDocument(const FileName: string);
begin
FPdf.LoadDocument(FileName);
if not V8FeaturesAvailable then
StatusBar.SimpleText :=
'JavaScript disabled: pdfium.v8.dll not deployed';
end;
تعتبر V8FeaturesAvailable مسبار وقت التشغيل الصادق: فهي تبلغ عما إذا كان الثنائي المحمل يحل بالفعل الصادرات المعتمدة على V8، وبالتالي يعمل الفحص بغض النظر عن مكتبة DLL التي انتهى المثبت بنشرها. ويقوم المحرك نفسه والعلامة نفسها بتشغيل عرض XFA الديناميكي؛ والخلفية حول كيفية تفاعل اكتشاف XFA والتحديد التلقائي لـ V8 مغطاة في اكتشاف نماذج XFA واستخراج حزم XFA باستخدام PDFium
أحداث المضيف للتنبيهات والطباعة والبريد وإرسال النماذج
كل شيء مرئي يفعله النص البرمجي يتم توجيهه مرة أخرى إلى كودك من خلال أحداث TPdf، ويتخلف كل منها إلى إجراء آمن فارغ عند عدم تعيينه. وتستقبل OnJavaScriptAlert الرسالة والعنوان ونوع الزر والأيقونة من app.alert وتتيح لك إرجاع نتيجة الزر المضغوط؛ وتخدم OnJavaScriptResponse مطالبات إدخال app.response بما في ذلك علامة كلمة المرور؛ وتظهر OnJavaScriptBeep، وOnJavaScriptPrint، وOnJavaScriptMail، وOnJavaScriptSubmitForm طلبات الصفارة والطباعة والبريد والإرسال مع مجموعات المعاملات الكاملة الخاصة بها. والعارض الذي لا يعين أي من هذه لا يزال يعرض ويحسب بشكل صحيح؛ وببساطة لا يمكن للمستند فتح مربعات الحوار أو الطباعة أو إرسال أي شيء
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// Route app.alert through the VCL so it is owned by our window
Result_ := MessageBox(Handle, PWideChar(Msg), PWideChar(Title),
MB_OK or MB_ICONINFORMATION);
Handled := True;
end;
procedure TViewerForm.PdfJavaScriptSubmitForm(Sender: TObject;
const Url: WString; const Data: TBytes);
begin
// Nothing is transmitted unless this handler chooses to send it
LogAudit(Format('Document requested form submit to %s (%d bytes)',
[Url, Length(Data)]));
end;
يعد تقسيم السلوك الافتراضي مهماً لنمذجة التهديدات. حيث إن OnJavaScriptMail وOnJavaScriptSubmitForm هما بنمط الإعلام: لا يقوم مكون PDFium بأي إجراء شبكة أو MAPI بمفرده، لذا فإن المستند الخبيث الذي يستدعي this.submitForm أو this.mailDoc ضد مضيف غير مستعد لا ينجز أي شيء على الإطلاق. ويجب على المضيف الاشتراك عن طريق تعيين معالج وتطبيق النقل بنفسه، مما يعني أن قرار نقل البيانات خارج الجهاز هو قرارك دائماً، ويتم اتخاذه في كودك، مع وجود عنوان URL والحمولة في متناول اليد
كيف تمنع ملف PDF خبيث من إطلاق عناوين URL؟
تعتبر إجراءات URI هي المكان الوحيد الذي كان فيه الافتراضي التاريخي نشطاً وليس سلبياً، وهذا هو السبب في حصولها على نقض (veto) مخصص. فقبل الإصدار v2.13.1، قامت استدعاءات FFI_DoURIActionWithKeyboardModifier بتنفيذ أي عنوان URI يوفره حقل XFA دون قيد أو شرط، مما يمنح المستند الخبيث القدرة على إطلاق عناوين URL عشوائية عند النقر. وتغلق OnXfaUriAction ذلك: حيث يستشير المكون الحدث قبل التنفيذ، والمعالج الذي يعين Handled إلى True يقمع افتراضي ShellExecuteW تماماً. وترك الحدث غير معين يحافظ على السلوك القديم للتوافقية، لذا يجب على العارض المهتم بالأمان تعيينه دائماً
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Veto anything that is not plain https; the default would
// otherwise ShellExecuteW the URI as-is
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Blocked URI action: ' + Uri);
Handled := True;
end;
end;
تعتبر القائمة المسموح بها في المخطط هي الحد الأدنى؛ ويؤكد العارض الأكثر صرامة مع المستخدم أو يوجه كل شيء من خلال مكون المتصفح المعزول الخاص به. وتمتد نفس فلسفة الفيتو عبر مجموعة أحداث v2.13.1: حيث يكشف كل من OnXfaEmail، وOnXfaHttpRequest، وOnXfaOpenFile عن المعلمات النصية للإجراء المطلوب بينما لا يقوم المكون نفسه بنقل الشبكة أو الملف. وكيف تتناسب هذه الحراس مع استراتيجية مستندات أوسع غير موثوقة، بما في ذلك عزل العرض، هو موضوع بناء معاينة PDF آمنة في دلفي
الكتابة إلى الحقل المركّز عليه باستخدام SetFocusedFormFieldText
تعتبر TPdf.SetFocusedFormFieldText، المضافة في الإصدار v2.13.2، هي الرفيق بجانب الكتابة لـ FocusedFormFieldText: فهي تحدد المحتوى الحالي للحقل المركّز عليه عبر FORM_SelectAllText وتكتب فوقه من خلال FORM_ReplaceSelection، تماماً كما لو كان المستخدم قد كتب البديل. ولأن النص يدخل من خلال مسار التحرير التفاعلي، فإن أي نصوص برمجية لضغط المفاتيح والتنسيق والحساب المرفقة بالحقل يتم تشغيلها بنفس الطريقة التي تعمل بها للإدخال اليدوي، مما يجعلها البدائية الصحيحة للتعبئة البرمجية في عارض يبقي لغة جافا سكريبت نشطة. ويتم تغطية التنقل في الحقول وإدارة التركيز حولها في الملاحة في حقول النموذج باستخدام مكون PDFium
if FPdf.FocusedFormFieldIndex >= 0 then
begin
if not FPdf.SetFocusedFormFieldText('42.50') then
ShowMessage('No focused field accepts text on this page');
// AcroForm: value commits to /V when focus leaves the field.
// XFA: the write stays in the in-memory edit buffer only and
// will not survive a save
end;
عدم تماثل الاستمرارية في هذا التعليق يمثل حداً حقيقياً، وليس تحذيراً لمجرد التوضيح. بالنسبة لحقول نص وكومبو AcroForm، يتم التزام القيمة المحررة بإدخال /V للحقل عند فقدان التركيز وتستمر عبر الحفظ. وبالنسبة لحقول نص XFA، تهبط الكتابة فقط في مخزن التحرير المؤقت في الذاكرة، لأن PDFium لا يكشف عن أي واجهة برمجة تطبيقات عامة لتسوية قيم الـ widget رجوعاً إلى حزمة بيانات XFA؛ ولن يحمل حفظ المستند التغيير. وإذا كان تحرير بيانات XFA الدائم هو المطلوب، فقم بتحرير datasets XML نفسها بدلاً من الـ widget
حدود يجدر معرفتها قبل الشحن
يتبقى حدان صادقان. أولاً، تعتبر واجهة برمجة تطبيقات جافا سكريبت التي يطبقها PDFium هي مجموعة فرعية عاملة من نموذج كائن Acrobat، وتتركز على جوهر كتابة نصوص النموذج: الوصول إلى الحقول، وأحداث الحساب والتنسيق، وapp.alert وapp.response، وطلبات الطباعة والبريد والإرسال. وستتدهور المستندات التي تعتمد على كائنات غريبة خاصة بـ Acrobat فقط، وعادة ما يكون ذلك بصمت، لذا اختبر النماذج الفعلية التي يتعامل معها مستخدموك بدلاً من افتراض التكافؤ. ثانياً، يعني تمكين V8 شحن pdfium.v8.dll، وهو ثنائي أكبر بكثير من البنية العادية؛ ويمكن للعارض الذي لا يحتاج أبداً إلى نصوص برمجية أو XFA الاحتفاظ بملف DLL الأصغر بشكل قانوني وترك m_pJsPlatform غير مرفق، وهو موقف أمني صالح في حد ذاته
النمط الذي يقع خارج كل هذا ممل بشكل ممتع: اضبط EnableV8Engine عند بدء التشغيل، وعين أحداث التنبيه والاستجابة بحيث تبدو مربعات الحوار أصلية، وعين OnXfaUriAction وسجل تدقيق أحداث البريد والإرسال، واترك كل شيء آخر عند افتراضيه غير الفعال حتى تنص المتطلبات على خلاف ذلك. ويتم توثيق مرجع الأحداث الكامل وسطح واجهة برمجة تطبيقات تعبئة النموذج على صفحة منتج مكون PDFium Component