diff --git a/sign/pdfcatalog.go b/sign/pdfcatalog.go index 8182a70..a8c0d07 100644 --- a/sign/pdfcatalog.go +++ b/sign/pdfcatalog.go @@ -2,7 +2,11 @@ package sign import ( "bytes" + "fmt" + "io" "strconv" + + "github.com/digitorus/pdf" ) func (context *SignContext) createCatalog() ([]byte, error) { @@ -28,32 +32,18 @@ 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") - } - if foundNames { - names := root.Key("Names").GetPtr() - catalog_buffer.WriteString(" /Names " + strconv.Itoa(int(names.GetID())) + " " + strconv.Itoa(int(names.GetGen())) + " R\n") } // Start the AcroForm dictionary with /NeedAppearances @@ -123,3 +113,53 @@ func (context *SignContext) createCatalog() ([]byte, error) { 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..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>>\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 /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>>\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 /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", }, }, }