Technical Article

PDFium ile Otomatik PDF Uçuş Öncesi Kontrolü ve Risk Denetimi

Kurumsal yazdırma, arşivleme ve belge uyumluluğu iş akışlarında bir PDF dosyası basitçe "işlenemez" (render). Denetlenmesi (audit) gerekir. Bir PDF, eski raster görüntü işlemcilerini (RIP'ler) çökerten düzleştirilmemiş saydamlık grupları (unflattened transparency groups), güvenlik riski oluşturan gömülü JavaScript veya 300 DPI'da yazdırıldığında korkunç görünecek düşük çözünürlüklü resimler içerebilir.

Bir PDF'i üretim iş akışına girmeden önce inceleme sürecine Uçuş Öncesi Kontrol (Preflighting) denir. Bu makalede, PDFium'un düşük seviyeli ayrıştırma (parsing) yeteneklerinden yararlanarak Delphi'de otomatik bir uçuş öncesi kontrol ve risk denetimi aracının nasıl oluşturulacağını keşfedeceğiz.

Temel Uçuş Öncesi Kontroller

Standart bir risk denetimi tipik olarak bir PDF içindeki aşağıdaki unsurları kontrol eder:

  • Etkileşimli Unsurlar: JavaScript eylemleri, Başlatma (Launch) eylemleri (harici programları yürütme) ve AcroForm'lar.
  • Kaynak Metrikleri: Düşük çözünürlüklü resimlerin veya eksik gömülü yazı tiplerinin varlığı.
  • Güvenlik Durumu: Belge şifrelemesi, parola gereksinimleri ve erişim izinleri (örneğin, yazdırmanın devre dışı bırakılması).
  • Belge Standartları: Belge meta verilerinde PDF/A veya PDF/X uygunluk işaretçilerinin doğrulanması.

PDFium ile PDF Meta Verilerini ve Ek Açıklamaları (Annotations) Çıkarma

PDFium, Delphi'nin tüketebileceği sağlam bir C API'si sağlar. Bir uçuş öncesi denetimi gerçekleştirmek için yalnızca sayfayı işlemeyiz (render); PDF nesne ağacında geziniriz. Olası riskli JavaScript eylemlerini bulmak için belge sayfalarında nasıl yineleneceğimize ve ek açıklamaları (annotations) nasıl inceleyeceğimize bakalım.

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;

Görüntü Çözünürlüğünü Kontrol Etme

Yazdırma iş akışları için bir diğer kritik uçuş öncesi adımı, hiçbir görüntünün belirli bir DPI eşiğinin altına düşmemesini sağlamaktır. PDFium, görüntü nesnelerini (image objects) doğrudan sayfa akışından çıkarmanıza olanak tanır. Çıkarılan görüntünün piksel boyutlarını, PDF sayfasında kapladığı fiziksel boyutlara (point cinsinden) bölerek etkili DPI'ı hesaplayabilirsiniz.

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 ile Entegrasyon

Ham PDFium C API'sini kullanarak sıfırdan sağlam bir uçuş öncesi (preflight) motoru oluşturmak, önemli miktarda basmakalıp kod (boilerplate code) ve PDF spesifikasyonu hakkında derin bilgi gerektirir. Bir sarmalayıcı (wrapper) kullanmak bunu büyük ölçüde basitleştirir. Üst düzey bir Delphi sarmalayıcısı ile karmaşık C işaretçi (pointer) yinelemelerini temiz, nesne yönelimli koda dönüştürebilirsiniz.

Uçuş öncesi aşamasını otomatikleştirerek, hatalı dosyaların üretim hattınızı tıkamasını önler, hem zamandan hem de işleme (rendering) kaynaklarından tasarruf edersiniz.

Not: Gelişmiş PDF ayrıştırma ve uçuş öncesi kontrol yetenekleri PDFium Component tarafından tam olarak desteklenmektedir.