From 681997c6802fcdde86339a92b0f0a6b1c65446d7 Mon Sep 17 00:00:00 2001 From: Paul van Brouwershaven Date: Tue, 25 Feb 2025 18:13:10 +0100 Subject: [PATCH] Fix PDF 1.7 with XRef Stream causes panic #61 --- sign/pdftrailer.go | 4 + sign/pdfxref.go | 172 ++++++++++++++++++++++----------------- sign/pdfxref_test.go | 59 ++++++++++++++ testfiles/testfile17.pdf | Bin 0 -> 9440 bytes 4 files changed, 159 insertions(+), 76 deletions(-) create mode 100644 sign/pdfxref_test.go create mode 100644 testfiles/testfile17.pdf diff --git a/sign/pdftrailer.go b/sign/pdftrailer.go index e88c502..8268731 100644 --- a/sign/pdftrailer.go +++ b/sign/pdftrailer.go @@ -49,6 +49,10 @@ func (context *SignContext) writeTrailer() error { if _, err := context.OutputBuffer.Write([]byte(trailer_string)); err != nil { return err } + } else if context.PDFReader.XrefInformation.Type == "stream" { + if _, err := context.OutputBuffer.Write([]byte("startxref\n")); err != nil { + return err + } } // Write the new xref start position. diff --git a/sign/pdfxref.go b/sign/pdfxref.go index 6be738d..4c88f6e 100644 --- a/sign/pdfxref.go +++ b/sign/pdfxref.go @@ -8,8 +8,6 @@ import ( "errors" "fmt" "io" - "strconv" - "strings" ) type xrefEntry struct { @@ -18,10 +16,11 @@ type xrefEntry struct { } const ( - xrefStreamColumns = 5 + xrefStreamColumns = 6 // Column width (1+4+1) xrefStreamPredictor = 12 - pngSubPredictor = 11 - pngUpPredictor = 12 + defaultPredictor = 1 // No prediction (the default value) + pngSubPredictor = 11 // PNG prediction (on encoding, PNG Sub on all rows) + pngUpPredictor = 12 // PNG prediction (on encoding, PNG Up on all rows) objectFooter = "\nendobj\n" ) @@ -100,35 +99,23 @@ func (context *SignContext) writeXref() error { } func (context *SignContext) getLastObjectIDFromXref() (uint32, error) { - // Seek to the start of the xref table - if _, err := context.InputFile.Seek(context.PDFReader.XrefInformation.StartPos, io.SeekStart); err != nil { - return 0, fmt.Errorf("failed to seek to xref table: %w", err) + xref := context.PDFReader.Xref() + if len(xref) == 0 { + return 0, fmt.Errorf("no xref entries found") } - // Read the existing xref table - xrefContent := make([]byte, context.PDFReader.XrefInformation.Length) - if _, err := context.InputFile.Read(xrefContent); err != nil { - return 0, fmt.Errorf("failed to read xref table: %w", err) + // Find highest used object ID + var maxID uint32 + for _, entry := range xref { + ptr := entry.Ptr() + + // TODO: Check if in use (&& entry.offset != 0) + if ptr.GetID() > maxID { + maxID = ptr.GetID() + } } - // Parse the xref header - xrefLines := strings.Split(string(xrefContent), "\n") - xrefHeader := strings.Fields(xrefLines[1]) - if len(xrefHeader) != 2 { - return 0, fmt.Errorf("invalid xref header format") - } - - firstObjectID, err := strconv.ParseUint(xrefHeader[0], 10, 32) - if err != nil { - return 0, fmt.Errorf("invalid first object ID: %w", err) - } - - itemCount, err := strconv.ParseUint(xrefHeader[1], 10, 32) - if err != nil { - return 0, fmt.Errorf("invalid item count: %w", err) - } - - return uint32(firstObjectID + itemCount), nil + return maxID + 1, nil } // writeIncrXrefTable writes the incremental cross-reference table to the output buffer. @@ -170,11 +157,14 @@ func (context *SignContext) writeIncrXrefTable() error { // writeXrefStream writes the cross-reference stream to the output buffer. func (context *SignContext) writeXrefStream() error { - buffer := new(bytes.Buffer) + var buffer bytes.Buffer predictor := context.PDFReader.Trailer().Key("DecodeParms").Key("Predictor").Int64() + if predictor == 0 { + predictor = xrefStreamPredictor + } - if err := writeXrefStreamEntries(buffer, context); err != nil { + if err := writeXrefStreamEntries(&buffer, context); err != nil { return fmt.Errorf("failed to write xref stream entries: %w", err) } @@ -183,19 +173,32 @@ func (context *SignContext) writeXrefStream() error { return fmt.Errorf("failed to encode xref stream: %w", err) } - if err := writeXrefStreamHeader(context, len(streamBytes)); err != nil { + var xrefStreamObject bytes.Buffer + + if err := writeXrefStreamHeader(&xrefStreamObject, context, len(streamBytes)); err != nil { return fmt.Errorf("failed to write xref stream header: %w", err) } - if err := writeXrefStreamContent(context, streamBytes); err != nil { + if err := writeXrefStreamContent(&xrefStreamObject, streamBytes); err != nil { return fmt.Errorf("failed to write xref stream content: %w", err) } + _, err = context.addObject(xrefStreamObject.Bytes()) + if err != nil { + return fmt.Errorf("failed to add xref stream object: %w", err) + } + return nil } // writeXrefStreamEntries writes the individual entries for the xref stream. func writeXrefStreamEntries(buffer *bytes.Buffer, context *SignContext) error { + // Write updated entries first + for _, entry := range context.updatedXrefEntries { + writeXrefStreamLine(buffer, 1, int(entry.Offset), 0) + } + + // Write new entries for _, entry := range context.newXrefEntries { writeXrefStreamLine(buffer, 1, int(entry.Offset), 0) } @@ -205,60 +208,77 @@ func writeXrefStreamEntries(buffer *bytes.Buffer, context *SignContext) error { // encodeXrefStream applies the appropriate encoding to the xref stream. func encodeXrefStream(data []byte, predictor int64) ([]byte, error) { - var streamBytes []byte - var err error - - switch predictor { - case pngSubPredictor: - streamBytes, err = EncodePNGSUBBytes(xrefStreamColumns, data) - case pngUpPredictor: - streamBytes, err = EncodePNGUPBytes(xrefStreamColumns, data) - default: - return nil, fmt.Errorf("unsupported predictor: %d", predictor) + // Use FlateDecode without prediction for xref streams + var b bytes.Buffer + w := zlib.NewWriter(&b) + if _, err := w.Write(data); err != nil { + return nil, err } - - if err != nil { - return nil, fmt.Errorf("failed to encode xref stream: %w", err) - } - - return streamBytes, nil + w.Close() + return b.Bytes(), nil } // writeXrefStreamHeader writes the header for the xref stream. -func writeXrefStreamHeader(context *SignContext, streamLength int) error { +func writeXrefStreamHeader(buffer *bytes.Buffer, context *SignContext, streamLength int) error { id := context.PDFReader.Trailer().Key("ID") - id0 := hex.EncodeToString([]byte(id.Index(0).RawString())) - id1 := hex.EncodeToString([]byte(id.Index(0).RawString())) - var buffer bytes.Buffer - buffer.WriteString(fmt.Sprintf("%d 0 obj\n", context.SignData.objectId)) + // Calculate total entries and create index array + totalEntries := uint32(context.PDFReader.XrefInformation.ItemCount) + var indexArray []uint32 + + // Add existing entries section + if len(context.updatedXrefEntries) > 0 { + for _, entry := range context.updatedXrefEntries { + indexArray = append(indexArray, entry.ID, 1) + } + } + + // Add new entries section + if len(context.newXrefEntries) > 0 { + indexArray = append(indexArray, context.lastXrefID+1, uint32(len(context.newXrefEntries))) + totalEntries += uint32(len(context.newXrefEntries)) + } + buffer.WriteString("<< /Type /XRef\n") buffer.WriteString(fmt.Sprintf(" /Length %d\n", streamLength)) buffer.WriteString(" /Filter /FlateDecode\n") - buffer.WriteString(fmt.Sprintf(" /DecodeParms << /Columns %d /Predictor %d >>\n", xrefStreamColumns, xrefStreamPredictor)) - buffer.WriteString(" /W [ 1 3 1 ]\n") + // Change W array to [1 4 1] to accommodate larger offsets + buffer.WriteString(" /W [ 1 4 1 ]\n") buffer.WriteString(fmt.Sprintf(" /Prev %d\n", context.PDFReader.XrefInformation.StartPos)) - buffer.WriteString(fmt.Sprintf(" /Size %d\n", context.PDFReader.XrefInformation.ItemCount+int64(len(context.newXrefEntries))+1)) - buffer.WriteString(fmt.Sprintf(" /Index [ %d 4 ]\n", context.PDFReader.XrefInformation.ItemCount)) - buffer.WriteString(fmt.Sprintf(" /Root %d 0 R\n", context.CatalogData.ObjectId)) - buffer.WriteString(fmt.Sprintf(" /ID [<%s><%s>]\n", id0, id1)) - buffer.WriteString(">>\n") + buffer.WriteString(fmt.Sprintf(" /Size %d\n", totalEntries+1)) - _, err := context.OutputBuffer.Write(buffer.Bytes()) - return err + // Write index array if we have entries + if len(indexArray) > 0 { + buffer.WriteString(" /Index [") + for _, idx := range indexArray { + buffer.WriteString(fmt.Sprintf(" %d", idx)) + } + buffer.WriteString(" ]\n") + } + + buffer.WriteString(fmt.Sprintf(" /Root %d 0 R\n", context.CatalogData.ObjectId)) + + if !id.IsNull() { + id0 := hex.EncodeToString([]byte(id.Index(0).RawString())) + id1 := hex.EncodeToString([]byte(id.Index(1).RawString())) + buffer.WriteString(fmt.Sprintf(" /ID [<%s><%s>]\n", id0, id1)) + } + + buffer.WriteString(">>\n") + return nil } // writeXrefStreamContent writes the content of the xref stream. -func writeXrefStreamContent(context *SignContext, streamBytes []byte) error { - if _, err := io.WriteString(context.OutputBuffer, "stream\n"); err != nil { +func writeXrefStreamContent(buffer *bytes.Buffer, streamBytes []byte) error { + if _, err := io.WriteString(buffer, "stream\n"); err != nil { return err } - if _, err := context.OutputBuffer.Write(streamBytes); err != nil { + if _, err := buffer.Write(streamBytes); err != nil { return err } - if _, err := io.WriteString(context.OutputBuffer, "\nendstream\n"); err != nil { + if _, err := io.WriteString(buffer, "\nendstream\n"); err != nil { return err } @@ -267,16 +287,16 @@ func writeXrefStreamContent(context *SignContext, streamBytes []byte) error { // writeXrefStreamLine writes a single line in the xref stream. func writeXrefStreamLine(b *bytes.Buffer, xreftype byte, offset int, gen byte) { + // Write type (1 byte) b.WriteByte(xreftype) - b.Write(encodeInt(offset)) - b.WriteByte(gen) -} -// encodeInt encodes an integer to a 3-byte slice. -func encodeInt(i int) []byte { - result := make([]byte, 4) - binary.BigEndian.PutUint32(result, uint32(i)) - return result[1:4] + // Write offset (4 bytes) + offsetBytes := make([]byte, 4) + binary.BigEndian.PutUint32(offsetBytes, uint32(offset)) + b.Write(offsetBytes) + + // Write generation (1 byte) + b.WriteByte(gen) } // EncodePNGSUBBytes encodes data using PNG SUB filter. diff --git a/sign/pdfxref_test.go b/sign/pdfxref_test.go new file mode 100644 index 0000000..ce177f8 --- /dev/null +++ b/sign/pdfxref_test.go @@ -0,0 +1,59 @@ +package sign + +import ( + "os" + "testing" + + "github.com/digitorus/pdf" +) + +func TestGetLastObjectIDFromXref(t *testing.T) { + testCases := []struct { + fileName string + expected uint32 + }{ + {"minimal.pdf", 5}, + {"testfile12.pdf", 16}, + {"testfile14.pdf", 15}, + {"testfile16.pdf", 567}, + {"testfile17.pdf", 20}, + {"testfile20.pdf", 10}, + {"testfile21.pdf", 16}, + {"small.pdf", 7}, + } + + for _, tc := range testCases { + t.Run(tc.fileName, func(st *testing.T) { + //st.Parallel() + + input_file, err := os.Open("../testfiles/" + tc.fileName) + if err != nil { + st.Fatalf("%s: %s", tc.fileName, err.Error()) + } + defer input_file.Close() + + finfo, err := input_file.Stat() + if err != nil { + st.Fatalf("%s: %s", tc.fileName, err.Error()) + } + size := finfo.Size() + + r, err := pdf.NewReader(input_file, size) + if err != nil { + st.Fatalf("%s: %s", tc.fileName, err.Error()) + } + + sc := &SignContext{ + InputFile: input_file, + PDFReader: r, + } + obj, err := sc.getLastObjectIDFromXref() + if err != nil { + st.Fatalf("%s: %s", tc.fileName, err.Error()) + } + if obj != tc.expected { + st.Fatalf("%s: expected object id %d, got %d", tc.fileName, tc.expected, obj) + } + }) + } +} diff --git a/testfiles/testfile17.pdf b/testfiles/testfile17.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0cf765a3c6f96322fbcd611df112e937f7407ac9 GIT binary patch literal 9440 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μ-}^t0jTIQ`f2xM^m*V%0?hSyt`u89-X9u`F8A zEZIoFXfT;5sS$VNHO87j4-<@IoIo|Ugi=JhB-t-W-KIaTdQO&w_wxrWq8y2ZzH8}S z*FG~vw@++aaUW{fh$i%{z^g#5gy+SCW4b_9qGwdH5HitB1MWypt3xZSk4~HIgf5Ho z1n+sR4kHwDAXmK~-i-Kwc3(T`MK=F<;2#RSl(Qo~4%C21iZ5ElwZyf?&@YrFRk`x@ z&Ak{yd@<tyI->G_H2D=-=f00mC7f?=$Dc*@|@?Hz8~cFh{W$HplwjrXvR%MlFag(F=I)Tzb2iT2M)`WEElgAF*+ zU;4$rXlDQ;7{NTn-=_LSX1Dqa({oVQKAs>q!kRy(iDq;{)2{>O_c`D-qW#)b$`<_F zA-GkA`70b5oQM_M*Dm8SRif1NPSyzI86J2*C2=y<24 zouwt0FcdOzp<7YEyHIvtk8u#JY0Wc~-cDNyzao_)_tM5K)sGSFp36tqTk8i9^{l>( z#bt3M-@^DZAY+J_qsgoK4DOkrWLlfZQ-^bcrW>xTG%h`%>j1P{2B(So$-r(`W*_$h z+E@*SMYk7M{VqR@X7&2EUhpSd=zIb~wA2Od0GIa&`GaSw?4xxEt#=J-Q`%t8vjwpU zQoN!~C~{AFm8kZ2G$U-8G}=f3*7P&BjcJq5gt2@qegJKj*L20Jnc?nz>GmUcR=brk z!{lguU|%5Suv!?(X25Z$doFKTv8<=MS(Y=`s6$UpWxBTouNtjhvX4ejcpKfR;;9C8 zR^dzvrCxX){bbSgD-7qsh93ocMQ#o`@082$f#ar}M@0(pex|NF(t@`uaD_Mg}PGY^4+0jv)L0(}JW zSvjBpHdY7#%<u!5if_6KW!m&ili+Ns!D zJa&2jK#!-=zh?2bssElvx&P6<0D4^E&$%W0Ps_q%`vMs9Cma7uPl-yLg4Jghymw1C zjH5%TCgU_!RSMzH7zPLt6_)zQ1TpGZ(&Gz^?r*5SyGC*^#CSQ5t|-R7ecdVXnI9@+ z9@c~{p+dp8#1(Z5usXnjgK0TAT*D!DJKLM_EG#88M?LsQO6r*na z0nyudACk)V@@tx-uJIQyJu~YKtcp8G3S(|$-sTCciycVYCAp@`WfudK?{$0LW!y5h zBYnwb91!CxI79DnVP z|B#W#?hYjjPm_n*{<9YVS|9)z0RD51DB9UM13>?DxG+oDn%V(Cj|KO;T>j|~GSb(- z{*@CJ5D@?`%mM;Gv;wh!kAp&&03a-YR34r<%M?5s4Kg6l7}GEe0d1BT8-yf2(BdCz w9!ehx2uxFeQOZlhkPs2!2GP-o{-@_o&IXRo?v4*mK|xS3IyJS3oGAML0jIF^O#lD@ literal 0 HcmV?d00001