diff options
author | Maurice Chu <mochu@google.com> | 2013-11-27 12:56:14 -0800 |
---|---|---|
committer | Maurice Chu <mochu@google.com> | 2013-11-27 12:56:14 -0800 |
commit | f6d1f23926672c8dd61da515f8d1bcb37ef4292d (patch) | |
tree | 2ee7029a869d3e5eab7600d54c136ed6d89e3cb2 /library/src | |
parent | 52eafa01b1e61e410cb4c5609eacee93c2a3e853 (diff) | |
download | multidex-f6d1f23926672c8dd61da515f8d1bcb37ef4292d.tar.gz |
Verify secondary dex zip file is a zip file and retry
Also, print out the SHA1 digest of the zip file for all
attempts at extracting the zip file.
Bug: 11895788
Change-Id: I4170c2362aa8370fd13bc7bed62f2e6eb3223768
Diffstat (limited to 'library/src')
-rw-r--r-- | library/src/android/support/multidex/MultiDexExtractor.java | 120 |
1 files changed, 118 insertions, 2 deletions
diff --git a/library/src/android/support/multidex/MultiDexExtractor.java b/library/src/android/support/multidex/MultiDexExtractor.java index 05d237a..df82e08 100644 --- a/library/src/android/support/multidex/MultiDexExtractor.java +++ b/library/src/android/support/multidex/MultiDexExtractor.java @@ -22,13 +22,17 @@ import android.util.Log; import java.io.Closeable; import java.io.File; import java.io.FileFilter; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.zip.ZipEntry; +import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; @@ -49,6 +53,12 @@ final class MultiDexExtractor { private static final String EXTRACTED_NAME_EXT = ".classes"; private static final String EXTRACTED_SUFFIX = ".zip"; + private static final int MAX_EXTRACT_ATTEMPTS = 3; + private static final int MAX_ATTEMPTS_NO_SUCH_ALGORITHM = 2; + + + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' }; private static final int BUFFER_SIZE = 0x4000; @@ -84,8 +94,33 @@ final class MultiDexExtractor { files.add(extractedFile); if (!extractedFile.isFile()) { - extract(apk, dexFile, extractedFile, extractedFilePrefix, - lastModified); + int numAttempts = 0; + boolean isExtractionSuccessful = false; + while (numAttempts < MAX_EXTRACT_ATTEMPTS && !isExtractionSuccessful) { + numAttempts++; + + // Create a zip file (extractedFile) containing only the secondary dex file + // (dexFile) from the apk. + extract(apk, dexFile, extractedFile, extractedFilePrefix, + lastModified); + + // Verify that the extracted file is indeed a zip file. + isExtractionSuccessful = verifyZipFile(extractedFile); + + // Log the sha1 of the extracted zip file + Log.i(TAG, "Extraction " + (isExtractionSuccessful ? "success" : "failed") + + " - SHA1 of " + extractedFile.getAbsolutePath() + ": " + + computeSha1Digest(extractedFile)); + if (!isExtractionSuccessful) { + // Delete the extracted file + extractedFile.delete(); + } + } + if (!isExtractionSuccessful) { + throw new IOException("Could not create zip file " + + extractedFile.getAbsolutePath() + " for secondary dex (" + + secondaryNumber + ")"); + } } secondaryNumber++; dexFile = apk.getEntry(DEX_PREFIX + secondaryNumber + DEX_SUFFIX); @@ -171,6 +206,26 @@ final class MultiDexExtractor { } /** + * Returns whether the file is a valid zip file. + */ + private static boolean verifyZipFile(File file) { + try { + ZipFile zipFile = new ZipFile(file); + try { + zipFile.close(); + } catch (IOException e) { + Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath()); + } + return true; + } catch (ZipException ex) { + Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", ex); + } catch (IOException ex) { + Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(), ex); + } + return false; + } + + /** * Closes the given {@code Closeable}. Suppresses any IO exceptions. */ private static void closeQuietly(Closeable closeable) { @@ -180,4 +235,65 @@ final class MultiDexExtractor { Log.w(TAG, "Failed to close resource", e); } } + + private static synchronized String computeSha1Digest(File file) { + MessageDigest messageDigest = getMessageDigest("SHA1"); + if (messageDigest == null) { + return ""; + } + FileInputStream in = null; + try { + in = new FileInputStream(file); + byte[] bytes = new byte[8192]; + int byteCount; + while ((byteCount = in.read(bytes)) != -1) { + messageDigest.update(bytes, 0, byteCount); + } + return toHex(messageDigest.digest(), false /* zeroTerminated */) + .toLowerCase(); + } catch (IOException e) { + return ""; + } finally { + if (in != null) { + closeQuietly(in); + } + } + } + + /** + * Encodes a byte array as a hexadecimal representation of bytes. + */ + private static String toHex(byte[] in, boolean zeroTerminated) { + int length = in.length; + StringBuilder out = new StringBuilder(length * 2); + for (int i = 0; i < length; i++) { + if (zeroTerminated && i == length - 1 && (in[i] & 0xff) == 0) { + break; + } + out.append(HEX_DIGITS[(in[i] & 0xf0) >>> 4]); + out.append(HEX_DIGITS[in[i] & 0x0f]); + } + return out.toString(); + } + + /** + * Retrieves the message digest instance for a given hash algorithm. Makes + * {@link #MAX_ATTEMPTS_NO_SUCH_ALGORITHM} to successfully retrieve the + * MessageDigest or will return null. + */ + private static MessageDigest getMessageDigest(String hashAlgorithm) { + for (int i = 0; i < MAX_ATTEMPTS_NO_SUCH_ALGORITHM; i++) { + try { + MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm); + if (messageDigest != null) { + return messageDigest; + } + } catch (NoSuchAlgorithmException e) { + // try again - this is needed due to a bug in MessageDigest that can have corrupted + // internal state. + continue; + } + } + return null; + } } |