Technical Article

Automatizuotas PDF išankstinis patikrinimas ir rizikos auditas su „PDFium“

Įmonių spausdinimo, archyvavimo ir dokumentų atitikties darbo eigose PDF failo negalima tiesiog „atvaizduoti“. Jį būtina audituoti. PDF faile gali būti nesuplokštintų skaidrumo grupių, kurios užlaužia (angl. crash) senus rastrinių vaizdų procesorius (RIP), įterptojo „JavaScript“, keliančio saugumo riziką, arba mažos raiškos vaizdų, kurie spausdinant 300 DPI raiška atrodys prastai.

Šis PDF patikrinimo procesas prieš jam patenkant į gamybos darbo eigą yra žinomas kaip išankstinis patikrinimas (angl. Preflighting). Šiame straipsnyje apžvelgsime, kaip „Delphi“ aplinkoje sukurti automatizuotą išankstinio patikrinimo ir rizikos audito įrankį, pasitelkiant žemo lygio „PDFium“ analizės galimybes.

Pagrindiniai išankstinio patikrinimo žingsniai

Standartinis rizikos auditas paprastai tikrina šiuos PDF elementus:

  • Interaktyvūs elementai: „JavaScript“ veiksmai, paleidimo (angl. Launch) veiksmai (vykdantys išorines programas) ir „AcroForms“.
  • Išteklių metrika: mažos raiškos vaizdai arba trūkstami įterptieji šriftai.
  • Saugumo būsena: dokumento šifravimas, slaptažodžio reikalavimai ir prieigos teisės (pvz., išjungtas spausdinimas).
  • Dokumentų standartai: PDF/A arba PDF/X atitikties žymeklių patvirtinimas dokumento metaduomenyse.

PDF metaduomenų ir anotacijų išskyrimas su „PDFium“

„PDFium“ teikia patikimą C API, kurią galima naudoti „Delphi“ aplinkoje. Norėdami atlikti išankstinį auditą, mes ne tik atvaizduojame puslapį, bet ir pereiname per PDF objektų medį. Pažiūrėkime, kaip pereiti per dokumento puslapius ir patikrinti anotacijas ieškant galimai rizikingų „JavaScript“ veiksmų.

uses
  System.SysUtils, pdfium_lib;

procedure AuditPdfSecurity(const FileName: string);
var
  Doc: FPDF_DOCUMENT;
  PageCount, i, j: Integer;
  Page: FPDF_PAGE;
  AnnotCount: Integer;
  Annot: FPDF_ANNOTATION;
  AnnotSubType: FPDF_ANNOTATION_SUBTYPE;
  Action: FPDF_ACTION;
  ActionType: ULONG;
begin
  FPDF_InitLibrary();
  try
    // Load the document without a password
    Doc := FPDF_LoadDocument(PAnsiChar(AnsiString(FileName)), nil);
    if Doc = nil then
      raise Exception.Create('Failed to load document or password required.');
      
    try
      PageCount := FPDF_GetPageCount(Doc);
      Writeln(Format('Auditing %d pages...', [PageCount]));
      
      for i := 0 to PageCount - 1 do
      begin
        Page := FPDF_LoadPage(Doc, i);
        if Page <> nil then
        begin
          AnnotCount := FPDFPage_GetAnnotCount(Page);
          for j := 0 to AnnotCount - 1 do
          begin
            Annot := FPDFPage_GetAnnot(Page, j);
            if Annot <> nil then
            begin
              AnnotSubType := FPDFAnnot_GetSubtype(Annot);
              
              // Check for Link Annotations that might have malicious actions
              if AnnotSubType = FPDF_ANNOT_LINK then
              begin
                Action := FPDFAnnot_GetLinkAction(Annot);
                if Action <> nil then
                begin
                  ActionType := FPDFAction_GetType(Action);
                  // 2 represents PDFACTION_URI, 3 represents PDFACTION_SOUND, 4 represents PDFACTION_MOVIE
                  // We specifically look out for PDFACTION_LAUNCH or PDFACTION_JAVA_SCRIPT
                  if (ActionType = PDFACTION_JAVA_SCRIPT) or (ActionType = PDFACTION_LAUNCH) then
                  begin
                    Writeln(Format('WARNING: Malicious action detected on page %d', [i + 1]));
                  end;
                end;
              end;
              FPDFPage_CloseAnnot(Annot);
            end;
          end;
          FPDF_ClosePage(Page);
        end;
      end;
    finally
      FPDF_CloseDocument(Doc);
    end;
  finally
    FPDF_DestroyLibrary();
  end;
end;

Vaizdų raiškos tikrinimas

Kitas svarbus išankstinio patikrinimo žingsnis spausdinimo darbo eigoms yra užtikrinti, kad joks vaizdas nebūtų žemesnės nei nurodytos DPI ribos. „PDFium“ leidžia išskirti vaizdų objektus tiesiogiai iš puslapio srauto. Padalijus išskirto vaizdo pikselių matmenis iš fizinių matmenų (taškais), kuriuos jis užima PDF puslapyje, galima apskaičiuoti efektyvųjį DPI.

procedure AuditPageImages(Page: FPDF_PAGE);
var
  ObjCount, i: Integer;
  PageObj: FPDF_PAGEOBJECT;
  ImgWidth, ImgHeight: Integer;
begin
  ObjCount := FPDFPage_CountObjects(Page);
  for i := 0 to ObjCount - 1 do
  begin
    PageObj := FPDFPage_GetObject(Page, i);
    if FPDFPageObj_GetType(PageObj) = FPDF_PAGEOBJ_IMAGE then
    begin
      FPDFImageObj_GetBitmap(PageObj); // Returns an FPDF_BITMAP you can inspect
      // Retrieve metadata using FPDFImageObj_GetImageMetadata
      // Calculate effective DPI based on quad coordinates
    end;
  end;
end;

Integravimas su „PDFium Component“

Kuriant patikimą išankstinio patikrinimo variklį nuo nulio naudojant neapdorotą „PDFium C API“, reikia parašyti daug standartinio kodo (angl. boilerplate code) ir turėti gilių žinių apie PDF specifikaciją. Naudojant apvalkalą (angl. wrapper), tai labai supaprastėja. Turėdami aukšto lygio „Delphi“ apvalkalą, sudėtingas C rodyklių iteracijas galite paversti švariu, objektiniu kodu.

Automatizuodami išankstinio patikrinimo fazę, užkirstate kelią blogiems failams sugadinti jūsų gamybos konvejerį, taip sutaupydami ir laiko, ir atvaizdavimo išteklių.

Pastaba: pažangios PDF analizės ir išankstinio patikrinimo galimybės yra visiškai palaikomos „PDFium Component VCL“ komponente.