HotPDF Component, bir FDF /Differences akışını Delphi tarafından THPDFHeadlessDocument.SaveFDFDifferencesToFile ile uygular: metot, yüklü PDF'in tam bayt uzunluğu ve SHA-256 ile tanımladığınız bir taban revizyonla hâlâ eşleştiğini kontrol eder, birikimli revizyon baytlarını tam olarak bir kez ekler, sonucu şifreleme izni ile DocMDP/FieldMDP kabulünden geçirir ve atomik olarak yayımlar. Aynı gönderiyi tekrarlamak bayt bayt bir no-op'tur
Varlık sebebi olan iş akışı gösterişsiz ve yaygındır. Bir sözleşmeyi ya da denetim formunu PDF olarak gönderirsiniz; biri kendi görüntüleyicisinde doldurur ve imzalar; geri dönen şey PDF değil bir FDF dosyasıdır. Alan değerleri /Fields içinde seyreder ama imzalı revizyonun kendisi — CMS imzasının gerçekten kapsadığı baytlar — /Differences içinde seyreder. Birleştirmeyi yanlış yaparsanız ya imzayı bozar ya da daha kötüsü, hiçbir imzanın kapsamadığı alan değerlerini kabul edersiniz
Bir FDF /Differences akışı gerçekte ne taşır?
Bir FDF /Differences akışı, hedef PDF için ham artımlı güncelleme baytları taşır; FDF düzeyinde bir geçmiş taşımaz. ISO 32000-1 §12.7.7.3, Tablo 243, FDF sözlüğünün Differences girdisini, alttaki PDF'in açıldığından bu yana yapılmış artımlı değişiklikleri olarak tanımlar; biçim, §7.5.6 altındaki bir PDF artımlı güncellemesiyle aynıdır: yeni ve değiştirilmiş nesneler, bir çapraz referans bölümü ve /Prev'i taban dosyanın içine işaret eden bir trailer
FDF kapsayıcısının kendisi tam tersidir. §12.7.7.1, FDF dosyalarına artımlı güncellemeleri yasaklar; bir FDF tek trailer, yalnızca generation 0 nesneler ve özgün nesne numaraları taşır. HotPDF Component bunu FDF ayrıştırıcısında aynen zorunlu kılar ve /Differences'i kolaylaştırmak için asla gevşetmez. Artımlı veri, kesinlikle artımsız bir kapsayıcının tek bir akışının içinde yaşar; o iki katmanı ayrı tutmak işin yarısıdır
Akışın iki özelliği, sonrasındaki her tasarım kararını biçimlendirir:
- Akış birikimlidir: hedef PDF açıldığından beri eklenen her baytı tutar; aynı oturumdan yapılan ikinci gönderi, hâlihazırda commit ettiğiniz revizyonu yineler ve yeniyi arkasına ekler
- Aynı FDF'nin dış
/Fieldsve/Annotsgirdilerinin/Differencesile uyuşacağı garanti edilmez; kolayca okuyabildiğiniz alan değerleri, imzanın kapsadığı veri değildir
İkinci nokta açık sözlülüğü hak eder. İçe aktarma kodunuz /Fields'ten /V'yi okuyup sonucu imzalı sayıyorsa — çünkü imza aynı dosyada gelmiştir — bir sahtecilik kanalı kurmuşsunuz demektir. HotPDF Component yolları bilerek ayrı tutar: FDF açıklama içe aktarıcısı /Differences'i tümüyle reddeder ve ona yalnızca özelleşmiş Differences taşıyıcısı dokunur
Eklemeyi açık bir taban revizyona bağlamak
SaveFDFDifferencesToFile, FDF'nin hangi revizyona ait olduğunu tahmin etmeyi reddeder. Çağıran BaseBytes — formun açıldığı eksiksiz PDF revizyonunun tam uzunluğu — ile BaseSHA256, yani 64 haneli onaltılık SHA-256 değerini verir. Metot yüklü belgenin o önekini yeniden özetler, uyuşmazlığı "The loaded PDF does not match the explicitly selected base" iletisiyle reddeder ve önekin, startxref'i gerçek çapraz referans verisini gösteren gerçek bir revizyon ucunda bittiğini kontrol eder
Pratik kural, iki sayıyı da formu gönderdiğinizde kaydetmektir; FDF geri döndüğünde değil. HPDFCrypt içindeki SHA256Init / SHA256Update / SHA256Final rutinlerinin üzerine küçük bir yardımcı yeter:
uses
SysUtils, Classes, HPDFCrypt;
function PrefixSHA256Hex(const FileName: string; PrefixBytes: Int64): AnsiString;
var
Stream: TFileStream;
Ctx: TSHA256Context;
Digest: TSHA256Digest;
Buffer: array[0..65535] of Byte;
Remaining: Int64;
Count, I: Integer;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
if (PrefixBytes <= 0) or (PrefixBytes > Stream.Size) then
raise EArgumentException.Create('Base length is outside the file');
SHA256Init(Ctx);
Remaining := PrefixBytes;
while Remaining > 0 do
begin
Count := SizeOf(Buffer);
if Remaining < Count then
Count := Integer(Remaining);
Stream.ReadBuffer(Buffer, Count);
SHA256Update(Ctx, Buffer, Count);
Dec(Remaining, Count);
end;
SHA256Final(Ctx, Digest);
finally
Stream.Free;
end;
Result := '';
for I := 0 to High(Digest) do
Result := Result + AnsiString(LowerCase(IntToHex(Digest[I], 2)));
end;
// Gönderim sırasında: giden dosyanın tamamı taban revizyondur
// BaseLength := 'out\contract-0042.pdf' boyutunun bayt cinsinden değeri
// BaseHash := PrefixSHA256Hex('out\contract-0042.pdf', BaseLength);
Yüklü belge tam olarak taban olmak zorunda değildir. Taban artı daha önceki gönderilerden hâlihazırda commit ettiğiniz revizyonlar olabilir; birikimli akışın yönetilebilir kalması da böyle sağlanır: BaseBytes özgün öneki adlandırmaya devam eder ve sonrasındaki her şey önceki ekleme baytları sayılır
Differences'ı SaveFDFDifferencesToFile ile uygulamak
THPDFHeadlessDocument.SaveFDFDifferencesToFile, çıktı dosyası adını, FDF baytlarını, taban uzunluğu ile özetini, isteğe bağlı bir gömülü FDF seçimini ve bir THPDFFDFPackageOptions sınır kaydını alır, bir THPDFHeadlessFDFDifferencesResult döndürür. İçeride FDF'yi bir THPDFFDFPackage olarak açar ve DecodeDifferences'ı kendisi çağırır; yani siz ön çözülmüş bir akış değil FDF dosyasını verirsiniz. Boş bir Selection üst düzey FDF demektir; boş olmayan biri önce gömülü bir alt FDF'ye girer
uses
SysUtils, IOUtils, HPDFHeadlessDocument, HPDFHeadlessCMS, HPDFFDFPackage;
procedure CommitReturnedForm(const CurrentPdf, NextPdf, FdfFile: string;
BaseLength: Int64; const BaseHash: AnsiString);
var
Doc: THPDFHeadlessDocument;
Policy: THPDFHeadlessCMSOptions;
Selection: THPDFFDFChildSelections;
Applied: THPDFHeadlessFDFDifferencesResult;
begin
Doc := THPDFHeadlessDocument.Create;
try
Policy := THPDFHeadlessCMSOptions.Default;
Policy.TrustedCAFile := 'trust\issuing-ca.pem';
Doc.SetSignaturePolicyOptions(Policy);
Doc.LoadFromFile(CurrentPdf);
Selection := nil; // üst düzey FDF, gömülü alt yok
Applied := Doc.SaveFDFDifferencesToFile(NextPdf,
TFile.ReadAllBytes(FdfFile), BaseLength, BaseHash,
Selection, THPDFFDFPackageOptions.Default);
Writeln(Format('base %d, prior %d, total %d, added %d, no-op %s',
[Applied.BaseBytes, Applied.PriorAppendBytes, Applied.TotalAppendBytes,
Applied.AddedBytes, BoolToStr(Applied.NoOp, True)]));
finally
Doc.Free;
end;
end;
Kontrollerin sırası çağrının kendisinden daha önemlidir. Taban özeti geçtikten sonra metot, hâlihazırda commit edilmiş ekleme baytlarını — BaseBytes ile yüklü dosyanın sonu arasındaki her şeyi — çözülmüş yükün başıyla karşılaştırır. Commit ettiğinizden daha kısa bir gönderi "A cumulative submission cannot remove committed append bytes" ile başarısız olur; onunla çelişen biri "The cumulative Differences conflict with the committed revision" ile. Yalnızca kalan sonek yenidir ve AddedBytes'a yalnızca o sonek düşer
Kurulan aday, hedef dizinde özel (exclusive) bir geçici dosyaya yazılır, diske boşaltılır, bağımsız bir revizyon kaynağı olarak yeniden açılır ve çıktının yerini almadan önce doğrulanır. Ayrıştırma, iptal, politika, bütçe ya da yayım başarısızlıkları hem yüklü belgeyi hem var olan çıktı dosyasını olduğu gibi bırakır. İki önkoşul insanları erkenden yakalar: çıktı yüklü kaynağın adı olamaz ve belgenin kaydedilmemiş düzenlemesi olamaz; çünkü opak bir ekleme onları sessizce atardı ("Publish pending document edits before applying Differences")
Tekrarlanan bir uygulama neden bayt bayt bir no-op'tur?
Aynı birikimli FDF'nin tekrarlanan uygulaması no-op'tur, çünkü yükünün her baytı zaten dosyadadır. Yük, önceki ekleme baytlarına tam olarak eşit olduğunda SaveFDFDifferencesToFile NoOp = True ile AddedBytes = 0 raporlar ve yayımladığı çıktı geçerli PDF ile özdeştir. Bu, "yalnızca ekleme"den daha güçlü bir garantisidir ve ağ zaman aşımı ya da çöken bir işçiden sonraki yeniden denemeleri güvenli kılan da budur
// Rev 1 zaten contract-0042-r1.pdf içine commit edildi; aynı FDF iki kez geliyor
// (Doc, Again, Fdf, Selection, BaseLength ve BaseHash yukarıdaki gibi bildirili)
Doc := THPDFHeadlessDocument.Create;
try
Doc.LoadFromFile('store\contract-0042-r1.pdf');
Again := Doc.SaveFDFDifferencesToFile('store\contract-0042-r1-retry.pdf',
Fdf, BaseLength, BaseHash, Selection, THPDFFDFPackageOptions.Default);
// Again.NoOp = True, Again.AddedBytes = 0,
// ve yeniden deneme dosyası contract-0042-r1.pdf ile bayt bayt özdeş
finally
Doc.Free;
end;
Kabul testlerinizi buna göre yazın. Yeniden deneme çıktısı ile geçerli revizyon arasında dosyanın küçülmediğini değil tam bayt eşitliğini öne sürün. Yalnızca yalnızca-ekleme davranışını kontrol eden bir test, aynı revizyonu ikinci kez ekleyen bir uygulamayı memnuniyetle geçer; bu da geçerli görünümlü ama yinelenmiş bir güncelleme bölümü taşıyan ve sonraki bir DocMDP denetiminin açıklamak zorunda kalacağı bir PDF üretir
Revizyon yayımlanmadan önce hangi kabul kontrolleri çalışır?
Yayımdan önce aday PDF kimlik, imza ve izin kontrollerinden geçmek zorundadır ve hiçbiri dış FDF alanlarına güvenmez. Trailer'ın /ID'sinin ilk elemanı değişmeden kalmalı ve yüklü belgede hâlihazırda bulunan her imza alanı, özdeş imza sözlüğüyle ve aynı imza değeri kimliğiyle varlığını sürdürmelidir. Var olan bir imzayı düşüren, değiştiren ya da yeniden yazan bir Differences yükü hdSignatureModificationUnsupported ile başarısız olur
Sıra yetkilendirme katmanlarında. Şifreli bir belge üzerinde boş olmayan bir eklemede değişen nesneler role göre sınıflanır (katalog ve sayfalar, açıklamalar, AcroForm alanları, appearance stream'ler) ve kimliği doğrulanmış kullanıcı izin bitleriyle karşılaştırılır; açıklama düzenleyen bir revizyon açıklama iznine ihtiyaç duyar, gerisi aynı. Sonra aday, HotPDF Component'in başka yerlerde kullandığı sertifika ve alan kilidi uygulamasından geçer: bir DocMDP sertifika imzası (ISO 32000-1 §12.8.2.2), sonraki revizyonların ne tür değişiklik yapabileceğini sınırlar; FieldMDP kilitleri (§12.8.2.4) adlandırılmış alanları korur. HotPDF'te DocMDP ve FieldMDP revizyon analizi, o izinlerin revizyon başına nasıl sınıflandığını anlatır
Hata işlemesi "bu FDF bozuk" ile "bu revizyona izin yok"u ayırmalıdır; çünkü ikinci durum çoğu zaman bir insana ihtiyaç duyar:
try
Applied := Doc.SaveFDFDifferencesToFile(NextPdf, Fdf, BaseLength, BaseHash,
Selection, THPDFFDFPackageOptions.Default);
except
on E: EHPDFFDFPackageError do
QuarantineFdf(FdfFile, 'No usable Differences stream: ' + E.Message);
on E: EHPDFHeadlessDocumentError do
case E.Code of
hdPermissionDenied, hdSignatureModificationUnsupported:
EscalateForReview(FdfFile, E.Message);
hdLimitExceeded:
QuarantineFdf(FdfFile, 'Over budget: ' + E.Message);
else
QuarantineFdf(FdfFile, E.Message); // taban uyuşmazlığı, çelişki, bozuk uç nokta
end;
end;
İşlemin asla yapmadıkları, kontrol ettikleri kadar önemlidir. SaveFDFDifferencesToFile, FDF'nin /F dosya belirtimini asla getirmez; FDF içinde adı geçen PDF, birleştirmeyi diskteki başka bir dosyaya ya da ağa yönlendiremez ve işlem JavaScript ya da eylem asla çalıştırmaz. Her girdi açıkça gelir: yüklü taban, FDF baytları ve yapılandırdığınız güven malzemesi
Windows işlemleri ve C ABI fdf.differences.apply
Aynı taşıyıcı, headless kodu doğrudan yazmadan da kullanılabilir. THPDFWindowsFormDataTransaction.ApplyFDFDifferences, FDF baytlarını, taban uzunluğunu, taban SHA-256'sını, seçimi, paket seçeneklerini ve bir parolayı alır, işi SaveFDFDifferencesToFile'a devreder ve işlemin bayt muhasebesini, rollback, iptal ve aynı tanıtıcıyla yeniden deneme davranışlarını ekler. Delphi dışı çağıranlar için C ABI, base'in uzunluğu ile özeti taşıdığı JSON işlemini fdf.differences.apply olarak ortaya koyar:
{ "schemaVersion": 1, "type": "fdf.differences.apply", "format": "fdf",
"base": { "bytes": 673, "sha256": "..." },
"signaturePolicy": { "trustedCAPEM": "..." } }
JSON yanıtı baseBytes, priorAppendBytes, totalAppendBytes, addedBytes ve noop bildirir; THPDFHeadlessFDFDifferencesResult ile aynı alanlardır. Bu taşıyıcının yayımlanan kabul kapıları Linux FPC 3.3.1, Windows Delphi 32 bit ve 64 bit ve Windows FPC 32 bit ve 64 bit üzerinde çalışır; her güvenlik hedefi, 1,244 yerel kontrol ve 382 callback komutuyla 21 durumu kapsar
Bilmeye değer sınırlar ve test verisi tuzakları
Differences taşıyıcısı bilinçli olarak dardır ve birkaç kenarı gerçek test setlerini ısırdı:
- Taban, eksiksiz bir PDF revizyonu olmalıdır. Dosyanın ortasına düşen bir uzunluk ya da
startxref'i gerçek çapraz referans verisini göstermeyen bir uzunluk, tek bir bayt kopyalanmadan reddedilir - İmza test verilerinde dolaylı bir
/Lockgerekir. Windows yüklü belge izin okuyucusu,/Lock'u satır içi sözlük olan bir imza alanını "Signature field /Lock must be an indirect reference" ile reddeder; bazı imzalama yolları satır içi biçimi tolere etse de kilit sözlüğünü kendi nesnesi olarak yazın - Çıplak
openssl req -x509ile yapılan bir test CA'sındaCA:TRUEilekeyCertSignyoktur; zincir doğrulaması, kütüphane hatasına benzeyen ama olmayan bir sertifika doğrulama hatasıyla başarısız olur - Dış
/Fieldsdeğerleri bu çağrıyla birleştirilmez. Onları belgede de istiyorsanız bilgilendirici olup olmadıklarına açıkça karar verin ve asla imzalı olarak sunmayın - FDF'nin kendisi artımsız kalır. Birden çok trailer, sıfırdan farklı generation ya da yinelenen nesne numaraları taşıyan bir FDF dosyası daha zengin değil bozuk bir FDF'dir
İmzalı revizyonların devrede olmadığı, FDF ve XFDF'nin sade değer tarafı için HotPDF'te çok seçimli FDF ve XFDF gidiş dönüşü ile Delphi'de XFDF açıklama içe ve dışa aktarımı daha iyi başlangıç noktalarıdır
SSS
FDF dosyası yerine çözülmüş Differences akışını verebilir miyim?
Hayır. SaveFDFDifferencesToFile, verdiğiniz FDF'yi bir THPDFFDFPackage olarak ayrıştırır, isteğe bağlı gömülü seçimi uygular ve DecodeDifferences'ı dahilen çağırır. Ham revizyon baytlarını incelemek ya da arşivlemek istediğinizde THPDFFDFPackage.DecodeDifferences'ı tek başına kullanın ama FDF dosyasını apply çağrısına verin
İstemci ikinci, daha büyük bir birikimli FDF gönderdiğinde ne olur?
İlk commit edilen revizyonu içeren PDF'i yükleyin ve aynı BaseBytes ile BaseSHA256'yı koruyun. Metot, yeni yükün tam olarak commit edilmiş baytlarla başladığını doğrular, yalnızca yeni soneki ekler ve onu AddedBytes'ta bildirir, sonra sonuç üzerinde tam izin ile DocMDP/FieldMDP kabulünü çalıştırır
Başarılı bir uygulama, /Fields içindeki alan değerlerinin imzalı olduğu anlamına gelir mi?
Hayır. ISO 32000-1, tek bir FDF'deki /Fields, /Annots ve /Differences'in uyuşmasını gerektirmez ve HotPDF Component onları uzlaştırmaz. İmzalı olan, eklenen revizyondaki CMS imzalarının kapsadığı her neyse odur; imzalı değerleri FDF sarmalayıcısından değil birleştirilmiş PDF'den okuyun
FDF Differences taşıyıcısı, headless imza politika motoru ve Windows form verisi işlemlerinin tamamı HotPDF Component içinde, Delphi, C++Builder ve Free Pascal ile gelir