blob: f6b6be5b728f9a1e99552d141a84b1fe2763ca5c [file] [log] [blame]
// 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;
}
}