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  }