github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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 } 239 240 var crossPlatform = []ZipTestFile{ 241 { 242 Name: "hello", 243 Content: []byte("world \r\n"), 244 Mode: 0666, 245 }, 246 { 247 Name: "dir/bar", 248 Content: []byte("foo \r\n"), 249 Mode: 0666, 250 }, 251 { 252 Name: "dir/empty/", 253 Content: []byte{}, 254 Mode: os.ModeDir | 0777, 255 }, 256 { 257 Name: "readonly", 258 Content: []byte("important \r\n"), 259 Mode: 0444, 260 }, 261 } 262 263 func TestReader(t *testing.T) { 264 for _, zt := range tests { 265 readTestZip(t, zt) 266 } 267 } 268 269 func readTestZip(t *testing.T, zt ZipTest) { 270 var z *Reader 271 var err error 272 if zt.Source != nil { 273 rat, size := zt.Source() 274 z, err = NewReader(rat, size) 275 } else { 276 var rc *ReadCloser 277 rc, err = OpenReader(filepath.Join("testdata", zt.Name)) 278 if err == nil { 279 z = &rc.Reader 280 } 281 } 282 if err != zt.Error { 283 t.Errorf("%s: error=%v, want %v", zt.Name, err, zt.Error) 284 return 285 } 286 287 // bail if file is not zip 288 if err == ErrFormat { 289 return 290 } 291 292 // bail here if no Files expected to be tested 293 // (there may actually be files in the zip, but we don't care) 294 if zt.File == nil { 295 return 296 } 297 298 if z.Comment != zt.Comment { 299 t.Errorf("%s: comment=%q, want %q", zt.Name, z.Comment, zt.Comment) 300 } 301 if len(z.File) != len(zt.File) { 302 t.Fatalf("%s: file count=%d, want %d", zt.Name, len(z.File), len(zt.File)) 303 } 304 305 // test read of each file 306 for i, ft := range zt.File { 307 readTestFile(t, zt, ft, z.File[i]) 308 } 309 310 // test simultaneous reads 311 n := 0 312 done := make(chan bool) 313 for i := 0; i < 5; i++ { 314 for j, ft := range zt.File { 315 go func(j int, ft ZipTestFile) { 316 readTestFile(t, zt, ft, z.File[j]) 317 done <- true 318 }(j, ft) 319 n++ 320 } 321 } 322 for ; n > 0; n-- { 323 <-done 324 } 325 } 326 327 func readTestFile(t *testing.T, zt ZipTest, ft ZipTestFile, f *File) { 328 if f.Name != ft.Name { 329 t.Errorf("%s: name=%q, want %q", zt.Name, f.Name, ft.Name) 330 } 331 332 if ft.Mtime != "" { 333 mtime, err := time.Parse("01-02-06 15:04:05", ft.Mtime) 334 if err != nil { 335 t.Error(err) 336 return 337 } 338 if ft := f.ModTime(); !ft.Equal(mtime) { 339 t.Errorf("%s: %s: mtime=%s, want %s", zt.Name, f.Name, ft, mtime) 340 } 341 } 342 343 testFileMode(t, zt.Name, f, ft.Mode) 344 345 size0 := f.UncompressedSize 346 347 var b bytes.Buffer 348 r, err := f.Open() 349 if err != nil { 350 t.Error(err) 351 return 352 } 353 354 if size1 := f.UncompressedSize; size0 != size1 { 355 t.Errorf("file %q changed f.UncompressedSize from %d to %d", f.Name, size0, size1) 356 } 357 358 _, err = io.Copy(&b, r) 359 if err != ft.ContentErr { 360 t.Errorf("%s: copying contents: %v (want %v)", zt.Name, err, ft.ContentErr) 361 } 362 if err != nil { 363 return 364 } 365 r.Close() 366 367 var c []byte 368 if ft.Content != nil { 369 c = ft.Content 370 } else if c, err = ioutil.ReadFile("testdata/" + ft.File); err != nil { 371 t.Error(err) 372 return 373 } 374 375 if b.Len() != len(c) { 376 t.Errorf("%s: len=%d, want %d", f.Name, b.Len(), len(c)) 377 return 378 } 379 380 for i, b := range b.Bytes() { 381 if b != c[i] { 382 t.Errorf("%s: content[%d]=%q want %q", f.Name, i, b, c[i]) 383 return 384 } 385 } 386 } 387 388 func testFileMode(t *testing.T, zipName string, f *File, want os.FileMode) { 389 mode := f.Mode() 390 if want == 0 { 391 t.Errorf("%s: %s mode: got %v, want none", zipName, f.Name, mode) 392 } else if mode != want { 393 t.Errorf("%s: %s mode: want %v, got %v", zipName, f.Name, want, mode) 394 } 395 } 396 397 func TestInvalidFiles(t *testing.T) { 398 const size = 1024 * 70 // 70kb 399 b := make([]byte, size) 400 401 // zeroes 402 _, err := NewReader(bytes.NewReader(b), size) 403 if err != ErrFormat { 404 t.Errorf("zeroes: error=%v, want %v", err, ErrFormat) 405 } 406 407 // repeated directoryEndSignatures 408 sig := make([]byte, 4) 409 binary.LittleEndian.PutUint32(sig, directoryEndSignature) 410 for i := 0; i < size-4; i += 4 { 411 copy(b[i:i+4], sig) 412 } 413 _, err = NewReader(bytes.NewReader(b), size) 414 if err != ErrFormat { 415 t.Errorf("sigs: error=%v, want %v", err, ErrFormat) 416 } 417 } 418 419 func messWith(fileName string, corrupter func(b []byte)) (r io.ReaderAt, size int64) { 420 data, err := ioutil.ReadFile(filepath.Join("testdata", fileName)) 421 if err != nil { 422 panic("Error reading " + fileName + ": " + err.Error()) 423 } 424 corrupter(data) 425 return bytes.NewReader(data), int64(len(data)) 426 } 427 428 func returnCorruptCRC32Zip() (r io.ReaderAt, size int64) { 429 return messWith("go-with-datadesc-sig.zip", func(b []byte) { 430 // Corrupt one of the CRC32s in the data descriptor: 431 b[0x2d]++ 432 }) 433 } 434 435 func returnCorruptNotStreamedZip() (r io.ReaderAt, size int64) { 436 return messWith("crc32-not-streamed.zip", func(b []byte) { 437 // Corrupt foo.txt's final crc32 byte, in both 438 // the file header and TOC. (0x7e -> 0x7f) 439 b[0x11]++ 440 b[0x9d]++ 441 442 // TODO(bradfitz): add a new test that only corrupts 443 // one of these values, and verify that that's also an 444 // error. Currently, the reader code doesn't verify the 445 // fileheader and TOC's crc32 match if they're both 446 // non-zero and only the second line above, the TOC, 447 // is what matters. 448 }) 449 } 450 451 // rZipBytes returns the bytes of a recursive zip file, without 452 // putting it on disk and triggering certain virus scanners. 453 func rZipBytes() []byte { 454 s := ` 455 0000000 50 4b 03 04 14 00 00 00 08 00 08 03 64 3c f9 f4 456 0000010 89 64 48 01 00 00 b8 01 00 00 07 00 00 00 72 2f 457 0000020 72 2e 7a 69 70 00 25 00 da ff 50 4b 03 04 14 00 458 0000030 00 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 459 0000040 b8 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 460 0000050 2f 00 d0 ff 00 25 00 da ff 50 4b 03 04 14 00 00 461 0000060 00 08 00 08 03 64 3c f9 f4 89 64 48 01 00 00 b8 462 0000070 01 00 00 07 00 00 00 72 2f 72 2e 7a 69 70 00 2f 463 0000080 00 d0 ff c2 54 8e 57 39 00 05 00 fa ff c2 54 8e 464 0000090 57 39 00 05 00 fa ff 00 05 00 fa ff 00 14 00 eb 465 00000a0 ff c2 54 8e 57 39 00 05 00 fa ff 00 05 00 fa ff 466 00000b0 00 14 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 467 00000c0 88 21 c4 00 00 14 00 eb ff 42 88 21 c4 00 00 14 468 00000d0 00 eb ff 42 88 21 c4 00 00 14 00 eb ff 42 88 21 469 00000e0 c4 00 00 00 00 ff ff 00 00 00 ff ff 00 34 00 cb 470 00000f0 ff 42 88 21 c4 00 00 00 00 ff ff 00 00 00 ff ff 471 0000100 00 34 00 cb ff 42 e8 21 5e 0f 00 00 00 ff ff 0a 472 0000110 f0 66 64 12 61 c0 15 dc e8 a0 48 bf 48 af 2a b3 473 0000120 20 c0 9b 95 0d c4 67 04 42 53 06 06 06 40 00 06 474 0000130 00 f9 ff 6d 01 00 00 00 00 42 e8 21 5e 0f 00 00 475 0000140 00 ff ff 0a f0 66 64 12 61 c0 15 dc e8 a0 48 bf 476 0000150 48 af 2a b3 20 c0 9b 95 0d c4 67 04 42 53 06 06 477 0000160 06 40 00 06 00 f9 ff 6d 01 00 00 00 00 50 4b 01 478 0000170 02 14 00 14 00 00 00 08 00 08 03 64 3c f9 f4 89 479 0000180 64 48 01 00 00 b8 01 00 00 07 00 00 00 00 00 00 480 0000190 00 00 00 00 00 00 00 00 00 00 00 72 2f 72 2e 7a 481 00001a0 69 70 50 4b 05 06 00 00 00 00 01 00 01 00 35 00 482 00001b0 00 00 6d 01 00 00 00 00` 483 s = regexp.MustCompile(`[0-9a-f]{7}`).ReplaceAllString(s, "") 484 s = regexp.MustCompile(`\s+`).ReplaceAllString(s, "") 485 b, err := hex.DecodeString(s) 486 if err != nil { 487 panic(err) 488 } 489 return b 490 } 491 492 func returnRecursiveZip() (r io.ReaderAt, size int64) { 493 b := rZipBytes() 494 return bytes.NewReader(b), int64(len(b)) 495 }