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