• 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"),
50	"l_sp":                []byte("a/a/a a/a/b c"),
51	"l2":                  []byte("missing\n"),
52	"manifest.txt":        fileCustomManifest,
53})
54
55func fh(name string, contents []byte, method uint16) zip.FileHeader {
56	return zip.FileHeader{
57		Name:               name,
58		Method:             method,
59		CRC32:              crc32.ChecksumIEEE(contents),
60		UncompressedSize64: uint64(len(contents)),
61		ExternalAttrs:      0,
62	}
63}
64
65func fhManifest(contents []byte) zip.FileHeader {
66	return zip.FileHeader{
67		Name:               "META-INF/MANIFEST.MF",
68		Method:             zip.Store,
69		CRC32:              crc32.ChecksumIEEE(contents),
70		UncompressedSize64: uint64(len(contents)),
71		ExternalAttrs:      (syscall.S_IFREG | 0700) << 16,
72	}
73}
74
75func fhLink(name string, to string) zip.FileHeader {
76	return zip.FileHeader{
77		Name:               name,
78		Method:             zip.Store,
79		CRC32:              crc32.ChecksumIEEE([]byte(to)),
80		UncompressedSize64: uint64(len(to)),
81		ExternalAttrs:      (syscall.S_IFLNK | 0777) << 16,
82	}
83}
84
85func fhDir(name string) zip.FileHeader {
86	return zip.FileHeader{
87		Name:               name,
88		Method:             zip.Store,
89		CRC32:              crc32.ChecksumIEEE(nil),
90		UncompressedSize64: 0,
91		ExternalAttrs:      (syscall.S_IFDIR|0700)<<16 | 0x10,
92	}
93}
94
95func fileArgsBuilder() *FileArgsBuilder {
96	return &FileArgsBuilder{
97		fs: mockFs,
98	}
99}
100
101func TestZip(t *testing.T) {
102	testCases := []struct {
103		name               string
104		args               *FileArgsBuilder
105		compressionLevel   int
106		emulateJar         bool
107		nonDeflatedFiles   map[string]bool
108		dirEntries         bool
109		manifest           string
110		storeSymlinks      bool
111		ignoreMissingFiles bool
112
113		files []zip.FileHeader
114		err   error
115	}{
116		{
117			name: "empty args",
118			args: fileArgsBuilder(),
119
120			files: []zip.FileHeader{},
121		},
122		{
123			name: "files",
124			args: fileArgsBuilder().
125				File("a/a/a").
126				File("a/a/b").
127				File("c"),
128			compressionLevel: 9,
129
130			files: []zip.FileHeader{
131				fh("a/a/a", fileA, zip.Deflate),
132				fh("a/a/b", fileB, zip.Deflate),
133				fh("c", fileC, zip.Deflate),
134			},
135		},
136		{
137			name: "files glob",
138			args: fileArgsBuilder().
139				SourcePrefixToStrip("a").
140				File("a/**/*"),
141			compressionLevel: 9,
142			storeSymlinks:    true,
143
144			files: []zip.FileHeader{
145				fh("a/a", fileA, zip.Deflate),
146				fh("a/b", fileB, zip.Deflate),
147				fhLink("a/c", "../../c"),
148				fhLink("a/d", "b"),
149			},
150		},
151		{
152			name: "dir",
153			args: fileArgsBuilder().
154				SourcePrefixToStrip("a").
155				Dir("a"),
156			compressionLevel: 9,
157			storeSymlinks:    true,
158
159			files: []zip.FileHeader{
160				fh("a/a", fileA, zip.Deflate),
161				fh("a/b", fileB, zip.Deflate),
162				fhLink("a/c", "../../c"),
163				fhLink("a/d", "b"),
164			},
165		},
166		{
167			name: "stored files",
168			args: fileArgsBuilder().
169				File("a/a/a").
170				File("a/a/b").
171				File("c"),
172			compressionLevel: 0,
173
174			files: []zip.FileHeader{
175				fh("a/a/a", fileA, zip.Store),
176				fh("a/a/b", fileB, zip.Store),
177				fh("c", fileC, zip.Store),
178			},
179		},
180		{
181			name: "symlinks in zip",
182			args: fileArgsBuilder().
183				File("a/a/a").
184				File("a/a/b").
185				File("a/a/c").
186				File("a/a/d"),
187			compressionLevel: 9,
188			storeSymlinks:    true,
189
190			files: []zip.FileHeader{
191				fh("a/a/a", fileA, zip.Deflate),
192				fh("a/a/b", fileB, zip.Deflate),
193				fhLink("a/a/c", "../../c"),
194				fhLink("a/a/d", "b"),
195			},
196		},
197		{
198			name: "follow symlinks",
199			args: fileArgsBuilder().
200				File("a/a/a").
201				File("a/a/b").
202				File("a/a/c").
203				File("a/a/d"),
204			compressionLevel: 9,
205			storeSymlinks:    false,
206
207			files: []zip.FileHeader{
208				fh("a/a/a", fileA, zip.Deflate),
209				fh("a/a/b", fileB, zip.Deflate),
210				fh("a/a/c", fileC, zip.Deflate),
211				fh("a/a/d", fileB, zip.Deflate),
212			},
213		},
214		{
215			name: "dangling symlinks",
216			args: fileArgsBuilder().
217				File("dangling"),
218			compressionLevel: 9,
219			storeSymlinks:    true,
220
221			files: []zip.FileHeader{
222				fhLink("dangling", "missing"),
223			},
224		},
225		{
226			name: "list",
227			args: fileArgsBuilder().
228				List("l_nl"),
229			compressionLevel: 9,
230
231			files: []zip.FileHeader{
232				fh("a/a/a", fileA, zip.Deflate),
233				fh("a/a/b", fileB, zip.Deflate),
234				fh("c", fileC, zip.Deflate),
235			},
236		},
237		{
238			name: "list",
239			args: fileArgsBuilder().
240				List("l_sp"),
241			compressionLevel: 9,
242
243			files: []zip.FileHeader{
244				fh("a/a/a", fileA, zip.Deflate),
245				fh("a/a/b", fileB, zip.Deflate),
246				fh("c", fileC, zip.Deflate),
247			},
248		},
249		{
250			name: "prefix in zip",
251			args: fileArgsBuilder().
252				PathPrefixInZip("foo").
253				File("a/a/a").
254				File("a/a/b").
255				File("c"),
256			compressionLevel: 9,
257
258			files: []zip.FileHeader{
259				fh("foo/a/a/a", fileA, zip.Deflate),
260				fh("foo/a/a/b", fileB, zip.Deflate),
261				fh("foo/c", fileC, zip.Deflate),
262			},
263		},
264		{
265			name: "relative root",
266			args: fileArgsBuilder().
267				SourcePrefixToStrip("a").
268				File("a/a/a").
269				File("a/a/b"),
270			compressionLevel: 9,
271
272			files: []zip.FileHeader{
273				fh("a/a", fileA, zip.Deflate),
274				fh("a/b", fileB, zip.Deflate),
275			},
276		},
277		{
278			name: "multiple relative root",
279			args: fileArgsBuilder().
280				SourcePrefixToStrip("a").
281				File("a/a/a").
282				SourcePrefixToStrip("a/a").
283				File("a/a/b"),
284			compressionLevel: 9,
285
286			files: []zip.FileHeader{
287				fh("a/a", fileA, zip.Deflate),
288				fh("b", fileB, zip.Deflate),
289			},
290		},
291		{
292			name: "emulate jar",
293			args: fileArgsBuilder().
294				File("a/a/a").
295				File("a/a/b"),
296			compressionLevel: 9,
297			emulateJar:       true,
298
299			files: []zip.FileHeader{
300				fhDir("META-INF/"),
301				fhManifest(fileManifest),
302				fhDir("a/"),
303				fhDir("a/a/"),
304				fh("a/a/a", fileA, zip.Deflate),
305				fh("a/a/b", fileB, zip.Deflate),
306			},
307		},
308		{
309			name: "emulate jar with manifest",
310			args: fileArgsBuilder().
311				File("a/a/a").
312				File("a/a/b"),
313			compressionLevel: 9,
314			emulateJar:       true,
315			manifest:         "manifest.txt",
316
317			files: []zip.FileHeader{
318				fhDir("META-INF/"),
319				fhManifest(customManifestAfter),
320				fhDir("a/"),
321				fhDir("a/a/"),
322				fh("a/a/a", fileA, zip.Deflate),
323				fh("a/a/b", fileB, zip.Deflate),
324			},
325		},
326		{
327			name: "dir entries",
328			args: fileArgsBuilder().
329				File("a/a/a").
330				File("a/a/b"),
331			compressionLevel: 9,
332			dirEntries:       true,
333
334			files: []zip.FileHeader{
335				fhDir("a/"),
336				fhDir("a/a/"),
337				fh("a/a/a", fileA, zip.Deflate),
338				fh("a/a/b", fileB, zip.Deflate),
339			},
340		},
341		{
342			name: "junk paths",
343			args: fileArgsBuilder().
344				JunkPaths(true).
345				File("a/a/a").
346				File("a/a/b"),
347			compressionLevel: 9,
348
349			files: []zip.FileHeader{
350				fh("a", fileA, zip.Deflate),
351				fh("b", fileB, zip.Deflate),
352			},
353		},
354		{
355			name: "non deflated files",
356			args: fileArgsBuilder().
357				File("a/a/a").
358				File("a/a/b"),
359			compressionLevel: 9,
360			nonDeflatedFiles: map[string]bool{"a/a/a": true},
361
362			files: []zip.FileHeader{
363				fh("a/a/a", fileA, zip.Store),
364				fh("a/a/b", fileB, zip.Deflate),
365			},
366		},
367		{
368			name: "ignore missing files",
369			args: fileArgsBuilder().
370				File("a/a/a").
371				File("a/a/b").
372				File("missing"),
373			compressionLevel:   9,
374			ignoreMissingFiles: true,
375
376			files: []zip.FileHeader{
377				fh("a/a/a", fileA, zip.Deflate),
378				fh("a/a/b", fileB, zip.Deflate),
379			},
380		},
381
382		// errors
383		{
384			name: "error missing file",
385			args: fileArgsBuilder().
386				File("missing"),
387			err: os.ErrNotExist,
388		},
389		{
390			name: "error missing dir",
391			args: fileArgsBuilder().
392				Dir("missing"),
393			err: os.ErrNotExist,
394		},
395		{
396			name: "error missing file in list",
397			args: fileArgsBuilder().
398				List("l2"),
399			err: os.ErrNotExist,
400		},
401		{
402			name: "error incorrect relative root",
403			args: fileArgsBuilder().
404				SourcePrefixToStrip("b").
405				File("a/a/a"),
406			err: IncorrectRelativeRootError{},
407		},
408	}
409
410	for _, test := range testCases {
411		t.Run(test.name, func(t *testing.T) {
412			if test.args.Error() != nil {
413				t.Fatal(test.args.Error())
414			}
415
416			args := ZipArgs{}
417			args.FileArgs = test.args.FileArgs()
418			args.CompressionLevel = test.compressionLevel
419			args.EmulateJar = test.emulateJar
420			args.AddDirectoryEntriesToZip = test.dirEntries
421			args.NonDeflatedFiles = test.nonDeflatedFiles
422			args.ManifestSourcePath = test.manifest
423			args.StoreSymlinks = test.storeSymlinks
424			args.IgnoreMissingFiles = test.ignoreMissingFiles
425			args.Filesystem = mockFs
426			args.Stderr = &bytes.Buffer{}
427
428			buf := &bytes.Buffer{}
429			err := ZipTo(args, buf)
430
431			if (err != nil) != (test.err != nil) {
432				t.Fatalf("want error %v, got %v", test.err, err)
433			} else if test.err != nil {
434				if os.IsNotExist(test.err) {
435					if !os.IsNotExist(test.err) {
436						t.Fatalf("want error %v, got %v", test.err, err)
437					}
438				} else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
439					if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
440						t.Fatalf("want error %v, got %v", test.err, err)
441					}
442				} else {
443					t.Fatalf("want error %v, got %v", test.err, err)
444				}
445				return
446			}
447
448			br := bytes.NewReader(buf.Bytes())
449			zr, err := zip.NewReader(br, int64(br.Len()))
450			if err != nil {
451				t.Fatal(err)
452			}
453
454			var files []zip.FileHeader
455			for _, f := range zr.File {
456				r, err := f.Open()
457				if err != nil {
458					t.Fatalf("error when opening %s: %s", f.Name, err)
459				}
460
461				crc := crc32.NewIEEE()
462				len, err := io.Copy(crc, r)
463				r.Close()
464				if err != nil {
465					t.Fatalf("error when reading %s: %s", f.Name, err)
466				}
467
468				if uint64(len) != f.UncompressedSize64 {
469					t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
470				}
471
472				if crc.Sum32() != f.CRC32 {
473					t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
474				}
475
476				files = append(files, f.FileHeader)
477			}
478
479			if len(files) != len(test.files) {
480				t.Fatalf("want %d files, got %d", len(test.files), len(files))
481			}
482
483			for i := range files {
484				want := test.files[i]
485				got := files[i]
486
487				if want.Name != got.Name {
488					t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
489					continue
490				}
491
492				if want.UncompressedSize64 != got.UncompressedSize64 {
493					t.Errorf("incorrect file %s length want %v got %v", want.Name,
494						want.UncompressedSize64, got.UncompressedSize64)
495				}
496
497				if want.ExternalAttrs != got.ExternalAttrs {
498					t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
499						want.ExternalAttrs, got.ExternalAttrs)
500				}
501
502				if want.CRC32 != got.CRC32 {
503					t.Errorf("incorrect file %s crc want %v got %v", want.Name,
504						want.CRC32, got.CRC32)
505				}
506
507				if want.Method != got.Method {
508					t.Errorf("incorrect file %s method want %v got %v", want.Name,
509						want.Method, got.Method)
510				}
511			}
512		})
513	}
514}
515
516func TestReadRespFile(t *testing.T) {
517	testCases := []struct {
518		name, in string
519		out      []string
520	}{
521		{
522			name: "single quoting test case 1",
523			in:   `./cmd '"'-C`,
524			out:  []string{"./cmd", `"-C`},
525		},
526		{
527			name: "single quoting test case 2",
528			in:   `./cmd '-C`,
529			out:  []string{"./cmd", `-C`},
530		},
531		{
532			name: "single quoting test case 3",
533			in:   `./cmd '\"'-C`,
534			out:  []string{"./cmd", `\"-C`},
535		},
536		{
537			name: "single quoting test case 4",
538			in:   `./cmd '\\'-C`,
539			out:  []string{"./cmd", `\\-C`},
540		},
541		{
542			name: "none quoting test case 1",
543			in:   `./cmd \'-C`,
544			out:  []string{"./cmd", `'-C`},
545		},
546		{
547			name: "none quoting test case 2",
548			in:   `./cmd \\-C`,
549			out:  []string{"./cmd", `\-C`},
550		},
551		{
552			name: "none quoting test case 3",
553			in:   `./cmd \"-C`,
554			out:  []string{"./cmd", `"-C`},
555		},
556		{
557			name: "double quoting test case 1",
558			in:   `./cmd "'"-C`,
559			out:  []string{"./cmd", `'-C`},
560		},
561		{
562			name: "double quoting test case 2",
563			in:   `./cmd "\\"-C`,
564			out:  []string{"./cmd", `\-C`},
565		},
566		{
567			name: "double quoting test case 3",
568			in:   `./cmd "\""-C`,
569			out:  []string{"./cmd", `"-C`},
570		},
571	}
572
573	for _, testCase := range testCases {
574		t.Run(testCase.name, func(t *testing.T) {
575			got := ReadRespFile([]byte(testCase.in))
576			if !reflect.DeepEqual(got, testCase.out) {
577				t.Errorf("expected %q got %q", testCase.out, got)
578			}
579		})
580	}
581}
582
583func TestSrcJar(t *testing.T) {
584	mockFs := pathtools.MockFs(map[string][]byte{
585		"wrong_package.java":       []byte("package foo;"),
586		"foo/correct_package.java": []byte("package foo;"),
587		"src/no_package.java":      nil,
588		"src2/parse_error.java":    []byte("error"),
589	})
590
591	want := []string{
592		"foo/",
593		"foo/wrong_package.java",
594		"foo/correct_package.java",
595		"no_package.java",
596		"src2/",
597		"src2/parse_error.java",
598	}
599
600	args := ZipArgs{}
601	args.FileArgs = NewFileArgsBuilder().File("**/*.java").FileArgs()
602
603	args.SrcJar = true
604	args.AddDirectoryEntriesToZip = true
605	args.Filesystem = mockFs
606	args.Stderr = &bytes.Buffer{}
607
608	buf := &bytes.Buffer{}
609	err := ZipTo(args, buf)
610	if err != nil {
611		t.Fatalf("got error %v", err)
612	}
613
614	br := bytes.NewReader(buf.Bytes())
615	zr, err := zip.NewReader(br, int64(br.Len()))
616	if err != nil {
617		t.Fatal(err)
618	}
619
620	var got []string
621	for _, f := range zr.File {
622		r, err := f.Open()
623		if err != nil {
624			t.Fatalf("error when opening %s: %s", f.Name, err)
625		}
626
627		crc := crc32.NewIEEE()
628		len, err := io.Copy(crc, r)
629		r.Close()
630		if err != nil {
631			t.Fatalf("error when reading %s: %s", f.Name, err)
632		}
633
634		if uint64(len) != f.UncompressedSize64 {
635			t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
636		}
637
638		if crc.Sum32() != f.CRC32 {
639			t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
640		}
641
642		got = append(got, f.Name)
643	}
644
645	if !reflect.DeepEqual(want, got) {
646		t.Errorf("want files %q, got %q", want, got)
647	}
648}
649