blob: a08fb126d7506f8f3aa91ed0bb6b1e6849c348a8 [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{
43 "a/a/a": fileA,
44 "a/a/b": fileB,
45 "a/a/c -> ../../c": nil,
46 "a/a/d -> b": nil,
47 "c": fileC,
48 "l": []byte("a/a/a\na/a/b\nc\n"),
49 "l2": []byte("missing\n"),
50 "manifest.txt": fileCustomManifest,
51})
52
53func fh(name string, contents []byte, method uint16) zip.FileHeader {
54 return zip.FileHeader{
55 Name: name,
56 Method: method,
57 CRC32: crc32.ChecksumIEEE(contents),
58 UncompressedSize64: uint64(len(contents)),
59 ExternalAttrs: 0,
60 }
61}
62
63func fhManifest(contents []byte) zip.FileHeader {
64 return zip.FileHeader{
65 Name: "META-INF/MANIFEST.MF",
66 Method: zip.Store,
67 CRC32: crc32.ChecksumIEEE(contents),
68 UncompressedSize64: uint64(len(contents)),
69 ExternalAttrs: (syscall.S_IFREG | 0700) << 16,
70 }
71}
72
73func fhLink(name string, to string) zip.FileHeader {
74 return zip.FileHeader{
75 Name: name,
76 Method: zip.Store,
77 CRC32: crc32.ChecksumIEEE([]byte(to)),
78 UncompressedSize64: uint64(len(to)),
79 ExternalAttrs: (syscall.S_IFLNK | 0777) << 16,
80 }
81}
82
83func fhDir(name string) zip.FileHeader {
84 return zip.FileHeader{
85 Name: name,
86 Method: zip.Store,
87 CRC32: crc32.ChecksumIEEE(nil),
88 UncompressedSize64: 0,
89 ExternalAttrs: (syscall.S_IFDIR|0700)<<16 | 0x10,
90 }
91}
92
93func fileArgsBuilder() *FileArgsBuilder {
94 return &FileArgsBuilder{
95 fs: mockFs,
96 }
97}
98
99func TestZip(t *testing.T) {
100 testCases := []struct {
101 name string
102 args *FileArgsBuilder
103 compressionLevel int
104 emulateJar bool
105 nonDeflatedFiles map[string]bool
106 dirEntries bool
107 manifest string
Colin Cross09f11052018-09-21 15:12:39 -0700108 storeSymlinks bool
Colin Cross05518bc2018-09-27 15:06:19 -0700109
110 files []zip.FileHeader
111 err error
112 }{
113 {
114 name: "empty args",
115 args: fileArgsBuilder(),
116
117 files: []zip.FileHeader{},
118 },
119 {
120 name: "files",
121 args: fileArgsBuilder().
122 File("a/a/a").
123 File("a/a/b").
124 File("c"),
125 compressionLevel: 9,
126
127 files: []zip.FileHeader{
128 fh("a/a/a", fileA, zip.Deflate),
129 fh("a/a/b", fileB, zip.Deflate),
130 fh("c", fileC, zip.Deflate),
131 },
132 },
133 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700134 name: "files glob",
135 args: fileArgsBuilder().
136 SourcePrefixToStrip("a").
137 File("a/**/*"),
138 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700139 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700140
141 files: []zip.FileHeader{
142 fh("a/a", fileA, zip.Deflate),
143 fh("a/b", fileB, zip.Deflate),
144 fhLink("a/c", "../../c"),
145 fhLink("a/d", "b"),
146 },
147 },
148 {
149 name: "dir",
150 args: fileArgsBuilder().
151 SourcePrefixToStrip("a").
152 Dir("a"),
153 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700154 storeSymlinks: true,
Colin Cross1d98ee22018-09-18 17:05:15 -0700155
156 files: []zip.FileHeader{
157 fh("a/a", fileA, zip.Deflate),
158 fh("a/b", fileB, zip.Deflate),
159 fhLink("a/c", "../../c"),
160 fhLink("a/d", "b"),
161 },
162 },
163 {
Colin Cross05518bc2018-09-27 15:06:19 -0700164 name: "stored files",
165 args: fileArgsBuilder().
166 File("a/a/a").
167 File("a/a/b").
168 File("c"),
169 compressionLevel: 0,
170
171 files: []zip.FileHeader{
172 fh("a/a/a", fileA, zip.Store),
173 fh("a/a/b", fileB, zip.Store),
174 fh("c", fileC, zip.Store),
175 },
176 },
177 {
178 name: "symlinks in zip",
179 args: fileArgsBuilder().
180 File("a/a/a").
181 File("a/a/b").
182 File("a/a/c").
183 File("a/a/d"),
184 compressionLevel: 9,
Colin Cross09f11052018-09-21 15:12:39 -0700185 storeSymlinks: true,
Colin Cross05518bc2018-09-27 15:06:19 -0700186
187 files: []zip.FileHeader{
188 fh("a/a/a", fileA, zip.Deflate),
189 fh("a/a/b", fileB, zip.Deflate),
190 fhLink("a/a/c", "../../c"),
191 fhLink("a/a/d", "b"),
192 },
193 },
194 {
Colin Cross09f11052018-09-21 15:12:39 -0700195 name: "follow symlinks",
196 args: fileArgsBuilder().
197 File("a/a/a").
198 File("a/a/b").
199 File("a/a/c").
200 File("a/a/d"),
201 compressionLevel: 9,
202 storeSymlinks: false,
203
204 files: []zip.FileHeader{
205 fh("a/a/a", fileA, zip.Deflate),
206 fh("a/a/b", fileB, zip.Deflate),
207 fh("a/a/c", fileC, zip.Deflate),
208 fh("a/a/d", fileB, zip.Deflate),
209 },
210 },
211 {
Colin Cross05518bc2018-09-27 15:06:19 -0700212 name: "list",
213 args: fileArgsBuilder().
214 List("l"),
215 compressionLevel: 9,
216
217 files: []zip.FileHeader{
218 fh("a/a/a", fileA, zip.Deflate),
219 fh("a/a/b", fileB, zip.Deflate),
220 fh("c", fileC, zip.Deflate),
221 },
222 },
223 {
224 name: "prefix in zip",
225 args: fileArgsBuilder().
226 PathPrefixInZip("foo").
227 File("a/a/a").
228 File("a/a/b").
229 File("c"),
230 compressionLevel: 9,
231
232 files: []zip.FileHeader{
233 fh("foo/a/a/a", fileA, zip.Deflate),
234 fh("foo/a/a/b", fileB, zip.Deflate),
235 fh("foo/c", fileC, zip.Deflate),
236 },
237 },
238 {
239 name: "relative root",
240 args: fileArgsBuilder().
241 SourcePrefixToStrip("a").
242 File("a/a/a").
243 File("a/a/b"),
244 compressionLevel: 9,
245
246 files: []zip.FileHeader{
247 fh("a/a", fileA, zip.Deflate),
248 fh("a/b", fileB, zip.Deflate),
249 },
250 },
251 {
252 name: "multiple relative root",
253 args: fileArgsBuilder().
254 SourcePrefixToStrip("a").
255 File("a/a/a").
256 SourcePrefixToStrip("a/a").
257 File("a/a/b"),
258 compressionLevel: 9,
259
260 files: []zip.FileHeader{
261 fh("a/a", fileA, zip.Deflate),
262 fh("b", fileB, zip.Deflate),
263 },
264 },
265 {
266 name: "emulate jar",
267 args: fileArgsBuilder().
268 File("a/a/a").
269 File("a/a/b"),
270 compressionLevel: 9,
271 emulateJar: true,
272
273 files: []zip.FileHeader{
274 fhDir("META-INF/"),
275 fhManifest(fileManifest),
276 fhDir("a/"),
277 fhDir("a/a/"),
278 fh("a/a/a", fileA, zip.Deflate),
279 fh("a/a/b", fileB, zip.Deflate),
280 },
281 },
282 {
283 name: "emulate jar with manifest",
284 args: fileArgsBuilder().
285 File("a/a/a").
286 File("a/a/b"),
287 compressionLevel: 9,
288 emulateJar: true,
289 manifest: "manifest.txt",
290
291 files: []zip.FileHeader{
292 fhDir("META-INF/"),
293 fhManifest(customManifestAfter),
294 fhDir("a/"),
295 fhDir("a/a/"),
296 fh("a/a/a", fileA, zip.Deflate),
297 fh("a/a/b", fileB, zip.Deflate),
298 },
299 },
300 {
301 name: "dir entries",
302 args: fileArgsBuilder().
303 File("a/a/a").
304 File("a/a/b"),
305 compressionLevel: 9,
306 dirEntries: true,
307
308 files: []zip.FileHeader{
309 fhDir("a/"),
310 fhDir("a/a/"),
311 fh("a/a/a", fileA, zip.Deflate),
312 fh("a/a/b", fileB, zip.Deflate),
313 },
314 },
315 {
316 name: "junk paths",
317 args: fileArgsBuilder().
318 JunkPaths(true).
319 File("a/a/a").
320 File("a/a/b"),
321 compressionLevel: 9,
322
323 files: []zip.FileHeader{
324 fh("a", fileA, zip.Deflate),
325 fh("b", fileB, zip.Deflate),
326 },
327 },
328 {
329 name: "non deflated files",
330 args: fileArgsBuilder().
331 File("a/a/a").
332 File("a/a/b"),
333 compressionLevel: 9,
334 nonDeflatedFiles: map[string]bool{"a/a/a": true},
335
336 files: []zip.FileHeader{
337 fh("a/a/a", fileA, zip.Store),
338 fh("a/a/b", fileB, zip.Deflate),
339 },
340 },
341
342 // errors
343 {
344 name: "error missing file",
345 args: fileArgsBuilder().
346 File("missing"),
347 err: os.ErrNotExist,
348 },
349 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700350 name: "error missing dir",
351 args: fileArgsBuilder().
352 Dir("missing"),
353 err: os.ErrNotExist,
354 },
355 {
Colin Cross05518bc2018-09-27 15:06:19 -0700356 name: "error missing file in list",
357 args: fileArgsBuilder().
358 List("l2"),
359 err: os.ErrNotExist,
360 },
Colin Cross1d98ee22018-09-18 17:05:15 -0700361 {
362 name: "error incorrect relative root",
363 args: fileArgsBuilder().
364 SourcePrefixToStrip("b").
365 File("a/a/a"),
366 err: IncorrectRelativeRootError{},
367 },
Colin Cross05518bc2018-09-27 15:06:19 -0700368 }
369
370 for _, test := range testCases {
371 t.Run(test.name, func(t *testing.T) {
372 if test.args.Error() != nil {
373 t.Fatal(test.args.Error())
374 }
375
376 args := ZipArgs{}
377 args.FileArgs = test.args.FileArgs()
378 args.CompressionLevel = test.compressionLevel
379 args.EmulateJar = test.emulateJar
380 args.AddDirectoryEntriesToZip = test.dirEntries
381 args.NonDeflatedFiles = test.nonDeflatedFiles
382 args.ManifestSourcePath = test.manifest
Colin Cross09f11052018-09-21 15:12:39 -0700383 args.StoreSymlinks = test.storeSymlinks
Colin Cross05518bc2018-09-27 15:06:19 -0700384 args.Filesystem = mockFs
385
386 buf := &bytes.Buffer{}
387 err := ZipTo(args, buf)
388
389 if (err != nil) != (test.err != nil) {
390 t.Fatalf("want error %v, got %v", test.err, err)
391 } else if test.err != nil {
392 if os.IsNotExist(test.err) {
393 if !os.IsNotExist(test.err) {
394 t.Fatalf("want error %v, got %v", test.err, err)
395 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700396 } else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
397 if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
398 t.Fatalf("want error %v, got %v", test.err, err)
399 }
Colin Cross05518bc2018-09-27 15:06:19 -0700400 } else {
401 t.Fatalf("want error %v, got %v", test.err, err)
402 }
403 return
404 }
405
406 br := bytes.NewReader(buf.Bytes())
407 zr, err := zip.NewReader(br, int64(br.Len()))
408 if err != nil {
409 t.Fatal(err)
410 }
411
412 var files []zip.FileHeader
413 for _, f := range zr.File {
414 r, err := f.Open()
415 if err != nil {
416 t.Fatalf("error when opening %s: %s", f.Name, err)
417 }
418
419 crc := crc32.NewIEEE()
420 len, err := io.Copy(crc, r)
421 r.Close()
422 if err != nil {
423 t.Fatalf("error when reading %s: %s", f.Name, err)
424 }
425
426 if uint64(len) != f.UncompressedSize64 {
427 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
428 }
429
430 if crc.Sum32() != f.CRC32 {
431 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
432 }
433
434 files = append(files, f.FileHeader)
435 }
436
437 if len(files) != len(test.files) {
438 t.Fatalf("want %d files, got %d", len(test.files), len(files))
439 }
440
441 for i := range files {
442 want := test.files[i]
443 got := files[i]
444
445 if want.Name != got.Name {
446 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
447 continue
448 }
449
450 if want.UncompressedSize64 != got.UncompressedSize64 {
451 t.Errorf("incorrect file %s length want %v got %v", want.Name,
452 want.UncompressedSize64, got.UncompressedSize64)
453 }
454
455 if want.ExternalAttrs != got.ExternalAttrs {
456 t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
457 want.ExternalAttrs, got.ExternalAttrs)
458 }
459
460 if want.CRC32 != got.CRC32 {
461 t.Errorf("incorrect file %s crc want %v got %v", want.Name,
462 want.CRC32, got.CRC32)
463 }
464
465 if want.Method != got.Method {
466 t.Errorf("incorrect file %s method want %v got %v", want.Name,
467 want.Method, got.Method)
468 }
469 }
470 })
471 }
472}
473
Nan Zhang674dd932018-01-26 18:30:36 -0800474func TestReadRespFile(t *testing.T) {
475 testCases := []struct {
476 name, in string
477 out []string
478 }{
479 {
480 name: "single quoting test case 1",
481 in: `./cmd '"'-C`,
482 out: []string{"./cmd", `"-C`},
483 },
484 {
485 name: "single quoting test case 2",
486 in: `./cmd '-C`,
487 out: []string{"./cmd", `-C`},
488 },
489 {
490 name: "single quoting test case 3",
491 in: `./cmd '\"'-C`,
492 out: []string{"./cmd", `\"-C`},
493 },
494 {
495 name: "single quoting test case 4",
496 in: `./cmd '\\'-C`,
497 out: []string{"./cmd", `\\-C`},
498 },
499 {
500 name: "none quoting test case 1",
501 in: `./cmd \'-C`,
502 out: []string{"./cmd", `'-C`},
503 },
504 {
505 name: "none quoting test case 2",
506 in: `./cmd \\-C`,
507 out: []string{"./cmd", `\-C`},
508 },
509 {
510 name: "none quoting test case 3",
511 in: `./cmd \"-C`,
512 out: []string{"./cmd", `"-C`},
513 },
514 {
515 name: "double quoting test case 1",
516 in: `./cmd "'"-C`,
517 out: []string{"./cmd", `'-C`},
518 },
519 {
520 name: "double quoting test case 2",
521 in: `./cmd "\\"-C`,
522 out: []string{"./cmd", `\-C`},
523 },
524 {
525 name: "double quoting test case 3",
526 in: `./cmd "\""-C`,
527 out: []string{"./cmd", `"-C`},
528 },
529 }
530
531 for _, testCase := range testCases {
532 t.Run(testCase.name, func(t *testing.T) {
533 got := ReadRespFile([]byte(testCase.in))
534 if !reflect.DeepEqual(got, testCase.out) {
535 t.Errorf("expected %q got %q", testCase.out, got)
536 }
537 })
538 }
539}