github.com/pdfcpu/pdfcpu@v0.11.1/pkg/api/test/attachment_test.go (about)

     1  /*
     2  Copyright 2019 The pdf Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package test
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/pdfcpu/pdfcpu/pkg/api"
    29  	"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
    30  )
    31  
    32  func prepareForAttachmentTest(t *testing.T) error {
    33  	t.Helper()
    34  	for _, fileName := range []string{"go.pdf", "golang.pdf", "T4.pdf", "go-lecture.pdf"} {
    35  		inFile := filepath.Join(inDir, fileName)
    36  		outFile := filepath.Join(outDir, fileName)
    37  		if err := copyFile(t, inFile, outFile); err != nil {
    38  			return err
    39  		}
    40  	}
    41  	return copyFile(t, filepath.Join(resDir, "test.wav"), filepath.Join(outDir, "test.wav"))
    42  }
    43  
    44  func listAttachments(t *testing.T, msg, fileName string, want int) {
    45  	t.Helper()
    46  
    47  	f, err := os.Open(fileName)
    48  	if err != nil {
    49  		t.Fatalf("%s open: %v\n", msg, err)
    50  	}
    51  	defer f.Close()
    52  
    53  	aa, err := api.Attachments(f, nil)
    54  	if err != nil {
    55  		t.Fatalf("%s list attachments: %v\n", msg, err)
    56  	}
    57  
    58  	got := len(aa)
    59  	if got != want {
    60  		t.Fatalf("%s: list attachments %s: want %d got %d\n", msg, fileName, want, got)
    61  	}
    62  }
    63  
    64  func TestAttachments(t *testing.T) {
    65  	msg := "testAttachments"
    66  
    67  	if err := prepareForAttachmentTest(t); err != nil {
    68  		t.Fatalf("%s prepare for attachments: %v\n", msg, err)
    69  	}
    70  
    71  	fileName := filepath.Join(outDir, "go.pdf")
    72  
    73  	// # of attachments must be 0
    74  	listAttachments(t, msg, fileName, 0)
    75  
    76  	// attach add 4 files
    77  	files := []string{
    78  		filepath.Join(outDir, "golang.pdf"),
    79  		filepath.Join(outDir, "T4.pdf"),
    80  		filepath.Join(outDir, "go-lecture.pdf"),
    81  		filepath.Join(outDir, "test.wav")}
    82  
    83  	if err := api.AddAttachmentsFile(fileName, "", files, false, nil); err != nil {
    84  		t.Fatalf("%s add attachments: %v\n", msg, err)
    85  	}
    86  
    87  	listAttachments(t, msg, fileName, 4)
    88  
    89  	// Extract all attachments.
    90  	if err := api.ExtractAttachmentsFile(fileName, outDir, nil, nil); err != nil {
    91  		t.Fatalf("%s extract all attachments: %v\n", msg, err)
    92  	}
    93  
    94  	// Extract 1 attachment.
    95  	if err := api.ExtractAttachmentsFile(fileName, outDir, []string{"golang.pdf"}, nil); err != nil {
    96  		t.Fatalf("%s extract one attachment: %v\n", msg, err)
    97  	}
    98  
    99  	// Remove 1 attachment.
   100  	if err := api.RemoveAttachmentsFile(fileName, "", []string{"golang.pdf"}, nil); err != nil {
   101  		t.Fatalf("%s remove one attachment: %v\n", msg, err)
   102  	}
   103  	listAttachments(t, msg, fileName, 3)
   104  
   105  	// Remove all attachments.
   106  	if err := api.RemoveAttachmentsFile(fileName, "", nil, nil); err != nil {
   107  		t.Fatalf("%s remove all attachments: %v\n", msg, err)
   108  	}
   109  	listAttachments(t, msg, fileName, 0)
   110  
   111  	// Validate the processed file.
   112  	if err := api.ValidateFile(fileName, nil); err != nil {
   113  		t.Fatalf("%s: validate: %v\n", msg, err)
   114  	}
   115  }
   116  
   117  // timeEqualsTimeFromDateTime returns true if t1 equals t2
   118  // working on the assumption that t2 is restored from a PDF
   119  // date string that does not have a way to include nanoseconds.
   120  func timeEqualsTimeFromDateTime(t1, t2 *time.Time) bool {
   121  	if t1 == nil && t2 == nil {
   122  		return true
   123  	}
   124  	if t1 == nil || t2 == nil {
   125  		return false
   126  	}
   127  	nanos := t1.Nanosecond()
   128  	t11 := t1.Add(-time.Duration(nanos) * time.Nanosecond)
   129  	return t11.Equal(*t2)
   130  }
   131  
   132  func addAttachment(t *testing.T, msg, outFile, id, desc, want string, modTime time.Time, ctx *model.Context) {
   133  	t.Helper()
   134  
   135  	a := model.Attachment{
   136  		Reader:  strings.NewReader(want),
   137  		ID:      id,
   138  		Desc:    desc,
   139  		ModTime: &modTime}
   140  
   141  	var err error
   142  	useCollection := false
   143  	if err = ctx.AddAttachment(a, useCollection); err != nil {
   144  		t.Fatalf("%s addAttachment: %v\n", msg, err)
   145  	}
   146  
   147  	// Write context to outFile after adding attachment.
   148  	if err = api.WriteContextFile(ctx, outFile); err != nil {
   149  		t.Fatalf("%s writeContext: %v\n", msg, err)
   150  	}
   151  }
   152  
   153  func extractAttachment(t *testing.T, msg string, a model.Attachment, ctx *model.Context) model.Attachment {
   154  	t.Helper()
   155  
   156  	a1, err := ctx.ExtractAttachment(a)
   157  	if err != nil {
   158  		t.Fatalf("%s extractAttachment: %v\n", msg, err)
   159  	}
   160  	if a1.ID != a.ID ||
   161  		a1.FileName != a.FileName ||
   162  		a1.Desc != a.Desc ||
   163  		!timeEqualsTimeFromDateTime(a.ModTime, a1.ModTime) {
   164  		t.Fatalf("%s extractAttachment: unexpected attachment: %s\n", msg, a1)
   165  	}
   166  	return *a1
   167  }
   168  
   169  func removeAttachment(t *testing.T, msg, outFile string, a model.Attachment, ctx *model.Context) {
   170  	t.Helper()
   171  	ok, err := ctx.RemoveAttachment(a)
   172  	if err != nil {
   173  		t.Fatalf("%s removeAttachment: %v\n", msg, err)
   174  	}
   175  	if !ok {
   176  		t.Fatalf("%s removeAttachment: attachment %s not found\n", msg, a.FileName)
   177  	}
   178  
   179  	// Write context to outFile after removing attachment.
   180  	if err := api.WriteContextFile(ctx, outFile); err != nil {
   181  		t.Fatalf("%s writeContext: %v\n", msg, err)
   182  	}
   183  
   184  	// Read outfile once again into a PDFContext.
   185  	ctx, err = api.ReadContextFile(outFile)
   186  	if err != nil {
   187  		t.Fatalf("%s readContext: %v\n", msg, err)
   188  	}
   189  
   190  	// List attachment.
   191  	aa, err := ctx.ListAttachments()
   192  	if err != nil {
   193  		t.Fatalf("%s listAttachments: %v\n", msg, err)
   194  	}
   195  	if len(aa) != 0 {
   196  		t.Fatalf("%s listAttachments: want 0 got %d\n", msg, len(aa))
   197  	}
   198  }
   199  
   200  func TestAttachmentsLowLevel(t *testing.T) {
   201  	msg := "TestAttachmentsLowLevel"
   202  
   203  	file := "go.pdf"
   204  	inFile := filepath.Join(inDir, file)
   205  	outFile := filepath.Join(outDir, file)
   206  	if err := copyFile(t, inFile, outFile); err != nil {
   207  		t.Fatalf("%s copyFile: %v\n", msg, err)
   208  	}
   209  
   210  	// Create a context.
   211  	ctx, err := api.ReadContextFile(outFile)
   212  	if err != nil {
   213  		t.Fatalf("%s readContext: %v\n", msg, err)
   214  	}
   215  
   216  	// Ensure zero attachments.
   217  	if aa, err := ctx.ListAttachments(); err != nil || len(aa) > 0 {
   218  		t.Fatalf("%s listAttachments: %v\n", msg, err)
   219  	}
   220  
   221  	id := "attachment1"
   222  	desc := "description"
   223  	want := "12345"
   224  	modTime := time.Now()
   225  	addAttachment(t, msg, outFile, id, desc, want, modTime, ctx)
   226  
   227  	// Read outfile again into a PDFContext.
   228  	ctx, err = api.ReadContextFile(outFile)
   229  	if err != nil {
   230  		t.Fatalf("%s readContext: %v\n", msg, err)
   231  	}
   232  
   233  	// List attachments.
   234  	aa, err := ctx.ListAttachments()
   235  	if err != nil {
   236  		t.Fatalf("%s listAttachments: %v\n", msg, err)
   237  	}
   238  	if len(aa) != 1 {
   239  		t.Fatalf("%s listAttachments: want 1 got %d\n", msg, len(aa))
   240  	}
   241  	if aa[0].FileName != id ||
   242  		aa[0].Desc != desc ||
   243  		!timeEqualsTimeFromDateTime(&modTime, aa[0].ModTime) {
   244  		t.Fatalf("%s listAttachments: unexpected attachment: %s\n", msg, aa[0])
   245  	}
   246  
   247  	a := extractAttachment(t, msg, aa[0], ctx)
   248  
   249  	// Compare extracted attachment bytes.
   250  	gotBytes, err := io.ReadAll(a)
   251  	if err != nil {
   252  		t.Fatalf("%s extractAttachment: attachment %s no data available\n", msg, id)
   253  	}
   254  	got := string(gotBytes)
   255  	if got != want {
   256  		t.Fatalf("%s\ngot:%s\nwant:%s", msg, got, want)
   257  	}
   258  
   259  	// Optional processing of attachment bytes:
   260  	// Process gotBytes..
   261  
   262  	removeAttachment(t, msg, outFile, a, ctx)
   263  }
   264  
   265  func TestSanitizePath(t *testing.T) {
   266  
   267  	msg := "TestSanitizePath"
   268  
   269  	testPaths := []string{
   270  		"",
   271  		".",
   272  		"..",
   273  		"../..",
   274  		"foo/.",
   275  		"bar/..",
   276  		"foo/bar/.",
   277  		"foo/bar/",
   278  		"foo/./bar/..",
   279  		"foo/./bar/./..",
   280  		"foo/./bar/../.",
   281  		"foo/./bar/../..",
   282  		"foo/./bar/",
   283  		"foo/../bar/..",
   284  		"docs/report.pdf",
   285  		"../../etc/passwd",
   286  		"/etc/passwd",
   287  		"subdir/../bar//../file.txt",
   288  	}
   289  
   290  	for _, path := range testPaths {
   291  		result := api.SanitizePath(path)
   292  		fmt.Printf("%s: %q -> %q \n", msg, path, result)
   293  	}
   294  
   295  }