• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package zip
16
17import (
18	"bytes"
19	"hash/crc32"
20	"io"
21	"os"
22	"reflect"
23	"syscall"
24	"testing"
25
26	"android/soong/third_party/zip"
27
28	"github.com/google/blueprint/pathtools"
29)
30
31var (
32	fileA        = []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
33	fileB        = []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB")
34	fileC        = []byte("CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC")
35	fileEmpty    = []byte("")
36	fileManifest = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\n\n")
37
38	fileCustomManifest  = []byte("Custom manifest: true\n")
39	customManifestAfter = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\nCustom manifest: true\n\n")
40)
41
42var mockFs = pathtools.MockFs(map[string][]byte{
43	"a/a/a":               fileA,
44	"a/a/b":               fileB,
45	"a/a/c -> ../../c":    nil,
46	"dangling -> missing": nil,
47	"a/a/d -> b":          nil,
48	"c":                   fileC,
49	"l_nl":                []byte("a/a/a\na/a/b\nc\n\\[\n"),
50	"l_sp":                []byte("a/a/a a/a/b c \\["),
51	"l2":                  []byte("missing\n"),
52	"rsp":                 []byte("'a/a/a'\na/a/b\n'@'\n'foo'\\''bar'\n'['"),
53	"@ -> c":              nil,
54	"foo'bar -> c":        nil,
55	"manifest.txt":        fileCustomManifest,
56	"[":                   fileEmpty,
57})
58
59func fh(name string, contents []byte, method uint16) zip.FileHeader {
60	return zip.FileHeader{
61		Name:               name,
62		Method:             method,
63		CRC32:              crc32.ChecksumIEEE(contents),
64		UncompressedSize64: uint64(len(contents)),
65		ExternalAttrs:      (syscall.S_IFREG | 0644) << 16,
66	}
67}
68
69func fhManifest(contents []byte) zip.FileHeader {
70	return zip.FileHeader{
71		Name:               "META-INF/MANIFEST.MF",
72		Method:             zip.Store,
73		CRC32:              crc32.ChecksumIEEE(contents),
74		UncompressedSize64: uint64(len(contents)),
75		ExternalAttrs:      (syscall.S_IFREG | 0644) << 16,
76	}
77}
78
79func fhLink(name string, to string) zip.FileHeader {
80	return zip.FileHeader{
81		Name:               name,
82		Method:             zip.Store,
83		CRC32:              crc32.ChecksumIEEE([]byte(to)),
84		UncompressedSize64: uint64(len(to)),
85		ExternalAttrs:      (syscall.S_IFLNK | 0777) << 16,
86	}
87}
88
89func fhDir(name string) zip.FileHeader {
90	return zip.FileHeader{
91		Name:               name,
92		Method:             zip.Store,
93		CRC32:              crc32.ChecksumIEEE(nil),
94		UncompressedSize64: 0,
95		ExternalAttrs:      (syscall.S_IFDIR|0755)<<16 | 0x10,
96	}
97}
98
99func fileArgsBuilder() *FileArgsBuilder {
100	return &FileArgsBuilder{
101		fs: mockFs,
102	}
103}
104
105func TestZip(t *testing.T) {
106	testCases := []struct {
107		name               string
108		args               *FileArgsBuilder
109		compressionLevel   int
110		emulateJar         bool
111		nonDeflatedFiles   map[string]bool
112		dirEntries         bool
113		manifest           string
114		storeSymlinks      bool
115		ignoreMissingFiles bool
116
117		files []zip.FileHeader
118		err   error
119	}{
120		{
121			name: "empty args",
122			args: fileArgsBuilder(),
123
124			files: []zip.FileHeader{},
125		},
126		{
127			name: "files",
128			args: fileArgsBuilder().
129				File("a/a/a").
130				File("a/a/b").
131				File("c").
132				File(`\[`),
133			compressionLevel: 9,
134
135			files: []zip.FileHeader{
136				fh("a/a/a", fileA, zip.Deflate),
137				fh("a/a/b", fileB, zip.Deflate),
138				fh("c", fileC, zip.Deflate),
139				fh("[", fileEmpty, zip.Store),
140			},
141		},
142		{
143			name: "files glob",
144			args: fileArgsBuilder().
145				SourcePrefixToStrip("a").
146				File("a/**/*"),
147			compressionLevel: 9,
148			storeSymlinks:    true,
149
150			files: []zip.FileHeader{
151				fh("a/a", fileA, zip.Deflate),
152				fh("a/b", fileB, zip.Deflate),
153				fhLink("a/c", "../../c"),
154				fhLink("a/d", "b"),
155			},
156		},
157		{
158			name: "dir",
159			args: fileArgsBuilder().
160				SourcePrefixToStrip("a").
161				Dir("a"),
162			compressionLevel: 9,
163			storeSymlinks:    true,
164
165			files: []zip.FileHeader{
166				fh("a/a", fileA, zip.Deflate),
167				fh("a/b", fileB, zip.Deflate),
168				fhLink("a/c", "../../c"),
169				fhLink("a/d", "b"),
170			},
171		},
172		{
173			name: "stored files",
174			args: fileArgsBuilder().
175				File("a/a/a").
176				File("a/a/b").
177				File("c"),
178			compressionLevel: 0,
179
180			files: []zip.FileHeader{
181				fh("a/a/a", fileA, zip.Store),
182				fh("a/a/b", fileB, zip.Store),
183				fh("c", fileC, zip.Store),
184			},
185		},
186		{
187			name: "symlinks in zip",
188			args: fileArgsBuilder().
189				File("a/a/a").
190				File("a/a/b").
191				File("a/a/c").
192				File("a/a/d"),
193			compressionLevel: 9,
194			storeSymlinks:    true,
195
196			files: []zip.FileHeader{
197				fh("a/a/a", fileA, zip.Deflate),
198				fh("a/a/b", fileB, zip.Deflate),
199				fhLink("a/a/c", "../../c"),
200				fhLink("a/a/d", "b"),
201			},
202		},
203		{
204			name: "follow symlinks",
205			args: fileArgsBuilder().
206				File("a/a/a").
207				File("a/a/b").
208				File("a/a/c").
209				File("a/a/d"),
210			compressionLevel: 9,
211			storeSymlinks:    false,
212
213			files: []zip.FileHeader{
214				fh("a/a/a", fileA, zip.Deflate),
215				fh("a/a/b", fileB, zip.Deflate),
216				fh("a/a/c", fileC, zip.Deflate),
217				fh("a/a/d", fileB, zip.Deflate),
218			},
219		},
220		{
221			name: "dangling symlinks",
222			args: fileArgsBuilder().
223				File("dangling"),
224			compressionLevel: 9,
225			storeSymlinks:    true,
226
227			files: []zip.FileHeader{
228				fhLink("dangling", "missing"),
229			},
230		},
231		{
232			name: "list",
233			args: fileArgsBuilder().
234				List("l_nl"),
235			compressionLevel: 9,
236
237			files: []zip.FileHeader{
238				fh("a/a/a", fileA, zip.Deflate),
239				fh("a/a/b", fileB, zip.Deflate),
240				fh("c", fileC, zip.Deflate),
241				fh("[", fileEmpty, zip.Store),
242			},
243		},
244		{
245			name: "list",
246			args: fileArgsBuilder().
247				List("l_sp"),
248			compressionLevel: 9,
249
250			files: []zip.FileHeader{
251				fh("a/a/a", fileA, zip.Deflate),
252				fh("a/a/b", fileB, zip.Deflate),
253				fh("c", fileC, zip.Deflate),
254				fh("[", fileEmpty, zip.Store),
255			},
256		},
257		{
258			name: "rsp",
259			args: fileArgsBuilder().
260				RspFile("rsp"),
261			compressionLevel: 9,
262
263			files: []zip.FileHeader{
264				fh("a/a/a", fileA, zip.Deflate),
265				fh("a/a/b", fileB, zip.Deflate),
266				fh("@", fileC, zip.Deflate),
267				fh("foo'bar", fileC, zip.Deflate),
268				fh("[", fileEmpty, zip.Store),
269			},
270		},
271		{
272			name: "prefix in zip",
273			args: fileArgsBuilder().
274				PathPrefixInZip("foo").
275				File("a/a/a").
276				File("a/a/b").
277				File("c"),
278			compressionLevel: 9,
279
280			files: []zip.FileHeader{
281				fh("foo/a/a/a", fileA, zip.Deflate),
282				fh("foo/a/a/b", fileB, zip.Deflate),
283				fh("foo/c", fileC, zip.Deflate),
284			},
285		},
286		{
287			name: "relative root",
288			args: fileArgsBuilder().
289				SourcePrefixToStrip("a").
290				File("a/a/a").
291				File("a/a/b"),
292			compressionLevel: 9,
293
294			files: []zip.FileHeader{
295				fh("a/a", fileA, zip.Deflate),
296				fh("a/b", fileB, zip.Deflate),
297			},
298		},
299		{
300			name: "multiple relative root",
301			args: fileArgsBuilder().
302				SourcePrefixToStrip("a").
303				File("a/a/a").
304				SourcePrefixToStrip("a/a").
305				File("a/a/b"),
306			compressionLevel: 9,
307
308			files: []zip.FileHeader{
309				fh("a/a", fileA, zip.Deflate),
310				fh("b", fileB, zip.Deflate),
311			},
312		},
313		{
314			name: "emulate jar",
315			args: fileArgsBuilder().
316				File("a/a/a").
317				File("a/a/b"),
318			compressionLevel: 9,
319			emulateJar:       true,
320
321			files: []zip.FileHeader{
322				fhDir("META-INF/"),
323				fhManifest(fileManifest),
324				fhDir("a/"),
325				fhDir("a/a/"),
326				fh("a/a/a", fileA, zip.Deflate),
327				fh("a/a/b", fileB, zip.Deflate),
328			},
329		},
330		{
331			name: "emulate jar with manifest",
332			args: fileArgsBuilder().
333				File("a/a/a").
334				File("a/a/b"),
335			compressionLevel: 9,
336			emulateJar:       true,
337			manifest:         "manifest.txt",
338
339			files: []zip.FileHeader{
340				fhDir("META-INF/"),
341				fhManifest(customManifestAfter),
342				fhDir("a/"),
343				fhDir("a/a/"),
344				fh("a/a/a", fileA, zip.Deflate),
345				fh("a/a/b", fileB, zip.Deflate),
346			},
347		},
348		{
349			name: "dir entries",
350			args: fileArgsBuilder().
351				File("a/a/a").
352				File("a/a/b"),
353			compressionLevel: 9,
354			dirEntries:       true,
355
356			files: []zip.FileHeader{
357				fhDir("a/"),
358				fhDir("a/a/"),
359				fh("a/a/a", fileA, zip.Deflate),
360				fh("a/a/b", fileB, zip.Deflate),
361			},
362		},
363		{
364			name: "junk paths",
365			args: fileArgsBuilder().
366				JunkPaths(true).
367				File("a/a/a").
368				File("a/a/b"),
369			compressionLevel: 9,
370
371			files: []zip.FileHeader{
372				fh("a", fileA, zip.Deflate),
373				fh("b", fileB, zip.Deflate),
374			},
375		},
376		{
377			name: "non deflated files",
378			args: fileArgsBuilder().
379				File("a/a/a").
380				File("a/a/b"),
381			compressionLevel: 9,
382			nonDeflatedFiles: map[string]bool{"a/a/a": true},
383
384			files: []zip.FileHeader{
385				fh("a/a/a", fileA, zip.Store),
386				fh("a/a/b", fileB, zip.Deflate),
387			},
388		},
389		{
390			name: "ignore missing files",
391			args: fileArgsBuilder().
392				File("a/a/a").
393				File("a/a/b").
394				File("missing"),
395			compressionLevel:   9,
396			ignoreMissingFiles: true,
397
398			files: []zip.FileHeader{
399				fh("a/a/a", fileA, zip.Deflate),
400				fh("a/a/b", fileB, zip.Deflate),
401			},
402		},
403
404		// errors
405		{
406			name: "error missing file",
407			args: fileArgsBuilder().
408				File("missing"),
409			err: os.ErrNotExist,
410		},
411		{
412			name: "error missing dir",
413			args: fileArgsBuilder().
414				Dir("missing"),
415			err: os.ErrNotExist,
416		},
417		{
418			name: "error missing file in list",
419			args: fileArgsBuilder().
420				List("l2"),
421			err: os.ErrNotExist,
422		},
423		{
424			name: "error incorrect relative root",
425			args: fileArgsBuilder().
426				SourcePrefixToStrip("b").
427				File("a/a/a"),
428			err: IncorrectRelativeRootError{},
429		},
430	}
431
432	for _, test := range testCases {
433		t.Run(test.name, func(t *testing.T) {
434			if test.args.Error() != nil {
435				t.Fatal(test.args.Error())
436			}
437
438			args := ZipArgs{}
439			args.FileArgs = test.args.FileArgs()
440			args.CompressionLevel = test.compressionLevel
441			args.EmulateJar = test.emulateJar
442			args.AddDirectoryEntriesToZip = test.dirEntries
443			args.NonDeflatedFiles = test.nonDeflatedFiles
444			args.ManifestSourcePath = test.manifest
445			args.StoreSymlinks = test.storeSymlinks
446			args.IgnoreMissingFiles = test.ignoreMissingFiles
447			args.Filesystem = mockFs
448			args.Stderr = &bytes.Buffer{}
449
450			buf := &bytes.Buffer{}
451			err := zipTo(args, buf)
452
453			if (err != nil) != (test.err != nil) {
454				t.Fatalf("want error %v, got %v", test.err, err)
455			} else if test.err != nil {
456				if os.IsNotExist(test.err) {
457					if !os.IsNotExist(test.err) {
458						t.Fatalf("want error %v, got %v", test.err, err)
459					}
460				} else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
461					if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
462						t.Fatalf("want error %v, got %v", test.err, err)
463					}
464				} else {
465					t.Fatalf("want error %v, got %v", test.err, err)
466				}
467				return
468			}
469
470			br := bytes.NewReader(buf.Bytes())
471			zr, err := zip.NewReader(br, int64(br.Len()))
472			if err != nil {
473				t.Fatal(err)
474			}
475
476			var files []zip.FileHeader
477			for _, f := range zr.File {
478				r, err := f.Open()
479				if err != nil {
480					t.Fatalf("error when opening %s: %s", f.Name, err)
481				}
482
483				crc := crc32.NewIEEE()
484				len, err := io.Copy(crc, r)
485				r.Close()
486				if err != nil {
487					t.Fatalf("error when reading %s: %s", f.Name, err)
488				}
489
490				if uint64(len) != f.UncompressedSize64 {
491					t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
492				}
493
494				if crc.Sum32() != f.CRC32 {
495					t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
496				}
497
498				files = append(files, f.FileHeader)
499			}
500
501			if len(files) != len(test.files) {
502				t.Fatalf("want %d files, got %d", len(test.files), len(files))
503			}
504
505			for i := range files {
506				want := test.files[i]
507				got := files[i]
508
509				if want.Name != got.Name {
510					t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
511					continue
512				}
513
514				if want.UncompressedSize64 != got.UncompressedSize64 {
515					t.Errorf("incorrect file %s length want %v got %v", want.Name,
516						want.UncompressedSize64, got.UncompressedSize64)
517				}
518
519				if want.ExternalAttrs != got.ExternalAttrs {
520					t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
521						want.ExternalAttrs, got.ExternalAttrs)
522				}
523
524				if want.CRC32 != got.CRC32 {
525					t.Errorf("incorrect file %s crc want %v got %v", want.Name,
526						want.CRC32, got.CRC32)
527				}
528
529				if want.Method != got.Method {
530					t.Errorf("incorrect file %s method want %v got %v", want.Name,
531						want.Method, got.Method)
532				}
533			}
534		})
535	}
536}
537
538func TestSrcJar(t *testing.T) {
539	mockFs := pathtools.MockFs(map[string][]byte{
540		"wrong_package.java":       []byte("package foo;"),
541		"foo/correct_package.java": []byte("package foo;"),
542		"src/no_package.java":      nil,
543		"src2/parse_error.java":    []byte("error"),
544	})
545
546	want := []string{
547		"foo/",
548		"foo/wrong_package.java",
549		"foo/correct_package.java",
550		"no_package.java",
551		"src2/",
552		"src2/parse_error.java",
553	}
554
555	args := ZipArgs{}
556	args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs()
557
558	args.SrcJar = true
559	args.AddDirectoryEntriesToZip = true
560	args.Filesystem = mockFs
561	args.Stderr = &bytes.Buffer{}
562
563	buf := &bytes.Buffer{}
564	err := zipTo(args, buf)
565	if err != nil {
566		t.Fatalf("got error %v", err)
567	}
568
569	br := bytes.NewReader(buf.Bytes())
570	zr, err := zip.NewReader(br, int64(br.Len()))
571	if err != nil {
572		t.Fatal(err)
573	}
574
575	var got []string
576	for _, f := range zr.File {
577		r, err := f.Open()
578		if err != nil {
579			t.Fatalf("error when opening %s: %s", f.Name, err)
580		}
581
582		crc := crc32.NewIEEE()
583		len, err := io.Copy(crc, r)
584		r.Close()
585		if err != nil {
586			t.Fatalf("error when reading %s: %s", f.Name, err)
587		}
588
589		if uint64(len) != f.UncompressedSize64 {
590			t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
591		}
592
593		if crc.Sum32() != f.CRC32 {
594			t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
595		}
596
597		got = append(got, f.Name)
598	}
599
600	if !reflect.DeepEqual(want, got) {
601		t.Errorf("want files %q, got %q", want, got)
602	}
603}
604