// Copyright 2008, The Android Open Source Project // // 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. // 3. Neither the name of Google Inc. nor the names of its contributors may be // used to endorse or promote products derived from this software without // specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 android.webkit.gears; import android.os.StatFs; import android.util.Log; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.zip.CRC32; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; /** * A class that can inflate a zip archive. */ public final class ZipInflater { /** * Logging tag */ private static final String TAG = "Gears-J-ZipInflater"; /** * The size of the buffer used to read from the archive. */ private static final int BUFFER_SIZE_BYTES = 32 * 1024; // 32 KB. /** * The path navigation component (i.e. "../"). */ private static final String PATH_NAVIGATION_COMPONENT = ".." + File.separator; /** * The root of the data partition. */ private static final String DATA_PARTITION_ROOT = "/data"; /** * We need two be able to store two versions of gears in parallel: * - the zipped version * - the unzipped version, which will be loaded next time the browser is started. * We are conservative and do not attempt to unpack unless there enough free * space on the device to store 4 times the new Gears size. */ private static final long SIZE_MULTIPLIER = 4; /** * Unzips the archive with the given name. * @param filename is the name of the zip to inflate. * @param path is the path where the zip should be unpacked. It must contain * a trailing separator, or the extraction will fail. * @return true if the extraction is successful and false otherwise. */ public static boolean inflate(String filename, String path) { Log.i(TAG, "Extracting " + filename + " to " + path); // Check that the path ends with a separator. if (!path.endsWith(File.separator)) { Log.e(TAG, "Path missing trailing separator: " + path); return false; } boolean result = false; // Use a ZipFile to get an enumeration of the entries and // calculate the overall uncompressed size of the archive. Also // check for existing files or directories that have the same // name as the entries in the archive. Also check for invalid // entry names (e.g names that attempt to navigate to the // parent directory). ZipInputStream zipStream = null; long uncompressedSize = 0; try { ZipFile zipFile = new ZipFile(filename); try { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = (ZipEntry) entries.nextElement(); uncompressedSize += entry.getSize(); // Check against entry names that may attempt to navigate // out of the destination directory. if (entry.getName().indexOf(PATH_NAVIGATION_COMPONENT) >= 0) { throw new IOException("Illegal entry name: " + entry.getName()); } // Check against entries with the same name as pre-existing files or // directories. File file = new File(path + entry.getName()); if (file.exists()) { // A file or directory with the same name already exist. // This must not happen, so we treat this as an error. throw new IOException( "A file or directory with the same name already exists."); } } } finally { zipFile.close(); } Log.i(TAG, "Determined uncompressed size: " + uncompressedSize); // Check we have enough space to unpack this archive. if (freeSpace() <= uncompressedSize * SIZE_MULTIPLIER) { throw new IOException("Not enough space to unpack this archive."); } zipStream = new ZipInputStream( new BufferedInputStream(new FileInputStream(filename))); ZipEntry entry; int counter; byte buffer[] = new byte[BUFFER_SIZE_BYTES]; // Iterate through the entries and write each of them to a file. while ((entry = zipStream.getNextEntry()) != null) { File file = new File(path + entry.getName()); if (entry.isDirectory()) { // If the entry denotes a directory, we need to create a // directory with the same name. file.mkdirs(); } else { CRC32 checksum = new CRC32(); BufferedOutputStream output = new BufferedOutputStream( new FileOutputStream(file), BUFFER_SIZE_BYTES); try { // Read the entry and write it to the file. while ((counter = zipStream.read(buffer, 0, BUFFER_SIZE_BYTES)) != -1) { output.write(buffer, 0, counter); checksum.update(buffer, 0, counter); } output.flush(); } finally { output.close(); } if (checksum.getValue() != entry.getCrc()) { throw new IOException( "Integrity check failed for: " + entry.getName()); } } zipStream.closeEntry(); } result = true; } catch (FileNotFoundException ex) { Log.e(TAG, "The zip file could not be found. " + ex); } catch (IOException ex) { Log.e(TAG, "Could not read or write an entry. " + ex); } catch(IllegalArgumentException ex) { Log.e(TAG, "Could not create the BufferedOutputStream. " + ex); } finally { if (zipStream != null) { try { zipStream.close(); } catch (IOException ex) { // Ignored. } } // Discard any exceptions and return the result to the native side. return result; } } private static final long freeSpace() { StatFs data_partition = new StatFs(DATA_PARTITION_ROOT); long freeSpace = data_partition.getAvailableBlocks() * data_partition.getBlockSize(); Log.i(TAG, "Free space on the data partition: " + freeSpace); return freeSpace; } }