Obrazac čiji se zbrojevi ponovno izračunavaju u Acrobatu, ali stoje zamrznuti u vašem vlastitom Delphi pregledniku, gotovo nikada nije pogreška pri iscrtavanju. PDFium onemogućuje sav JavaScript dokumenta osim ako domaćin ne priključi JS platformu, a PDFium komponenta automatski povezuje tu platformu od verzije 2.13.2 nadalje, tako da se AcroForm JavaScript, skripte na razini dokumenta i izračuni polja izvode kad god je učitan DLL s omogućenim V8. Domaćin zadržava kontrolu kroz skup događaja koji mogu prikazati, preusmjeriti ili uložiti veto na sve što skripta pokušava učiniti
Ta jedna rečenica rješava pitanje podrške koje smo vidjeli u nekoliko oblika: "moj narudžbenik izračunava ukupne iznose u Acrobatu, ali ne i u mojoj aplikaciji", "app.alert se nikada ne pokreće", "polje datuma se ne oblikuje samo". Svi oni vuku korijene iz istog ugovora, a razumijevanje tog ugovora vrijedi deset minuta jer je isti ugovor ujedno i vaša sigurnosna granica protiv zlonamjernih dokumenata
Zašto izračuni PDF obrazaca ne rade u PDFium-u?
PDFium tretira JavaScript dokumenta kao strogo izbornu opciju (opt-in): ako je član m_pJsPlatform okruženja za popunjavanje obrazaca NULL, svaka skripta u dokumentu se tiho preskače. Bez pogreške, bez retka dnevnika, bez djelomičnog izvođenja. Izračunata polja zadržavaju svoju posljednju pohranjenu vrijednost, app.alert nestaje, skripte za oblikovanje se nikada ne pokreću. Acrobat, koji uvijek isporučuje vlastiti JavaScript mehanizam, rado pokreće istu datoteku, zbog čega neslaganje izgleda kao pogreška u vašem kodu, iako je to zapravo namjerna zadana vrijednost u knjižnici ispod
PDFium komponenta imala je i drugi, povijesni sloj u ovome. Do verzije 2.13.1 povezivanje je priključivalo m_pJsPlatform samo unutar grane inicijalizacije XFA-a, pa obični AcroForm dokumenti, koji čine veliku većinu skriptiranih PDF-ova, uopće nisu dobivali JavaScript platformu čak ni kada je DLL s omogućenim V8 bio prisutan. Verzija 2.13.2 izvukla je to priključivanje iz XFA odluke: InitializeFormFill sada gradi tablicu IPDF_JsPlatform kad god V8FeaturesAvailable vraća True, neovisno o tome je li dokument XFA. Praktični rezultat je da se skripte na razini dokumenta, lanci izračuna polja i app.alert sada izvode i u običnim AcroForm dokumentima
Koji V8 DLL treba AcroForm JavaScript u Delphiju?
AcroForm JavaScript treba PDFium binarnu datoteku prevedenu s V8 mehanizmom, koju PDFium komponenta učitava kao pdfium.v8.dll kada je globalna zastavica EnableV8Engine True prije nego što se knjižnica prvi put učita. Ovdje postoji jedna zamka koju vrijedi jasno navesti: komponenta automatski otkriva XFA dokumente prethodnim skeniranjem /XFA oznaka i postavlja EnableV8Engine umjesto vas, ali običan AcroForm dokument s JavaScriptom ne nosi XFA oznaku, pa se automatsko otkrivanje ne događa. Ako bi vaš preglednik trebao izvoditi skripte obrazaca, sami postavite zastavicu, jednom, prije učitavanja prvog dokumenta; nakon što se učita običan pdfium.dll, proces više ne može mijenjati mehanizme
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 je stvarna provjera u vrijeme izvođenja: ona javlja rješava li učitana binarna datoteka izvoze koji ovise o V8, tako da provjera radi bez obzira na to koji je DLL instalacijski program na kraju postavio. Isti mehanizam i ista zastavica također pokreću dinamičko XFA iscrtavanje; pozadina o tome kako XFA otkrivanje i automatski odabir V8 međusobno djeluju pokrivena je u otkrivanju XFA obrazaca i izdvajanju XFA paketa s PDFium-om
Događaji domaćina za upozorenja, ispis, poštu i slanje obrazaca
Svaka vidljiva stvar koju skripta učini usmjerava se natrag u vaš kôd putem TPdf događaja, a svaki od njih prema zadanim postavkama ne radi ništa (safe no-op) ako nije dodijeljen. Događaj OnJavaScriptAlert prima poruku, naslov, vrstu gumba i ikonu iz app.alert-a i omogućuje vam vraćanje rezultata pritisnutog gumba; OnJavaScriptResponse opslužuje unosne upite app.response uključujući zastavicu lozinke; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail i OnJavaScriptSubmitForm prikazuju zvučne signale (beep), zahtjeve za ispis, poštu i slanje s njihovim punim skupovima parametara. Preglednik koji ne dodijeli nijedan od ovih događaja i dalje ispravno iscrtava i računa; dokument jednostavno ne može otvarati dijaloške okvire, ispisivati ili slati bilo što
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;
Podjela na zadanu i prilagođenu aktivnost važna je za modeliranje prijetnji. Događaji OnJavaScriptMail i OnJavaScriptSubmitForm su u stilu obavijesti: PDFium komponenta ne obavlja nikakve mrežne ili MAPI radnje sama, pa zlonamjerni dokument koji poziva this.submitForm ili this.mailDoc protiv nepripremljenog domaćina ne postiže apsolutno ništa. Domaćin se mora uključiti (opt-in) dodjelom rukovatelja i samom implementacijom prijenosa, što znači da je odluka o slanju podataka s računala uvijek vaša, donesena u vašem kodu, s URL-om i teretom u rukama
Kako zaustaviti zlonamjerni PDF u pokretanju URL-ova?
URI radnje su jedino mjesto gdje je povijesna zadana postavka bila aktivna, a ne pasivna, i zato su dobile namjenski veto. Prije verzije 2.13.1, povratni poziv FFI_DoURIActionWithKeyboardModifier izvodio je (shell-execute) bilo koji URI koji bi XFA polje ponudilo, bezuvjetno, što je zlonamjernom dokumentu davalo mogućnost pokretanja proizvoljnih URL-ova pri kliku. Događaj OnXfaUriAction to zatvara: komponenta se savjetuje s događajem prije izvođenja, a rukovatelj koji postavi Handled na True u potpunosti potiskuje zadani ShellExecuteW. Ostavljanje događaja nedodijeljenim čuva staro ponašanje radi kompatibilnosti, pa bi preglednik svjestan sigurnosti uvijek trebao to dodijeliti
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;
Popis dopuštenih (allow-list) shema je minimum; stroži preglednik to potvrđuje s korisnikom ili sve usmjerava kroz vlastitu izoliranu (sandboxed) komponentu preglednika. Isti veto pristup proteže se kroz skup događaja verzije v2.13.1: OnXfaEmail, OnXfaHttpRequest i OnXfaOpenFile izlažu tekstualne parametre tražene radnje dok sama komponenta ne obavlja nikakav mrežni ili datotečni prijenos. Kako se ti zaštitni mehanizmi uklapaju u širu strategiju nepouzdanih dokumenata, uključujući izolaciju iscrtavanja, tema je izgradnje sigurne PDF pretpremijere u Delphiju
Pisanje u fokusirano polje pomoću SetFocusedFormFieldText
Pisanje u fokusirano polje pomoću SetFocusedFormFieldText
Funkcija TPdf.SetFocusedFormFieldText, dodana u verziji 2.13.2, je pratitelj na strani pisanja za FocusedFormFieldText: odabire trenutni sadržaj fokusiranog polja kupovanjem fokusa i prepisuje ga putem FORM_ReplaceSelection, točno kao da je korisnik utipkao zamjenu. Budući da tekst ulazi kroz interaktivni put uređivanja, sve skripte za pritisak tipke (keystroke), oblikovanje i izračun koje su pridružene polju pokreću se na isti način kao i za ručni unos, što ga čini pravim primitivom za programsko popunjavanje u pregledniku koji održava JavaScript aktivnim. Kretanje kroz polja i upravljanje fokusom oko njih pokriveni su u navigaciji poljima obrasca s PDFium komponentom
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;
Asimetrija postojanosti (persistence asymmetry) u tom komentaru je stvarna granica, a ne samo formalno upozorenje. Za tekstualna i kombinirana (combo) polja AcroForma, uređena vrijednost se predaje u unos /V polja nakon gubitka fokusa i ostaje postojana nakon spremanja. Za tekstualna polja XFA-a, zapis slijeće samo u memorijski međuspremnik za uređivanje, jer PDFium ne izlaže javni API za usklađivanje vrijednosti widgeta natrag u paket podataka (datasets packet) XFA-a; spremanje dokumenta neće prenijeti promjenu. Ako je zahtjev trajno uređivanje XFA podataka, uredite sam XML paketa podataka radije nego widget
Granice koje vrijedi znati prije isporuke
Preostaju dvije stvarne granice. Prvo, JavaScript API koji PDFium implementira je radni podskup objektnog modela Acrobata, usredotočen na jezgru skriptiranja obrazaca: pristup poljima, događaje izračuna i oblikovanja, app.alert i app.response, te zahtjeve za ispis, poštu i slanje. Dokumenti koji se oslanjaju na egzotične objekte specifične samo za Acrobat degradirat će, obično tiho, stoga testirajte stvarne obrasce s kojima vaši korisnici rade radije nego da pretpostavljate paritet. Drugo, omogućavanje V8-a znači isporuku datoteke pdfium.v8.dll, znatno veće binarne datoteke od obične verzije; preglednik koji nikada ne treba skriptiranje ili XFA može legitimno zadržati manji DLL i ostaviti m_pJsPlatform nepriključenim, što je samo po sebi valjan sigurnosni stav
Obrazac koji proizlazi iz svega ovoga je ugodno jednostavan: postavite EnableV8Engine pri pokretanju, dodijelite događaje upozorenja i odgovora kako bi dijalozi izgledali izvorno, dodijelite OnXfaUriAction i zabilježite u dnevnik (audit-log) događaje pošte i slanja, te ostavite sve ostalo na zadanim postavkama koje ne čine ništa dok zahtjev ne nalaže drugačije. Potpuna referenca događaja i API sučelje za popunjavanje obrazaca dokumentirani su na stranici proizvoda PDFium Component