github.com/ader1990/go@v0.0.0-20140630135419-8c24447fa791/src/pkg/archive/zip/reader_test.go (about) 1 // Copyright 2010 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package zip 6 7 import ( 8 "bytes" 9 "encoding/binary" 10 "encoding/hex" 11 "io" 12 "io/ioutil" 13 "os" 14 "path/filepath" 15 "regexp" 16 "testing" 17 "time" 18 ) 19 20 type ZipTest struct { 21 Name string 22 Source func() (r io.ReaderAt, size int64) // if non-nil, used instead of testdata/<Name> file 23 Comment string 24 File []ZipTestFile 25 Error error // the error that Opening this file should return 26 } 27 28 type ZipTestFile struct { 29 Name string 30 Content []byte // if blank, will attempt to compare against File 31 ContentErr error 32 File string // name of file to compare to (relative to testdata/) 33 Mtime string // modified time in format "mm-dd-yy hh:mm:ss" 34 Mode os.FileMode 35 } 36 37 // Caution: The Mtime values found for the test files should correspond to 38 // the values listed with unzip -l <zipfile>. However, the values 39 // listed by unzip appear to be off by some hours. When creating 40 // fresh test files and testing them, this issue is not present. 41 // The test files were created in Sydney, so there might be a time 42 // zone issue. The time zone information does have to be encoded 43 // somewhere, because otherwise unzip -l could not provide a different 44 // time from what the archive/zip package provides, but there appears 45 // to be no documentation about this. 46 47 var tests = []ZipTest{ 48 { 49 Name: "test.zip", 50 Comment: "This is a zipfile comment.", 51 File: []ZipTestFile{ 52 { 53 Name: "test.txt", 54 Content: []byte("This is a test text file.\n"), 55 Mtime: "09-05-10 12:12:02", 56 Mode: 0644, 57 }, 58 { 59 Name: "gophercolor16x16.png", 60 File: "gophercolor16x16.png", 61 Mtime: "09-05-10 15:52:58", 62 Mode: 0644, 63 }, 64 }, 65 }, 66 { 67 Name: "test-trailing-junk.zip", 68 Comment: "This is a zipfile comment.", 69 File: []ZipTestFile{ 70 { 71 Name: "test.txt", 72 Content: []byte("This is a test text file.\n"), 73 Mtime: "09-05-10 12:12:02", 74 Mode: 0644, 75 }, 76 { 77 Name: "gophercolor16x16.png", 78 File: "gophercolor16x16.png", 79 Mtime: "09-05-10 15:52:58", 80 Mode: 0644, 81 }, 82 }, 83 }, 84 { 85 Name: "r.zip", 86 Source: returnRecursiveZip, 87 File: []ZipTestFile{ 88 { 89 Name: "r/r.zip", 90 Content: rZipBytes(), 91 Mtime: "03-04-10 00:24:16", 92 Mode: 0666, 93 }, 94 }, 95 }, 96 { 97 Name: "symlink.zip", 98 File: []ZipTestFile{ 99 { 100 Name: "symlink", 101 Content: []byte("../target"), 102 Mode: 0777 | os.ModeSymlink, 103 }, 104 }, 105 }, 106 { 107 Name: "readme.zip", 108 }, 109 { 110 Name: "readme.notzip", 111 Error: ErrFormat, 112 }, 113 { 114 Name: "dd.zip", 115 File: []ZipTestFile{ 116 { 117 Name: "filename", 118 Content: []byte("This is a test textfile.\n"), 119 Mtime: "02-02-11 13:06:20", 120 Mode: 0666, 121 }, 122 }, 123 }, 124 { 125 // created in windows XP file manager. 126 Name: "winxp.zip", 127 File: crossPlatform, 128 }, 129 { 130 // created by Zip 3.0 under Linux 131 Name: "unix.zip", 132 File: crossPlatform, 133 }, 134 { 135 // created by Go, before we wrote the "optional" data 136 // descriptor signatures (which are required by OS X) 137 Name: "go-no-datadesc-sig.zip", 138 File: []ZipTestFile{ 139 { 140 Name: "foo.txt", 141 Content: []byte("foo\n"), 142 Mtime: "03-08-12 16:59:10", 143 Mode: 0644, 144 }, 145 { 146 Name: "bar.txt", 147 Content: []byte("bar\n"), 148 Mtime: "03-08-12 16:59:12", 149 Mode: 0644, 150 }, 151 }, 152 }, 153 { 154 // created by Go, after we wrote the "optional" data 155 // descriptor signatures (which are required by OS X) 156 Name: "go-with-datadesc-sig.zip", 157 File: []ZipTestFile{ 158 { 159 Name: "foo.txt", 160 Content: []byte("foo\n"), 161 Mode: 0666, 162 }, 163 { 164 Name: "bar.txt", 165 Content: []byte("bar\n"), 166 Mode: 0666, 167 }, 168 }, 169 }, 170 { 171 Name: "Bad-CRC32-in-data-descriptor", 172 Source: returnCorruptCRC32Zip, 173 File: []ZipTestFile{ 174 { 175 Name: "foo.txt", 176 Content: []byte("foo\n"), 177 Mode: 0666, 178 ContentErr: ErrChecksum, 179 }, 180 { 181 Name: "bar.txt", 182 Content: []byte("bar\n"), 183 Mode: 0666, 184 }, 185 }, 186 }, 187 // Tests that we verify (and accept valid) crc32s on files 188 // with crc32s in their file header (not in data descriptors) 189 { 190 Name: "crc32-not-streamed.zip", 191 File: []ZipTestFile{ 192 { 193 Name: "foo.txt", 194 Content: []byte("foo\n"), 195 Mtime: "03-08-12 16:59:10", 196 Mode: 0644, 197 }, 198 { 199 Name: "bar.txt", 200 Content: []byte("bar\n"), 201 Mtime: "03-08-12 16:59:12", 202 Mode: 0644, 203 }, 204 }, 205 }, 206 // Tests that we verify (and reject invalid) crc32s on files 207 // with crc32s in their file header (not in data descriptors) 208 { 209 Name: "crc32-not-streamed.zip", 210 Source: returnCorruptNotStreamedZip, 211 File: []ZipTestFile{ 212 { 213 Name: "foo.txt", 214 Content: []byte("foo\n"), 215 Mtime: "03-08-12 16:59:10", 216 Mode: 0644, 217 ContentErr: ErrChecksum, 218 }, 219 { 220 Name: "bar.txt", 221 Content: []byte("bar\n"), 222 Mtime: "03-08-12 16:59:12", 223 Mode: 0644, 224 }, 225 }, 226 }, 227 { 228 Name: "zip64.zip", 229 File: []ZipTestFile{ 230 { 231 Name: "README", 232 Content: []byte("This small file is in ZIP64 format.\n"), 233 Mtime: "08-10-12 14:33:32", 234 Mode: 0644, 235 }, 236 }, 237 }, 238 // Another zip64 file with different Extras fields. (golang.org/issue/7069) 239 { 240 Name: "zip64-2.zip", 241 File: []ZipTestFile{ 242 { 243 Name: "README", 244 Content: []byte("This small file is in ZIP64 format.\n"), 245 Mtime: "08-10-12 14:33:32", 246 Mode: 0644, 247 }, 248 }, 249 }, 250 } 251 252 var crossPlatform = []ZipTestFile{ 253 { 254 Name: "hello", 255 Content: []byte("world \r\n"), 256 Mode: 0666, 257 }, 258 { 259 Name: "dir/bar", 260 Content: []byte("foo \r\n"), 261 Mode: 0666, 262 }, 263 { 264 Name: "dir/empty/", 265 Content: []byte{}, 266 Mode: os.ModeDir | 0777, 267 }, 268 { 269 Name: "readonly", 270 Content: []byte("important \r\n"), 271 Mode: 0444, 272 }, 273 } 274 275 func TestReader(t *testing.T) { 276 for _, zt := range tests { 277 readTestZip(t, zt) 278 } 279 } 280 281 func readTestZip(t *testing.T, zt ZipTest) { 282 var z *Reader 283 var err error 284 if zt.Source != nil { 285 rat, size := zt.Source() 286 z, err = NewReader(rat, size) 287 } else { 288 var rc *ReadCloser 289 rc, err = OpenReader(filepath.Join("testdata", zt.Name)) 290 if err == nil { 291 defer rc.Close() 292 z = &rc.Reader 293 } 294 } 295 if err != zt.Error { 296 t.Errorf("%s: error=%v, want %v", zt.Name, err, zt.Error) 297 return 298 } 299 300 // bail if file is not zip 301 if err == ErrFormat { 302 return 303 } 304 305 // bail here if no Files expected to be tested 306 // (there may actually be files in the zip, but we don't care) 307 if zt.File == nil { 308 return 309 } 310 311 if z.Comment != zt.Comment { 312 t.Errorf("%s: comment=%q, want %q", zt.Name, z.Comment, zt.Comment) 313 } 314 if len(z.File) != len(zt.File) { 315 t.Fatalf("%s: file count=%d, want %d", zt.Name, len(z.File), len(zt.File)) 316 } 317 318 // test read of each file 319 for i, ft := range zt.File { 320 readTestFile(t, zt, ft, z.File[i]) 321 } 322 323 // test simultaneous reads 324 n := 0 325 done := make(chan bool) 326 for i := 0; i < 5; i++ { 327 for j, ft := range zt.File { 328 go func(j int, ft ZipTestFile) { 329 readTestFile(t, zt, ft, z.File[j]) 330 done <- true 331 }(j, ft) 332 n++ 333 } 334 } 335 for ; n > 0; n-- { 336 <-done 337 } 338 } 339 340 func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) { 341 if f.Name != ft.Name { 342 t.Errorf("%s: name=%q, want %q", zt.Name, f.Name, ft.Name) 343 } 344 345 if ft.Mtime != "" { 346 mtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime) 347 if err != nil { 348 t.Error(err) 349 return 350 } 351 if ft := f.ModTime(); !ft.Equal(mtime) { 352 t.Errorf("%s: %s: mtime=%s, want %s", zt.Name, f.Name, ft, mtime) 353 } 354 } 355 356 testFileMode(t, zt.Name, f, ft.Mode) 357 358 var b bytes.Buffer 359 r, err := f.Open() 360 if err != nil { 361 t.Errorf("%s: %v", zt.Name, err) 362 return 363 } 364 365 _, err = io.Copy(&b, r) 366 if err != ft.ContentErr { 367 t.Errorf("%s: copying contents: %v (want %v)", zt.Name, err, ft.ContentErr) 368 } 369 if err != nil { 370 return 371 } 372 r.Close() 373 374 size := uint64(f.UncompressedSize) 375 if size == uint32max { 376 size = f.UncompressedSize64 377 } 378 if g := uint64(b.Len()); g != size { 379 t.Errorf("%v: read %v bytes but f.UncompressedSize == %v", f.Name, g, size) 380 } 381 382 var c []byte 383 if ft.Content != nil { 384 c = ft.Content 385 } else if c, err = ioutil.ReadFile("testdata/" + ft.File); err != nil { 386 t.Error(err) 387 return 388 } 389 390 if b.Len() != len(c) { 391 t.Errorf("%s: len=%d, want %d", f.Name, b.Len(), len(c)) 392 return 393 } 394 395 for i, b := range b.Bytes() { 396 if b != c[i] { 397 t.Errorf("%s: content[%d]=%q want %q", f.Name, i, b, c[i]) 398 return 399 } 400 } 401 } 402 403 func testFileMode(t *testing.T, zipName string, f *File, want os.FileMode) { 404 mode := f.Mode() 405 if want == 0 { 406 t.Errorf("%s: %s mode: got %v, want none", zipName, f.Name, mode) 407 } else if mode != want { 408 t.Errorf("%s: %s mode: want %v, got %v", zipName, f.Name, want, mode) 409 } 410 } 411 412 func TestInvalidFiles(t *testing.T) { 413 const size = 1024 * 70 // 70kb 414 b := make([]byte, size) 415 416 // zeroes 417 _, err := NewReader(bytes.NewReader(b), size) 418 if err != ErrFormat { 419 t.Errorf("zeroes: error=%v, want %v", err, ErrFormat) 420 } 421 422 // repeated directoryEndSignatures 423 sig := make([]byte, 4) 424 binary.LittleEndian.PutUint32(sig, directoryEndSignature) 425 for i := 0; i < size-4; i += 4 { 426 copy(b[i:i+4], sig) 427 } 428 _, err = NewReader(bytes.NewReader(b), size) 429 if err != ErrFormat { 430 t.Errorf("sigs: error=%v, want %v", err, ErrFormat) 431 } 432 } 433 434 func messWith(fileName string, corrupter func(b []byte)) (r io.ReaderAt, size int64) { 435 data, err := ioutil.ReadFile(filepath.Join("testdata", fileName)) 436 if err != nil { 437 panic("Error reading " + fileName + ": " + err.Error()) 438 } 439 corrupter(data) 440 return bytes.NewReader(data), int64(len(data)) 441 } 442 443 func returnCorruptCRC32Zip() (r io.ReaderAt, size int64) { 444 return messWith("go-with-datadesc-sig.zip", func(b []byte) { 445 // Corrupt one of the CRC32s in the data descriptor: 446 b[0x2d]++ 447 }) 448 } 449 450 func returnCorruptNotStreamedZip() (r io.ReaderAt, size int64) { 451 return messWith("crc32-not-streamed.zip", func(b []byte) { 452 // Corrupt foo.txt's final crc32 byte, in both 453 // the file header and TOC. (0x7e -> 0x7f) 454 b[0x11]++ 455 b[0x9d]++ 456 457 // TODO(bradfitz): add a new test that only corrupts 458 // one of these values, and verify that that's also an 459 // error. Currently, the reader code doesn't verify the 460 // fileheader and TOC's crc32 match if they're both 461 // non-zero and only the second line above, the TOC, 462 // is what matters. 463 }) 464 } 465 466 // rZipBytes returns the bytes of a recursive zip file, without 467 // putting it on disk and triggering certain virus scanners. 468 func rZipBytes() []byte { 469 s := ` 470 0000000 50 4b 03 04 14 00 00 00 08 00 08 03 64 3c f9 f4 471 0000010 89 64 48 01 00 00 b8 01 00 00 07 00 00 00 72 2f 472 0000020 72 2e 7a 69 70 00 25 00 da ff 50 4b 03 04 14 00 473 0000030 00 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 474 0000040 b8 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 475 0000050 2f 00 d0 ff 00 25 00 da ff 50 4b 03 04 14 00 00 476 0000060 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 b8 477 0000070 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 2f 478 0000080 00 d0 ff c2 54 8e 57 39 00 05 00 fa ff c2 54 8e 479 0000090 57 39 00 05 00 fa ff 00 05 00 fa ff 00 14 00 eb 480 00000a0 ff c2 54 8e 57 39 00 05 00 fa ff 00 05 00 fa ff 481 00000b0 00 14 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 482 00000c0 88 21 c4 00 00 14 00 eb ff 42 88 21 c4 00 00 14 483 00000d0 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 88 21 484 00000e0 c4 00 00 00 00 ff ff 00 00 00 ff ff 00 34 00 cb 485 00000f0 ff 42 88 21 c4 00 00 00 00 ff ff 00 00 00 ff ff 486 0000100 00 34 00 cb ff 42 e8 21 5e 0f 00 00 00 ff ff 0a 487 0000110 f0 66 64 12 61 c0 15 dc e8 a0 48 bf 48 af 2a b3 488 0000120 20 c0 9b 95 0d c4 67 04 42 53 06 06 06 40 00 06 489 0000130 00 f9 ff 6d 01 00 00 00 00 42 e8 21 5e 0f 00 00 490 0000140 00 ff ff 0a f0 66 64 12 61 c0 15 dc e8 a0 48 bf 491 0000150 48 af 2a b3 20 c0 9b 95 0d c4 67 04 42 53 06 06 492 0000160 06 40 00 06 00 f9 ff 6d 01 00 00 00 00 50 4b 01 493 0000170 02 14 00 14 00 00 00 08 00 08 03 64 3c f9 f4 89 494 0000180 64 48 01 00 00 b8 01 00 00 07 00 00 00 00 00 00 495 0000190 00 00 00 00 00 00 00 00 00 00 00 72 2f 72 2e 7a 496 00001a0 69 70 50 4b 05 06 00 00 00 00 01 00 01 00 35 00 497 00001b0 00 00 6d 01 00 00 00 00` 498 s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "") 499 s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "") 500 b, err := hex.DecodeString(s) 501 if err != nil { 502 panic(err) 503 } 504 return b 505 } 506 507 func returnRecursiveZip() (r io.ReaderAt, size int64) { 508 b := rZipBytes() 509 return bytes.NewReader(b), int64(len(b)) 510 }