001/* 002 * (C) Copyright 2006-2016 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Nuxeo - initial API and implementation 018 */ 019package org.nuxeo.common.utils; 020 021import static java.nio.charset.StandardCharsets.UTF_8; 022 023import java.io.BufferedInputStream; 024import java.io.BufferedOutputStream; 025import java.io.File; 026import java.io.FileInputStream; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.io.InputStream; 030import java.io.OutputStream; 031import java.net.URL; 032import java.util.ArrayList; 033import java.util.Enumeration; 034import java.util.List; 035import java.util.function.Function; 036import java.util.function.Predicate; 037import java.util.zip.ZipEntry; 038import java.util.zip.ZipFile; 039import java.util.zip.ZipInputStream; 040import java.util.zip.ZipOutputStream; 041 042import org.apache.commons.io.FileUtils; 043import org.apache.commons.io.IOUtils; 044import org.apache.commons.lang3.StringUtils; 045 046/** 047 * @author bstefanescu 048 */ 049public final class ZipUtils { 050 051 // This is an utility class 052 private ZipUtils() { 053 } 054 055 // _____________________________ ZIP ________________________________ 056 057 public static void _putDirectoryEntry(String entryName, ZipOutputStream out) throws IOException { 058 ZipEntry zentry = new ZipEntry(entryName + '/'); 059 out.putNextEntry(zentry); 060 out.closeEntry(); 061 } 062 063 public static void _putFileEntry(File file, String entryName, ZipOutputStream out) throws IOException { 064 try (FileInputStream in = new FileInputStream(file)) { 065 _zip(entryName, in, out); 066 } 067 } 068 069 public static void _zip(String entryName, InputStream in, ZipOutputStream out) throws IOException { 070 ZipEntry zentry = new ZipEntry(entryName); 071 out.putNextEntry(zentry); 072 // Transfer bytes from the input stream to the ZIP file 073 IOUtils.copy(in, out); 074 out.closeEntry(); 075 } 076 077 public static void _zip(String entryName, File file, ZipOutputStream out) throws IOException { 078 // System.out.println("Compressing "+entryName); 079 if (file.isDirectory()) { 080 entryName += '/'; 081 ZipEntry zentry = new ZipEntry(entryName); 082 out.putNextEntry(zentry); 083 out.closeEntry(); 084 File[] files = file.listFiles(); 085 for (File child : files) { 086 _zip(entryName + child.getName(), child, out); 087 } 088 } else { 089 InputStream in = null; 090 try { 091 in = new BufferedInputStream(new FileInputStream(file)); 092 _zip(entryName, in, out); 093 } finally { 094 if (in != null) { 095 in.close(); 096 } 097 } 098 } 099 } 100 101 public static void _zip(File[] files, ZipOutputStream out, String prefix) throws IOException { 102 String normalizedPrefix = normalizePrefix(prefix); 103 for (File file : files) { 104 String name = normalizedPrefix + file.getName(); 105 _zip(name, file, out); 106 } 107 } 108 109 public static void zip(File file, OutputStream out, String prefix) throws IOException { 110 prefix = normalizePrefix(prefix); 111 String name = normalizePrefix(prefix) + file.getName(); 112 try (ZipOutputStream zout = new ZipOutputStream(out)) { 113 _zip(name, file, zout); 114 } 115 } 116 117 /** 118 * @return the empty string if prefix is null or empty, otherwise append a '/' at the end if needed 119 */ 120 protected static String normalizePrefix(String prefix) { 121 String result = StringUtils.defaultString(prefix); 122 int len = result.length(); 123 if (len > 0 && result.charAt(len - 1) != '/') { 124 result += '/'; 125 } 126 return result; 127 } 128 129 public static void zip(File[] files, OutputStream out, String prefix) throws IOException { 130 try (ZipOutputStream zout = new ZipOutputStream(out)) { 131 _zip(files, zout, prefix); 132 } 133 } 134 135 public static void zip(File file, File zip) throws IOException { 136 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(zip))) { 137 zip(file, out, null); 138 } 139 } 140 141 public static void zip(File[] files, File zip) throws IOException { 142 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(zip))) { 143 zip(files, out, null); 144 } 145 } 146 147 public static void zip(File file, File zip, String prefix) throws IOException { 148 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(zip))) { 149 zip(file, out, prefix); 150 } 151 } 152 153 public static void zip(File[] files, File zip, String prefix) throws IOException { 154 try (OutputStream out = new BufferedOutputStream(new FileOutputStream(zip))) { 155 zip(files, out, prefix); 156 } 157 } 158 159 public static void zipFilesUsingPrefix(String prefix, File[] files, OutputStream out) throws IOException { 160 try (ZipOutputStream zout = new ZipOutputStream(out)) { 161 if (prefix != null && prefix.length() > 0) { 162 int p = prefix.indexOf('/'); 163 while (p > -1) { 164 _putDirectoryEntry(prefix.substring(0, p), zout); 165 p = prefix.indexOf(p + 1, '/'); 166 } 167 _putDirectoryEntry(prefix, zout); 168 prefix += '/'; 169 } else { 170 prefix = ""; 171 } 172 // prefix = prefix + '/'; 173 for (File file : files) { 174 _putFileEntry(file, prefix + file.getName(), zout); 175 } 176 } 177 } 178 179 // _____________________________ UNZIP ________________________________ 180 181 public static void unzip(String prefix, InputStream zipStream, File dir) throws IOException { 182 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(zipStream))) { 183 unzip(prefix, in, dir); 184 } 185 } 186 187 public static void unzip(InputStream zipStream, File dir) throws IOException { 188 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(zipStream))) { 189 unzip(in, dir); 190 } 191 } 192 193 public static void unzip(String prefix, URL zip, File dir) throws IOException { 194 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(zip.openStream()))) { 195 unzip(prefix, in, dir); 196 } 197 } 198 199 public static void unzip(URL zip, File dir) throws IOException { 200 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(zip.openStream()))) { 201 unzip(in, dir); 202 } 203 } 204 205 public static void unzip(String prefix, File zip, File dir) throws IOException { 206 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip)))) { 207 unzip(prefix, in, dir); 208 } 209 } 210 211 public static void unzip(File zip, File dir) throws IOException { 212 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip)))) { 213 unzip(in, dir); 214 } 215 } 216 217 public static void unzip(String prefix, ZipInputStream in, File dir) throws IOException { 218 unzip(in, dir, entry -> entry.getName().startsWith(prefix), name -> name.substring(prefix.length())); 219 } 220 221 public static void unzip(ZipInputStream in, File dir) throws IOException { 222 unzip(in, dir, entry -> true, Function.identity()); 223 } 224 225 private static void unzip(ZipInputStream in, File dir, Predicate<ZipEntry> filter, 226 Function<String, String> nameFormatter) throws IOException { 227 dir.mkdirs(); 228 ZipEntry entry; 229 while ((entry = in.getNextEntry()) != null) { 230 if (!entry.getName().contains("..") && filter.test(entry)) { 231 File file = new File(dir, nameFormatter.apply(entry.getName())); 232 if (entry.isDirectory()) { 233 file.mkdirs(); 234 } else { 235 file.getParentFile().mkdirs(); 236 try (FileOutputStream output = FileUtils.openOutputStream(file)) { 237 IOUtils.copy(in, output); 238 } 239 } 240 } 241 } 242 } 243 244 public static void unzipIgnoreDirs(ZipInputStream in, File dir) throws IOException { 245 unzip(in, dir, entry -> !entry.isDirectory(), Function.identity()); 246 } 247 248 public static void unzipIgnoreDirs(InputStream zipStream, File dir) throws IOException { 249 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(zipStream))) { 250 unzipIgnoreDirs(in, dir); 251 } 252 } 253 254 public static void unzip(File zip, File dir, PathFilter filter) throws IOException { 255 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip)))) { 256 unzip(in, dir, filter); 257 } 258 } 259 260 public static void unzip(ZipInputStream in, File dir, PathFilter filter) throws IOException { 261 if (filter == null) { 262 unzip(in, dir); 263 } else { 264 unzip(in, dir, toPredicate(filter), Function.identity()); 265 } 266 } 267 268 public static void unzip(String prefix, File zip, File dir, PathFilter filter) throws IOException { 269 try (ZipInputStream in = new ZipInputStream(new BufferedInputStream(new FileInputStream(zip)))) { 270 unzip(prefix, in, dir, filter); 271 } 272 } 273 274 public static void unzip(String prefix, ZipInputStream in, File dir, PathFilter filter) throws IOException { 275 if (filter == null) { 276 unzip(prefix, in, dir); 277 } else { 278 unzip(in, dir, toPredicate(filter).and(entry -> entry.getName().startsWith(prefix)), 279 name -> name.substring(prefix.length())); 280 } 281 } 282 283 private static Predicate<ZipEntry> toPredicate(PathFilter filter) { 284 return entry -> filter.accept(new org.nuxeo.common.utils.Path(entry.getName())); 285 } 286 287 // ________________ Entries ________________ 288 /** 289 * Unzip directly the entry. The returned InputStream has to be closed. 290 * 291 * @return the input stream of the desired entry - has to be closed by the caller, or null if not found 292 * @param file the source file 293 * @param entryName the entry name that has to be extracted 294 * @deprecated since 10.1 (unused and fails to close a ZipFile) 295 */ 296 @Deprecated 297 public static InputStream getEntryContentAsStream(File file, String entryName) throws IOException { 298 InputStream result = null; 299 ZipFile zip = new ZipFile(file); 300 ZipEntry entry = zip.getEntry(entryName); 301 if (entry != null) { 302 result = zip.getInputStream(entry); 303 } 304 return result; 305 } 306 307 /** 308 * Unzip directly the entry. 309 * 310 * @return the String content of the entry with name entryName 311 * @param file the source file 312 * @param entryName the entry name that has to be extracted 313 * @deprecated since 10.1 (unused and fails to close a ZipFile) 314 */ 315 @Deprecated 316 public static String getEntryContentAsString(File file, String entryName) throws IOException { 317 try (InputStream resultStream = getEntryContentAsStream(file, entryName)) { 318 return IOUtils.toString(resultStream, UTF_8); 319 } 320 } 321 322 /** 323 * Unzips directly the entry. 324 * 325 * @return The byte array content of the entry with name entryName 326 * @param file the source file 327 * @param entryName the entry name that has to be extracted 328 * @deprecated since 10.1 (unused and fails to close a ZipFile) 329 */ 330 @Deprecated 331 public static byte[] getEntryContentAsBytes(File file, String entryName) throws IOException { 332 try (InputStream resultStream = getEntryContentAsStream(file, entryName)) { 333 return IOUtils.toByteArray(resultStream); 334 } 335 } 336 337 /** 338 * Lists the entries on the zip file. 339 * 340 * @param file The zip file 341 * @return The list of entries 342 */ 343 public static List<String> getEntryNames(File file) throws IOException { 344 List<String> result = new ArrayList<>(); 345 try (ZipFile zip = new ZipFile(file)) { 346 Enumeration<? extends ZipEntry> entries = zip.entries(); 347 while (entries.hasMoreElements()) { 348 ZipEntry entry = entries.nextElement(); 349 result.add(entry.getName()); 350 } 351 } 352 return result; 353 } 354 355 /** 356 * Checks if a zip file contains a specified entry name. 357 * 358 * @param file the zip file 359 * @param entryName The content to be checked 360 * @return True if the file contains entryName. False otherwise 361 */ 362 public static boolean hasEntry(File file, String entryName) throws IOException { 363 List<String> elements = getEntryNames(file); 364 return elements.contains(entryName); 365 } 366 367 public static InputStream getEntryContentAsStream(InputStream stream, String entryName) throws IOException { 368 ZipInputStream zip = new ZipInputStream(stream); 369 ZipEntry entry = zip.getNextEntry(); 370 while (entry != null) { 371 if (entry.getName().equals(entryName)) { 372 return zip; 373 } 374 entry = zip.getNextEntry(); 375 } 376 return null; 377 } 378 379 public static String getEntryContentAsString(InputStream stream, String searchedEntryName) throws IOException { 380 try (InputStream resultStream = getEntryContentAsStream(stream, searchedEntryName)) { 381 return IOUtils.toString(resultStream, UTF_8); 382 } 383 } 384 385 public static byte[] getEntryContentAsBytes(InputStream stream, String searchedEntryName) throws IOException { 386 try (InputStream resultStream = getEntryContentAsStream(stream, searchedEntryName)) { 387 return IOUtils.toByteArray(resultStream); 388 } 389 } 390 391 public static List<String> getEntryNames(InputStream stream) throws IOException { 392 393 List<String> result = new ArrayList<>(); 394 try (ZipInputStream zip = new ZipInputStream(stream)) { 395 while (zip.available() == 1) { 396 ZipEntry entry = zip.getNextEntry(); 397 if (entry != null) { 398 result.add(entry.getName()); 399 } 400 } 401 } 402 return result; 403 } 404 405 public static boolean hasEntry(InputStream stream, String entryName) throws IOException { 406 List<String> elements = getEntryNames(stream); 407 return elements.contains(entryName); 408 } 409 410 public static InputStream getEntryContentAsStream(URL url, String entryName) throws IOException { 411 return getEntryContentAsStream(url.openStream(), entryName); 412 } 413 414 public static String getEntryContentAsString(URL url, String entryName) throws IOException { 415 try (InputStream resultStream = getEntryContentAsStream(url, entryName)) { 416 return IOUtils.toString(resultStream, UTF_8); 417 } 418 } 419 420 public static byte[] getEntryContentAsBytes(URL url, String entryName) throws IOException { 421 try (InputStream resultStream = getEntryContentAsStream(url, entryName)) { 422 return IOUtils.toByteArray(resultStream); 423 } 424 } 425 426 public static List<String> getEntryNames(URL url) throws IOException { 427 return getEntryNames(url.openStream()); 428 } 429 430 public static boolean hasEntry(URL url, String entryName) throws IOException { 431 return hasEntry(url.openStream(), entryName); 432 } 433 434 /** 435 * Checks if the content of the {@link InputStream} is a valid zip. 436 * The method does not close the stream. 437 * @param stream the {@link InputStream} to be validated 438 * @return true if the {@link InputStream} is a valid zip, false otherwise 439 */ 440 public static boolean isValid(InputStream stream) { 441 try (ZipInputStream zip = new ZipInputStream(stream)) { 442 return zip.getNextEntry() != null; 443 } catch (IOException e) { 444 return false; 445 } 446 } 447 448}