// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.io; import jodd.util.StringPool; import jodd.util.StringUtil; import jodd.util.Wildcard; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; /** * Performs zip/gzip/zlib operations on files and directories. * These are just tools over existing <code>java.util.zip</code> classes, * meaning that existing behavior and bugs are persisted. * Most common issue is not being able to use UTF8 in file names, * because implementation uses old ZIP format that supports only * IBM Code Page 437. This bug was resolved in JDK7: * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4244499 */ public class ZipUtil { public static final String ZIP_EXT = ".zip"; public static final String GZIP_EXT = ".gz"; public static final String ZLIB_EXT = ".zlib"; // ---------------------------------------------------------------- deflate /** * Compresses a file into zlib archive. */ public static File zlib(String file) throws IOException { return zlib(new File(file)); } /** * Compresses a file into zlib archive. */ public static File zlib(File file) throws IOException { if (file.isDirectory()) { throw new IOException("Can't zlib folder"); } FileInputStream fis = new FileInputStream(file); Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION); String zlibFileName = file.getAbsolutePath() + ZLIB_EXT; DeflaterOutputStream dos = new DeflaterOutputStream(new FileOutputStream(zlibFileName), deflater); try { StreamUtil.copy(fis, dos); } finally { StreamUtil.close(dos); StreamUtil.close(fis); } return new File(zlibFileName); } // ---------------------------------------------------------------- gzip /** * Compresses a file into gzip archive. */ public static File gzip(String fileName) throws IOException { return gzip(new File(fileName)); } /** * Compresses a file into gzip archive. */ public static File gzip(File file) throws IOException { if (file.isDirectory()) { throw new IOException("Can't gzip folder"); } FileInputStream fis = new FileInputStream(file); String gzipName = file.getAbsolutePath() + GZIP_EXT; GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream(gzipName)); try { StreamUtil.copy(fis, gzos); } finally { StreamUtil.close(gzos); StreamUtil.close(fis); } return new File(gzipName); } /** * Decompress gzip archive. */ public static File ungzip(String file) throws IOException { return ungzip(new File(file)); } /** * Decompress gzip archive. */ public static File ungzip(File file) throws IOException { String outFileName = FileNameUtil.removeExtension(file.getAbsolutePath()); File out = new File(outFileName); out.createNewFile(); FileOutputStream fos = new FileOutputStream(out); GZIPInputStream gzis = new GZIPInputStream(new FileInputStream(file)); try { StreamUtil.copy(gzis, fos); } finally { StreamUtil.close(fos); StreamUtil.close(gzis); } return out; } // ---------------------------------------------------------------- zip /** * Zips a file or a folder. * @see #zip(java.io.File) */ public static File zip(String file) throws IOException { return zip(new File(file)); } /** * Zips a file or a folder. If adding a folder, all its content will be added. */ public static File zip(File file) throws IOException { String zipFile = file.getAbsolutePath() + ZIP_EXT; return ZipBuilder.createZipFile(zipFile) .add(file).recursive().save() .toZipFile(); } // ---------------------------------------------------------------- unzip /** * Lists zip content. */ public static List<String> listZip(File zipFile) throws IOException { List<String> entries = new ArrayList<>(); ZipFile zip = new ZipFile(zipFile); Enumeration zipEntries = zip.entries(); while (zipEntries.hasMoreElements()) { ZipEntry entry = (ZipEntry) zipEntries.nextElement(); String entryName = entry.getName(); entries.add(entryName); } return Collections.unmodifiableList(entries); } /** * Extracts zip file content to the target directory. * @see #unzip(java.io.File, java.io.File, String...) */ public static void unzip(String zipFile, String destDir, String... patterns) throws IOException { unzip(new File(zipFile), new File(destDir), patterns); } /** * Extracts zip file to the target directory. If patterns are provided * only matched paths are extracted. * * @param zipFile zip file * @param destDir destination directory * @param patterns optional wildcard patterns of files to extract, may be <code>null</code> */ public static void unzip(File zipFile, File destDir, String... patterns) throws IOException { ZipFile zip = new ZipFile(zipFile); Enumeration zipEntries = zip.entries(); while (zipEntries.hasMoreElements()) { ZipEntry entry = (ZipEntry) zipEntries.nextElement(); String entryName = entry.getName(); if (patterns != null && patterns.length > 0) { if (Wildcard.matchPathOne(entryName, patterns) == -1) { continue; } } File file = (destDir != null) ? new File(destDir, entryName) : new File(entryName); if (entry.isDirectory()) { if (!file.mkdirs()) { if (!file.isDirectory()) { throw new IOException("Failed to create directory: " + file); } } } else { File parent = file.getParentFile(); if (parent != null && !parent.exists()) { if (!parent.mkdirs()) { if (!file.isDirectory()) { throw new IOException("Failed to create directory: " + parent); } } } InputStream in = zip.getInputStream(entry); OutputStream out = null; try { out = new FileOutputStream(file); StreamUtil.copy(in, out); } finally { StreamUtil.close(out); StreamUtil.close(in); } } } close(zip); } // ---------------------------------------------------------------- zip stream /** * Adds single entry to ZIP output stream. * * @param zos zip output stream * @param file file or folder to add * @param path relative path of file entry; if <code>null</code> files name will be used instead * @param comment optional comment * @param recursive when set to <code>true</code> content of added folders will be added, too */ public static void addToZip(ZipOutputStream zos, File file, String path, String comment, boolean recursive) throws IOException { if (!file.exists()) { throw new FileNotFoundException(file.toString()); } if (path == null) { path = file.getName(); } while (path.length() != 0 && path.charAt(0) == '/') { path = path.substring(1); } boolean isDir = file.isDirectory(); if (isDir) { // add folder record if (!StringUtil.endsWithChar(path, '/')) { path += '/'; } } ZipEntry zipEntry = new ZipEntry(path); zipEntry.setTime(file.lastModified()); if (comment != null) { zipEntry.setComment(comment); } if (isDir) { zipEntry.setSize(0); zipEntry.setCrc(0); } zos.putNextEntry(zipEntry); if (!isDir) { InputStream is = new FileInputStream(file); try { StreamUtil.copy(is, zos); } finally { StreamUtil.close(is); } } zos.closeEntry(); // continue adding if (recursive && file.isDirectory()) { boolean noRelativePath = StringUtil.isEmpty(path); final File[] children = file.listFiles(); if (children != null && children.length != 0) { for (File child : children) { String childRelativePath = (noRelativePath ? StringPool.EMPTY : path) + child.getName(); addToZip(zos, child, childRelativePath, comment, recursive); } } } } /** * Adds byte content into the zip as a file. */ public static void addToZip(ZipOutputStream zos, byte[] content, String path, String comment) throws IOException { while (path.length() != 0 && path.charAt(0) == '/') { path = path.substring(1); } if (StringUtil.endsWithChar(path, '/')) { path = path.substring(0, path.length() - 1); } ZipEntry zipEntry = new ZipEntry(path); zipEntry.setTime(System.currentTimeMillis()); if (comment != null) { zipEntry.setComment(comment); } zos.putNextEntry(zipEntry); InputStream is = new ByteArrayInputStream(content); try { StreamUtil.copy(is, zos); } finally { StreamUtil.close(is); } zos.closeEntry(); } public static void addFolderToZip(ZipOutputStream zos, String path, String comment) throws IOException { while (path.length() != 0 && path.charAt(0) == '/') { path = path.substring(1); } // add folder record if (!StringUtil.endsWithChar(path, '/')) { path += '/'; } ZipEntry zipEntry = new ZipEntry(path); zipEntry.setTime(System.currentTimeMillis()); if (comment != null) { zipEntry.setComment(comment); } zipEntry.setSize(0); zipEntry.setCrc(0); zos.putNextEntry(zipEntry); zos.closeEntry(); } // ---------------------------------------------------------------- close /** * Closes zip file safely. */ public static void close(ZipFile zipFile) { if (zipFile != null) { try { zipFile.close(); } catch (IOException ioex) { // ignore } } } }