Teknisk artikkel

Kjøring av AcroForm JavaScript i Delphi med PDFium Component

Et skjema der totalsummene rekalkuleres i Acrobat, men forblir frosset i din egen Delphi-viser, er nesten aldri en tegningsfeil. PDFium deaktiverer alt dokument-JavaScript med mindre verten kobler til en JS-plattform, og PDFium-komponenten kobler opp denne plattformen automatisk fra versjon 2.13.2 og utover, slik at AcroForm JavaScript, skript på dokumentnivå og feltberegninger kjøres når den V8-aktiverte DLL-en er lastet. Verten beholder kontrollen via et sett med hendelser som kan vise, omdirigere eller avvise alt skriptet prøver å gjøre

Denne ene setningen løser et støttespørsmål vi har sett i flere varianter: "bestillingsskjemaet mitt beregner linjetotaler i Acrobat, men ikke i appen min", "app.alert utløses aldri", "datosfeltet formaterer seg ikke". Alle disse sporer tilbake til samme kontrakt, og det er verdt ti minutter å forstå den fordi den samme kontrakten også er din sikkerhetsgrense mot fiendtlige dokumenter

Hvorfor fungerer ikke PDF-skjemaberegninger i PDFium?

PDFium behandler dokument-JavaScript som strengt opt-in: hvis elementet m_pJsPlatform i skjemafyllingsmiljøet er NULL, blir hvert skript i dokumentet lydløst hoppet over. Ingen feil, ingen logglinje, ingen delvis kjøring. Beregnede felt beholder sin sist lagrede verdi, app.alert forsvinner, format-skript kjører aldri. Acrobat, som alltid leveres med sin egen JavaScript-motor, kjører gladelig den samme filen, og det er grunnen til at avviket ser så mye ut som en feil i koden din når det egentlig er en bevisst standardinnstilling i det underliggende biblioteket

PDFium-komponenten hadde et andre, historisk lag i dette. Til og med versjon 2.13.1 koblet bindingen bare til m_pJsPlatform inni initialiseringsgrenen for XFA, så vanlige AcroForm-dokumenter, som utgjør det overveldende flertallet av skriptede PDF-er, fikk aldri noen JavaScript-plattform i det hele tatt, selv når den V8-aktiverte DLL-en var til stede. Versjon 2.13.2 trakk tilkoblingen ut av XFA-beslutningen: InitializeFormFill bygger nå IPDF_JsPlatform-tabellen når V8FeaturesAvailable returnerer True, uavhengig av om dokumentet er XFA. Det praktiske resultatet er at skript på dokumentnivå, feltberegningskjeder og app.alert nå også kjører i vanlige AcroForm-dokumenter

Hvilken V8 DLL trenger AcroForm JavaScript i Delphi?

AcroForm JavaScript trenger en PDFium-binærfil kompilert med V8-motoren, som PDFium-komponenten laster som pdfium.v8.dll når det globale flagget EnableV8Engine er satt til True før biblioteket først lastes. Det er én felle her som er verdt å nevne tydelig: komponenten oppdager automatisk XFA-dokumenter ver å forhåndsskannet etter /XFA-markører og flipper EnableV8Engine for deg, men et vanlig AcroForm-dokument med JavaScript bærer ingen XFA-markør, så ingen automatisk oppdagelse skjer. Hvis viseren din skal kjøre skjemaskript, må du sette flagget selv, én gang, før det første dokumentet lastes; etter at en vanlig pdfium.dll er lastet inn, kan ikke prosessen bytte motorer

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 er den reelle kjøretids-sjekken: den rapporterer om den lastede binærfilen faktisk løser opp de V8-avhengige eksportene, slik at sjekken fungerer uansett hvilken DLL et installasjonsprogram endte opp med å distribuere. Den samme motoren og det samme flagget driver også dynamisk XFA-tegning; bakgrunnen for hvordan XFA-deteksjon og det automatiske V8-valget samhandler, dekkes i oppdagelse av XFA-skjemaer og utpakking av XFA-pakker med PDFium

Vertshendelser for varsler, utskrift, e-post og skjemainnsending

Hver synlig ting et skript gjør, rutes tilbake til koden din via TPdf-hendelser, og hver av dem faller tilbake på en sikker no-op (ingen handling) når de ikke er tilordnet. OnJavaScriptAlert mottar meldingen, tittelen, knappetypen og ikonet fra app.alert og lar deg returnere resultatet av den trykkede knappen; OnJavaScriptResponse betjener app.response-inndataprompter, inkludert passordflagget; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail og OnJavaScriptSubmitForm overflatebehandler forespørsler om pip, utskrift, e-post og innsending med sine fulle parametersett. En viser som ikke tildeler noen av disse, rendrer og beregner fremdeles riktig; dokumentet kan ganske enkelt bare ikke åpne dialogbokser, skrive ut eller sende noe

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;

Splitten i standardoppførsel har betydning for trusselmodellering. OnJavaScriptMail og OnJavaScriptSubmitForm er av varslingstype: PDFium-komponenten utfører ingen nettverks- eller MAPI-handling på egen hånd, så et skadelig dokument som kaller this.submitForm eller this.mailDoc mot en uforberedt vert oppnår nøyaktig ingenting. Verten må melde seg på ved å tilordne en behandler og implementere transporten selv, noe som betyr at beslutningen om å flytte data bort fra maskinen alltid er din, tatt i din kode, med URL-en og dataene i hånden

Hvordan forhindrer du en skadelig PDF fra å åpne URL-er?

URI-handlinger er det eneste stedet der den historiske standardoppførselen var aktiv i stedet for passiv, og det er derfor de fikk et dedikert veto. Før versjon 2.13.1 kjørte FFI_DoURIActionWithKeyboardModifier-tilbakekallet (via systemets skall) enhver URI et XFA-felt oppga, ubetinget, noe som ga et skadelig dokument muligheten til å åpne vilkårlige URL-er ved klikk. OnXfaUriAction lukker dette: komponenten rådspør hendelsen før kjøring, og en behandler som setter Handled til True, undertrykker ShellExecuteW-standarden helt. Å la hendelsen være utilordnet bevarer den gamle oppførselen av kompatibilitetsgrunner, så en sikkerhetsbevisst viser bør alltid tilordne den

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;

En tillatelsesliste (allow-list) på ordningen er et minimum; en strengere viser bekrefter med brukeren eller ruter alt gjennom sin egen sandkasse-browser-komponent. Den samme veto-filosofien strekker seg over hendelsessettet i v2.13.1: OnXfaEmail, OnXfaHttpRequest og OnXfaOpenFile eksponerer hver de tekstlige parameterne for den forespurte handlingen, mens komponenten selv ikke utfører nettverks- eller filtransport. Hvordan disse beskyttelsene passer inn i en bredere strategi for ukjente dokumenter, inkludert tegningsisolering, er temaet i oppbygging av en sikker PDF-forhåndsvisning i Delphi

Skrive til det fokuserte feltet med SetFocusedFormFieldText

TPdf.SetFocusedFormFieldText, som ble lagt til i versjon 2.13.2, er skriveside-ledsageren til FocusedFormFieldText: den velger det fokuserte feltets gjeldende innhold via FORM_SelectAllText og skriver over det via FORM_ReplaceSelection, nøyaktig som om brukeren hadde skrevet erstatningen. Fordi teksten kommer inn via den interaktive redigeringsbanen, vil alle tastetrykk-, format- og beregningsskript knyttet til feltet utløses på samme måte som ved manuell inndata, noe som gjør det til den riktige primitiven for programmatisk utfylling i en viser som holder JavaScript aktiv. Feltgjennomgang og fokusstyring rundt dette dekkes i skjemafeltnavigasjon med PDFium Component

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;

Persistens-asymmetrien i den kommentaren er en reell grense, ikke bare et forbehold. For AcroForm-tekst og kombinasjonsfelt overføres den redigerte verdien til feltets /V-oppføring ved tap av fokus, og bevares ved lagring. For XFA-tekstfelt lander skrivingen kun i redigeringsbufferen i minnet, fordi PDFium ikke eksponerer noe offentlig API for å flette widget-verdier tilbake inn i XFA-datasettpakken; lagring av dokumentet vil ikke føre endringen videre. Hvis holdbar XFA-dataredigering er kravet, rediger selve datasett-XML-en i stedet for widgeten

Grenser verdt å vite før du sender ut programvaren

To reelle grenser gjenstår. For det første er JavaScript-API-et PDFium implementerer, en fungerende delmengde av Acrobat objektmodell, sentrert rundt kjernen i skjemaskripting: felt-tilgang, beregnings- og formathendelser, app.alert og app.response, samt forespørsler om utskrift, e-post og innsending. Dokumenter som støtter seg på eksotiske Acrobat-eksklusive objekter vil degradere, vanligvis lydløst, så test de faktiske skjemaene brukerne dine håndterer i stedet for å anta fullt samsvar. For det andre betyr det å aktivere V8 at du må distribuere pdfium.v8.dll, en betydelig større binærfil enn standardbygget; en viser som aldri trenger skripting eller XFA kan legitimt beholde den mindre DLL-en og la m_pJsPlatform forbli utilkoblet, noe som i seg selv er en gyldig sikkerhetsholdning

Mønsteret som faller ut av alt dette er behagelig kjedelig: sett EnableV8Engine ved oppstart, tilordne varsel- og svarhendelsene slik at dialogbokser ser innfødte ut, tilordne OnXfaUriAction og loggfør e-post- og innsendingshendelser for revisjon, og la alt annet forbli på standard no-op inntil et krav sier noe annet. Den fullstendige hendelsesreferansen og API-flaten for skjemafylling er dokumentert på produktsiden for PDFium Component