1// Copyright 2018 The Bazel Authors. 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 15// Package repack provides functionality to repack zip/jar/apk archives. 16package repack 17 18import ( 19 "archive/zip" 20 "flag" 21 "io" 22 "log" 23 "os" 24 "path/filepath" 25 "strings" 26 "sync" 27 28 "src/common/golang/flags" 29 "src/tools/ak/types" 30) 31 32var ( 33 // Cmd defines the command to run repack 34 Cmd = types.Command{ 35 Init: Init, 36 Run: Run, 37 Desc: desc, 38 Flags: []string{ 39 "in", 40 "dir", 41 "out", 42 "filtered_out", 43 "filter_r", 44 "filter_jar_res", 45 "filter_manifest", 46 "compress", 47 "remove_dirs", 48 }, 49 } 50 51 // Variables that hold flag values 52 in flags.StringList 53 dir flags.StringList 54 out string 55 filteredOut string 56 filterR bool 57 filterJarRes bool 58 filterManifest bool 59 compress bool 60 removeDirs bool 61 62 b2i = map[bool]int8{false: 0, true: 1} 63 seen = make(map[string]bool) 64 initOnce sync.Once 65) 66 67// Init initializes repack. 68func Init() { 69 initOnce.Do(func() { 70 flag.Var(&in, "in", "Path to input(s), must be a zip archive.") 71 flag.Var(&dir, "dir", "Path to directories to pack in the zip.") 72 flag.StringVar(&out, "out", "", "Path to output.") 73 flag.StringVar(&filteredOut, "filtered_out", "", "(optional) Path to output for filtered files.") 74 flag.BoolVar(&filterR, "filter_r", false, "Whether to filter R classes or not.") 75 flag.BoolVar(&filterJarRes, "filter_jar_res", false, "Whether to filter java resources or not.") 76 flag.BoolVar(&filterManifest, "filter_manifest", false, "Whether to filter AndroidManifest.xml or not.") 77 flag.BoolVar(&compress, "compress", false, "Whether to compress or just store files in all outputs.") 78 flag.BoolVar(&removeDirs, "remove_dirs", true, "Whether to remove directory entries or not.") 79 }) 80} 81 82func desc() string { 83 return "Repack zip/jar/apk archives." 84} 85 86type filterFunc func(name string) bool 87 88func filterNone(name string) bool { 89 return false 90} 91 92func isRClass(name string) bool { 93 return strings.HasSuffix(name, "/R.class") || strings.Contains(name, "/R$") 94} 95 96func isJavaRes(name string) bool { 97 return !(strings.HasSuffix(name, ".class") || strings.HasPrefix(name, "META-INF/")) 98} 99 100func isManifest(name string) bool { 101 return name == "AndroidManifest.xml" 102} 103 104func repackZip(in *zip.Reader, out *zip.Writer, filteredZipOut *zip.Writer, filter filterFunc, method uint16) error { 105 for _, f := range in.File { 106 if removeDirs && strings.HasSuffix(f.Name, "/") { 107 continue 108 } 109 reader, err := f.Open() 110 if err != nil { 111 return err 112 } 113 if err := writeToZip(f.Name, reader, out, filteredZipOut, filter, method); err != nil { 114 return err 115 } 116 if err := reader.Close(); err != nil { 117 return err 118 } 119 } 120 return nil 121} 122 123func repackDir(dir string, out *zip.Writer, filteredZipOut *zip.Writer, filter filterFunc, method uint16) error { 124 return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 125 if err != nil { 126 return err 127 } 128 129 name, err := filepath.Rel(dir, path) 130 if info.IsDir() { 131 if removeDirs { 132 return nil 133 } 134 name += "/" 135 } 136 137 reader, err := os.Open(path) 138 if err != nil { 139 return err 140 } 141 defer reader.Close() 142 143 return writeToZip(name, reader, out, filteredZipOut, filter, method) 144 }) 145} 146 147func writeToZip(name string, in io.Reader, out, filteredZipOut *zip.Writer, filter filterFunc, method uint16) error { 148 if seen[name] { 149 return nil 150 } 151 seen[name] = true 152 153 if filter(name) { 154 if filteredZipOut != nil { 155 write(filteredZipOut, in, name, method) 156 } 157 return nil 158 } 159 return write(out, in, name, method) 160} 161 162func write(out *zip.Writer, in io.Reader, name string, method uint16) error { 163 writer, err := out.CreateHeader(&zip.FileHeader{ 164 Name: name, 165 Method: method, 166 }) 167 if err != nil { 168 return err 169 } 170 171 // Only files have data, io.Copy will fail for directories. Header entry is required for both. 172 if !strings.HasSuffix(name, "/") { 173 if _, err := io.Copy(writer, in); err != nil { 174 return err 175 } 176 } 177 return nil 178} 179 180// Run is the entry point for repack. 181func Run() { 182 if in == nil && dir == nil { 183 log.Fatal("Flags -in or -dir must be specified.") 184 } 185 if out == "" { 186 log.Fatal("Flags -out must be specified.") 187 } 188 189 if b2i[filterR]+b2i[filterJarRes]+b2i[filterManifest] > 1 { 190 log.Fatal("Only one filter is allowed.") 191 } 192 193 filter := filterNone 194 if filterR { 195 filter = isRClass 196 } else if filterJarRes { 197 filter = isJavaRes 198 } else if filterManifest { 199 filter = isManifest 200 } 201 202 w, err := os.Create(out) 203 if err != nil { 204 log.Fatalf("os.Create(%q) failed: %v", out, err) 205 } 206 defer w.Close() 207 208 zipOut := zip.NewWriter(w) 209 defer zipOut.Close() 210 211 var filteredZipOut *zip.Writer 212 if filteredOut != "" { 213 w, err := os.Create(filteredOut) 214 if err != nil { 215 log.Fatalf("os.Create(%q) failed: %v", filteredOut, err) 216 } 217 defer w.Close() 218 filteredZipOut = zip.NewWriter(w) 219 defer filteredZipOut.Close() 220 } 221 222 method := zip.Store 223 if compress { 224 method = zip.Deflate 225 } 226 227 for _, d := range dir { 228 if err := repackDir(d, zipOut, filteredZipOut, filter, method); err != nil { 229 log.Fatal(err) 230 } 231 } 232 233 for _, f := range in { 234 file, err := os.Open(f) 235 if err != nil { 236 log.Fatalf("os.Open(%q) failed: %v", f, err) 237 } 238 fi, err := file.Stat() 239 if err != nil { 240 file.Close() 241 log.Fatalf("File.Stat() failed for %q: %v", f, err) 242 } 243 size := fi.Size() 244 // Skip empty Zip archives. An empty zip is 22 bytes contains only an EOCD. 245 // https://en.wikipedia.org/wiki/Zip_(file_format)#Limits 246 if size <= 22 { 247 continue 248 } 249 zipIn, err := zip.NewReader(file, size) 250 if err != nil { 251 file.Close() 252 log.Fatalf("zip.OpenReader(%q) failed: %v", f, err) 253 } 254 255 err = repackZip(zipIn, zipOut, filteredZipOut, filter, method) 256 file.Close() 257 if err != nil { 258 log.Fatal(err) 259 } 260 } 261} 262