unipdf/model/appender_test.go
2019-06-02 16:21:07 +00:00

1578 lines
39 KiB
Go

/*
* This file is subject to the terms and conditions defined in
* file 'LICENSE.md', which is part of this source code package.
*/
package model_test
import (
"bytes"
"crypto/rsa"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/pkcs12"
"github.com/unidoc/unipdf/v3/common"
"github.com/unidoc/unipdf/v3/annotator"
"github.com/unidoc/unipdf/v3/core"
"github.com/unidoc/unipdf/v3/model"
"github.com/unidoc/unipdf/v3/model/sighandler"
)
// This test file contains multiple tests to generate PDFs from existing Pdf files. The outputs are written
// into TMPDIR as files. The files themselves need to be observed to check for correctness as we don't have
// a good way to automatically check if every detail is correct.
func init() {
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
}
const testPdfFile1 = "./testdata/minimal.pdf"
const testPdfLoremIpsumFile = "./testdata/lorem.pdf"
const testPdf3pages = "./testdata/pages3.pdf"
const imgPdfFile1 = "./testdata/img1-1.pdf"
const imgPdfFile2 = "./testdata/img1-2.pdf"
// source http://foersom.com/net/HowTo/data/OoPdfFormExample.pdf
const testPdfAcroFormFile1 = "./testdata/OoPdfFormExample.pdf"
const testPdfSignedPDFDocument = "./testdata/SampleSignedPDFDocument.pdf"
const testPKS12Key = "./testdata/certificate.p12"
const testPKS12KeyPassword = "password"
func tempFile(name string) string {
return filepath.Join(os.TempDir(), name)
}
// Appender with no data added should output an equivalent file.
func TestAppenderNoop(t *testing.T) {
f, err := os.Open("./testdata/minimal.pdf")
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
appender, err := model.NewPdfAppender(reader)
require.NoError(t, err)
model.SetPdfProducer("UniPDF")
model.SetPdfCreator("UniDoc UniPDF")
err = appender.WriteToFile(tempFile("appender_noop.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// The first part of the appended file should be same as in the original.
minimal, err := ioutil.ReadFile("./testdata/minimal.pdf")
require.NoError(t, err)
out, err := ioutil.ReadFile(tempFile("appender_noop.pdf"))
require.NoError(t, err)
require.Equal(t, minimal, out[0:len(minimal)])
origReader := reader
origObjNums := origReader.GetObjectNums()
require.Equal(t, []int{1, 2, 3, 4}, origObjNums)
// Check cross references table of original.
page, err := origReader.GetPage(1)
require.NoError(t, err)
origParser := page.GetPageAsIndirectObject().GetParser()
origXref := origParser.GetXrefTable()
expected := map[int]core.XrefObject{
1: core.XrefObject{ObjectNumber: 1, XType: 0, Offset: 18},
2: core.XrefObject{ObjectNumber: 2, XType: 0, Offset: 77},
3: core.XrefObject{ObjectNumber: 3, XType: 0, Offset: 178},
4: core.XrefObject{ObjectNumber: 4, XType: 0, Offset: 457},
}
require.Equal(t, expected, origXref.ObjectMap)
// Check the output.
{
f, err := os.Open(tempFile("appender_noop.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// Number of objects should be equal.
// The appended version should only add 2 objects (new Info and Catalog).
require.Equal(t, len(origObjNums)+2, len(objNums))
require.Equal(t, []int{1, 2, 3, 4, 5, 6}, objNums)
// Check cross references table of appended version.
page, err := reader.GetPage(1)
require.NoError(t, err)
parser := page.GetPageAsIndirectObject().GetParser()
xrefs := parser.GetXrefTable()
expected := map[int]core.XrefObject{
1: core.XrefObject{ObjectNumber: 1, XType: 0, Offset: 18},
2: core.XrefObject{ObjectNumber: 2, XType: 0, Offset: 77},
3: core.XrefObject{ObjectNumber: 3, XType: 0, Offset: 178},
4: core.XrefObject{ObjectNumber: 4, XType: 0, Offset: 457},
5: core.XrefObject{ObjectNumber: 5, XType: 0, Offset: 740},
6: core.XrefObject{ObjectNumber: 6, XType: 0, Offset: 802},
}
require.Equal(t, expected, xrefs.ObjectMap)
}
}
func TestAppenderRemovePage(t *testing.T) {
f, err := os.Open(testPdf3pages)
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
appender, err := model.NewPdfAppender(reader)
require.NoError(t, err)
appender.RemovePage(1)
appender.RemovePage(2)
err = appender.WriteToFile(tempFile("appender_remove_page_1.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
origReader := reader
origObjNums := origReader.GetObjectNums()
// Check the output.
{
f, err := os.Open(tempFile("appender_remove_page_1.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// Number of objects should be equal.
// The appended version should only add 2 objects (new Info and Catalog).
require.Equal(t, len(origObjNums)+2, len(objNums))
obj2, err := reader.GetIndirectObjectByNumber(2)
require.NoError(t, err)
pagesDict, ok := core.GetDict(obj2)
require.True(t, ok)
kidsArr, ok := core.GetArray(pagesDict.Get("Kids"))
require.True(t, ok)
require.Len(t, kidsArr.Elements(), 1)
}
}
func TestAppenderReplacePage(t *testing.T) {
f1, err := os.Open(testPdf3pages)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// if the page is already in the same file, expect that the Pages object is only updated
appender.ReplacePage(1, pdf1.PageList[1])
appender.ReplacePage(3, pdf2.PageList[0])
err = appender.WriteToFile(tempFile("appender_replace_page_1.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
origReader := pdf1
origObjNums := origReader.GetObjectNums()
// Check the output.
{
f, err := os.Open(tempFile("appender_replace_page_1.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// Number of objects should be equal.
// The appended version adds 8 new objects: Related to the new page and new Info, Catalog, Pages.
// As well as updating some previous objects.
// TODO: Check specifically the xrefs table regarding updates, new objects etc.
require.Equal(t, len(origObjNums)+9, len(objNums))
obj2, err := reader.GetIndirectObjectByNumber(2)
require.NoError(t, err)
pagesDict, ok := core.GetDict(obj2)
require.True(t, ok)
kidsArr, ok := core.GetArray(pagesDict.Get("Kids"))
require.True(t, ok)
require.Len(t, kidsArr.Elements(), 3)
// The first page is a copy of the second one. And the third one is from another file.
// All new objects.
require.Equal(t, "[IObject:39, IObject:21, IObject:42]", kidsArr.String())
}
}
func TestAppenderAddAnnotation(t *testing.T) {
f1, err := os.Open(testPdf3pages)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
page := pdf1.PageList[0]
annotation := model.NewPdfAnnotationSquare()
rect := model.PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
annotation.Rect = rect.ToPdfObject()
annotation.IC = core.MakeArrayFromFloats([]float64{4.0, 0.0, 0.3})
annotation.CA = core.MakeFloat(0.5)
page.AddAnnotation(annotation.PdfAnnotation)
//appender.ReplacePage(1, page)
appender.UpdatePage(page)
err = appender.WriteToFile(tempFile("appender_add_annotation_1.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Check the output.
{
f, err := os.Open(tempFile("appender_add_annotation_1.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
require.NotNil(t, reader)
require.Nil(t, reader.AcroForm)
page, err := reader.GetPage(1)
require.NoError(t, err)
require.NotNil(t, page.Annots)
annots, err := page.GetAnnotations()
require.NoError(t, err)
require.NotNil(t, annots)
require.Len(t, annots, 1)
}
}
// Append annotation to page which already has annotations.
func TestAppenderAddAnnotation2(t *testing.T) {
f1, err := os.Open("testdata/OoPdfFormExample.pdf")
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
page := pdf1.PageList[0]
annotation := model.NewPdfAnnotationSquare()
rect := model.PdfRectangle{Ury: 250.0, Urx: 150.0, Lly: 50.0, Llx: 50.0}
annotation.Rect = rect.ToPdfObject()
annotation.IC = core.MakeArrayFromFloats([]float64{4.0, 0.0, 0.3})
annotation.CA = core.MakeFloat(0.5)
page.AddAnnotation(annotation.PdfAnnotation)
appender.UpdatePage(page)
err = appender.WriteToFile(tempFile("appender_add_annotation_2.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Check the output.
{
f, err := os.Open(tempFile("appender_add_annotation_2.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
require.NotNil(t, reader)
require.NotNil(t, reader.AcroForm)
require.Len(t, reader.AcroForm.AllFields(), 17)
}
}
// Implements example H.7 Updating example (PDF32000_2008).
// The original file is the minimal pdf file (H.2). The updates are in 4 stages with the file saved after
// each stage:
// a) Four text annotations are added.
// b) The text of one of the annotations is altered.
// c) Two of the text annotations are deleted.
// d) Three text annotations are added.
func TestAppenderUpdatingExample(t *testing.T) {
stage1 := func() {
t.Logf("------- Stage 1")
f, err := os.Open("./testdata/minimal.pdf")
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
appender, err := model.NewPdfAppender(reader)
require.NoError(t, err)
// Stage 1: Add four text annotations.
annot1 := model.NewPdfAnnotationText()
annot1.Rect = core.MakeArrayFromIntegers([]int{44, 616, 162, 735})
annot1.Contents = core.MakeString("Text #1")
annot1.Open = core.MakeBool(true)
annot2 := model.NewPdfAnnotationText()
annot2.Rect = core.MakeArrayFromIntegers([]int{224, 668, 457, 735})
annot2.Contents = core.MakeString("Text #2")
annot2.Open = core.MakeBool(false)
annot3 := model.NewPdfAnnotationText()
annot3.Rect = core.MakeArrayFromIntegers([]int{293, 393, 328, 622})
annot3.Contents = core.MakeString("Text #3")
annot3.Open = core.MakeBool(true)
annot4 := model.NewPdfAnnotationText()
annot4.Rect = core.MakeArrayFromIntegers([]int{34, 398, 225, 575})
annot4.Contents = core.MakeString("Text #4")
annot4.Open = core.MakeBool(false)
page, err := reader.GetPage(1)
require.NoError(t, err)
page.AddAnnotation(annot1.PdfAnnotation)
page.AddAnnotation(annot2.PdfAnnotation)
page.AddAnnotation(annot3.PdfAnnotation)
page.AddAnnotation(annot4.PdfAnnotation)
appender.UpdatePage(page)
err = appender.WriteToFile(tempFile("appender_h7_stage1.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
stage1()
// Check the output of stage 1.
func() {
f, err := os.Open("./testdata/minimal.pdf")
require.NoError(t, err)
defer f.Close()
origReader, err := model.NewPdfReader(f)
origObjNums := origReader.GetObjectNums()
f, err = os.Open(tempFile("appender_h7_stage1.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// The new revision should contain:
// - Updated Page
// - 4 New annotation objects
// - New Info object (refrenced from the trailer).
// - New Catalog
require.Equal(t, len(origObjNums)+6, len(objNums))
// Check that the Pages object number is unchanged.
obj2, err := reader.GetIndirectObjectByNumber(2)
require.NoError(t, err)
pagesDict, ok := core.GetDict(obj2)
require.True(t, ok)
kidsArr, ok := core.GetArray(pagesDict.Get("Kids"))
require.True(t, ok)
require.Len(t, kidsArr.Elements(), 1)
require.Equal(t, "[IObject:3]", kidsArr.String())
}()
stage2 := func() {
t.Logf("------- Stage 2")
f, err := os.Open(tempFile("appender_h7_stage1.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
appender, err := model.NewPdfAppender(reader)
require.NoError(t, err)
page, err := reader.GetPage(1)
require.NoError(t, err)
annots, err := page.GetAnnotations()
require.NoError(t, err)
require.Len(t, annots, 4)
annot3 := annots[2]
annot3.Contents = core.MakeString("Modified Text #3")
appender.UpdateObject(annot3.GetContext().ToPdfObject())
err = appender.WriteToFile(tempFile("appender_h7_stage2.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
stage2()
// Check the output of stage 2.
func() {
f, err := os.Open(tempFile("appender_h7_stage1.pdf"))
require.NoError(t, err)
defer f.Close()
origReader, err := model.NewPdfReader(f)
origObjNums := origReader.GetObjectNums()
f, err = os.Open(tempFile("appender_h7_stage2.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// The new revision should contain:
// - Updated Annots object.
// - New Info
// - New Catalog
// - Updated Pages
// - No other changes
require.Equal(t, len(origObjNums)+2, len(objNums))
// Check that the Pages object number is unchanged.
obj2, err := reader.GetIndirectObjectByNumber(2)
require.NoError(t, err)
pagesDict, ok := core.GetDict(obj2)
require.True(t, ok)
kidsArr, ok := core.GetArray(pagesDict.Get("Kids"))
require.True(t, ok)
require.Len(t, kidsArr.Elements(), 1)
require.Equal(t, "[IObject:3]", kidsArr.String())
annots, err := reader.PageList[0].GetAnnotations()
require.NoError(t, err)
str, ok := core.GetString(annots[2].Contents)
require.True(t, ok)
require.Equal(t, "Modified Text #3", str.String())
textAnnot, ok := annots[2].GetContext().(*model.PdfAnnotationText)
require.True(t, ok)
open, ok := core.GetBool(textAnnot.Open)
require.True(t, ok)
require.Equal(t, "true", open.String())
}()
stage3 := func() {
t.Logf("------- Stage 3")
f, err := os.Open(tempFile("appender_h7_stage2.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
appender, err := model.NewPdfAppender(reader)
require.NoError(t, err)
page, err := reader.GetPage(1)
require.NoError(t, err)
annots, err := page.GetAnnotations()
require.NoError(t, err)
require.Len(t, annots, 4)
// Remove two annotations.
annots = annots[2:]
page.SetAnnotations(annots)
appender.UpdatePage(page)
err = appender.WriteToFile(tempFile("appender_h7_stage3.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
stage3()
// Check output of stage 3. Expected is just new Annots array + xrefs.
func() {
f, err := os.Open(tempFile("appender_h7_stage2.pdf"))
require.NoError(t, err)
defer f.Close()
origReader, err := model.NewPdfReader(f)
origObjNums := origReader.GetObjectNums()
f, err = os.Open(tempFile("appender_h7_stage3.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// The new revision should contain:
// - Updated Page object (including the Annots).
// - New Info
// - New Catalog
// - No other changes
require.Equal(t, len(origObjNums)+2, len(objNums))
// Check that the Pages object number is unchanged.
obj2, err := reader.GetIndirectObjectByNumber(2)
require.NoError(t, err)
pagesDict, ok := core.GetDict(obj2)
require.True(t, ok)
kidsArr, ok := core.GetArray(pagesDict.Get("Kids"))
require.True(t, ok)
require.Len(t, kidsArr.Elements(), 1)
require.Equal(t, "[IObject:3]", kidsArr.String())
annots, err := reader.PageList[0].GetAnnotations()
require.NoError(t, err)
require.Len(t, annots, 2)
}()
stage4 := func() {
t.Logf("------- Stage 4")
f, err := os.Open(tempFile("appender_h7_stage3.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
appender, err := model.NewPdfAppender(reader)
require.NoError(t, err)
// Stage 4: Add 3 new annotations.
newAnnot1 := model.NewPdfAnnotationText()
newAnnot1.Rect = core.MakeArrayFromIntegers([]int{58, 657, 172, 742})
newAnnot1.Contents = core.MakeString("New Text #1")
newAnnot1.Open = core.MakeBool(true)
newAnnot2 := model.NewPdfAnnotationText()
newAnnot2.Rect = core.MakeArrayFromIntegers([]int{389, 459, 570, 537})
newAnnot2.Contents = core.MakeString("New Text #2")
newAnnot2.Open = core.MakeBool(false)
newAnnot3 := model.NewPdfAnnotationText()
newAnnot3.Rect = core.MakeArrayFromIntegers([]int{44, 253, 473, 337})
newAnnot3.Contents = core.MakeString("New Text #3")
newAnnot3.Open = core.MakeBool(true)
page, err := reader.GetPage(1)
require.NoError(t, err)
page.AddAnnotation(newAnnot1.PdfAnnotation)
page.AddAnnotation(newAnnot2.PdfAnnotation)
page.AddAnnotation(newAnnot3.PdfAnnotation)
appender.UpdatePage(page)
err = appender.WriteToFile(tempFile("appender_h7_stage4.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
stage4()
// Check output of stage 4.
func() {
f, err := os.Open(tempFile("appender_h7_stage3.pdf"))
require.NoError(t, err)
defer f.Close()
origReader, err := model.NewPdfReader(f)
origObjNums := origReader.GetObjectNums()
f, err = os.Open(tempFile("appender_h7_stage4.pdf"))
require.NoError(t, err)
defer f.Close()
reader, err := model.NewPdfReader(f)
require.NoError(t, err)
objNums := reader.GetObjectNums()
// The new revision should contain:
// - Updated Page object (including the Annots).
// - New Info
// - New Catalog
// - 3 New Annots
// - No other changes
require.Equal(t, len(origObjNums)+5, len(objNums))
annots, err := reader.PageList[0].GetAnnotations()
require.NoError(t, err)
require.Len(t, annots, 5)
}()
}
func TestAppenderAddPage(t *testing.T) {
f1, err := os.Open(testPdfLoremIpsumFile)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender.AddPages(pdf1.PageList...)
appender.AddPages(pdf2.PageList...)
appender.AddPages(pdf2.PageList...)
err = appender.WriteToFile(tempFile("appender_add_page_1.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
func TestAppenderAddPage2(t *testing.T) {
f1, err := os.Open(testPdfLoremIpsumFile)
require.NoError(t, err)
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
require.NoError(t, err)
f2, err := os.Open(testPdfAcroFormFile1)
require.NoError(t, err)
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
defer f2.Close()
appender, err := model.NewPdfAppender(pdf1)
defer f2.Close()
appender.AddPages(pdf2.PageList...)
// AcroForm from a different file.
appender.ReplaceAcroForm(pdf2.AcroForm)
err = appender.WriteToFile(tempFile("appender_add_page_2.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// TODO: Check outputs.
}
func TestAppenderMergePage(t *testing.T) {
f1, err := os.Open(testPdf3pages)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.MergePageWith(1, pdf2.PageList[0])
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.WriteToFile(tempFile("appender_merge_page_1.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
func TestAppenderMergePage2(t *testing.T) {
f1, err := os.Open(imgPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(imgPdfFile2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.MergePageWith(1, pdf2.PageList[0])
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender.AddPages(pdf2.PageList...)
err = appender.WriteToFile(tempFile("appender_merge_page_2.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
func TestAppenderMergePage3(t *testing.T) {
f1, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(testPdfLoremIpsumFile)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.MergePageWith(1, pdf2.PageList[0])
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.WriteToFile(tempFile("appender_merge_page_3.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
}
func validateFile(t *testing.T, fileName string) {
t.Logf("Validating %s", fileName)
data, err := ioutil.ReadFile(fileName)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
reader, err := model.NewPdfReader(bytes.NewReader(data))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
handler, _ := sighandler.NewAdobeX509RSASHA1(nil, nil)
handler2, _ := sighandler.NewAdobePKCS7Detached(nil, nil)
handlers := []model.SignatureHandler{handler, handler2}
res, err := reader.ValidateSignatures(handlers)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
if len(res) == 0 {
t.Errorf("Fail: signature fields not found")
return
}
if !res[0].IsSigned || !res[0].IsVerified {
t.Errorf("Fail: validation failed")
return
}
for i, item := range res {
t.Logf("== Signature %d", i+1)
t.Logf("%s", item.String())
}
}
func parseByteRange(byteRange *core.PdfObjectArray) ([]int64, error) {
if byteRange == nil {
return nil, errors.New("byte range cannot be nil")
}
if byteRange.Len() != 4 {
return nil, errors.New("invalid byte range length")
}
s1, err := core.GetNumberAsInt64(byteRange.Get(0))
if err != nil {
return nil, errors.New("invalid byte range value")
}
l1, err := core.GetNumberAsInt64(byteRange.Get(1))
if err != nil {
return nil, errors.New("invalid byte range value")
}
s2, err := core.GetNumberAsInt64(byteRange.Get(2))
if err != nil {
return nil, errors.New("invalid byte range value")
}
l2, err := core.GetNumberAsInt64(byteRange.Get(3))
if err != nil {
return nil, errors.New("invalid byte range value")
}
return []int64{s1, s1 + l1, s2, s2 + l2}, nil
}
func TestAppenderSignPage4(t *testing.T) {
// TODO move to reader_test.go
validateFile(t, testPdfSignedPDFDocument)
f1, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f, _ := ioutil.ReadFile(testPKS12Key)
privateKey, cert, err := pkcs12.Decode(f, testPKS12KeyPassword)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Create signature field and appearance.
signature := model.NewPdfSignature(handler)
signature.SetName("Test Appender")
signature.SetReason("TestAppenderSignPage4")
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return
}
sigField := model.NewPdfFieldSignature(signature)
sigField.T = core.MakeString("Signature1")
sigField.Rect = core.MakeArray(
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
)
if err = appender.Sign(1, sigField); err != nil {
t.Errorf("Fail: %v\n", err)
return
}
err = appender.WriteToFile(tempFile("appender_sign_page_4.pdf"))
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
validateFile(t, tempFile("appender_sign_page_4.pdf"))
}
// Multiple revisions of signing.
func TestAppenderSignMultiple(t *testing.T) {
inputPath := "./testdata/minimal.pdf"
for i := 0; i < 3; i++ {
t.Logf("======================================")
t.Logf("--> Signature revision %d", i+1)
t.Logf("Input %s", inputPath)
f, err := os.Open(inputPath)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
pdfReader, err := model.NewPdfReader(f)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
t.Logf("Fields: %d", len(pdfReader.AcroForm.AllFields()))
if len(pdfReader.AcroForm.AllFields()) != i {
t.Fatalf("fields != %d (got %d)", i, len(pdfReader.AcroForm.AllFields()))
}
annotations, err := pdfReader.PageList[0].GetAnnotations()
require.NoError(t, err)
t.Logf("Annotations: %d", len(annotations))
if len(annotations) != i {
t.Fatalf("page annotations != %d (got %d)", i, len(annotations))
}
for j, annot := range annotations {
t.Logf("i=%d Annots page object equal? %v == %v?", j, pdfReader.PageList[0].GetContainingPdfObject(), annot.P)
require.Equal(t, pdfReader.PageList[0].GetContainingPdfObject(), annot.P)
}
appender, err := model.NewPdfAppender(pdfReader)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
pfxData, _ := ioutil.ReadFile("./testdata/JohnSmith.pfx")
privateKey, cert, err := pkcs12.Decode(pfxData, "password")
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
// Create signature field and appearance.
signature := model.NewPdfSignature(handler)
signature.SetName(fmt.Sprintf("Test Appender - Round %d", i+1))
signature.SetReason(fmt.Sprintf("Test Appender - Round %d", i+1))
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return
}
sigField := model.NewPdfFieldSignature(signature)
sigField.T = core.MakeString(fmt.Sprintf("Signature %d", i+1))
sigField.Rect = core.MakeArray(
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
core.MakeInteger(0),
)
if err = appender.Sign(1, sigField); err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
outPath := tempFile(fmt.Sprintf("appender_sign_multiple_%d.pdf", i+1))
t.Logf("Signing to %s", outPath)
err = appender.WriteToFile(outPath)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
validateFile(t, outPath)
inputPath = outPath
f.Close()
}
}
func TestSignatureAppearance(t *testing.T) {
f, err := os.Open(testPdf3pages)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f.Close()
pdfReader, err := model.NewPdfReader(f)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
t.Logf("Fields: %d", len(pdfReader.AcroForm.AllFields()))
appender, err := model.NewPdfAppender(pdfReader)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
pfxData, _ := ioutil.ReadFile(testPKS12Key)
privateKey, cert, err := pkcs12.Decode(pfxData, testPKS12KeyPassword)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Create signature.
signature := model.NewPdfSignature(handler)
signature.SetName("Test Signature Appearance Name")
signature.SetReason("TestSignatureAppearance Reason")
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return
}
numPages, err := pdfReader.GetNumPages()
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
for i := 0; i < numPages; i++ {
pageNum := i + 1
// Annot1
opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}
sigField, err := annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "Jane Doe"),
annotator.NewSignatureLine("Date", "2019.01.03"),
annotator.NewSignatureLine("Reason", "Some reason"),
annotator.NewSignatureLine("Location", "New York"),
annotator.NewSignatureLine("DN", "authority1:name1"),
},
opts,
)
sigField.T = core.MakeString(fmt.Sprintf("Signature %d", pageNum))
if err = appender.Sign(pageNum, sigField); err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Annot2
opts = annotator.NewSignatureFieldOpts()
opts.FontSize = 8
opts.Rect = []float64{250, 25, 325, 70}
opts.TextColor = model.NewPdfColorDeviceRGB(255, 0, 0)
sigField, err = annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "John Doe"),
annotator.NewSignatureLine("Date", "2019.03.14"),
annotator.NewSignatureLine("Reason", "No reason"),
annotator.NewSignatureLine("Location", "London"),
annotator.NewSignatureLine("DN", "authority2:name2"),
},
opts,
)
sigField.T = core.MakeString(fmt.Sprintf("Signature2 %d", pageNum))
if err = appender.Sign(pageNum, sigField); err != nil {
log.Fatalf("Fail: %v\n", err)
}
// Annot3
opts = annotator.NewSignatureFieldOpts()
opts.BorderSize = 1
opts.FontSize = 10
opts.Rect = []float64{475, 25, 590, 80}
opts.FillColor = model.NewPdfColorDeviceRGB(255, 255, 0)
opts.TextColor = model.NewPdfColorDeviceRGB(0, 0, 200)
sigField, err = annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "John Smith"),
annotator.NewSignatureLine("Date", "2019.02.19"),
annotator.NewSignatureLine("Reason", "Another reason"),
annotator.NewSignatureLine("Location", "Paris"),
annotator.NewSignatureLine("DN", "authority3:name3"),
},
opts,
)
sigField.T = core.MakeString(fmt.Sprintf("Signature3 %d", pageNum))
if err = appender.Sign(pageNum, sigField); err != nil {
log.Fatalf("Fail: %v\n", err)
}
}
outPath := tempFile("appender_signature_appearance.pdf")
if err = appender.WriteToFile(outPath); err != nil {
t.Errorf("Fail: %v\n", err)
return
}
validateFile(t, outPath)
}
// Multiple revisions of signing with appearances.
func TestAppenderSignMultipleAppearances(t *testing.T) {
inputPath := testPdf3pages
for i := 0; i < 3; i++ {
t.Logf("======================================")
t.Logf("--> Signature revision %d", i+1)
t.Logf("Input %s", inputPath)
f, err := os.Open(inputPath)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
pdfReader, err := model.NewPdfReader(f)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
numPages, err := pdfReader.GetNumPages()
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
t.Logf("Fields: %d", len(pdfReader.AcroForm.AllFields()))
if len(pdfReader.AcroForm.AllFields()) != i*numPages {
t.Fatalf("fields != %d (got %d)", i*numPages, len(pdfReader.AcroForm.AllFields()))
}
annotations, err := pdfReader.PageList[0].GetAnnotations()
require.NoError(t, err)
t.Logf("Annotations: %d", len(annotations))
if len(annotations) != i {
t.Fatalf("page annotations != %d (got %d)", i, len(annotations))
}
for j, annot := range annotations {
t.Logf("i=%d Annots page object equal? %v == %v?", j, pdfReader.PageList[0].GetContainingPdfObject(), annot.P)
require.Equal(t, pdfReader.PageList[0].GetContainingPdfObject(), annot.P)
}
appender, err := model.NewPdfAppender(pdfReader)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
pfxData, _ := ioutil.ReadFile("./testdata/JohnSmith.pfx")
privateKey, cert, err := pkcs12.Decode(pfxData, "password")
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
// Create signature field and appearance.
signature := model.NewPdfSignature(handler)
signature.SetName(fmt.Sprintf("Test Appender - Round %d", i+1))
signature.SetReason(fmt.Sprintf("Test Appender - Round %d", i+1))
signature.SetDate(time.Now(), "")
if err := signature.Initialize(); err != nil {
return
}
for j := 0; j < numPages; j++ {
pageNum := j + 1
opts := annotator.NewSignatureFieldOpts()
opts.BorderSize = 1
opts.FontSize = 10
opts.Rect = []float64{float64(200*i) + 50, 25, float64(200*i) + 150, 80}
opts.FillColor = model.NewPdfColorDeviceRGB(255, 255, 0)
opts.TextColor = model.NewPdfColorDeviceRGB(0, 0, 200)
sigField, err := annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", fmt.Sprintf("John Smith %d", i+1)),
annotator.NewSignatureLine("Date", fmt.Sprintf("2019.0%d.%d", i+1, i+1)),
annotator.NewSignatureLine("Reason", fmt.Sprintf("Reason %d", i+1)),
annotator.NewSignatureLine("Location", "New York"),
annotator.NewSignatureLine("DN", fmt.Sprintf("authority%d:name%d", i+1, i+1)),
},
opts,
)
sigField.T = core.MakeString(fmt.Sprintf("Signature %d-%d", i+1, j+1))
if err = appender.Sign(pageNum, sigField); err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
}
outPath := tempFile(fmt.Sprintf("appender_sign_multiple_appearances_%d.pdf", i+1))
t.Logf("Signing to %s", outPath)
if err = appender.WriteToFile(outPath); err != nil {
t.Errorf("Fail: %v\n", err)
f.Close()
return
}
//validateFile(t, outPath)
inputPath = outPath
f.Close()
}
}
func TestAppenderExternalSignature(t *testing.T) {
validateFile(t, testPdfSignedPDFDocument)
// Function to generate signed PDF using the specified signature handler.
generateSignedFile := func(handler model.SignatureHandler) ([]byte, *model.PdfSignature, error) {
file, err := os.Open(testPdfFile1)
if err != nil {
return nil, nil, err
}
defer file.Close()
reader, err := model.NewPdfReader(file)
if err != nil {
return nil, nil, err
}
appender, err := model.NewPdfAppender(reader)
if err != nil {
return nil, nil, err
}
// Create signature.
signature := model.NewPdfSignature(handler)
signature.SetName("Test External Signature")
signature.SetReason("TestAppenderExternalSignature")
signature.SetDate(time.Date(2019, 3, 24, 7, 30, 24, 0, time.UTC), "")
if err := signature.Initialize(); err != nil {
return nil, nil, err
}
// Create signature field and appearance.
opts := annotator.NewSignatureFieldOpts()
opts.FontSize = 10
opts.Rect = []float64{10, 25, 75, 60}
field, err := annotator.NewSignatureField(
signature,
[]*annotator.SignatureLine{
annotator.NewSignatureLine("Name", "John Doe"),
annotator.NewSignatureLine("Date", "2019.15.03"),
annotator.NewSignatureLine("Reason", "External signature test"),
},
opts,
)
field.T = core.MakeString("External signature")
if err = appender.Sign(1, field); err != nil {
return nil, nil, err
}
// Write PDF file to buffer.
pdfBuf := bytes.NewBuffer(nil)
if err = appender.Write(pdfBuf); err != nil {
return nil, nil, err
}
return pdfBuf.Bytes(), signature, nil
}
// Function which signs PDF and returns the signature data.
getExternalSignature := func() ([]byte, error) {
certFile, err := ioutil.ReadFile(testPKS12Key)
if err != nil {
return nil, err
}
privateKey, cert, err := pkcs12.Decode(certFile, testPKS12KeyPassword)
if err != nil {
return nil, err
}
handler, err := sighandler.NewAdobePKCS7Detached(privateKey.(*rsa.PrivateKey), cert)
if err != nil {
return nil, err
}
// Get external signature data.
_, signature, err := generateSignedFile(handler)
if err != nil {
return nil, err
}
return signature.Contents.Bytes(), nil
}
// Generate PDF file signed with empty signature.
handler, err := sighandler.NewEmptyAdobePKCS7Detached(8192)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
pdfData, signature, err := generateSignedFile(handler)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Parse signature byte range.
byteRange, err := parseByteRange(signature.ByteRange)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// This would be the time to send the PDF buffer to a signing device or
// signing web service and get back the signature. We will simulate this by
// signing the PDF using UniDoc and returning the signature data.
signatureData, err := getExternalSignature()
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Apply external signature to the PDF data buffer.
sigBytes := make([]byte, 8192)
copy(sigBytes, signatureData)
sig := core.MakeHexString(string(sigBytes)).WriteString()
copy(pdfData[byteRange[1]:byteRange[2]], []byte(sig))
// Write output file.
outputPath := tempFile("appender_sign_external_signature.pdf")
if err := ioutil.WriteFile(outputPath, pdfData, os.ModePerm); err != nil {
t.Errorf("Fail: %v\n", err)
return
}
// Validate output file.
validateFile(t, outputPath)
}
// Each Appender can only be written out once, further invokations of Write should result in an error.
func TestAppenderAttemptMultiWrite(t *testing.T) {
f1, err := os.Open(testPdfLoremIpsumFile)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f1.Close()
pdf1, err := model.NewPdfReader(f1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
f2, err := os.Open(testPdfFile1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
defer f2.Close()
pdf2, err := model.NewPdfReader(f2)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender, err := model.NewPdfAppender(pdf1)
if err != nil {
t.Errorf("Fail: %v\n", err)
return
}
appender.AddPages(pdf1.PageList...)
appender.AddPages(pdf2.PageList...)
appender.AddPages(pdf2.PageList...)
// Write twice to buffer and compare results.
var buf1, buf2 bytes.Buffer
err = appender.Write(&buf1)
if err != nil {
t.Fatalf("Error: %v", err)
}
err = appender.Write(&buf2)
if err == nil {
t.Fatalf("Second invokation of appender.Write should yield an error")
}
}