Teknisk artikel

Afvikling af AcroForm-JavaScript i Delphi med PDFium Component

En formular, hvis totaler genberegnes i Acrobat, men forbliver frosne i din egen Delphi-fremviser, er næsten aldrig en renderingsfejl. PDFium deaktiverer al dokument-JavaScript, medmindre værten tilknytter en JS-platform, og PDFium Component forbinder denne platform automatisk fra version 2.13.2 og frem, så AcroForm-JavaScript, scripts på dokumentniveau og feltberegninger udføres, når den V8-aktiverede DLL er indlæst. Værten bevarer kontrollen via et sæt hændelser, der kan vise, omdirigere eller nedlægge veto mod alt, hvad scriptet forsøger at gøre

Hvorfor virker PDF-formularberegninger ikke i PDFium?

Som standard behandler PDFium dokument-JavaScript som strengt opt-in: Hvis m_pJsPlatform-medlemmet af formularudfyldningsmiljøet er NULL, springes hvert script i dokumentet lydløst over. Ingen fejl, ingen loglinje, ingen delvis udførelse. Beregnede felter beholder deres sidst gemte værdi, app.alert forsvinder, formateringsscripts kører aldrig. Acrobat, der altid leveres med sin egen JavaScript-motor, kører gladeligt den samme fil, hvilket er grunden til, at afvigelsen ligner en fejl i din kode, når det i virkeligheden er en bevidst standardindstilling i det underliggende bibliotek

PDFium Component havde et andet, historisk lag i dette. Til og med version 2.13.1 tilknyttede bindingen kun m_pJsPlatform inde i XFA-initialiseringsgrenen, så almindelige AcroForm-dokumenter, som er det overvældende flertal af scriptede PDF'er, fik aldrig en JavaScript-platform, selv når den V8-aktiverede DLL var til stede. Version 2.13.2 trak tilknytningen ud af XFA-beslutningen: InitializeFormFill bygger nu IPDF_JsPlatform-tabellen, hver gang V8FeaturesAvailable returnerer True, uafhængigt af om dokumentet er XFA. Det praktiske resultat er, at scripts på dokumentniveau, feltberegningskæder og app.alert nu også udføres i almindelige AcroForm-dokumenter

Hvilken V8 DLL kræver AcroForm-JavaScript i Delphi?

AcroForm-JavaScript kræver en PDFium-binær fil kompileret med V8-motoren, som PDFium Component indlæser som pdfium.v8.dll, når det globale EnableV8Engine-flag er True, før biblioteket indlæses første gang. Der er en enkelt fælde her, som er værd at nævne tydeligt: Komponenten registrerer automatisk XFA-dokumenter ved at scanne forud efter /XFA-markører og slår EnableV8Engine til for dig, men et almindeligt AcroForm-dokument med JavaScript bærer ingen XFA-markør, så der sker ingen automatisk registrering. Hvis din fremviser skal køre formularscripts, skal du selv sætte flaget én gang, før det første dokument indlæses; efter at en almindelig pdfium.dll er indlæst, kan processen ikke skifte motor

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 runtime-undersøgelse: Den rapporterer, om den indlæste binære fil faktisk løser de V8-afhængige eksporter, så kontrollen virker uafhængigt, hvilken DLL et installationsprogram endte med at distribuere. Den samme motor og det samme flag driver også dynamisk XFA-rendering; baggrunden for, hvordan XFA-registrering og det automatiske V8-valg interagerer, er dækket i registrering af XFA-formularer og udtrækning af XFA-pakker med PDFium

Værtshændelser for advarsler, udskrivning, e-mail og formularindsendelse

Alt synligt, et script gør, ledes tilbage til din kode via TPdf-hændelser, og hver af dem har som standard en sikker nul-operation (no-op), når de ikke er tildelt. OnJavaScriptAlert modtager besked, titel, knap-type og ikon fra app.alert og lader dig returnere resultatet af den trykkede knap; OnJavaScriptResponse betjener app.response-indtastningsprompter, inklusive kodeordsflaget; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail og OnJavaScriptSubmitForm overfladebehandler bip, udskrivnings-, e-mail- og formularindsendelsesanmodninger med deres fulde parametersæt. En fremviser, der ikke tildeler nogen af disse, renderer og beregner stadig korrekt; dokumentet kan blot ikke åbne dialogbokse, udskrive eller sende noget

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;

Opdelingen af standardadfærd betyder noget for trusselsmodellering. OnJavaScriptMail og OnJavaScriptSubmitForm er i meddelelsesstil: PDFium Component udfører ingen netværks- eller MAPI-handling på egen hånd, så et ondsindet dokument, der kalder this.submitForm eller this.mailDoc mod en uforberedt vært, udretter absolut intet. Værten skal tilvælge dette ved at tildele en behandler og selv implementere transporten, hvilket betyder, at beslutningen om at flytte data væk fra maskinen altid er din, truffet i din kode, med URL og indholdspakke i hånden

Hvordan forhindrer du en ondsindet PDF i at starte URL'er?

URI-handlinger er det eneste sted, hvor den historiske standard var aktiv snarere end passiv, og det er grunden til, at de fik et dedikeret veto. Før version 2.13.1 udførte FFI_DoURIActionWithKeyboardModifier-tilbagekaldet (callback) en shell-kørsel af enhver URI, som et XFA-felt leverede, uden betingelser, hvilket gav et ondsindet dokument mulighed for at starte vilkårlige URL'er ved klik. OnXfaUriAction lukker dette: Komponenten rådfører sig med hændelsen før udførelse, og en behandler, der sætter Handled til True, undertrykker ShellExecuteW-standarden helt. At efterlade hændelsen utildelt bevarer den gamle adfærd af hensyn til kompatibilitet, så en sikkerhedsbevidst fremviser bør altid tildele 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 tilladelsesliste (allow-list) for skemaet er minimum; en strengere fremviser bekræfter med brugeren eller leder alt gennem sin egen sandboxed browserkomponent. Den samme veto-filosofi strækker sig over v2.13.1-hændelsessættet: OnXfaEmail, OnXfaHttpRequest og OnXfaOpenFile eksponerer hver især tekstparametrene for den anmodede handling, mens komponenten selv ikke udfører nogen netværks- eller filtransport. Hvordan disse beskyttelser passer ind i en bredere strategi for ikke-betroede dokumenter, herunder renderings-isolering, er emnet for opbygning af en sikker PDF-forhåndsvisning i Delphi

Skrivning til det fokuserede felt med SetFocusedFormFieldText

TPdf.SetFocusedFormFieldText, tilføjet i version 2.13.2, er skriveside-ledsageren til FocusedFormFieldText: Den vælger det fokuserede felts aktuelle indhold via FORM_SelectAllText og overskriver det via FORM_ReplaceSelection, præcis som hvis brugeren havde indtastet erstatningen. Fordi teksten kommer ind via den interaktive redigeringssti, udløses eventuelle keystroke-, format- og calculate-scripts, der er knyttet til feltet, på samme måde som ved manuel indtastning, hvilket gør det til den rigtige primitiv til programmatisk udfyldning i en fremviser, der holder JavaScript live. Feltgennemgang og fokusstyring omkring det er dækket i formularfeltnavigation 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 kommentar er en reel grænse, ikke et forbehold for syns skyld. For AcroForm-tekst- og kombinationsfelter overføres den redigerede værdi til feltets /V-post ved tab af fokus og bevares på tværs af gemninger. For XFA-tekstfelter lander skrivningen kun i redigeringsbufferen i hukommelsen, fordi PDFium ikke eksponerer nogen offentlig API til at afstemme widget-værdier tilbage i XFA-datasæt-pakken; gemning af dokumentet vil ikke føre ændringen med sig. Hvis holdbar redigering af XFA-data er kravet, skal du redigere selve datasæt-XML'en frem for widget'en

Grænser, der er værd at kende, før du distribuerer

To reelle grænser står tilbage. For det første er den JavaScript-API, PDFium implementerer, en fungerende undermængde af Acrobat-objektmodellen, centreret om formular-scripting-kernen: Feltadgang, beregnings- og formateringshændelser, app.alert og app.response, udskrivnings-, e-mail- og indsendelsesanmodninger. Dokumenter, der læner sig op ad eksotiske Acrobat-specifikke objekter, vil forringes, normalt lydløst, så test de faktiske formularer, dine brugere håndterer, frem for at antage overensstemmelse. For det andet betyder aktivering af V8, at du skal distribuere pdfium.v8.dll, en væsentligt større binær fil end den almindelige version; en fremviser, der aldrig har brug for scripting eller XFA, kan legitimt beholde den mindre DLL og lade m_pJsPlatform forblive utildelt, hvilket er en gyldig sikkerhedsholdning i sig selv

Mønsteret, der falder ud af alt dette, er behageligt kedeligt: Sæt EnableV8Engine ved opstart, tildel alert- og response-hændelserne, så dialogbokse ser indfødte ud, tildel OnXfaUriAction og før revisionslog over mail- og submit-hændelser, og efterlad alt andet på sin no-op-standard, indtil et krav siger andet. Den komplette hændelsesreference og overfladen af formularudfyldnings-API'en er dokumenteret på produktsiden for PDFium Component