Tekninen artikkeli

AcroForm JavaScriptin suorittaminen Delphissä PDFium-komponentilla

Lomake, jonka loppusummat lasketaan uudelleen Acrobatissa mutta pysyvät muuttumattomina omassa Delphi-katseluohjelmassasi, ei ole lähes koskaan renderöintivirhe. PDFium poistaa käytöstä kaiken dokumentin JavaScriptin, ellei isäntä liitä JS-alustaa. PDFium-komponentti kytkee tämän alustan automaattisesti versiosta 2.13.2 alkaen, joten AcroForm JavaScript, dokumenttitason skriptit ja kenttien laskennat suoritetaan aina, kun V8-tuella varustettu DLL on ladattuna. Isäntäohjelma pysyy hallinnassa sellaisten tapahtumien kautta, jotka voivat näyttää, uudelleenohjata tai estää kaiken, mitä skripti yrittää tehdä

Tämä yksi virke ratkaisee tukikysymyksen, jonka olemme nähneet useissa muodoissa: "tilauslomakkeeni laskee rivisummat Acrobatissa mutta ei sovelluksessani", "app.alert ei koskaan käynnisty", "päivämääräkenttä ei muotoile itseään". Ne kaikki juontavat juurensa samaan sopimukseen, ja sen ymmärtäminen on kymmenen minuutin arvoista, koska sama sopimus on myös tietoturvarajasi haitallisia dokumentteja vastaan

Miksi PDF-lomakkeen laskennat eivät toimi PDFiumissa?

PDFium käsittelee dokumentin JavaScriptiä tiukasti valinnaisena (opt-in): jos lomakkeen täyttöympäristön m_pJsPlatform-jäsen on NULL, jokainen dokumentin skripti ohitetaan hiljaisesti. Ei virhettä, ei lokiriviä, ei osittaista suoritusta. Lasketut kentät säilyttävät viimeksi tallennetun arvonsa, app.alert katoaa ja muotoiluskriptit eivät koskaan ajaudu. Acrobat, joka sisältää aina oman JavaScript-moottorinsa, suorittaa saman tiedoston ongelmitta, minkä vuoksi ero näyttää helposti virheeltä koodissasi, vaikka kyseessä on taustalla olevan kirjaston tietoinen oletusasetus

PDFium-komponentilla oli tähän toinen, historiallinen kerros. Versioon 2.13.1 asti sidonta liitti m_pJsPlatform-jäsenen vain XFA-alustushaaraan, joten plain AcroForm-dokumentit, jotka muodostavat valtaosan skriptatuista PDF-tiedostoista, eivät koskaan saaneet JavaScript-alustaa, vaikka V8-yhteensopiva DLL oli käytössä. Versio 2.13.2 siirsi liitoksen pois XFA-päätöksestä: InitializeFormFill rakentaa nyt IPDF_JsPlatform-taulukon aina, kun V8FeaturesAvailable palauttaa True, riippumatta siitä, onko kyseessä XFA-dokumentti. Käytännön tuloksena dokumenttitason skriptit, kenttien laskentaketjut ja app.alert suoritetaan nyt myös tavallisissa AcroForm-dokumenteissa

Mitä V8-DLL-tiedostoa AcroForm JavaScript tarvitsee Delphissä?

AcroForm JavaScript tarvitsee V8-moottorilla käännetyn PDFium-binäärin, jonka PDFium-komponentti lataa nimellä pdfium.v8.dll, kun globaali EnableV8Engine-lippu on True ennen kirjaston ensimmäistä lataamista. Tässä on yksi sudenkuoppa, joka on syytä mainita suoraan: komponentti tunnistaa XFA-dokumentit automaattisesti etsimällä /XFA-merkintöjä ja ottaa EnableV8Engine-asetuksen käyttöön puolestasi, mutta tavallinen AcroForm-dokumentti, joka sisältää JavaScriptiä, ei sisällä XFA-merkintää, joten automaattista tunnistusta ei tapahdu. Jos katseluohjelmasi pitäisi suorittaa lomakeskriptejä, aseta lippu itse kerran ennen ensimmäisen dokumentin lataamista; tavallisen pdfium.dll-tiedoston lataamisen jälkeen prosessi ei voi enää vaihtaa moottoria

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 on luotettava ajonaikainen tarkistus: se kertoo, ratkaiseeko ladattu binääri todella V8-riippuvaiset viennit, joten tarkistus toimii riippumatta siitä, minkä DLL-tiedoston asennusohjelma lopulta asensi. Sama moottori ja sama lippu ohjaavat myös dynaamista XFA-renderöintiä; taustaa XFA-tunnistuksen ja V8:n automaattisen valinnan vuorovaikutuksesta käsitellään artikkelissa XFA-lomakkeiden tunnistamisesta ja XFA-pakettien purkamisesta PDFiumilla

Isäntätapahtumat hälytyksille, tulostukselle, sähköpostille ja lomakkeen lähetykselle

Jokainen skriptin tekemä näkyvä asia reitittyy takaisin koodiisi TPdf-tapahtumien kautta, ja jokainen niistä on oletuksena turvallinen tyhjä toiminto (no-op), jos sitä ei ole määritetty. OnJavaScriptAlert vastaanottaa viestin, otsikon, painiketyypin ja kuvakkeen app.alert-kutsusta ja antaa sinun palauttaa painetun painikkeen tuloksen. OnJavaScriptResponse palvelee app.response-syötekehotteita, mukaan lukien salasanalippu. OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail ja OnJavaScriptSubmitForm tuovat esiin piippaus-, tulostus-, sähköposti- ja lähetyspyynnöt täysillä parametrisarjoillaan. Katseluohjelma, joka ei määritä mitään näistä, renderöi ja laskee silti oikein; dokumentti ei vain pysty avaamaan valintaikkunoita, tulostamaan tai lähettämään mitään

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;

Oletustoiminnan jako on tärkeä uhkamallinnuksessa. OnJavaScriptMail ja OnJavaScriptSubmitForm ovat ilmoitustyyppisiä: PDFium-komponentti ei suorita verkko- tai MAPI-toimintoja itsenäisesti, joten haitallinen dokumentti, joka kutsuu this.submitForm tai this.mailDoc valmistautumatonta isäntää vastaan, ei saavuta mitään. Isämän on valittava toiminto käyttöön määrittämällä käsittelijä ja toteuttamalla tiedonsiirto itse, mikä tarkoittaa, että päätös tietojen siirtämisestä pois koneelta on aina sinun, tehty koodissasi, URL-osoite ja hyötykuorma tiedossa

Miten estät haitallista PDF-tiedostoja avaamasta URL-osoitteita?

Miten estät haitallista PDF-tiedostoa avaamasta URL-osoitteita?

URI-toiminnot olivat ainoa paikka, jossa historiallinen oletus oli aktiivinen passiivisen sijaan, ja siksi ne saivat oman veto-oikeutensa. Ennen versiota 2.13.1 FFI_DoURIActionWithKeyboardModifier-takaisinkutsu suoritti ShellExecute-kutsulla minkä tahansa XFA-kentän tarjoaman URI-osoitteen ehdottomasti, mikä antoi haitalliselle dokumentille mahdollisuuden avata mielivaltaisia URL-osoitteita klikkauksella. OnXfaUriAction sulkee tämän aukon: komponentti kysyy tapahtumaa ennen suoritusta, ja käsittelijä, joka asettaa Handled-muuttujan arvoon True, estää ShellExecuteW-oletustoiminnan kokonaan. Tapahtuman jättäminen määrittämättä säilyttää vanhan toiminnan yhteensopivuuden vuoksi, joten tietoturvasta huolehtivan katseluohjelman tulisi aina määrittää se

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;

Sallittujen protokollien luettelo (allow-list) on vähimmäisvaatimus; tiukempi katseluohjelma varmistaa toiminnan käyttäjältä tai ohjaa kaiken oman hiekkalaatikoidun selainkomponenttinsa kautta. Sama veto-filosofia ulottuu version 2.13.1 tapahtumasarjaan: OnXfaEmail, OnXfaHttpRequest ja OnXfaOpenFile kukin tuovat esiin pyydetyn toiminnon tekstiparametrit, kun taas komponentti itse ei suorita verkko- tai tiedostonsiirtoa. Sitä, miten nämä suojaukset sopivat laajempaan luotettavuudeltaan epävarmojen dokumenttien strategiaan, mukaan lukien renderöinnin eristäminen, käsitellään artikkelissa turvallisen PDF-esikatselun rakentamisesta Delphissä

Kirjoittaminen kohdistettuun kenttään SetFocusedFormFieldText-metodilla

Versiossa 2.13.2 lisätty TPdf.SetFocusedFormFieldText on kirjoituspuolen kumppani FocusedFormFieldText-ominaisuudelle: se valitsee kohdistetun kentän nykyisen sisällön FORM_SelectAllText-metodilla ja korvaa sen FORM_ReplaceSelection-metodilla, aivan kuin käyttäjä olisi kirjoittanut korvaavan tekstin. Koska teksti syötetään interaktiivisen muokkauspolun kautta, kaikki kenttään liitetyt näppäily-, muotoilu- ja laskentaskriptit käynnistyvät samalla tavalla kuin manuaalisessa syötössä. Tämä tekee siitä oikean perustyökalun ohjelmalliseen täyttöön katseluohjelmassa, joka pitää JavaScriptin aktiivisena. Kenttien läpikäyntiä ja kohdistuksen hallintaa sen ympärillä käsitellään artikkelissa lomakekentissä navigoinnista PDFium-komponentilla

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;

Tämän kommentin tallennuksen epäsymmetria on todellinen raja, ei mikään pelkkä huomautus. AcroForm-teksti- ja valintakentissä muokattu arvo tallentuu kentän /V-merkintään, kun kohdistus poistuu, ja säilytys säilyy tallennuksen yli. XFA-tekstikentissä kirjoitus päätyy vain muistissa olevaan muokkauspuskuriin, koska PDFium ei tarjoa julkista API:a widget-arvojen synkronointiin takaisin XFA-tietopakettiin (datasets packet); dokumentin tallentaminen ei säilytä muutosta. Jos vaatimuksena on pysyvä XFA-tietojen muokkaus, muokkaa itse tietojoukon XML-koodia widgetin sijaan

Rajoitukset, jotka kannattaa tietää ennen käyttöönottoa

Jäljelle jää kaksi todellista rajoitusta. Ensinnäkin JavaScript-API, jonka PDFium toteuttaa, on toimiva osajoukko Acrobatin objektimallista ja keskittyy lomakeskriptauksen ytimeen: kenttien hallintaan, laskenta- ja muotoilutapahtumiin, app.alert- ja app.response-kutsuihin sekä tulostus-, sähköposti- ja lähetyspyyntöihin. Dokumentit, jotka tukeutuvat eksoottisiin Acrobat-yksinoikeudellisiin objekteihin, heikentyvät yleensä hiljaisesti, joten testaa todellisia lomakkeita, joita käyttäjäsi käsittelevät, sen sijaan että olettaisit täydellistä yhteensopivuutta. Toiseksi, V8-moottorin käyttöönotto vaatii pdfium.v8.dll-tiedoston toimittamista, mikä on huomattavasti suurempi binääri kuin tavallinen versio. Katseluohjelma, joka ei koskaan tarvitse skriptausta tai XFA-tukea, voi perustellusti käyttää pienempää DLL-tiedostoa ja jättää m_pJsPlatform-alustan liittämättä, mikä on jo itsessään pätevä tietoturvaratkaisu

Kaikesta tästä syntyvä toimintamalli on miellyttävän yksinkertainen: aseta EnableV8Engine käynnistyksen yhteydessä, määritä alert- ja response-tapahtumat niin, että valintaikkunat näyttävät natiiveilta, määritä OnXfaUriAction sekä kirjaa sähköposti- ja lähetystapahtumat lokiin, ja jätä kaikki muu oletusarvoisiksi tyhjiksi toiminnoiksi, kunnes toisin vaaditaan. Täydellinen tapahtumaviite ja lomakkeidentäyttörajapinta on dokumentoitu PDFium-komponentin tuotesivulla