1// Copyright 2016 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 main 16 17import ( 18 "flag" 19 "fmt" 20 "io" 21 "log" 22 "os" 23 "path/filepath" 24 "sort" 25 "strings" 26 "time" 27 28 "github.com/google/blueprint/pathtools" 29 30 "android/soong/jar" 31 "android/soong/third_party/zip" 32) 33 34var ( 35 input = flag.String("i", "", "zip file to read from") 36 output = flag.String("o", "", "output file") 37 sortGlobs = flag.Bool("s", false, "sort matches from each glob (defaults to the order from the input zip file)") 38 sortJava = flag.Bool("j", false, "sort using jar ordering within each glob (META-INF/MANIFEST.MF first)") 39 setTime = flag.Bool("t", false, "set timestamps to 2009-01-01 00:00:00") 40 41 staticTime = time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC) 42 43 excludes multiFlag 44 includes multiFlag 45 uncompress multiFlag 46) 47 48func init() { 49 flag.Var(&excludes, "x", "exclude a filespec from the output") 50 flag.Var(&includes, "X", "include a filespec in the output that was previously excluded") 51 flag.Var(&uncompress, "0", "convert a filespec to uncompressed in the output") 52} 53 54func main() { 55 flag.Usage = func() { 56 fmt.Fprintln(os.Stderr, "usage: zip2zip -i zipfile -o zipfile [-s|-j] [-t] [filespec]...") 57 flag.PrintDefaults() 58 fmt.Fprintln(os.Stderr, " filespec:") 59 fmt.Fprintln(os.Stderr, " <name>") 60 fmt.Fprintln(os.Stderr, " <in_name>:<out_name>") 61 fmt.Fprintln(os.Stderr, " <glob>[:<out_dir>]") 62 fmt.Fprintln(os.Stderr, "") 63 fmt.Fprintln(os.Stderr, "<glob> uses the rules at https://godoc.org/github.com/google/blueprint/pathtools/#Match") 64 fmt.Fprintln(os.Stderr, "") 65 fmt.Fprintln(os.Stderr, "Files will be copied with their existing compression from the input zipfile to") 66 fmt.Fprintln(os.Stderr, "the output zipfile, in the order of filespec arguments.") 67 fmt.Fprintln(os.Stderr, "") 68 fmt.Fprintln(os.Stderr, "If no filepsec is provided all files and directories are copied.") 69 } 70 71 flag.Parse() 72 73 if *input == "" || *output == "" { 74 flag.Usage() 75 os.Exit(1) 76 } 77 78 log.SetFlags(log.Lshortfile) 79 80 reader, err := zip.OpenReader(*input) 81 if err != nil { 82 log.Fatal(err) 83 } 84 defer reader.Close() 85 86 output, err := os.Create(*output) 87 if err != nil { 88 log.Fatal(err) 89 } 90 defer output.Close() 91 92 writer := zip.NewWriter(output) 93 defer func() { 94 err := writer.Close() 95 if err != nil { 96 log.Fatal(err) 97 } 98 }() 99 100 if err := zip2zip(&reader.Reader, writer, *sortGlobs, *sortJava, *setTime, 101 flag.Args(), excludes, includes, uncompress); err != nil { 102 103 log.Fatal(err) 104 } 105} 106 107type pair struct { 108 *zip.File 109 newName string 110 uncompress bool 111} 112 113func zip2zip(reader *zip.Reader, writer *zip.Writer, sortOutput, sortJava, setTime bool, 114 args []string, excludes, includes multiFlag, uncompresses []string) error { 115 116 matches := []pair{} 117 118 sortMatches := func(matches []pair) { 119 if sortJava { 120 sort.SliceStable(matches, func(i, j int) bool { 121 return jar.EntryNamesLess(matches[i].newName, matches[j].newName) 122 }) 123 } else if sortOutput { 124 sort.SliceStable(matches, func(i, j int) bool { 125 return matches[i].newName < matches[j].newName 126 }) 127 } 128 } 129 130 for _, arg := range args { 131 // Reserve escaping for future implementation, so make sure no 132 // one is using \ and expecting a certain behavior. 133 if strings.Contains(arg, "\\") { 134 return fmt.Errorf("\\ characters are not currently supported") 135 } 136 137 input, output := includeSplit(arg) 138 139 var includeMatches []pair 140 141 for _, file := range reader.File { 142 var newName string 143 if match, err := pathtools.Match(input, file.Name); err != nil { 144 return err 145 } else if match { 146 if output == "" { 147 newName = file.Name 148 } else { 149 if pathtools.IsGlob(input) { 150 // If the input is a glob then the output is a directory. 151 rel, err := filepath.Rel(constantPartOfPattern(input), file.Name) 152 if err != nil { 153 return err 154 } else if strings.HasPrefix("../", rel) { 155 return fmt.Errorf("globbed path %q was not in %q", file.Name, constantPartOfPattern(input)) 156 } 157 newName = filepath.Join(output, rel) 158 } else { 159 // Otherwise it is a file. 160 newName = output 161 } 162 } 163 includeMatches = append(includeMatches, pair{file, newName, false}) 164 } 165 } 166 167 sortMatches(includeMatches) 168 matches = append(matches, includeMatches...) 169 } 170 171 if len(args) == 0 { 172 // implicitly match everything 173 for _, file := range reader.File { 174 matches = append(matches, pair{file, file.Name, false}) 175 } 176 sortMatches(matches) 177 } 178 179 var matchesAfterExcludes []pair 180 seen := make(map[string]*zip.File) 181 182 for _, match := range matches { 183 // Filter out matches whose original file name matches an exclude filter, unless it also matches an 184 // include filter 185 if exclude, err := excludes.Match(match.File.Name); err != nil { 186 return err 187 } else if exclude { 188 if include, err := includes.Match(match.File.Name); err != nil { 189 return err 190 } else if !include { 191 continue 192 } 193 } 194 195 // Check for duplicate output names, ignoring ones that come from the same input zip entry. 196 if prev, exists := seen[match.newName]; exists { 197 if prev != match.File { 198 return fmt.Errorf("multiple entries for %q with different contents", match.newName) 199 } 200 continue 201 } 202 seen[match.newName] = match.File 203 204 for _, u := range uncompresses { 205 if uncompressMatch, err := pathtools.Match(u, match.newName); err != nil { 206 return err 207 } else if uncompressMatch { 208 match.uncompress = true 209 break 210 } 211 } 212 213 matchesAfterExcludes = append(matchesAfterExcludes, match) 214 } 215 216 for _, match := range matchesAfterExcludes { 217 if setTime { 218 match.File.SetModTime(staticTime) 219 } 220 if match.uncompress && match.File.FileHeader.Method != zip.Store { 221 fh := match.File.FileHeader 222 fh.Name = match.newName 223 fh.Method = zip.Store 224 fh.CompressedSize64 = fh.UncompressedSize64 225 226 zw, err := writer.CreateHeaderAndroid(&fh) 227 if err != nil { 228 return err 229 } 230 231 zr, err := match.File.Open() 232 if err != nil { 233 return err 234 } 235 236 _, err = io.Copy(zw, zr) 237 zr.Close() 238 if err != nil { 239 return err 240 } 241 } else { 242 err := writer.CopyFrom(match.File, match.newName) 243 if err != nil { 244 return err 245 } 246 } 247 } 248 249 return nil 250} 251 252func includeSplit(s string) (string, string) { 253 split := strings.SplitN(s, ":", 2) 254 if len(split) == 2 { 255 return split[0], split[1] 256 } else { 257 return split[0], "" 258 } 259} 260 261type multiFlag []string 262 263func (m *multiFlag) String() string { 264 return strings.Join(*m, " ") 265} 266 267func (m *multiFlag) Set(s string) error { 268 *m = append(*m, s) 269 return nil 270} 271 272func (m *multiFlag) Match(s string) (bool, error) { 273 if m == nil { 274 return false, nil 275 } 276 for _, f := range *m { 277 if match, err := pathtools.Match(f, s); err != nil { 278 return false, err 279 } else if match { 280 return true, nil 281 } 282 } 283 return false, nil 284} 285 286func constantPartOfPattern(pattern string) string { 287 ret := "" 288 for pattern != "" { 289 var first string 290 first, pattern = splitFirst(pattern) 291 if pathtools.IsGlob(first) { 292 return ret 293 } 294 ret = filepath.Join(ret, first) 295 } 296 return ret 297} 298 299func splitFirst(path string) (string, string) { 300 i := strings.IndexRune(path, filepath.Separator) 301 if i < 0 { 302 return path, "" 303 } 304 return path[:i], path[i+1:] 305} 306