diff --git a/payload_reader/src/main/java/com/meituan/android/walle/ApkUtil.java b/payload_reader/src/main/java/com/meituan/android/walle/ApkUtil.java index 133bed7..85cfbe7 100644 --- a/payload_reader/src/main/java/com/meituan/android/walle/ApkUtil.java +++ b/payload_reader/src/main/java/com/meituan/android/walle/ApkUtil.java @@ -32,40 +32,11 @@ private ApkUtil() { public static final String DEFAULT_CHARSET = "UTF-8"; - /** - * check zip comment - * - * @param fileChannel fileChannel - * @return true if has comment - * @throws IOException - */ - public static boolean checkComment(final FileChannel fileChannel) throws IOException { - // End of central directory record (EOCD) - // Offset Bytes Description[23] - // 0 4 End of central directory signature = 0x06054b50 - // 4 2 Number of this disk - // 6 2 Disk where central directory starts - // 8 2 Number of central directory records on this disk - // 10 2 Total number of central directory records - // 12 4 Size of central directory (bytes) - // 16 4 Offset of start of central directory, relative to start of archive - // 20 2 Comment length (n) - // 22 n Comment - // For a zip with no archive comment, the - // end-of-central-directory record will be 22 bytes long, so - // we expect to find the EOCD marker 22 bytes from the end. - final ByteBuffer byteBuffer = ByteBuffer.allocate(4); - fileChannel.position(fileChannel.size() - 22); - fileChannel.read(byteBuffer); - - if (byteBuffer.get(0) != 0x50 || - byteBuffer.get(1) != 0x4b || - byteBuffer.get(2) != 0x05 || - byteBuffer.get(3) != 0x06) { - return true; - } - return false; - } + private static final int ZIP_EOCD_REC_MIN_SIZE = 22; + private static final int ZIP_EOCD_REC_SIG = 0x06054b50; + private static final int UINT16_MAX_VALUE = 0xffff; + private static final int ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET = 20; + private static final int ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET = 16; public static long findCentralDirStartOffset(final FileChannel fileChannel) throws IOException { // End of central directory record (EOCD) @@ -82,12 +53,51 @@ public static long findCentralDirStartOffset(final FileChannel fileChannel) thro // For a zip with no archive comment, the // end-of-central-directory record will be 22 bytes long, so // we expect to find the EOCD marker 22 bytes from the end. - final ByteBuffer zipEndOfCentralDirectory = ByteBuffer.allocate(4); - zipEndOfCentralDirectory.order(ByteOrder.LITTLE_ENDIAN); - fileChannel.position(fileChannel.size() - 6); // 6 = 2 (Comment length) + 4 (Offset of start of central directory, relative to start of archive) - fileChannel.read(zipEndOfCentralDirectory); - final long centralDirStartOffset = zipEndOfCentralDirectory.getInt(0); - return centralDirStartOffset; + + + long archiveSize = fileChannel.size(); + if (archiveSize < ZIP_EOCD_REC_MIN_SIZE) { + throw new IOException("zip file to small"); + } + // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive. + // The record can be identified by its 4-byte signature/magic which is located at the very + // beginning of the record. A complication is that the record is variable-length because of + // the comment field. + // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from + // end of the buffer for the EOCD record signature. Whenever we find a signature, we check + // the candidate record's comment length is such that the remainder of the record takes up + // exactly the remaining bytes in the buffer. The search is bounded because the maximum + // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number. + long maxCommentLength = Math.min(archiveSize - ZIP_EOCD_REC_MIN_SIZE, UINT16_MAX_VALUE); + long eocdWithEmptyCommentStartPosition = archiveSize - ZIP_EOCD_REC_MIN_SIZE; + for (int expectedCommentLength = 0; expectedCommentLength <= maxCommentLength; + expectedCommentLength++) { + long eocdStartPos = eocdWithEmptyCommentStartPosition - expectedCommentLength; + + final ByteBuffer byteBuffer = ByteBuffer.allocate(4); + fileChannel.position(eocdStartPos); + fileChannel.read(byteBuffer); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + if (byteBuffer.getInt() == ZIP_EOCD_REC_SIG) { + final ByteBuffer commentLengthByteBuffer = ByteBuffer.allocate(2); + fileChannel.position(eocdStartPos + ZIP_EOCD_COMMENT_LENGTH_FIELD_OFFSET); + fileChannel.read(commentLengthByteBuffer); + commentLengthByteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + int actualCommentLength = commentLengthByteBuffer.getInt(); + if (actualCommentLength == expectedCommentLength) { + final ByteBuffer zipCentralDirectoryStart = ByteBuffer.allocate(4); + zipCentralDirectoryStart.order(ByteOrder.LITTLE_ENDIAN); + fileChannel.position(eocdStartPos + ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_OFFSET); // Offset of start of central directory + fileChannel.read(zipCentralDirectoryStart); + final long centralDirStartOffset = zipCentralDirectoryStart.getInt(); + return centralDirStartOffset; + } + } + } + throw new IOException("zip file eocd not found"); + } public static Pair findApkSigningBlock( diff --git a/payload_reader/src/main/java/com/meituan/android/walle/PayloadReader.java b/payload_reader/src/main/java/com/meituan/android/walle/PayloadReader.java index 2f8e98c..e04817e 100644 --- a/payload_reader/src/main/java/com/meituan/android/walle/PayloadReader.java +++ b/payload_reader/src/main/java/com/meituan/android/walle/PayloadReader.java @@ -60,10 +60,6 @@ private static Map getAll(final File apkFile) { try { randomAccessFile = new RandomAccessFile(apkFile, "r"); fileChannel = randomAccessFile.getChannel(); - final boolean hasComment = ApkUtil.checkComment(fileChannel); - if (hasComment) { - throw new IllegalArgumentException("zip data already has an archive comment"); - } final ByteBuffer apkSigningBlock2 = ApkUtil.findApkSigningBlock(fileChannel).getFirst(); idValues = ApkUtil.findIdValues(apkSigningBlock2); } catch (IOException ignore) { diff --git a/payload_writer/src/main/java/com/meituan/android/walle/PayloadWriter.java b/payload_writer/src/main/java/com/meituan/android/walle/PayloadWriter.java index bf3d0ea..632a43c 100644 --- a/payload_writer/src/main/java/com/meituan/android/walle/PayloadWriter.java +++ b/payload_writer/src/main/java/com/meituan/android/walle/PayloadWriter.java @@ -67,11 +67,6 @@ static void handleApkSigningBlock(final File apkFile, final ApkSigningBlockHandl try { fIn = new RandomAccessFile(apkFile, "rw"); fileChannel = fIn.getChannel(); - final boolean hasComment = ApkUtil.checkComment(fileChannel); - if (hasComment) { - throw new IOException("zip data already has an archive comment"); - } - final long centralDirStartOffset = ApkUtil.findCentralDirStartOffset(fileChannel); // Find the APK Signing Block. The block immediately precedes the Central Directory. final Pair apkSigningBlockAndOffset = ApkUtil.findApkSigningBlock(fileChannel, centralDirStartOffset);