374 lines
10 KiB
Go
374 lines
10 KiB
Go
package sign
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/zlib"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
)
|
|
|
|
type xrefEntry struct {
|
|
ID uint32
|
|
Offset int64
|
|
}
|
|
|
|
const (
|
|
xrefStreamColumns = 6 // Column width (1+4+1)
|
|
xrefStreamPredictor = 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"
|
|
)
|
|
|
|
func (context *SignContext) addObject(object []byte) (uint32, error) {
|
|
if context.lastXrefID == 0 {
|
|
lastXrefID, err := context.getLastObjectIDFromXref()
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to get last object ID: %w", err)
|
|
}
|
|
context.lastXrefID = lastXrefID
|
|
}
|
|
|
|
objectID := context.lastXrefID + uint32(len(context.newXrefEntries)) + 1
|
|
context.newXrefEntries = append(context.newXrefEntries, xrefEntry{
|
|
ID: objectID,
|
|
Offset: int64(context.OutputBuffer.Buff.Len()) + 1,
|
|
})
|
|
|
|
err := context.writeObject(objectID, object)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to write object: %w", err)
|
|
}
|
|
|
|
return objectID, nil
|
|
}
|
|
|
|
func (context *SignContext) updateObject(id uint32, object []byte) error {
|
|
context.updatedXrefEntries = append(context.updatedXrefEntries, xrefEntry{
|
|
ID: id,
|
|
Offset: int64(context.OutputBuffer.Buff.Len()) + 1,
|
|
})
|
|
|
|
err := context.writeObject(id, object)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to write object: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (context *SignContext) writeObject(id uint32, object []byte) error {
|
|
// Write the object header
|
|
if _, err := context.OutputBuffer.Write([]byte(fmt.Sprintf("\n%d 0 obj\n", id))); err != nil {
|
|
return fmt.Errorf("failed to write object header: %w", err)
|
|
}
|
|
|
|
// Write the object content
|
|
object = bytes.TrimSpace(object)
|
|
if _, err := context.OutputBuffer.Write(object); err != nil {
|
|
return fmt.Errorf("failed to write object content: %w", err)
|
|
}
|
|
|
|
// Write the object footer
|
|
if _, err := context.OutputBuffer.Write([]byte(objectFooter)); err != nil {
|
|
return fmt.Errorf("failed to write object footer: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeXref writes the cross-reference table or stream based on the PDF type.
|
|
func (context *SignContext) writeXref() error {
|
|
if _, err := context.OutputBuffer.Write([]byte("\n")); err != nil {
|
|
return fmt.Errorf("failed to write newline before xref: %w", err)
|
|
}
|
|
context.NewXrefStart = int64(context.OutputBuffer.Buff.Len())
|
|
|
|
switch context.PDFReader.XrefInformation.Type {
|
|
case "table":
|
|
return context.writeIncrXrefTable()
|
|
case "stream":
|
|
return context.writeXrefStream()
|
|
default:
|
|
return fmt.Errorf("unknown xref type: %s", context.PDFReader.XrefInformation.Type)
|
|
}
|
|
}
|
|
|
|
func (context *SignContext) getLastObjectIDFromXref() (uint32, error) {
|
|
xref := context.PDFReader.Xref()
|
|
if len(xref) == 0 {
|
|
return 0, fmt.Errorf("no xref entries found")
|
|
}
|
|
|
|
// 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()
|
|
}
|
|
}
|
|
|
|
return maxID + 1, nil
|
|
}
|
|
|
|
// writeIncrXrefTable writes the incremental cross-reference table to the output buffer.
|
|
func (context *SignContext) writeIncrXrefTable() error {
|
|
// Write xref header
|
|
if _, err := context.OutputBuffer.Write([]byte("xref\n")); err != nil {
|
|
return fmt.Errorf("failed to write incremental xref header: %w", err)
|
|
}
|
|
|
|
// Write updated entries
|
|
for _, entry := range context.updatedXrefEntries {
|
|
pageXrefObj := fmt.Sprintf("%d %d\n", entry.ID, 1)
|
|
if _, err := context.OutputBuffer.Write([]byte(pageXrefObj)); err != nil {
|
|
return fmt.Errorf("failed to write updated xref object: %w", err)
|
|
}
|
|
|
|
xrefLine := fmt.Sprintf("%010d 00000 n\r\n", entry.Offset)
|
|
if _, err := context.OutputBuffer.Write([]byte(xrefLine)); err != nil {
|
|
return fmt.Errorf("failed to write updated incremental xref entry: %w", err)
|
|
}
|
|
}
|
|
|
|
// Write xref subsection header
|
|
startXrefObj := fmt.Sprintf("%d %d\n", context.lastXrefID+1, len(context.newXrefEntries))
|
|
if _, err := context.OutputBuffer.Write([]byte(startXrefObj)); err != nil {
|
|
return fmt.Errorf("failed to write starting xref object: %w", err)
|
|
}
|
|
|
|
// Write new entries
|
|
for _, entry := range context.newXrefEntries {
|
|
xrefLine := fmt.Sprintf("%010d 00000 n\r\n", entry.Offset)
|
|
if _, err := context.OutputBuffer.Write([]byte(xrefLine)); err != nil {
|
|
return fmt.Errorf("failed to write incremental xref entry: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// writeXrefStream writes the cross-reference stream to the output buffer.
|
|
func (context *SignContext) writeXrefStream() error {
|
|
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 {
|
|
return fmt.Errorf("failed to write xref stream entries: %w", err)
|
|
}
|
|
|
|
streamBytes, err := encodeXrefStream(buffer.Bytes(), predictor)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encode xref stream: %w", err)
|
|
}
|
|
|
|
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(&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)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// encodeXrefStream applies the appropriate encoding to the xref stream.
|
|
func encodeXrefStream(data []byte, predictor int64) ([]byte, error) {
|
|
// 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
|
|
}
|
|
w.Close()
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// writeXrefStreamHeader writes the header for the xref stream.
|
|
func writeXrefStreamHeader(buffer *bytes.Buffer, context *SignContext, streamLength int) error {
|
|
id := context.PDFReader.Trailer().Key("ID")
|
|
|
|
// 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")
|
|
// 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", totalEntries+1))
|
|
|
|
// 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(buffer *bytes.Buffer, streamBytes []byte) error {
|
|
if _, err := io.WriteString(buffer, "stream\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := buffer.Write(streamBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := io.WriteString(buffer, "\nendstream\n"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
|
|
// 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.
|
|
func EncodePNGSUBBytes(columns int, data []byte) ([]byte, error) {
|
|
rowCount := len(data) / columns
|
|
if len(data)%columns != 0 {
|
|
return nil, errors.New("invalid row/column length")
|
|
}
|
|
|
|
buffer := bytes.NewBuffer(nil)
|
|
tmpRowData := make([]byte, columns)
|
|
for i := 0; i < rowCount; i++ {
|
|
rowData := data[columns*i : columns*(i+1)]
|
|
tmpRowData[0] = rowData[0]
|
|
for j := 1; j < columns; j++ {
|
|
tmpRowData[j] = byte(int(rowData[j]-rowData[j-1]) % 256)
|
|
}
|
|
|
|
buffer.WriteByte(1)
|
|
buffer.Write(tmpRowData)
|
|
}
|
|
|
|
data = buffer.Bytes()
|
|
|
|
var b bytes.Buffer
|
|
w := zlib.NewWriter(&b)
|
|
if _, err := w.Write(data); err != nil {
|
|
return nil, err
|
|
}
|
|
w.Close()
|
|
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
// EncodePNGUPBytes encodes data using PNG UP filter.
|
|
func EncodePNGUPBytes(columns int, data []byte) ([]byte, error) {
|
|
rowCount := len(data) / columns
|
|
if len(data)%columns != 0 {
|
|
return nil, errors.New("invalid row/column length")
|
|
}
|
|
|
|
prevRowData := make([]byte, columns)
|
|
|
|
// Initially all previous data is zero.
|
|
for i := 0; i < columns; i++ {
|
|
prevRowData[i] = 0
|
|
}
|
|
|
|
buffer := bytes.NewBuffer(nil)
|
|
tmpRowData := make([]byte, columns)
|
|
for i := 0; i < rowCount; i++ {
|
|
rowData := data[columns*i : columns*(i+1)]
|
|
for j := 0; j < columns; j++ {
|
|
tmpRowData[j] = byte(int(rowData[j]-prevRowData[j]) % 256)
|
|
}
|
|
|
|
// Save the previous row for prediction.
|
|
copy(prevRowData, rowData)
|
|
|
|
buffer.WriteByte(2)
|
|
buffer.Write(tmpRowData)
|
|
}
|
|
|
|
data = buffer.Bytes()
|
|
|
|
var b bytes.Buffer
|
|
w := zlib.NewWriter(&b)
|
|
if _, err := w.Write(data); err != nil {
|
|
return nil, err
|
|
}
|
|
w.Close()
|
|
|
|
return b.Bytes(), nil
|
|
}
|