001/* 002 * SonarQube 003 * Copyright (C) 2009-2017 SonarSource SA 004 * mailto:info AT sonarsource DOT com 005 * 006 * This program is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020package org.sonar.api.utils; 021 022import java.io.BufferedInputStream; 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.FileOutputStream; 026import java.io.IOException; 027import java.io.InputStream; 028import java.io.OutputStream; 029import java.util.Enumeration; 030import java.util.function.Predicate; 031import java.util.zip.ZipEntry; 032import java.util.zip.ZipFile; 033import java.util.zip.ZipInputStream; 034import java.util.zip.ZipOutputStream; 035import org.apache.commons.io.FileUtils; 036import org.apache.commons.io.IOUtils; 037 038/** 039 * Utility to zip directories and unzip files. 040 * 041 * @since 1.10 042 */ 043public final class ZipUtils { 044 045 private static final String ERROR_CREATING_DIRECTORY = "Error creating directory: "; 046 047 private ZipUtils() { 048 // only static methods 049 } 050 051 /** 052 * Unzip a file into a directory. The directory is created if it does not exist. 053 * 054 * @return the target directory 055 */ 056 public static File unzip(File zip, File toDir) throws IOException { 057 return unzip(zip, toDir, (Predicate<ZipEntry>) ze -> true); 058 } 059 060 public static File unzip(InputStream zip, File toDir) throws IOException { 061 return unzip(zip, toDir, (Predicate<ZipEntry>) ze -> true); 062 } 063 064 /** 065 * @deprecated replaced by {@link #unzip(InputStream, File, Predicate)} in 6.2. 066 */ 067 @Deprecated 068 public static File unzip(InputStream stream, File toDir, ZipEntryFilter filter) throws IOException { 069 return unzip(stream, toDir, new ZipEntryFilterDelegate(filter)); 070 } 071 072 /** 073 * Unzip a file to a directory. 074 * 075 * @param stream the zip input file 076 * @param toDir the target directory. It is created if needed. 077 * @param filter filter zip entries so that only a subset of directories/files can be 078 * extracted to target directory. 079 * @return the parameter {@code toDir} 080 * @since 6.2 081 */ 082 public static File unzip(InputStream stream, File toDir, Predicate<ZipEntry> filter) throws IOException { 083 if (!toDir.exists()) { 084 FileUtils.forceMkdir(toDir); 085 } 086 087 ZipInputStream zipStream = new ZipInputStream(stream); 088 try { 089 ZipEntry entry; 090 while ((entry = zipStream.getNextEntry()) != null) { 091 if (filter.test(entry)) { 092 unzipEntry(entry, zipStream, toDir); 093 } 094 } 095 return toDir; 096 097 } finally { 098 zipStream.close(); 099 } 100 } 101 102 private static void unzipEntry(ZipEntry entry, ZipInputStream zipStream, File toDir) throws IOException { 103 File to = new File(toDir, entry.getName()); 104 if (entry.isDirectory()) { 105 throwExceptionIfDirectoryIsNotCreatable(to); 106 } else { 107 File parent = to.getParentFile(); 108 throwExceptionIfDirectoryIsNotCreatable(parent); 109 copy(zipStream, to); 110 } 111 } 112 113 private static void throwExceptionIfDirectoryIsNotCreatable(File to) throws IOException { 114 if (!to.exists() && !to.mkdirs()) { 115 throw new IOException(ERROR_CREATING_DIRECTORY + to); 116 } 117 } 118 119 /** 120 * @deprecated replaced by {@link #unzip(File, File, Predicate)} in 6.2. 121 */ 122 @Deprecated 123 public static File unzip(File zip, File toDir, ZipEntryFilter filter) throws IOException { 124 return unzip(zip, toDir, new ZipEntryFilterDelegate(filter)); 125 } 126 127 /** 128 * Unzip a file to a directory. 129 * 130 * @param zip the zip file. It must exist. 131 * @param toDir the target directory. It is created if needed. 132 * @param filter filter zip entries so that only a subset of directories/files can be 133 * extracted to target directory. 134 * @return the parameter {@code toDir} 135 * @since 6.2 136 */ 137 public static File unzip(File zip, File toDir, Predicate<ZipEntry> filter) throws IOException { 138 if (!toDir.exists()) { 139 FileUtils.forceMkdir(toDir); 140 } 141 142 ZipFile zipFile = new ZipFile(zip); 143 try { 144 Enumeration<? extends ZipEntry> entries = zipFile.entries(); 145 while (entries.hasMoreElements()) { 146 ZipEntry entry = entries.nextElement(); 147 if (filter.test(entry)) { 148 File to = new File(toDir, entry.getName()); 149 if (entry.isDirectory()) { 150 throwExceptionIfDirectoryIsNotCreatable(to); 151 } else { 152 File parent = to.getParentFile(); 153 throwExceptionIfDirectoryIsNotCreatable(parent); 154 copy(zipFile, entry, to); 155 } 156 } 157 } 158 return toDir; 159 160 } finally { 161 zipFile.close(); 162 } 163 } 164 165 private static void copy(ZipInputStream zipStream, File to) throws IOException { 166 FileOutputStream fos = null; 167 try { 168 fos = new FileOutputStream(to); 169 IOUtils.copy(zipStream, fos); 170 } finally { 171 IOUtils.closeQuietly(fos); 172 } 173 } 174 175 private static void copy(ZipFile zipFile, ZipEntry entry, File to) throws IOException { 176 FileOutputStream fos = new FileOutputStream(to); 177 InputStream input = null; 178 try { 179 input = zipFile.getInputStream(entry); 180 IOUtils.copy(input, fos); 181 } finally { 182 IOUtils.closeQuietly(input); 183 IOUtils.closeQuietly(fos); 184 } 185 } 186 187 public static void zipDir(File dir, File zip) throws IOException { 188 OutputStream out = null; 189 ZipOutputStream zout = null; 190 try { 191 out = FileUtils.openOutputStream(zip); 192 zout = new ZipOutputStream(out); 193 doZipDir(dir, zout); 194 195 } finally { 196 IOUtils.closeQuietly(zout); 197 IOUtils.closeQuietly(out); 198 } 199 } 200 201 private static void doZip(String entryName, InputStream in, ZipOutputStream out) throws IOException { 202 ZipEntry entry = new ZipEntry(entryName); 203 out.putNextEntry(entry); 204 IOUtils.copy(in, out); 205 out.closeEntry(); 206 } 207 208 private static void doZip(String entryName, File file, ZipOutputStream out) throws IOException { 209 if (file.isDirectory()) { 210 entryName += "/"; 211 ZipEntry entry = new ZipEntry(entryName); 212 out.putNextEntry(entry); 213 out.closeEntry(); 214 File[] files = file.listFiles(); 215 // java.io.File#listFiles() returns null if object is a directory (not possible here) or if 216 // an I/O error occurs (weird!) 217 if (files == null) { 218 throw new IllegalStateException("Fail to list files of directory " + file.getAbsolutePath()); 219 } 220 for (File f : files) { 221 doZip(entryName + f.getName(), f, out); 222 } 223 224 } else { 225 try (InputStream in = new BufferedInputStream(new FileInputStream(file))) { 226 doZip(entryName, in, out); 227 } 228 } 229 } 230 231 private static void doZipDir(File dir, ZipOutputStream out) throws IOException { 232 File[] children = dir.listFiles(); 233 if (children == null) { 234 throw new IllegalStateException("Fail to list files of directory " + dir.getAbsolutePath()); 235 } 236 for (File child : children) { 237 doZip(child.getName(), child, out); 238 } 239 } 240 241 /** 242 * @see #unzip(File, File, Predicate) 243 * @deprecated replaced by {@link Predicate<ZipEntry>} in 6.2. 244 */ 245 @Deprecated 246 @FunctionalInterface 247 public interface ZipEntryFilter { 248 boolean accept(ZipEntry entry); 249 } 250 251 private static class ZipEntryFilterDelegate implements Predicate<ZipEntry> { 252 private final ZipEntryFilter delegate; 253 254 private ZipEntryFilterDelegate(ZipEntryFilter delegate) { 255 this.delegate = delegate; 256 } 257 258 @Override 259 public boolean test(ZipEntry zipEntry) { 260 return delegate.accept(zipEntry); 261 } 262 } 263}