Fixes and support for ApprovalSignature and TimeStampSignature

This commit is contained in:
Paul van Brouwershaven
2024-11-14 13:00:58 +01:00
parent 0899875647
commit 7946d5266d
20 changed files with 1204 additions and 841 deletions

View File

@@ -2,69 +2,112 @@ package sign
import (
"strconv"
"strings"
)
func (context *SignContext) createCatalog() (catalog string, err error) {
catalog = strconv.Itoa(int(context.CatalogData.ObjectId)) + " 0 obj\n"
catalog += "<< /Type /Catalog"
catalog += " /Version /" + context.PDFReader.PDFVersion
func (context *SignContext) createCatalog() (string, error) {
var catalogBuilder strings.Builder
// Start the catalog object
catalogBuilder.WriteString(strconv.Itoa(int(context.CatalogData.ObjectId)) + " 0 obj\n")
catalogBuilder.WriteString("<< /Type /Catalog")
// (Optional; PDF 1.4) The version of the PDF specification to which
// the document conforms (for example, 1.4) if later than the version
// specified in the files header (see 7.5.2, "File header"). If the header
// specifies a later version, or if this entry is absent, the document
// shall conform to the version specified in the header. This entry
// enables a PDF processor to update the version using an incremental
// update; see 7.5.6, "Incremental updates".
// The value of this entry shall be a name object, not a number, and
// therefore shall be preceded by a SOLIDUS (2Fh) character (/) when
// written in the PDF file (for example, /1.4).
//
// If an incremental upgrade requires a version that is higher than specified by the document.
// if context.PDFReader.PDFVersion < "2.0" {
// catalogBuilder.WriteString(" /Version /2.0")
// }
// Retrieve the root and check for necessary keys in one loop
root := context.PDFReader.Trailer().Key("Root")
root_keys := root.Keys()
found_pages := false
found_names := false
for _, key := range root_keys {
if key == "Pages" {
found_pages = true
break
}
}
for _, key := range root_keys {
if key == "Names" {
found_names = true
break
}
}
rootPtr := root.GetPtr()
context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R"
if found_names {
names := root.Key("Names").GetPtr()
catalog += " /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R"
foundPages, foundNames := false, false
for _, key := range root.Keys() {
switch key {
case "Pages":
foundPages = true
case "Names":
foundNames = true
}
if foundPages && foundNames {
break
}
}
if found_pages {
// Add Pages and Names references if they exist
if foundPages {
pages := root.Key("Pages").GetPtr()
catalog += " /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R"
catalogBuilder.WriteString(" /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R")
}
if foundNames {
names := root.Key("Names").GetPtr()
catalogBuilder.WriteString(" /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R")
}
catalog += " /AcroForm <<"
catalog += " /Fields [" + strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R]"
// Start the AcroForm dictionary with /NeedAppearances
catalogBuilder.WriteString(" /AcroForm << /Fields [")
switch context.SignData.Signature.CertType {
case CertificationSignature, UsageRightsSignature:
catalog += " /NeedAppearances false"
// Add existing signatures to the AcroForm dictionary
for i, sig := range context.SignData.ExistingSignatures {
if i > 0 {
catalogBuilder.WriteString(" ")
}
catalogBuilder.WriteString(strconv.Itoa(int(sig.ObjectId)) + " 0 R")
}
// Add the visual signature field to the AcroForm dictionary
if len(context.SignData.ExistingSignatures) > 0 {
catalogBuilder.WriteString(" ")
}
catalogBuilder.WriteString(strconv.Itoa(int(context.VisualSignData.ObjectId)) + " 0 R")
catalogBuilder.WriteString("]") // close Fields array
catalogBuilder.WriteString(" /NeedAppearances false")
// Signature flags (Table 225)
//
// Bit position 1: SignaturesExist
// If set, the document contains at least one signature field. This
// flag allows an interactive PDF processor to enable user
// interface items (such as menu items or push-buttons) related to
// signature processing without having to scan the entire
// document for the presence of signature fields.
//
// Bit position 2: AppendOnly
// If set, the document contains signatures that may be invalidated
// if the PDF file is saved (written) in a way that alters its previous
// contents, as opposed to an incremental update. Merely updating
// the PDF file by appending new information to the end of the
// previous version is safe (see H.7, "Updating example").
// Interactive PDF processors may use this flag to inform a user
// requesting a full save that signatures will be invalidated and
// require explicit confirmation before continuing with the
// operation.
//
// Set SigFlags and Permissions based on Signature Type
switch context.SignData.Signature.CertType {
case CertificationSignature:
catalog += " /SigFlags 3"
case CertificationSignature, ApprovalSignature, TimeStampSignature:
catalogBuilder.WriteString(" /SigFlags 3")
case UsageRightsSignature:
catalog += " /SigFlags 1"
catalogBuilder.WriteString(" /SigFlags 1")
}
catalog += " >>"
// Finalize the AcroForm and Catalog object
catalogBuilder.WriteString(" >>") // Close AcroForm
catalogBuilder.WriteString(" >>\nendobj\n") // Close catalog object
switch context.SignData.Signature.CertType {
case CertificationSignature:
catalog += " /Perms << /DocMDP " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"
case UsageRightsSignature:
catalog += " /Perms << /UR3 " + strconv.Itoa(int(context.SignData.ObjectId)) + " 0 R >>"
}
catalog += " >>"
catalog += "\nendobj\n"
return catalog, nil
return catalogBuilder.String(), nil
}