Skip to content

Commit

Permalink
新增zip注释区支持。 fix #52
Browse files Browse the repository at this point in the history
  • Loading branch information
GavinCT authored and achellies committed Sep 26, 2018
1 parent c1a01c5 commit 0362581
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 49 deletions.
90 changes: 50 additions & 40 deletions payload_reader/src/main/java/com/meituan/android/walle/ApkUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<ByteBuffer, Long> findApkSigningBlock(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ private static Map<Integer, ByteBuffer> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ByteBuffer, Long> apkSigningBlockAndOffset = ApkUtil.findApkSigningBlock(fileChannel, centralDirStartOffset);
Expand Down

0 comments on commit 0362581

Please sign in to comment.