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