blob: 9705d6c4986e5364dc0bac37412a9ebe6903ac4a [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"),
52 "manifest.txt": fileCustomManifest,
Colin Cross05518bc2018-09-27 15:06:19 -070053})
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 {
Colin Cross4be8f9e2018-09-28 15:16:48 -0700103 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
Colin Cross05518bc2018-09-27 15:06:19 -0700112
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 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700137 name: "files glob",
138 args: fileArgsBuilder().
139 SourcePrefixToStrip("a").
140 File("a/**/*"),
141 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700142 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700143
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,
Colin Cross09f11052018-09-21 15:12:39 -0700157 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700158
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 {
Colin Cross05518bc2018-09-27 15:06:19 -0700167 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,
Colin Cross09f11052018-09-21 15:12:39 -0700188 storeSymlinks: true,
Colin Cross05518bc2018-09-27 15:06:19 -0700189
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 {
Colin Cross09f11052018-09-21 15:12:39 -0700198 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 {
Colin Cross9cb51db2019-06-17 14:12:41 -0700215 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 {
Colin Cross05518bc2018-09-27 15:06:19 -0700226 name: "list",
227 args: fileArgsBuilder().
Jiyong Park04bbf982019-11-04 13:18:41 +0900228 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"),
Colin Cross05518bc2018-09-27 15:06:19 -0700241 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 },
Colin Cross4be8f9e2018-09-28 15:16:48 -0700367 {
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 },
Colin Cross05518bc2018-09-27 15:06:19 -0700381
382 // errors
383 {
384 name: "error missing file",
385 args: fileArgsBuilder().
386 File("missing"),
387 err: os.ErrNotExist,
388 },
389 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700390 name: "error missing dir",
391 args: fileArgsBuilder().
392 Dir("missing"),
393 err: os.ErrNotExist,
394 },
395 {
Colin Cross05518bc2018-09-27 15:06:19 -0700396 name: "error missing file in list",
397 args: fileArgsBuilder().
398 List("l2"),
399 err: os.ErrNotExist,
400 },
Colin Cross1d98ee22018-09-18 17:05:15 -0700401 {
402 name: "error incorrect relative root",
403 args: fileArgsBuilder().
404 SourcePrefixToStrip("b").
405 File("a/a/a"),
406 err: IncorrectRelativeRootError{},
407 },
Colin Cross05518bc2018-09-27 15:06:19 -0700408 }
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
Colin Cross09f11052018-09-21 15:12:39 -0700423 args.StoreSymlinks = test.storeSymlinks
Colin Cross4be8f9e2018-09-28 15:16:48 -0700424 args.IgnoreMissingFiles = test.ignoreMissingFiles
Colin Cross05518bc2018-09-27 15:06:19 -0700425 args.Filesystem = mockFs
Colin Cross4be8f9e2018-09-28 15:16:48 -0700426 args.Stderr = &bytes.Buffer{}
Colin Cross05518bc2018-09-27 15:06:19 -0700427
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 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700438 } 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 }
Colin Cross05518bc2018-09-27 15:06:19 -0700442 } 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
Nan Zhang674dd932018-01-26 18:30:36 -0800516func 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}
Colin Cross9cb51db2019-06-17 14:12:41 -0700582
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}