zip2zip: add flag to uncompress files

Add -0 flag to convert files in a zip to stored instead of deflated.

Bug: 69500920
Test: zip2zip_test.go
Change-Id: I6c2b10f3b200a53a3339e3c97a78f65192b309ca
diff --git a/cmd/zip2zip/zip2zip.go b/cmd/zip2zip/zip2zip.go
index e8ea9b9..d3b349c 100644
--- a/cmd/zip2zip/zip2zip.go
+++ b/cmd/zip2zip/zip2zip.go
@@ -17,6 +17,7 @@
 import (
 	"flag"
 	"fmt"
+	"io"
 	"log"
 	"os"
 	"path/filepath"
@@ -39,11 +40,13 @@
 
 	staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC)
 
-	excludes excludeArgs
+	excludes   multiFlag
+	uncompress multiFlag
 )
 
 func init() {
 	flag.Var(&excludes, "x", "exclude a filespec from the output")
+	flag.Var(&uncompress, "0", "convert a filespec to uncompressed in the output")
 }
 
 func main() {
@@ -93,7 +96,7 @@
 	}()
 
 	if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime,
-		flag.Args(), excludes); err != nil {
+		flag.Args(), excludes, uncompress); err != nil {
 
 		log.Fatal(err)
 	}
@@ -101,11 +104,12 @@
 
 type pair struct {
 	*zip.File
-	newName string
+	newName    string
+	uncompress bool
 }
 
 func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool,
-	includes []string, excludes []string) error {
+	includes, excludes, uncompresses []string) error {
 
 	matches := []pair{}
 
@@ -149,7 +153,7 @@
 						newName = output
 					}
 				}
-				includeMatches = append(includeMatches, pair{file, newName})
+				includeMatches = append(includeMatches, pair{file, newName, false})
 			}
 		}
 
@@ -160,7 +164,7 @@
 	if len(includes) == 0 {
 		// implicitly match everything
 		for _, file := range reader.File {
-			matches = append(matches, pair{file, file.Name})
+			matches = append(matches, pair{file, file.Name, false})
 		}
 		sortMatches(matches)
 	}
@@ -193,6 +197,15 @@
 		}
 		seen[match.newName] = match.File
 
+		for _, u := range uncompresses {
+			if uncompressMatch, err := pathtools.Match(u, match.newName); err != nil {
+				return err
+			} else if uncompressMatch {
+				match.uncompress = true
+				break
+			}
+		}
+
 		matchesAfterExcludes = append(matchesAfterExcludes, match)
 	}
 
@@ -200,8 +213,32 @@
 		if setTime {
 			match.File.SetModTime(staticTime)
 		}
-		if err := writer.CopyFrom(match.File, match.newName); err != nil {
-			return err
+		if match.uncompress && match.File.FileHeader.Method != zip.Store {
+			fh := match.File.FileHeader
+			fh.Name = match.newName
+			fh.Method = zip.Store
+			fh.CompressedSize64 = fh.UncompressedSize64
+
+			zw, err := writer.CreateHeaderAndroid(&fh)
+			if err != nil {
+				return err
+			}
+
+			zr, err := match.File.Open()
+			if err != nil {
+				return err
+			}
+
+			_, err = io.Copy(zw, zr)
+			zr.Close()
+			if err != nil {
+				return err
+			}
+		} else {
+			err := writer.CopyFrom(match.File, match.newName)
+			if err != nil {
+				return err
+			}
 		}
 	}
 
@@ -217,13 +254,13 @@
 	}
 }
 
-type excludeArgs []string
+type multiFlag []string
 
-func (e *excludeArgs) String() string {
+func (e *multiFlag) String() string {
 	return strings.Join(*e, " ")
 }
 
-func (e *excludeArgs) Set(s string) error {
+func (e *multiFlag) Set(s string) error {
 	*e = append(*e, s)
 	return nil
 }
diff --git a/cmd/zip2zip/zip2zip_test.go b/cmd/zip2zip/zip2zip_test.go
index 212ab28..e032fe6 100644
--- a/cmd/zip2zip/zip2zip_test.go
+++ b/cmd/zip2zip/zip2zip_test.go
@@ -26,13 +26,15 @@
 var testCases = []struct {
 	name string
 
-	inputFiles []string
-	sortGlobs  bool
-	sortJava   bool
-	args       []string
-	excludes   []string
+	inputFiles   []string
+	sortGlobs    bool
+	sortJava     bool
+	args         []string
+	excludes     []string
+	uncompresses []string
 
 	outputFiles []string
+	storedFiles []string
 	err         error
 }{
 	{
@@ -251,6 +253,79 @@
 
 		outputFiles: nil,
 	},
+	{
+		name: "uncompress one",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		uncompresses: []string{"a/a"},
+
+		outputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		storedFiles: []string{
+			"a/a",
+		},
+	},
+	{
+		name: "uncompress two",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		uncompresses: []string{"a/a", "a/b"},
+
+		outputFiles: []string{
+			"a/a",
+			"a/b",
+		},
+		storedFiles: []string{
+			"a/a",
+			"a/b",
+		},
+	},
+	{
+		name: "uncompress glob",
+
+		inputFiles: []string{
+			"a/a",
+			"a/b",
+			"a/c.so",
+			"a/d.so",
+		},
+		uncompresses: []string{"a/*.so"},
+
+		outputFiles: []string{
+			"a/a",
+			"a/b",
+			"a/c.so",
+			"a/d.so",
+		},
+		storedFiles: []string{
+			"a/c.so",
+			"a/d.so",
+		},
+	},
+	{
+		name: "uncompress rename",
+
+		inputFiles: []string{
+			"a/a",
+		},
+		args:         []string{"a/a:a/b"},
+		uncompresses: []string{"a/b"},
+
+		outputFiles: []string{
+			"a/b",
+		},
+		storedFiles: []string{
+			"a/b",
+		},
+	},
 }
 
 func errorString(e error) string {
@@ -282,7 +357,8 @@
 			}
 
 			outputWriter := zip.NewWriter(outputBuf)
-			err = zip2zip(inputReader, outputWriter, testCase.sortGlobs, testCase.sortJava, false, testCase.args, testCase.excludes)
+			err = zip2zip(inputReader, outputWriter, testCase.sortGlobs, testCase.sortJava, false,
+				testCase.args, testCase.excludes, testCase.uncompresses)
 			if errorString(testCase.err) != errorString(err) {
 				t.Fatalf("Unexpected error:\n got: %q\nwant: %q", errorString(err), errorString(testCase.err))
 			}
@@ -294,15 +370,22 @@
 				t.Fatal(err)
 			}
 			var outputFiles []string
+			var storedFiles []string
 			if len(outputReader.File) > 0 {
 				outputFiles = make([]string, len(outputReader.File))
 				for i, file := range outputReader.File {
 					outputFiles[i] = file.Name
+					if file.Method == zip.Store {
+						storedFiles = append(storedFiles, file.Name)
+					}
 				}
 			}
 
 			if !reflect.DeepEqual(testCase.outputFiles, outputFiles) {
-				t.Fatalf("Output file list does not match:\n got: %v\nwant: %v", outputFiles, testCase.outputFiles)
+				t.Fatalf("Output file list does not match:\nwant: %v\n got: %v", testCase.outputFiles, outputFiles)
+			}
+			if !reflect.DeepEqual(testCase.storedFiles, storedFiles) {
+				t.Fatalf("Stored file list does not match:\nwant: %v\n got: %v", testCase.storedFiles, storedFiles)
 			}
 		})
 	}