package documentclean import ( "context" "math" "strings" "testing" "github.com/pdfcpu/pdfcpu/pkg/pdfcpu" "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestIsValidDPI(t *testing.T) { t.Run("no imgs", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) err = pdfCtx.EnsurePageCount() require.NoError(t, err) dims := types.Dim{ Width: TEXTRACT_MIN_DIMENSION, Height: TEXTRACT_MIN_DIMENSION, } boxes, err := pdfCtx.PageBoundaries(types.IntSet{ 1: true, }) require.NoError(t, err) reason := pdf.isValidDPI(boxes[0].MediaBox(), dims) assert.Nil(t, reason) }) t.Run("with img", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfWithImg)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) err = pdfCtx.EnsurePageCount() require.NoError(t, err) dims := types.Dim{ Width: TEXTRACT_MIN_DIMENSION, Height: TEXTRACT_MIN_DIMENSION, } boxes, err := pdfCtx.PageBoundaries(types.IntSet{ 1: true, }) require.NoError(t, err) reason := pdf.isValidDPI(boxes[0].MediaBox(), dims) assert.Nil(t, reason) }) t.Run("with tiny dpi", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfWithImg)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) err = pdfCtx.EnsurePageCount() require.NoError(t, err) dims := types.Dim{ Width: math.MaxFloat64, Height: math.MaxFloat64, } boxes, err := pdfCtx.PageBoundaries(types.IntSet{ 1: true, }) require.NoError(t, err) reason := pdf.isValidDPI(boxes[0].MediaBox(), dims) assert.Equal(t, InvalidDocumentSmallDPI, *reason) }) t.Run("with tiny width dpi", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfWithImg)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) err = pdfCtx.EnsurePageCount() require.NoError(t, err) dims := types.Dim{ Width: math.MaxFloat64, Height: 1, } boxes, err := pdfCtx.PageBoundaries(types.IntSet{ 1: true, }) require.NoError(t, err) reason := pdf.isValidDPI(boxes[0].MediaBox(), dims) assert.Equal(t, InvalidDocumentSmallDPI, *reason) }) t.Run("with tiny height dpi", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfWithImg)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) err = pdfCtx.EnsurePageCount() require.NoError(t, err) dims := types.Dim{ Width: 1, Height: math.MaxFloat64, } boxes, err := pdfCtx.PageBoundaries(types.IntSet{ 1: true, }) require.NoError(t, err) reason := pdf.isValidDPI(boxes[0].MediaBox(), dims) assert.Equal(t, InvalidDocumentSmallDPI, *reason) }) t.Run("with huge dpi", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfWithImg)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) err = pdfCtx.EnsurePageCount() require.NoError(t, err) dims := types.Dim{ Width: 1, Height: 1, } boxes, err := pdfCtx.PageBoundaries(types.IntSet{ 1: true, }) require.NoError(t, err) reason := pdf.isValidDPI(boxes[0].MediaBox(), dims) assert.Nil(t, reason) }) } func TestIsValidDimensions(t *testing.T) { t.Run("valid", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) reason := pdf.isValidDimensions(pdfCtx) assert.Nil(t, reason) }) t.Run("large dimensions", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) pdf.maxDimension = 1 reason := pdf.isValidDimensions(pdfCtx) assert.Equal(t, InvalidDocumentLargeDimensions, *reason) }) t.Run("small dimensions", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) pdf.minDimension = TEXTRACT_MAX_DIMENSION reason := pdf.isValidDimensions(pdfCtx) assert.Equal(t, InvalidDocumentSmallDimensions, *reason) }) } func TestIsValidPageCount(t *testing.T) { t.Run("valid", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) reason := pdf.isValidPageCount(pdfCtx) assert.Nil(t, reason) }) t.Run("large page count", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfTwoPages)) pdfCtx, err := pdfcpu.ReadWithContext(ctx, pdf.pdf, pdf.config) require.NoError(t, err) pdf.maxPages = 1 reason := pdf.isValidPageCount(pdfCtx) assert.Equal(t, InvalidDocumentLargePageCount, *reason) }) } func TestIsCorrupt(t *testing.T) { t.Run("valid", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("no content", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader("%PDF-1.4 %%EOF")) reason := pdf.isCorrupt(ctx) assert.Equal(t, InvalidDocumentRead, *reason) }) t.Run("large file size", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdf.maxBytes = 1 reason := pdf.isCorrupt(ctx) assert.Equal(t, InvalidDocumentLargeFile, *reason) }) t.Run("large dimensions", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfHelloWorld)) pdf.maxDimension = 1 reason := pdf.isCorrupt(ctx) assert.Equal(t, InvalidDocumentLargeDimensions, *reason) }) t.Run("large page count", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(pdfTwoPages)) pdf.maxPages = 1 reason := pdf.isCorrupt(ctx) assert.Equal(t, InvalidDocumentLargePageCount, *reason) }) t.Run("version 1.0", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_0)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.1", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_1)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.2", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_2)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.3", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_3)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.4", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_4)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.5", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_5)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.6", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_6)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 1.7", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF1_7)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) t.Run("version 2.0", func(t *testing.T) { ctx := context.Background() pdf := NewPDF(strings.NewReader(PDF2_0)) reason := pdf.isCorrupt(ctx) assert.Nil(t, reason) }) } func TestNewPDF(t *testing.T) { t.Run("valid", func(t *testing.T) { pdf := NewPDF(strings.NewReader(pdfHelloWorld)) assert.NotNil(t, pdf.pdf) assert.NotNil(t, pdf.config) assert.Equal(t, TEXTRACT_PAGE_LIMIT, pdf.maxPages) assert.Equal(t, TEXTRACT_SYNC_LIMIT, pdf.maxBytes) }) } const PDF1_0 = `%PDF-1.0 %¥±ë % Binary comment for PDF parsers 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.0 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj xref 0 6 0000000000 65535 f 0000000018 00000 n 0000000072 00000 n 0000000134 00000 n 0000000309 00000 n 0000000411 00000 n trailer << /Size 6 /Root 1 0 R >> startxref 485 %%EOF` const PDF1_1 = `%PDF-1.1 %âãÏÓ 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.1 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj xref 0 6 0000000000 65535 f 0000000018 00000 n 0000000072 00000 n 0000000134 00000 n 0000000309 00000 n 0000000411 00000 n trailer << /Size 6 /Root 1 0 R >> startxref 485 %%EOF` const PDF1_2 = `%PDF-1.2 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R >> endobj 4 0 obj << /Length 65 /Filter /FlateDecode >> stream x�s �2�30W0 P&� � � � �(X�Y��\��R!$�K��H����PS!$��RR�P�� �)� endstream endobj xref 0 5 0000000000 65535 f 0000000009 00000 n 0000000063 00000 n 0000000125 00000 n 0000000210 00000 n trailer << /Size 5 /Root 1 0 R >> startxref 325 %%EOF` const PDF1_3 = `%PDF-1.3 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.3 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj xref 0 6 0000000000 65535 f 0000000009 00000 n 0000000063 00000 n 0000000125 00000 n 0000000300 00000 n 0000000402 00000 n trailer << /Size 6 /Root 1 0 R >> startxref 476 %%EOF` const PDF1_4 = `%PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> /ExtGState << /GS1 6 0 R >> >> >> endobj 4 0 obj << /Length 78 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.4 Example with Transparency) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 6 0 obj << /Type /ExtGState /ca 0.5 /CA 0.5 >> endobj xref 0 7 0000000000 65535 f 0000000009 00000 n 0000000063 00000 n 0000000125 00000 n 0000000342 00000 n 0000000470 00000 n 0000000544 00000 n trailer << /Size 7 /Root 1 0 R >> startxref 603 %%EOF` const PDF1_5 = `%PDF-1.5 1 0 obj << /Type /Catalog /Pages 2 0 R /Version /1.5 >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.5 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 6 0 obj << /Type /ObjStm /N 1 /First 4 /Length 19 >> stream 7 0 << /Im 8 0 R >> endstream endobj xref 0 7 0000000000 65535 f 0000000009 00000 n 0000000075 00000 n 0000000137 00000 n 0000000312 00000 n 0000000414 00000 n 0000000488 00000 n trailer << /Size 7 /Root 1 0 R >> startxref 585 %%EOF` const PDF1_6 = `%PDF-1.6 1 0 obj << /Type /Catalog /Pages 2 0 R /Version /1.6 >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> /Annots [6 0 R] >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.6 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 6 0 obj << /Type /Annot /Subtype /3D /Rect [100 100 400 500] /3DD 7 0 R /AP << /N 8 0 R >> >> endobj 7 0 obj << /Type /3DStream /Subtype /U3D /Length 14 >> stream Placeholder3D endstream endobj 8 0 obj << /Type /XObject /Subtype /Form /BBox [0 0 300 400] /Length 25 >> stream q 1 0 0 1 0 0 cm /Fm1 Do Q endstream endobj xref 0 9 0000000000 65535 f 0000000009 00000 n 0000000075 00000 n 0000000137 00000 n 0000000332 00000 n 0000000434 00000 n 0000000508 00000 n 0000000621 00000 n 0000000708 00000 n trailer << /Size 9 /Root 1 0 R >> startxref 835 %%EOF` const PDF1_7 = `%PDF-1.7 1 0 obj << /Type /Catalog /Pages 2 0 R /Version /1.7 >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> /Annots [6 0 R] >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 1.7 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj 6 0 obj << /Type /Annot /Subtype /RichMedia /Rect [100 100 400 500] /RichMediaContent 7 0 R >> endobj 7 0 obj << /Type /RichMediaContent /Assets 8 0 R /Configurations [9 0 R] >> endobj 8 0 obj << /Names [(asset1) 10 0 R] >> endobj 9 0 obj << /Type /RichMediaConfiguration /Instances [<< /Asset (asset1) >>] >> endobj 10 0 obj << /Type /Filespec /F (media.mp4) /EF << /F 11 0 R >> >> endobj 11 0 obj << /Length 22 >> stream PlaceholderMediaContent endstream endobj xref 0 12 0000000000 65535 f 0000000009 00000 n 0000000075 00000 n 0000000137 00000 n 0000000332 00000 n 0000000434 00000 n 0000000508 00000 n 0000000612 00000 n 0000000696 00000 n 0000000741 00000 n 0000000826 00000 n 0000000901 00000 n trailer << /Size 12 /Root 1 0 R >> startxref 975 %%EOF` const PDF2_0 = `%PDF-2.0 %âãÏÓ 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text] /Font << /F1 5 0 R >> >> >> endobj 4 0 obj << /Length 52 >> stream BT /F1 24 Tf 100 700 Td (PDF 2.0 Example) Tj ET endstream endobj 5 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj xref 0 6 0000000000 65535 f 0000000018 00000 n 0000000072 00000 n 0000000134 00000 n 0000000309 00000 n 0000000411 00000 n trailer << /Size 6 /Root 1 0 R >> startxref 485 %%EOF` const pdfTwoPages = `%PDF-1.4 %âãÏÓ 1 0 obj <> endobj 2 0 obj <> endobj 3 0 obj <>>>/MediaBox[0 0 612 792]/Contents 6 0 R>> endobj 4 0 obj <>>>/MediaBox[0 0 612 792]/Contents 7 0 R>> endobj 5 0 obj <> endobj 6 0 obj <> stream BT /F1 14 Tf 200 400 Td (Hello from page one!) Tj ET endstream endobj 7 0 obj <> stream BT /F1 14 Tf 200 400 Td (Goodbye from page two!) Tj ET endstream endobj xref 0 8 0000000000 65535 f 0000000015 00000 n 0000000060 00000 n 0000000111 00000 n 0000000212 00000 n 0000000313 00000 n 0000000374 00000 n 0000000492 00000 n trailer <> startxref 612 %%EOF` const pdfWithImg = `%PDF-1.7 1 0 obj <> endobj 2 0 obj <> endobj 3 0 obj <>>>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]>>/MediaBox[0 0 595 842]/Contents 4 0 R>> endobj 4 0 obj <> stream BT /F1 12 Tf 50 750 Td (This is the first line of a multiline string.) Tj 0 -15 Td (This is the second line of text.) Tj 0 -15 Td (Below this text will be an inline image.) Tj ET q 50 650 Td BI /W 500 /H 500 /BPC 8 /CS /RGB /F [/DCTDecode] ID [BINARY JPEG IMAGE DATA WOULD BE HERE - 500x500 pixels] EI Q BT /F1 12 Tf 50 120 Td (This text appears after the image in the content stream.) Tj 0 -15 Td (The entire content is a single multiline string in the PDF.) Tj ET endstream endobj xref 0 5 0000000000 65535 f 0000000009 00000 n 0000000056 00000 n 0000000107 00000 n 0000000302 00000 n trailer <> startxref 887 %%EOF`