github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/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  }