Technical Article

בדיקות Preflight אוטומטיות וביקורת סיכונים ל-PDF עם PDFium

בתהליכי עבודה ארגוניים של הדפסה, ארכיבאות ותאימות מסמכים, קובץ PDF אינו יכול פשוט להיות "מעובד לתצוגה" (rendered). חובה לבצע לו ביקורת. קובץ PDF עשוי להכיל קבוצות שקיפות שאינן משוטחות (unflattened transparency groups) שגורמות לקריסת מעבדי תמונה (RIPs) ישנים, קוד JavaScript מוטמע שמהווה סיכון אבטחה, או תמונות ברזולוציה נמוכה שייראו נורא בהדפסה ב-300 DPI.

תהליך זה של בדיקת קובץ PDF לפני כניסתו לתהליך עבודה בסביבת ייצור ידוע בשם Preflighting (קדם-טיסה). במאמר זה, נחקור כיצד לבנות כלי אוטומטי לבדיקות preflight וביקורת סיכונים ב-Delphi על ידי ניצול יכולות הניתוח ברמה הנמוכה (low-level parsing) של PDFium.

בדיקות Preflight מרכזיות

ביקורת סיכונים סטנדרטית בודקת בדרך כלל את האלמנטים הבאים בתוך ה-PDF:

  • אלמנטים אינטראקטיביים: פעולות JavaScript, פעולות Launch (הפעלת תוכנות חיצוניות), ו-AcroForms.
  • מדדי משאבים: נוכחות של תמונות ברזולוציה נמוכה או חוסר בגופנים מוטמעים.
  • סטטוס אבטחה: הצפנת מסמך, דרישות סיסמה, והרשאות גישה (למשל, הדפסה מושבתת).
  • תקני מסמכים: אימות סמני תאימות של PDF/A או PDF/X במטא-נתונים של המסמך.

חילוץ מטא-נתונים והערות של PDF עם PDFium

PDFium מספק ממשק C API רובסטי ש-Delphi יכולה לצרוך. כדי לבצע ביקורת preflight, אנחנו לא רק מעבדים את הדף לתצוגה; אנו עוברים על עץ האובייקטים של ה-PDF. בואו נראה כיצד לעבור בלולאה על דפי המסמך ולבדוק הערות (annotations) כדי למצוא פעולות JavaScript שעלולות להיות מסוכנות.

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;

בדיקת רזולוציית תמונה

צעד קריטי נוסף ב-preflight עבור תהליכי הדפסה הוא לוודא שאף תמונה אינה נופלת מתחת לסף DPI מסוים. PDFium מאפשר לכם לחלץ אובייקטי תמונה ישירות מזרם הדף. על ידי חלוקת מימדי הפיקסלים של התמונה שחולצה במימדים הפיזיים (בנקודות) שהיא תופסת בדף ה-PDF, תוכלו לחשב את ה-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;

שילוב עם PDFium Component

בניית מנוע preflight רובסטי מאפס באמצעות ממשק ה-C API הגולמי של PDFium דורשת קוד תשתית (boilerplate) משמעותי והיכרות מעמיקה עם מפרט ה-PDF. שימוש בעטיפה (wrapper) מפשט זאת לאין שיעור. עם עטיפת Delphi ברמה גבוהה (high-level), תוכלו להפוך איטרציות מורכבות של מצביעי C לקוד מונחה עצמים נקי.

על ידי אוטומציה של שלב ה-preflight, תמנעו מקבצים פגומים לתקוע את צינור הייצור (production pipeline) שלכם, ותחסכו זמן ומשאבי עיבוד.

הערה: יכולות מתקדמות של ניתוח PDF ובדיקות preflight נתמכות באופן מלא על ידי PDFium Component.