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  }