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.URL;
030import java.util.ArrayList;
031import java.util.Enumeration;
032import java.util.List;
033import java.util.function.Function;
034import java.util.function.Predicate;
035import java.util.zip.ZipEntry;
036import java.util.zip.ZipFile;
037import java.util.zip.ZipInputStream;
038import java.util.zip.ZipOutputStream;
039
040import org.apache.commons.io.Charsets;
041import org.apache.commons.io.FileUtils;
042import org.apache.commons.io.IOUtils;
043
044/**
045 * @author bstefanescu
046 */
047public final class ZipUtils {
048
049    // This is an utility class
050    private ZipUtils() {
051    }
052
053    // _____________________________ ZIP ________________________________
054
055    public static void _putDirectoryEntry(String entryName, ZipOutputStream out) throws IOException {
056        ZipEntry zentry = new ZipEntry(entryName + '/');
057        out.putNextEntry(zentry);
058        out.closeEntry();
059    }
060
061    public static void _putFileEntry(File file, String entryName, ZipOutputStream out) throws IOException {
062        try (FileInputStream in = new FileInputStream(file)) {
063            _zip(entryName, in, out);
064        }
065    }
066
067    public static void _zip(String entryName, InputStream in, ZipOutputStream out) throws IOException {
068        ZipEntry zentry = new ZipEntry(entryName);
069        out.putNextEntry(zentry);
070        // Transfer bytes from the input stream to the ZIP file
071        IOUtils.copy(in, out);
072        out.closeEntry();
073    }
074
075    public static void _zip(String entryName, File file, ZipOutputStream out) throws IOException {
076        // System.out.println("Compressing "+entryName);
077        if (file.isDirectory()) {
078            entryName += '/';
079            ZipEntry zentry = new ZipEntry(entryName);
080            out.putNextEntry(zentry);
081            out.closeEntry();
082            File[] files = file.listFiles();
083            for (int i = 0, len = files.length; i < len; i++) {
084                _zip(entryName + files[i].getName(), files[i], out);
085            }
086        } else {
087            InputStream in = null;
088            try {
089                in = new BufferedInputStream(new FileInputStream(file));
090                _zip(entryName, in, out);
091            } finally {
092                if (in != null) {
093                    in.close();
094                }
095            }
096        }
097    }
098
099    public static void _zip(File[] files, ZipOutputStream out, String prefix) throws IOException {
100        if (prefix != null) {
101            int len = prefix.length();
102            if (len == 0) {
103                prefix = null;
104            } else if (prefix.charAt(len - 1) != '/') {
105                prefix += '/';
106            }
107        }
108        for (int i = 0, len = files.length; i < len; i++) {
109            String name = prefix != null ? prefix + files[i].getName() : files[i].getName();
110            _zip(name, files[i], out);
111        }
112    }
113
114    public static void zip(File file, OutputStream out, String prefix) throws IOException {
115        if (prefix != null) {
116            int len = prefix.length();
117            if (len == 0) {
118                prefix = null;
119            } else if (prefix.charAt(len - 1) != '/') {
120                prefix += '/';
121            }
122        }
123        String name = prefix != null ? prefix + file.getName() : file.getName();
124        try (ZipOutputStream zout = new ZipOutputStream(out)) {
125            _zip(name, file, zout);
126        }
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 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     */
295    public static InputStream getEntryContentAsStream(File file, String entryName) throws IOException {
296        InputStream result = null;
297        ZipFile zip = new ZipFile(file);
298        ZipEntry entry = zip.getEntry(entryName);
299        if (entry != null) {
300            result = zip.getInputStream(entry);
301        }
302        return result;
303    }
304
305    /**
306     * Unzip directly the entry.
307     *
308     * @return the String content of the entry with name entryName
309     * @param file the source file
310     * @param entryName the entry name that has to be extracted
311     */
312    public static String getEntryContentAsString(File file, String entryName) throws IOException {
313        try (InputStream resultStream = getEntryContentAsStream(file, entryName)) {
314            return IOUtils.toString(resultStream, Charsets.UTF_8);
315        }
316    }
317
318    /**
319     * Unzips directly the entry.
320     *
321     * @return The byte array content of the entry with name entryName
322     * @param file the source file
323     * @param entryName the entry name that has to be extracted
324     */
325    public static byte[] getEntryContentAsBytes(File file, String entryName) throws IOException {
326        try (InputStream resultStream = getEntryContentAsStream(file, entryName)) {
327            return IOUtils.toByteArray(resultStream);
328        }
329    }
330
331    /**
332     * Lists the entries on the zip file.
333     *
334     * @param file The zip file
335     * @return The list of entries
336     */
337    public static List<String> getEntryNames(File file) throws IOException {
338        List<String> result = new ArrayList<String>();
339        try (ZipFile zip = new ZipFile(file)) {
340            Enumeration<? extends ZipEntry> entries = zip.entries();
341            while (entries.hasMoreElements()) {
342                ZipEntry entry = entries.nextElement();
343                result.add(entry.getName());
344            }
345        }
346        return result;
347    }
348
349    /**
350     * Checks if a zip file contains a specified entry name.
351     *
352     * @param file the zip file
353     * @param entryName The content to be checked
354     * @return True if the file contains entryName. False otherwise
355     */
356    public static boolean hasEntry(File file, String entryName) throws IOException {
357        List<String> elements = getEntryNames(file);
358        return elements.contains(entryName);
359    }
360
361    public static InputStream getEntryContentAsStream(InputStream stream, String entryName) throws IOException {
362        ZipInputStream zip = new ZipInputStream(stream);
363        ZipEntry entry = zip.getNextEntry();
364        while (entry != null) {
365            if (entry.getName().equals(entryName)) {
366                return zip;
367            }
368            entry = zip.getNextEntry();
369        }
370        return null;
371    }
372
373    public static String getEntryContentAsString(InputStream stream, String searchedEntryName) throws IOException {
374        try (InputStream resultStream = getEntryContentAsStream(stream, searchedEntryName)) {
375            return IOUtils.toString(resultStream, Charsets.UTF_8);
376        }
377    }
378
379    public static byte[] getEntryContentAsBytes(InputStream stream, String searchedEntryName) throws IOException {
380        try (InputStream resultStream = getEntryContentAsStream(stream, searchedEntryName)) {
381            return IOUtils.toByteArray(resultStream);
382        }
383    }
384
385    public static List<String> getEntryNames(InputStream stream) throws IOException {
386
387        List<String> result = new ArrayList<String>();
388        try (ZipInputStream zip = new ZipInputStream(stream)) {
389            while (zip.available() == 1) {
390                ZipEntry entry = zip.getNextEntry();
391                if (entry != null) {
392                    result.add(entry.getName());
393                }
394            }
395        }
396        return result;
397    }
398
399    public static boolean hasEntry(InputStream stream, String entryName) throws IOException {
400        List<String> elements = getEntryNames(stream);
401        return elements.contains(entryName);
402    }
403
404    public static InputStream getEntryContentAsStream(URL url, String entryName) throws IOException {
405        return getEntryContentAsStream(url.openStream(), entryName);
406    }
407
408    public static String getEntryContentAsString(URL url, String entryName) throws IOException {
409        try (InputStream resultStream = getEntryContentAsStream(url, entryName)) {
410            return IOUtils.toString(resultStream, Charsets.UTF_8);
411        }
412    }
413
414    public static byte[] getEntryContentAsBytes(URL url, String entryName) throws IOException {
415        try (InputStream resultStream = getEntryContentAsStream(url, entryName)) {
416            return IOUtils.toByteArray(resultStream);
417        }
418    }
419
420    public static List<String> getEntryNames(URL url) throws IOException {
421        return getEntryNames(url.openStream());
422    }
423
424    public static boolean hasEntry(URL url, String entryName) throws IOException {
425        return hasEntry(url.openStream(), entryName);
426    }
427
428}