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