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 }