From 94e7fa22a9f6d9b94d560e4795aac6ed8bebbafb Mon Sep 17 00:00:00 2001 From: Paul van Brouwershaven Date: Fri, 21 Feb 2025 21:32:38 +0100 Subject: [PATCH 1/2] Revert "Revert "fix: Existing catalog entries are being discarded"" --- sign/pdfcatalog.go | 69 ++++++++++++++++++++++++++++++++++++++++- sign/pdfcatalog_test.go | 12 +++---- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/sign/pdfcatalog.go b/sign/pdfcatalog.go index 8182a70..2768cf0 100644 --- a/sign/pdfcatalog.go +++ b/sign/pdfcatalog.go @@ -2,15 +2,21 @@ package sign import ( "bytes" + "fmt" + "github.com/digitorus/pdf" + "io" + "slices" "strconv" ) func (context *SignContext) createCatalog() ([]byte, error) { + var overwrittenCatalogKeys []string var catalog_buffer bytes.Buffer // Start the catalog object catalog_buffer.WriteString("<<\n") catalog_buffer.WriteString(" /Type /Catalog\n") + overwrittenCatalogKeys = append(overwrittenCatalogKeys, "Type") // (Optional; PDF 1.4) The version of the PDF specification to which // the document conforms (for example, 1.4) if later than the version @@ -50,13 +56,16 @@ func (context *SignContext) createCatalog() ([]byte, error) { if foundPages { pages := root.Key("Pages").GetPtr() catalog_buffer.WriteString(" /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R\n") + overwrittenCatalogKeys = append(overwrittenCatalogKeys, "Pages") } if foundNames { names := root.Key("Names").GetPtr() catalog_buffer.WriteString(" /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R\n") + overwrittenCatalogKeys = append(overwrittenCatalogKeys, "Names") } // Start the AcroForm dictionary with /NeedAppearances + overwrittenCatalogKeys = append(overwrittenCatalogKeys, "AcroForm") catalog_buffer.WriteString(" /AcroForm <<\n") catalog_buffer.WriteString(" /Fields [") @@ -119,7 +128,65 @@ func (context *SignContext) createCatalog() ([]byte, error) { // Finalize the AcroForm and Catalog object catalog_buffer.WriteString(" >>\n") // Close AcroForm - catalog_buffer.WriteString(">>\n") // Close Catalog + + // Copy over existing catalog entries from the original document that we did not need to override. + for _, key := range root.Keys() { + if !slices.Contains(overwrittenCatalogKeys, key) { + _, _ = fmt.Fprintf(&catalog_buffer, "/%s ", key) + context.serializeCatalogEntry(&catalog_buffer, rootPtr.GetID(), root.Key(key)) + } + } + catalog_buffer.WriteString(">>\n") // Close Catalog return catalog_buffer.Bytes(), nil } + +// serializeCatalogEntry takes a pdf.Value and serializes it to the given writer. +func (context *SignContext) serializeCatalogEntry(w io.Writer, rootObjId uint32, value pdf.Value) { + if ptr := value.GetPtr(); ptr.GetID() != rootObjId { + // Indirect object + _, _ = fmt.Fprintf(w, "%d %d R", ptr.GetID(), ptr.GetGen()) + return + } + + // Direct object + switch value.Kind() { + case pdf.String: + _, _ = fmt.Fprintf(w, "(%s)", value.RawString()) + case pdf.Null: + _, _ = fmt.Fprint(w, "null") + case pdf.Bool: + if value.Bool() { + _, _ = fmt.Fprint(w, "true") + } else { + _, _ = fmt.Fprint(w, "false") + } + case pdf.Integer: + _, _ = fmt.Fprintf(w, "%d", value.Int64()) + case pdf.Real: + _, _ = fmt.Fprintf(w, "%f", value.Float64()) + case pdf.Name: + _, _ = fmt.Fprintf(w, "/%s", value.Name()) + case pdf.Dict: + _, _ = fmt.Fprint(w, "<<") + for idx, key := range value.Keys() { + if idx > 0 { + _, _ = fmt.Fprint(w, " ") // Space between items + } + _, _ = fmt.Fprintf(w, "/%s ", key) + context.serializeCatalogEntry(w, rootObjId, value.Key(key)) + } + _, _ = fmt.Fprint(w, ">>") + case pdf.Array: + _, _ = fmt.Fprint(w, "[") + for idx := range value.Len() { + if idx > 0 { + _, _ = fmt.Fprint(w, " ") // Space between items + } + context.serializeCatalogEntry(w, rootObjId, value.Index(idx)) + } + _, _ = fmt.Fprint(w, "]") + case pdf.Stream: + panic("stream cannot be a direct object") + } +} diff --git a/sign/pdfcatalog_test.go b/sign/pdfcatalog_test.go index c4e25e6..f996116 100644 --- a/sign/pdfcatalog_test.go +++ b/sign/pdfcatalog_test.go @@ -15,17 +15,17 @@ var testFiles = []struct { { file: "../testfiles/testfile20.pdf", expectedCatalogs: map[CertType]string{ - CertificationSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n>>\n", - UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 1\n >>\n>>\n", - ApprovalSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n>>\n", + CertificationSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n/Metadata 2 0 R>>\n", + UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 1\n >>\n/Metadata 2 0 R>>\n", + ApprovalSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n/Metadata 2 0 R>>\n", }, }, { file: "../testfiles/testfile21.pdf", expectedCatalogs: map[CertType]string{ - CertificationSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n>>\n", - UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 1\n >>\n>>\n", - ApprovalSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n>>\n", + CertificationSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n/Metadata 8 0 R>>\n", + UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 1\n >>\n/Metadata 8 0 R>>\n", + ApprovalSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n/Metadata 8 0 R>>\n", }, }, } From 2d533be1399c67c9a58fd47b3a1cebefb30b61a1 Mon Sep 17 00:00:00 2001 From: Paul van Brouwershaven Date: Tue, 25 Feb 2025 14:57:28 +0100 Subject: [PATCH 2/2] Removing code that is no longer necessary --- sign/pdfcatalog.go | 45 +++++++++-------------------------------- sign/pdfcatalog_test.go | 12 +++++------ 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/sign/pdfcatalog.go b/sign/pdfcatalog.go index 2768cf0..a8c0d07 100644 --- a/sign/pdfcatalog.go +++ b/sign/pdfcatalog.go @@ -3,20 +3,18 @@ package sign import ( "bytes" "fmt" - "github.com/digitorus/pdf" "io" - "slices" "strconv" + + "github.com/digitorus/pdf" ) func (context *SignContext) createCatalog() ([]byte, error) { - var overwrittenCatalogKeys []string var catalog_buffer bytes.Buffer // Start the catalog object catalog_buffer.WriteString("<<\n") catalog_buffer.WriteString(" /Type /Catalog\n") - overwrittenCatalogKeys = append(overwrittenCatalogKeys, "Type") // (Optional; PDF 1.4) The version of the PDF specification to which // the document conforms (for example, 1.4) if later than the version @@ -34,38 +32,21 @@ func (context *SignContext) createCatalog() ([]byte, error) { // catalog_buffer.WriteString(" /Version /2.0") // } - // Retrieve the root and check for necessary keys in one loop + // Retrieve the root, its pointer and set the root string root := context.PDFReader.Trailer().Key("Root") rootPtr := root.GetPtr() context.CatalogData.RootString = strconv.Itoa(int(rootPtr.GetID())) + " " + strconv.Itoa(int(rootPtr.GetGen())) + " R" - foundPages, foundNames := false, false + // Copy over existing catalog entries except for type and AcroForum for _, key := range root.Keys() { - switch key { - case "Pages": - foundPages = true - case "Names": - foundNames = true + if key != "Type" && key != "AcroForm" { + _, _ = fmt.Fprintf(&catalog_buffer, " /%s ", key) + context.serializeCatalogEntry(&catalog_buffer, rootPtr.GetID(), root.Key(key)) + catalog_buffer.WriteString("\n") } - if foundPages && foundNames { - break - } - } - - // Add Pages and Names references if they exist - if foundPages { - pages := root.Key("Pages").GetPtr() - catalog_buffer.WriteString(" /Pages " + strconv.Itoa(int(pages.GetID())) + " " + strconv.Itoa(int(pages.GetGen())) + " R\n") - overwrittenCatalogKeys = append(overwrittenCatalogKeys, "Pages") - } - if foundNames { - names := root.Key("Names").GetPtr() - catalog_buffer.WriteString(" /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R\n") - overwrittenCatalogKeys = append(overwrittenCatalogKeys, "Names") } // Start the AcroForm dictionary with /NeedAppearances - overwrittenCatalogKeys = append(overwrittenCatalogKeys, "AcroForm") catalog_buffer.WriteString(" /AcroForm <<\n") catalog_buffer.WriteString(" /Fields [") @@ -128,15 +109,7 @@ func (context *SignContext) createCatalog() ([]byte, error) { // Finalize the AcroForm and Catalog object catalog_buffer.WriteString(" >>\n") // Close AcroForm - - // Copy over existing catalog entries from the original document that we did not need to override. - for _, key := range root.Keys() { - if !slices.Contains(overwrittenCatalogKeys, key) { - _, _ = fmt.Fprintf(&catalog_buffer, "/%s ", key) - context.serializeCatalogEntry(&catalog_buffer, rootPtr.GetID(), root.Key(key)) - } - } - catalog_buffer.WriteString(">>\n") // Close Catalog + catalog_buffer.WriteString(">>\n") // Close Catalog return catalog_buffer.Bytes(), nil } diff --git a/sign/pdfcatalog_test.go b/sign/pdfcatalog_test.go index f996116..e47a4ab 100644 --- a/sign/pdfcatalog_test.go +++ b/sign/pdfcatalog_test.go @@ -15,17 +15,17 @@ var testFiles = []struct { { file: "../testfiles/testfile20.pdf", expectedCatalogs: map[CertType]string{ - CertificationSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n/Metadata 2 0 R>>\n", - UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 1\n >>\n/Metadata 2 0 R>>\n", - ApprovalSignature: "<<\n /Type /Catalog\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n/Metadata 2 0 R>>\n", + CertificationSignature: "<<\n /Type /Catalog\n /Metadata 2 0 R\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n>>\n", + UsageRightsSignature: "<<\n /Type /Catalog\n /Metadata 2 0 R\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 1\n >>\n>>\n", + ApprovalSignature: "<<\n /Type /Catalog\n /Metadata 2 0 R\n /Pages 3 0 R\n /AcroForm <<\n /Fields [10 0 R]\n /SigFlags 3\n >>\n>>\n", }, }, { file: "../testfiles/testfile21.pdf", expectedCatalogs: map[CertType]string{ - CertificationSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n/Metadata 8 0 R>>\n", - UsageRightsSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 1\n >>\n/Metadata 8 0 R>>\n", - ApprovalSignature: "<<\n /Type /Catalog\n /Pages 9 0 R\n /Names 6 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n/Metadata 8 0 R>>\n", + CertificationSignature: "<<\n /Type /Catalog\n /Metadata 8 0 R\n /Names 6 0 R\n /Pages 9 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n>>\n", + UsageRightsSignature: "<<\n /Type /Catalog\n /Metadata 8 0 R\n /Names 6 0 R\n /Pages 9 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 1\n >>\n>>\n", + ApprovalSignature: "<<\n /Type /Catalog\n /Metadata 8 0 R\n /Names 6 0 R\n /Pages 9 0 R\n /AcroForm <<\n /Fields [16 0 R]\n /SigFlags 3\n >>\n>>\n", }, }, }