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