1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.tradefed.util; 17 18 import com.android.tradefed.log.LogUtil.CLog; 19 20 import java.io.BufferedInputStream; 21 import java.io.BufferedOutputStream; 22 import java.io.File; 23 import java.io.FileInputStream; 24 import java.io.FileOutputStream; 25 import java.io.IOException; 26 import java.io.InputStream; 27 import java.io.OutputStream; 28 import java.util.Enumeration; 29 import java.util.LinkedList; 30 import java.util.List; 31 import java.util.zip.GZIPOutputStream; 32 import java.util.zip.ZipEntry; 33 import java.util.zip.ZipException; 34 import java.util.zip.ZipFile; 35 import java.util.zip.ZipOutputStream; 36 37 /** 38 * A helper class for compression-related operations 39 */ 40 public class ZipUtil { 41 42 private static final String DEFAULT_DIRNAME = "dir"; 43 private static final String DEFAULT_FILENAME = "files"; 44 private static final String ZIP_EXTENSION = ".zip"; 45 46 /** 47 * Utility method to verify that a zip file is not corrupt. 48 * 49 * @param zipFile the {@link File} to check 50 * @param thorough Whether to attempt to fully extract the archive. If {@code false}, this 51 * method will fail to detect CRC errors in a well-formed archive. 52 * @throws IOException if the file could not be opened or read 53 * @return {@code false} if the file appears to be corrupt; {@code true} otherwise 54 */ isZipFileValid(File zipFile, boolean thorough)55 public static boolean isZipFileValid(File zipFile, boolean thorough) throws IOException { 56 if (zipFile != null && !zipFile.exists()) { 57 CLog.d("Zip file does not exist: %s", zipFile.getAbsolutePath()); 58 return false; 59 } 60 61 try (ZipFile z = new ZipFile(zipFile)) { 62 if (thorough) { 63 // Reading the entire file is the only way to detect CRC errors within the archive 64 final File extractDir = FileUtil.createTempDir("extract-" + zipFile.getName()); 65 try { 66 extractZip(z, extractDir); 67 } finally { 68 FileUtil.recursiveDelete(extractDir); 69 } 70 } 71 } catch (ZipException e) { 72 // File is likely corrupted 73 CLog.d("Detected corrupt zip file %s:", zipFile.getCanonicalPath()); 74 CLog.e(e); 75 return false; 76 } 77 78 return true; 79 } 80 81 /** 82 * Utility method to extract entire contents of zip file into given directory 83 * 84 * @param zipFile the {@link ZipFile} to extract 85 * @param destDir the local dir to extract file to 86 * @throws IOException if failed to extract file 87 */ extractZip(ZipFile zipFile, File destDir)88 public static void extractZip(ZipFile zipFile, File destDir) throws IOException { 89 Enumeration<? extends ZipEntry> entries = zipFile.entries(); 90 while (entries.hasMoreElements()) { 91 92 ZipEntry entry = entries.nextElement(); 93 File childFile = new File(destDir, entry.getName()); 94 childFile.getParentFile().mkdirs(); 95 if (entry.isDirectory()) { 96 continue; 97 } else { 98 FileUtil.writeToFile(zipFile.getInputStream(entry), childFile); 99 } 100 } 101 } 102 103 /** 104 * Utility method to extract one specific file from zip file into a tmp file 105 * 106 * @param zipFile the {@link ZipFile} to extract 107 * @param filePath the filePath of to extract 108 * @throws IOException if failed to extract file 109 * @return the {@link File} or null if not found 110 */ extractFileFromZip(ZipFile zipFile, String filePath)111 public static File extractFileFromZip(ZipFile zipFile, String filePath) throws IOException { 112 ZipEntry entry = zipFile.getEntry(filePath); 113 if (entry == null) { 114 return null; 115 } 116 File createdFile = FileUtil.createTempFile("extracted", 117 FileUtil.getExtension(filePath)); 118 FileUtil.writeToFile(zipFile.getInputStream(entry), createdFile); 119 return createdFile; 120 } 121 122 /** 123 * Utility method to create a temporary zip file containing the given directory and 124 * all its contents. 125 * 126 * @param dir the directory to zip 127 * @return a temporary zip {@link File} containing directory contents 128 * @throws IOException if failed to create zip file 129 */ createZip(File dir)130 public static File createZip(File dir) throws IOException { 131 return createZip(dir, DEFAULT_DIRNAME); 132 } 133 134 /** 135 * Utility method to create a temporary zip file containing the given directory and 136 * all its contents. 137 * 138 * @param dir the directory to zip 139 * @param name the base name of the zip file created without the extension. 140 * @return a temporary zip {@link File} containing directory contents 141 * @throws IOException if failed to create zip file 142 */ createZip(File dir, String name)143 public static File createZip(File dir, String name) throws IOException { 144 File zipFile = FileUtil.createTempFile(name, ZIP_EXTENSION); 145 createZip(dir, zipFile); 146 return zipFile; 147 } 148 149 /** 150 * Utility method to create a zip file containing the given directory and 151 * all its contents. 152 * 153 * @param dir the directory to zip 154 * @param zipFile the zip file to create - it should not already exist 155 * @throws IOException if failed to create zip file 156 */ createZip(File dir, File zipFile)157 public static void createZip(File dir, File zipFile) throws IOException { 158 ZipOutputStream out = null; 159 try { 160 FileOutputStream fileStream = new FileOutputStream(zipFile); 161 out = new ZipOutputStream(new BufferedOutputStream(fileStream)); 162 addToZip(out, dir, new LinkedList<String>()); 163 } catch (IOException e) { 164 zipFile.delete(); 165 throw e; 166 } catch (RuntimeException e) { 167 zipFile.delete(); 168 throw e; 169 } finally { 170 StreamUtil.close(out); 171 } 172 } 173 174 /** 175 * Utility method to create a temporary zip file containing the given files 176 * 177 * @param files list of files to zip 178 * @return a temporary zip {@link File} containing directory contents 179 * @throws IOException if failed to create zip file 180 */ createZip(List<File> files)181 public static File createZip(List<File> files) throws IOException { 182 return createZip(files, DEFAULT_FILENAME); 183 } 184 185 /** 186 * Utility method to create a temporary zip file containing the given files. 187 * 188 * @param files list of files to zip 189 * @param name the base name of the zip file created without the extension. 190 * @return a temporary zip {@link File} containing directory contents 191 * @throws IOException if failed to create zip file 192 */ createZip(List<File> files, String name)193 public static File createZip(List<File> files, String name) throws IOException { 194 File zipFile = FileUtil.createTempFile(name, ZIP_EXTENSION); 195 createZip(files, zipFile); 196 return zipFile; 197 } 198 199 /** 200 * Utility method to create a zip file containing the given files 201 * 202 * @param files list of files to zip 203 * @param zipFile the zip file to create - it should not already exist 204 * @throws IOException if failed to create zip file 205 */ createZip(List<File> files, File zipFile)206 public static void createZip(List<File> files, File zipFile) throws IOException { 207 ZipOutputStream out = null; 208 try { 209 FileOutputStream fileStream = new FileOutputStream(zipFile); 210 out = new ZipOutputStream(new BufferedOutputStream(fileStream)); 211 for (File file : files) { 212 addToZip(out, file, new LinkedList<String>()); 213 } 214 } catch (IOException|RuntimeException e) { 215 zipFile.delete(); 216 throw e; 217 } finally { 218 StreamUtil.close(out); 219 } 220 } 221 222 /** 223 * Recursively adds given file and its contents to ZipOutputStream 224 * 225 * @param out the {@link ZipOutputStream} 226 * @param file the {@link File} to add to the stream 227 * @param relativePathSegs the relative path of file, including separators 228 * @throws IOException if failed to add file to zip 229 */ addToZip(ZipOutputStream out, File file, List<String> relativePathSegs)230 public static void addToZip(ZipOutputStream out, File file, List<String> relativePathSegs) 231 throws IOException { 232 relativePathSegs.add(file.getName()); 233 if (file.isDirectory()) { 234 // note: it appears even on windows, ZipEntry expects '/' as a path separator 235 relativePathSegs.add("/"); 236 } 237 ZipEntry zipEntry = new ZipEntry(buildPath(relativePathSegs)); 238 out.putNextEntry(zipEntry); 239 if (file.isFile()) { 240 writeToStream(file, out); 241 } 242 out.closeEntry(); 243 if (file.isDirectory()) { 244 // recursively add contents 245 File[] subFiles = file.listFiles(); 246 if (subFiles == null) { 247 throw new IOException(String.format("Could not read directory %s", 248 file.getAbsolutePath())); 249 } 250 for (File subFile : subFiles) { 251 addToZip(out, subFile, relativePathSegs); 252 } 253 // remove the path separator 254 relativePathSegs.remove(relativePathSegs.size()-1); 255 } 256 // remove the last segment, added at beginning of method 257 relativePathSegs.remove(relativePathSegs.size()-1); 258 } 259 260 /** 261 * Close an open {@link ZipFile}, ignoring any exceptions. 262 * 263 * @param zipFile the file to close 264 */ closeZip(ZipFile zipFile)265 public static void closeZip(ZipFile zipFile) { 266 if (zipFile != null) { 267 try { 268 zipFile.close(); 269 } catch (IOException e) { 270 // ignore 271 } 272 } 273 } 274 275 /** 276 * Helper method to create a gzipped version of a single file. 277 * 278 * @param file the original file 279 * @param gzipFile the file to place compressed contents in 280 * @throws IOException 281 */ gzipFile(File file, File gzipFile)282 public static void gzipFile(File file, File gzipFile) throws IOException { 283 GZIPOutputStream out = null; 284 try { 285 FileOutputStream fileStream = new FileOutputStream(gzipFile); 286 out = new GZIPOutputStream(new BufferedOutputStream(fileStream, 64 * 1024)); 287 writeToStream(file, out); 288 } catch (IOException e) { 289 gzipFile.delete(); 290 throw e; 291 } catch (RuntimeException e) { 292 gzipFile.delete(); 293 throw e; 294 } finally { 295 StreamUtil.close(out); 296 } 297 } 298 299 /** 300 * Helper method to write input file contents to output stream. 301 * 302 * @param file the input {@link File} 303 * @param out the {@link OutputStream} 304 * 305 * @throws IOException 306 */ writeToStream(File file, OutputStream out)307 private static void writeToStream(File file, OutputStream out) throws IOException { 308 InputStream inputStream = null; 309 try { 310 inputStream = new BufferedInputStream(new FileInputStream(file)); 311 StreamUtil.copyStreams(inputStream, out); 312 } finally { 313 StreamUtil.close(inputStream); 314 } 315 } 316 317 /** 318 * Builds a file system path from a stack of relative path segments 319 * 320 * @param relativePathSegs the list of relative paths 321 * @return a {@link String} containing all relativePathSegs 322 */ buildPath(List<String> relativePathSegs)323 private static String buildPath(List<String> relativePathSegs) { 324 StringBuilder pathBuilder = new StringBuilder(); 325 for (String segment : relativePathSegs) { 326 pathBuilder.append(segment); 327 } 328 return pathBuilder.toString(); 329 } 330 331 /** 332 * Extract a zip file to a temp directory prepended with a string 333 * 334 * @param zipFile the zip file to extract 335 * @param nameHint a prefix for the temp directory 336 * @return a {@link File} pointing to the temp directory 337 */ extractZipToTemp(File zipFile, String nameHint)338 public static File extractZipToTemp(File zipFile, String nameHint) 339 throws IOException, ZipException { 340 File localRootDir = FileUtil.createTempDir(nameHint); 341 try (ZipFile zip = new ZipFile(zipFile)) { 342 extractZip(zip, localRootDir); 343 return localRootDir; 344 } catch (IOException e) { 345 // clean tmp file since we couldn't extract. 346 FileUtil.recursiveDelete(localRootDir); 347 throw e; 348 } 349 } 350 } 351