package com.quran.labs.androidquran.util; import android.support.annotation.VisibleForTesting; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import timber.log.Timber; public class ZipUtils { private static final int BUFFER_SIZE = 512; private static final int MAX_FILES = 2048; // Max number of files @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) static int MAX_UNZIPPED_SIZE = 0x1f400000; // Max size of unzipped data, 500MB /** * Unzip a file given the file, an item, and the listener * Does similar checks to those shown in rule 0's IDS04-J rule from: * https://www.securecoding.cert.org/confluence/display/java * @param zipFile the path to the zip file * @param destDirectory the directory to extract the file in * @param item any data object passed back to the listener * @param listener a progress listener * @param <T> the type of the item passed in * @return a boolean representing whether we succeeded to unzip the file or not */ public static <T> boolean unzipFile(String zipFile, String destDirectory, T item, ZipListener<T> listener){ try { File file = new File(zipFile); Timber.d("unzipping %s, size: %d", zipFile, file.length()); ZipFile zip = new ZipFile(file, ZipFile.OPEN_READ); int numberOfFiles = zip.size(); Enumeration<? extends ZipEntry> entries = zip.entries(); String canonicalPath = new File(destDirectory).getCanonicalPath(); long total = 0; int processedFiles = 0; while (entries.hasMoreElements()) { processedFiles++; ZipEntry entry = entries.nextElement(); File currentEntryFile = new File(destDirectory, entry.getName()); if (currentEntryFile.getCanonicalPath().startsWith(canonicalPath)) { if (entry.isDirectory()) { if (!currentEntryFile.exists()) { currentEntryFile.mkdirs(); } continue; } else if (currentEntryFile.exists()) { // delete files that already exist currentEntryFile.delete(); } InputStream is = zip.getInputStream(entry); FileOutputStream ostream = new FileOutputStream(currentEntryFile); int size; byte[] buf = new byte[BUFFER_SIZE]; while (total + BUFFER_SIZE <= MAX_UNZIPPED_SIZE && (size = is.read(buf)) > 0) { ostream.write(buf, 0, size); total += size; } is.close(); ostream.close(); if (processedFiles >= MAX_FILES || total >= MAX_UNZIPPED_SIZE) { throw new IllegalStateException("Invalid zip file."); } if (listener != null) { listener.onProcessingProgress(item, processedFiles, numberOfFiles); } } else { throw new IllegalStateException("Invalid zip file."); } } zip.close(); return true; } catch (IOException ioe) { Timber.e(ioe, "Error unzipping file"); return false; } } public interface ZipListener<T> { void onProcessingProgress(T obj, int processed, int total); } }