blob: e77801b91406218142757878068c0d90fcfb3ebd [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
108
109 files []zip.FileHeader
110 err error
111 }{
112 {
113 name: "empty args",
114 args: fileArgsBuilder(),
115
116 files: []zip.FileHeader{},
117 },
118 {
119 name: "files",
120 args: fileArgsBuilder().
121 File("a/a/a").
122 File("a/a/b").
123 File("c"),
124 compressionLevel: 9,
125
126 files: []zip.FileHeader{
127 fh("a/a/a", fileA, zip.Deflate),
128 fh("a/a/b", fileB, zip.Deflate),
129 fh("c", fileC, zip.Deflate),
130 },
131 },
132 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700133 name: "files glob",
134 args: fileArgsBuilder().
135 SourcePrefixToStrip("a").
136 File("a/**/*"),
137 compressionLevel: 9,
138
139 files: []zip.FileHeader{
140 fh("a/a", fileA, zip.Deflate),
141 fh("a/b", fileB, zip.Deflate),
142 fhLink("a/c", "../../c"),
143 fhLink("a/d", "b"),
144 },
145 },
146 {
147 name: "dir",
148 args: fileArgsBuilder().
149 SourcePrefixToStrip("a").
150 Dir("a"),
151 compressionLevel: 9,
152
153 files: []zip.FileHeader{
154 fh("a/a", fileA, zip.Deflate),
155 fh("a/b", fileB, zip.Deflate),
156 fhLink("a/c", "../../c"),
157 fhLink("a/d", "b"),
158 },
159 },
160 {
Colin Cross05518bc2018-09-27 15:06:19 -0700161 name: "stored files",
162 args: fileArgsBuilder().
163 File("a/a/a").
164 File("a/a/b").
165 File("c"),
166 compressionLevel: 0,
167
168 files: []zip.FileHeader{
169 fh("a/a/a", fileA, zip.Store),
170 fh("a/a/b", fileB, zip.Store),
171 fh("c", fileC, zip.Store),
172 },
173 },
174 {
175 name: "symlinks in zip",
176 args: fileArgsBuilder().
177 File("a/a/a").
178 File("a/a/b").
179 File("a/a/c").
180 File("a/a/d"),
181 compressionLevel: 9,
182
183 files: []zip.FileHeader{
184 fh("a/a/a", fileA, zip.Deflate),
185 fh("a/a/b", fileB, zip.Deflate),
186 fhLink("a/a/c", "../../c"),
187 fhLink("a/a/d", "b"),
188 },
189 },
190 {
191 name: "list",
192 args: fileArgsBuilder().
193 List("l"),
194 compressionLevel: 9,
195
196 files: []zip.FileHeader{
197 fh("a/a/a", fileA, zip.Deflate),
198 fh("a/a/b", fileB, zip.Deflate),
199 fh("c", fileC, zip.Deflate),
200 },
201 },
202 {
203 name: "prefix in zip",
204 args: fileArgsBuilder().
205 PathPrefixInZip("foo").
206 File("a/a/a").
207 File("a/a/b").
208 File("c"),
209 compressionLevel: 9,
210
211 files: []zip.FileHeader{
212 fh("foo/a/a/a", fileA, zip.Deflate),
213 fh("foo/a/a/b", fileB, zip.Deflate),
214 fh("foo/c", fileC, zip.Deflate),
215 },
216 },
217 {
218 name: "relative root",
219 args: fileArgsBuilder().
220 SourcePrefixToStrip("a").
221 File("a/a/a").
222 File("a/a/b"),
223 compressionLevel: 9,
224
225 files: []zip.FileHeader{
226 fh("a/a", fileA, zip.Deflate),
227 fh("a/b", fileB, zip.Deflate),
228 },
229 },
230 {
231 name: "multiple relative root",
232 args: fileArgsBuilder().
233 SourcePrefixToStrip("a").
234 File("a/a/a").
235 SourcePrefixToStrip("a/a").
236 File("a/a/b"),
237 compressionLevel: 9,
238
239 files: []zip.FileHeader{
240 fh("a/a", fileA, zip.Deflate),
241 fh("b", fileB, zip.Deflate),
242 },
243 },
244 {
245 name: "emulate jar",
246 args: fileArgsBuilder().
247 File("a/a/a").
248 File("a/a/b"),
249 compressionLevel: 9,
250 emulateJar: true,
251
252 files: []zip.FileHeader{
253 fhDir("META-INF/"),
254 fhManifest(fileManifest),
255 fhDir("a/"),
256 fhDir("a/a/"),
257 fh("a/a/a", fileA, zip.Deflate),
258 fh("a/a/b", fileB, zip.Deflate),
259 },
260 },
261 {
262 name: "emulate jar with manifest",
263 args: fileArgsBuilder().
264 File("a/a/a").
265 File("a/a/b"),
266 compressionLevel: 9,
267 emulateJar: true,
268 manifest: "manifest.txt",
269
270 files: []zip.FileHeader{
271 fhDir("META-INF/"),
272 fhManifest(customManifestAfter),
273 fhDir("a/"),
274 fhDir("a/a/"),
275 fh("a/a/a", fileA, zip.Deflate),
276 fh("a/a/b", fileB, zip.Deflate),
277 },
278 },
279 {
280 name: "dir entries",
281 args: fileArgsBuilder().
282 File("a/a/a").
283 File("a/a/b"),
284 compressionLevel: 9,
285 dirEntries: true,
286
287 files: []zip.FileHeader{
288 fhDir("a/"),
289 fhDir("a/a/"),
290 fh("a/a/a", fileA, zip.Deflate),
291 fh("a/a/b", fileB, zip.Deflate),
292 },
293 },
294 {
295 name: "junk paths",
296 args: fileArgsBuilder().
297 JunkPaths(true).
298 File("a/a/a").
299 File("a/a/b"),
300 compressionLevel: 9,
301
302 files: []zip.FileHeader{
303 fh("a", fileA, zip.Deflate),
304 fh("b", fileB, zip.Deflate),
305 },
306 },
307 {
308 name: "non deflated files",
309 args: fileArgsBuilder().
310 File("a/a/a").
311 File("a/a/b"),
312 compressionLevel: 9,
313 nonDeflatedFiles: map[string]bool{"a/a/a": true},
314
315 files: []zip.FileHeader{
316 fh("a/a/a", fileA, zip.Store),
317 fh("a/a/b", fileB, zip.Deflate),
318 },
319 },
320
321 // errors
322 {
323 name: "error missing file",
324 args: fileArgsBuilder().
325 File("missing"),
326 err: os.ErrNotExist,
327 },
328 {
Colin Cross1d98ee22018-09-18 17:05:15 -0700329 name: "error missing dir",
330 args: fileArgsBuilder().
331 Dir("missing"),
332 err: os.ErrNotExist,
333 },
334 {
Colin Cross05518bc2018-09-27 15:06:19 -0700335 name: "error missing file in list",
336 args: fileArgsBuilder().
337 List("l2"),
338 err: os.ErrNotExist,
339 },
Colin Cross1d98ee22018-09-18 17:05:15 -0700340 {
341 name: "error incorrect relative root",
342 args: fileArgsBuilder().
343 SourcePrefixToStrip("b").
344 File("a/a/a"),
345 err: IncorrectRelativeRootError{},
346 },
Colin Cross05518bc2018-09-27 15:06:19 -0700347 }
348
349 for _, test := range testCases {
350 t.Run(test.name, func(t *testing.T) {
351 if test.args.Error() != nil {
352 t.Fatal(test.args.Error())
353 }
354
355 args := ZipArgs{}
356 args.FileArgs = test.args.FileArgs()
357 args.CompressionLevel = test.compressionLevel
358 args.EmulateJar = test.emulateJar
359 args.AddDirectoryEntriesToZip = test.dirEntries
360 args.NonDeflatedFiles = test.nonDeflatedFiles
361 args.ManifestSourcePath = test.manifest
362 args.Filesystem = mockFs
363
364 buf := &bytes.Buffer{}
365 err := ZipTo(args, buf)
366
367 if (err != nil) != (test.err != nil) {
368 t.Fatalf("want error %v, got %v", test.err, err)
369 } else if test.err != nil {
370 if os.IsNotExist(test.err) {
371 if !os.IsNotExist(test.err) {
372 t.Fatalf("want error %v, got %v", test.err, err)
373 }
Colin Cross1d98ee22018-09-18 17:05:15 -0700374 } else if _, wantRelativeRootErr := test.err.(IncorrectRelativeRootError); wantRelativeRootErr {
375 if _, gotRelativeRootErr := err.(IncorrectRelativeRootError); !gotRelativeRootErr {
376 t.Fatalf("want error %v, got %v", test.err, err)
377 }
Colin Cross05518bc2018-09-27 15:06:19 -0700378 } else {
379 t.Fatalf("want error %v, got %v", test.err, err)
380 }
381 return
382 }
383
384 br := bytes.NewReader(buf.Bytes())
385 zr, err := zip.NewReader(br, int64(br.Len()))
386 if err != nil {
387 t.Fatal(err)
388 }
389
390 var files []zip.FileHeader
391 for _, f := range zr.File {
392 r, err := f.Open()
393 if err != nil {
394 t.Fatalf("error when opening %s: %s", f.Name, err)
395 }
396
397 crc := crc32.NewIEEE()
398 len, err := io.Copy(crc, r)
399 r.Close()
400 if err != nil {
401 t.Fatalf("error when reading %s: %s", f.Name, err)
402 }
403
404 if uint64(len) != f.UncompressedSize64 {
405 t.Errorf("incorrect length for %s, want %d got %d", f.Name, f.UncompressedSize64, len)
406 }
407
408 if crc.Sum32() != f.CRC32 {
409 t.Errorf("incorrect crc for %s, want %x got %x", f.Name, f.CRC32, crc)
410 }
411
412 files = append(files, f.FileHeader)
413 }
414
415 if len(files) != len(test.files) {
416 t.Fatalf("want %d files, got %d", len(test.files), len(files))
417 }
418
419 for i := range files {
420 want := test.files[i]
421 got := files[i]
422
423 if want.Name != got.Name {
424 t.Errorf("incorrect file %d want %q got %q", i, want.Name, got.Name)
425 continue
426 }
427
428 if want.UncompressedSize64 != got.UncompressedSize64 {
429 t.Errorf("incorrect file %s length want %v got %v", want.Name,
430 want.UncompressedSize64, got.UncompressedSize64)
431 }
432
433 if want.ExternalAttrs != got.ExternalAttrs {
434 t.Errorf("incorrect file %s attrs want %x got %x", want.Name,
435 want.ExternalAttrs, got.ExternalAttrs)
436 }
437
438 if want.CRC32 != got.CRC32 {
439 t.Errorf("incorrect file %s crc want %v got %v", want.Name,
440 want.CRC32, got.CRC32)
441 }
442
443 if want.Method != got.Method {
444 t.Errorf("incorrect file %s method want %v got %v", want.Name,
445 want.Method, got.Method)
446 }
447 }
448 })
449 }
450}
451
Nan Zhang674dd932018-01-26 18:30:36 -0800452func TestReadRespFile(t *testing.T) {
453 testCases := []struct {
454 name, in string
455 out []string
456 }{
457 {
458 name: "single quoting test case 1",
459 in: `./cmd '"'-C`,
460 out: []string{"./cmd", `"-C`},
461 },
462 {
463 name: "single quoting test case 2",
464 in: `./cmd '-C`,
465 out: []string{"./cmd", `-C`},
466 },
467 {
468 name: "single quoting test case 3",
469 in: `./cmd '\"'-C`,
470 out: []string{"./cmd", `\"-C`},
471 },
472 {
473 name: "single quoting test case 4",
474 in: `./cmd '\\'-C`,
475 out: []string{"./cmd", `\\-C`},
476 },
477 {
478 name: "none quoting test case 1",
479 in: `./cmd \'-C`,
480 out: []string{"./cmd", `'-C`},
481 },
482 {
483 name: "none quoting test case 2",
484 in: `./cmd \\-C`,
485 out: []string{"./cmd", `\-C`},
486 },
487 {
488 name: "none quoting test case 3",
489 in: `./cmd \"-C`,
490 out: []string{"./cmd", `"-C`},
491 },
492 {
493 name: "double quoting test case 1",
494 in: `./cmd "'"-C`,
495 out: []string{"./cmd", `'-C`},
496 },
497 {
498 name: "double quoting test case 2",
499 in: `./cmd "\\"-C`,
500 out: []string{"./cmd", `\-C`},
501 },
502 {
503 name: "double quoting test case 3",
504 in: `./cmd "\""-C`,
505 out: []string{"./cmd", `"-C`},
506 },
507 }
508
509 for _, testCase := range testCases {
510 t.Run(testCase.name, func(t *testing.T) {
511 got := ReadRespFile([]byte(testCase.in))
512 if !reflect.DeepEqual(got, testCase.out) {
513 t.Errorf("expected %q got %q", testCase.out, got)
514 }
515 })
516 }
517}