Obrazac čiji se ukupni iznosi ponovo izračunavaju u Acrobat-u, ali ostaju zamrznuti u vašem sopstvenom Delphi pregledaču, skoro nikada nije greška u renderovanju. PDFium onemogućava sav JavaScript u dokumentu osim ako domaćin ne poveže JS platformu, a PDFium komponenta automatski povezuje tu platformu počevši od verzije 2.13.2 pa nadalje, tako da se AcroForm JavaScript, skripte na nivou dokumenta i proračuni polja izvršavaju kad god je učitana DLL datoteka sa omogućenim V8. Domaćin zadržava kontrolu kroz skup događaja koji mogu prikazati, preusmeriti ili uložiti veto na sve što skripta pokuša da uradi
Ta jedna rečenica rešava pitanje podrške koje smo viđali u nekoliko oblika: "moj obrazac za narudžbinu računa ukupne vrednosti stavki u Acrobat-u, ali ne i u mojoj aplikaciji", "app.alert se nikada ne pokreće", "polje za datum se ne formatira". Sve ove situacije se vraćaju na isti ugovor, a njegovo razumevanje vredi deset minuta jer je isti ugovor ujedno i vaša bezbednosna granica protiv zlonamernih dokumenata
Zašto proračuni PDF obrazaca ne rade u PDFium-u
PDFium tretira JavaScript u dokumentu kao striktno opcioni (opt-in): ako je član m_pJsPlatform okruženja za popunjavanje obrazaca jednak NULL, svaka skripta u dokumentu se prećutno preskače. Nema greške, nema linije u dnevniku, nema delimičnog izvršavanja. Izračunata polja zadržavaju svoju poslednju sačuvanu vrednost, app.alert nestaje, a skripte za formatiranje se nikada ne pokreću. Acrobat, koji uvek dolazi sa sopstvenim JavaScript mehanizmom, rado pokreće istu datoteku, zbog čega ovo neslaganje izgleda kao greška u vašem kodu, a zapravo je namerna podrazumevana postavka u biblioteci ispod
PDFium komponenta je imala i drugi, istorijski sloj u ovome. Do verzije 2.13.1 povezivanje je zakačinjalo m_pJsPlatform samo unutar XFA grane inicijalizacije, tako da obični AcroForm dokumenti, koji čine ogromnu većinu PDF-ova sa skriptama, uopšte nisu dobijali JavaScript platformu čak i kada je DLL sa omogućenim V8 bio prisutan. Verzija 2.13.2 je izvukla povezivanje iz XFA odlučivanja: funkcija InitializeFormFill sada gradi tabelu IPDF_JsPlatform kad god V8FeaturesAvailable vrati True, bez obzira na to da li je dokument XFA. Praktični rezultat je da se skripte na nivou dokumenta, lanci proračuna polja i app.alert sada izvršavaju i u običnim AcroForm dokumentima
Koja V8 DLL datoteka je potrebna AcroForm JavaScript-u u Delphi-ju
AcroForm JavaScript zahteva PDFium binarnu datoteku kompajliranu sa V8 mehanizmom, koju PDFium komponenta učitava kao pdfium.v8.dll kada je globalna zastavica EnableV8Engine postavljena na True pre prvog učitavanja biblioteke. Ovde postoji jedna zamka koju vredi jasno navesti: komponenta automatski detektuje XFA dokumente pre-skeniranjem za /XFA markere i menja EnableV8Engine umesto vas, ali običan AcroForm dokument sa JavaScript-om ne nosi XFA marker, pa se automatska detekcija ne dešava. Ako vaš pregledač treba da pokreće skripte obrazaca, sami postavite zastavicu jednom, pre nego što se prvi dokument učita. Nakon što se učita običan pdfium.dll, proces više ne može da promeni mehanizam
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;
Funkcija V8FeaturesAvailable je pouzdana provera u vreme izvršavanja: ona prijavljuje da li učitani binarni fajl zapravo razrešava izvoze zavisne od V8, tako da provera radi bez obzira na to koji DLL je instalater na kraju primenio. Isti mehanizam i ista zastavica takođe napajaju dinamičko XFA renderovanje. Pozadina o tome kako XFA detekcija i V8 automatska selekcija međusobno deluju pokrivena je u članku o detektovanju XFA obrazaca i ekstrahovanju XFA paketa pomoću PDFium-a
Događaji domaćina za upozorenja, štampanje, slanje pošte i slanje obrazaca
Svaka vidljiva stvar koju skripta uradi usmerava se nazad na vaš kod kroz TPdf događaje, i svaki od njih se podrazumeva na bezopasnu praznu operaciju (no-op) kada nije dodeljen. OnJavaScriptAlert prima poruku, naslov, tip dugmeta i ikonu iz app.alert i omogućava vam da vratite rezultat pritisnutog dugmeta. OnJavaScriptResponse opslužuje unosne promptove app.response uključujući zastavicu lozinke. OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail i OnJavaScriptSubmitForm iznose zahteve za beep, štampanje, slanje e-pošte i slanje obrazaca sa njihovim kompletnim setovima parametara. Pregledač koji ne dodeli nijedan od ovih događaja i dalje ispravno renderuje i računa podatke, ali dokument jednostavno ne može da otvara dijaloge, štampa niti šalje bilo šta
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;
Podela podrazumevanog ponašanja je važna za modeliranje pretnji. Događaji OnJavaScriptMail i OnJavaScriptSubmitForm su u stilu obaveštenja: PDFium komponenta ne obavlja nikakvu mrežnu ili MAPI akciju sama od sebe, tako da zlonamerni dokument koji poziva this.submitForm ili this.mailDoc protiv nepripremljenog domaćina ne postiže apsolutno ništa. Domaćin mora da se opredeli (opt-in) dodeljivanjem hendlera i samostalnom implementacijom prenosa, što znači da je odluka o prenosu podataka van mašine uvek vaša, doneta u vašem kodu, sa URL-om i podacima o sadržaju pri ruci
Kako sprečiti zlonamerni PDF da pokreće URL-ove
URI akcije su jedino mesto gde je istorijski podrazumevano ponašanje bilo aktivno, a ne pasivno, i zato su dobile namenski veto. Pre verzije 2.13.1 povratni poziv (callback) FFI_DoURIActionWithKeyboardModifier je bezuslovno izvršavao (shell-execute) bilo koji URI koji je XFA polje obezbedilo, što je zlonamernom dokumentu davalo mogućnost da na klik pokrene proizvoljne URL-ove. Događaj OnXfaUriAction zatvara taj propust: komponenta konsultuje događaj pre izvršavanja, a hendler koji postavi Handled na True u potpunosti potiskuje podrazumevani ShellExecuteW. Ostavljanje događaja nedodeljenim čuva staro ponašanje radi kompatibilnosti, pa bi pregledač koji vodi računa o bezbednosti uvek trebalo da ga dodeli
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;
Bela lista šema (allow-list) je minimum. Stroži pregledač traži potvrdu od korisnika ili usmerava sve kroz sopstvenu izolovanu (sandboxed) komponentu pregledača. Isti princip veta se proteže kroz skup događaja verzije v2.13.1: OnXfaEmail, OnXfaHttpRequest i OnXfaOpenFile izlažu tekstualne parametre tražene akcije, dok sama komponenta ne vrši nikakav mrežni ili datotečni transport. Kako se ove zaštite uklapaju u širu strategiju sa nepouzdanim dokumentima, uključujući izolaciju renderovanja, tema je članka o izgradnji bezbednog PDF pregleda u Delphi-ju
Upisivanje u fokusirano polje pomoću SetFocusedFormFieldText
Metoda TPdf.SetFocusedFormFieldText, dodata u verziji 2.13.2, jeste partner za upisivanje svojstva FocusedFormFieldText: ona bira trenutni sadržaj fokusiranog polja preko FORM_SelectAllText i prepisuje ga preko FORM_ReplaceSelection, tačno kao da je korisnik ukucao zamenu. Pošto tekst ulazi kroz interaktivnu putanju izmene, sve skripte za pritisak tastera, formatiranje i računanje koje su priključene polju aktiviraju se na isti način kao i kod ručnog unosa, što je čini pravim primitivom za programsko popunjavanje u pregledaču koji održava JavaScript aktivnim. Prelazak kroz polja i upravljanje fokusom oko njih pokriveni su u članku o kretanju kroz polja obrazaca sa 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 perzistencije u tom komentaru je stvarna granica, a ne samo formalno upozorenje. Za AcroForm tekstualna i kombinovana (combo) polja, izmenjena vrednost se upisuje u unos /V polja pri gubljenju fokusa i opstaje nakon čuvanja. Za XFA tekstualna polja, upisivanje ostaje samo u izmenjenom baferu u memoriji, jer PDFium ne izlaže javni API za usklađivanje vrednosti widget-a nazad u paket XFA skupa podataka (datasets packet); čuvanje dokumenta neće preneti izmenu. Ako je traženi zahtev trajna izmena XFA podataka, menjajte sam XML skupa podataka umesto widget-a
Ograničenja koja vredi znati pre isporuke softvera
Dve jasne granice ostaju. Prvo, JavaScript API koji PDFium implementira jeste radni podskup Acrobat objektnog modela, centriran na jezgro skriptovanja obrazaca: pristup poljima, događaje proračuna i formatiranja, app.alert i app.response, kao i zahteve za štampanje, slanje pošte i slanje obrazaca. Dokumenti koji se oslanjaju na egzotične objekte specifične samo za Acrobat će degradirati, obično prećutno, stoga testirajte stvarne obrasce sa kojima vaši korisnici rade umesto da pretpostavljate potpunu usklađenost. Drugo, omogućavanje V8 znači isporuku datoteke pdfium.v8.dll, koja je znatno veća binarna datoteka od obične verzije. Pregledač kome nikada nije potrebno skriptovanje ili XFA može legitimno zadržati manji DLL i ostaviti m_pJsPlatform nepovezanim, što je samo po sebi ispravan bezbednosni stav
Obrazac koji proizilazi iz svega ovoga je prijatno jednostavan: postavite EnableV8Engine pri pokretanju, dodelite događaje za upozorenja i odgovore kako bi dijalozi izgledali prirodno, dodelite OnXfaUriAction i beležite događaje slanja pošte i obrazaca u revizorski dnevnik, a sve ostalo ostavite na podrazumevanim praznim operacijama dok zahtevi ne nalože drugačije. Kompletna referenca događaja i interfejs API-ja za popunjavanje obrazaca dokumentovani su na stranici proizvoda PDFium komponente