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  }