mirror of
https://github.com/revanced/ARSCLib.git
synced 2025-04-30 14:24:25 +02:00
implement APK signature block reading
This commit is contained in:
parent
3b669f6f92
commit
2eb4e7f96d
@ -166,12 +166,12 @@ public class ApkModule implements ApkFile {
|
||||
return null;
|
||||
}
|
||||
AndroidManifestBlock manifestBlock = getAndroidManifestBlock();
|
||||
Integer version = manifestBlock.getCompileSdkVersion();
|
||||
Integer version = manifestBlock.getTargetSdkVersion();
|
||||
if(version == null){
|
||||
version = manifestBlock.getPlatformBuildVersionCode();
|
||||
}
|
||||
if(version == null){
|
||||
version = manifestBlock.getTargetSdkVersion();
|
||||
version = manifestBlock.getCompileSdkVersion();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import com.reandroid.archive2.block.LocalFileHeader;
|
||||
import com.reandroid.archive2.io.ArchiveFile;
|
||||
import com.reandroid.archive2.io.ArchiveUtil;
|
||||
import com.reandroid.archive2.io.ZipSource;
|
||||
import com.reandroid.archive2.model.ApkSigBlock;
|
||||
import com.reandroid.archive2.model.LocalFileDirectory;
|
||||
|
||||
import java.io.File;
|
||||
@ -37,6 +38,7 @@ public class Archive {
|
||||
private final ZipSource zipSource;
|
||||
private final List<ArchiveEntry> entryList;
|
||||
private final EndRecord endRecord;
|
||||
private final ApkSigBlock apkSigBlock;
|
||||
public Archive(ZipSource zipSource) throws IOException {
|
||||
this.zipSource = zipSource;
|
||||
LocalFileDirectory lfd = new LocalFileDirectory();
|
||||
@ -52,6 +54,7 @@ public class Archive {
|
||||
}
|
||||
this.entryList = entryList;
|
||||
this.endRecord = lfd.getCentralFileDirectory().getEndRecord();
|
||||
this.apkSigBlock = lfd.getApkSigBlock();
|
||||
}
|
||||
public Archive(File file) throws IOException {
|
||||
this(new ArchiveFile(file));
|
||||
@ -71,6 +74,13 @@ public class Archive {
|
||||
return entryList;
|
||||
}
|
||||
|
||||
public ApkSigBlock getApkSigBlock() {
|
||||
return apkSigBlock;
|
||||
}
|
||||
public EndRecord getEndRecord() {
|
||||
return endRecord;
|
||||
}
|
||||
|
||||
// for test
|
||||
public void extract(File dir) throws IOException {
|
||||
for(ArchiveEntry archiveEntry:getEntryList()){
|
||||
|
202
src/main/java/com/reandroid/archive2/block/ApkSignature.java
Normal file
202
src/main/java/com/reandroid/archive2/block/ApkSignature.java
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.reandroid.archive2.block;
|
||||
|
||||
import com.reandroid.arsc.decoder.ValueDecoder;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ApkSignature extends ZipBlock{
|
||||
public ApkSignature() {
|
||||
super(MIN_SIZE);
|
||||
}
|
||||
public boolean isValid(){
|
||||
return getSigSize() > MIN_SIZE && getId() != null;
|
||||
}
|
||||
@Override
|
||||
public int readBytes(InputStream inputStream) throws IOException {
|
||||
setBytesLength(8, false);
|
||||
byte[] bytes = getBytesInternal();
|
||||
int read = inputStream.read(bytes, 0, bytes.length);
|
||||
if(read != bytes.length){
|
||||
return read;
|
||||
}
|
||||
int offset = bytes.length;
|
||||
int length = (int) getSigSize();
|
||||
setSigSize(length);
|
||||
|
||||
bytes = getBytesInternal();
|
||||
int dataRead = inputStream.read(bytes, offset, length);
|
||||
if(dataRead != length){
|
||||
setSigSize(0);
|
||||
}
|
||||
return read + dataRead;
|
||||
}
|
||||
public long getSigSize(){
|
||||
return getLong(OFFSET_size);
|
||||
}
|
||||
public void setSigSize(long size){
|
||||
int length = (int) (OFFSET_id + size);
|
||||
setBytesLength(length, false);
|
||||
putLong(OFFSET_size, size);
|
||||
}
|
||||
public int getIdValue(){
|
||||
return getInteger(OFFSET_id);
|
||||
}
|
||||
public Id getId(){
|
||||
return Id.valueOf(getIdValue());
|
||||
}
|
||||
public void setId(int id){
|
||||
putInteger(OFFSET_id, id);
|
||||
}
|
||||
public void setId(Id signatureId){
|
||||
setId(signatureId == null? 0 : signatureId.getId());
|
||||
}
|
||||
public byte[] getData(){
|
||||
return getBytes(OFFSET_data, (int) getSigSize() - 4, true);
|
||||
}
|
||||
public void setData(byte[] data){
|
||||
if(data == null){
|
||||
data = new byte[0];
|
||||
}
|
||||
setData(data, 0, data.length);
|
||||
}
|
||||
public void setData(byte[] data, int offset, int length){
|
||||
setSigSize(length);
|
||||
putBytes(data, offset, OFFSET_data, data.length);
|
||||
}
|
||||
public void writeData(OutputStream outputStream) throws IOException{
|
||||
byte[] bytes = getBytesInternal();
|
||||
outputStream.write(bytes, OFFSET_data, (int) (getSigSize() - 4));
|
||||
}
|
||||
public void writeData(File file) throws IOException{
|
||||
File dir = file.getParentFile();
|
||||
if(dir != null && !dir.exists()){
|
||||
dir.mkdirs();
|
||||
}
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
writeData(outputStream);
|
||||
outputStream.close();
|
||||
}
|
||||
public File writeToDir(File dir) throws IOException{
|
||||
File file = new File(dir, getId().toFileName());
|
||||
writeData(file);
|
||||
return file;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return getId() + ", size=" + getSigSize();
|
||||
}
|
||||
|
||||
|
||||
public static class Id {
|
||||
private final String name;
|
||||
private final int id;
|
||||
|
||||
private Id(String name, int id) {
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
}
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
public String toFileName(){
|
||||
if(this.name != null){
|
||||
return name + FILE_EXTENSION;
|
||||
}
|
||||
return String.format("0x%08x", id) + FILE_EXTENSION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Id that = (Id) obj;
|
||||
return id == that.id;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
String name = this.name;
|
||||
if(name != null){
|
||||
return name;
|
||||
}
|
||||
return "UNKNOWN("+String.format("0x%08x", id)+")";
|
||||
}
|
||||
public static Id valueOf(String name){
|
||||
if(name == null){
|
||||
return null;
|
||||
}
|
||||
String ext = FILE_EXTENSION;
|
||||
if(name.endsWith(ext)){
|
||||
name = name.substring(0, name.length()-ext.length());
|
||||
}
|
||||
for(Id signatureId:VALUES){
|
||||
if(name.equalsIgnoreCase(signatureId.name())){
|
||||
return signatureId;
|
||||
}
|
||||
}
|
||||
if(ValueDecoder.isHex(name)){
|
||||
return new Id(null, ValueDecoder.parseHex(name));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public static Id valueOf(int id){
|
||||
if(id == 0){
|
||||
return null;
|
||||
}
|
||||
for(Id signatureId:VALUES){
|
||||
if(id == signatureId.getId()){
|
||||
return signatureId;
|
||||
}
|
||||
}
|
||||
return new Id(null, id);
|
||||
}
|
||||
public static Id[] values() {
|
||||
return VALUES.clone();
|
||||
}
|
||||
public static final Id V2 = new Id("V2", 0x7109871A);
|
||||
public static final Id V3 = new Id("V3",0xF05368C0);
|
||||
public static final Id V31 = new Id("V31",0x1B93AD61);
|
||||
public static final Id STAMP_V1 = new Id("STAMP_V1", 0x2B09189E);
|
||||
public static final Id STAMP_V2 = new Id("STAMP_V2", 0x6DFF800D);
|
||||
public static final Id PADDING = new Id("PADDING", 0x42726577);
|
||||
|
||||
|
||||
private static final Id[] VALUES = new Id[]{
|
||||
V2, V3, V31, STAMP_V1, STAMP_V2, PADDING
|
||||
};
|
||||
|
||||
private static final String FILE_EXTENSION = ".bin";
|
||||
}
|
||||
|
||||
public static final int MIN_SIZE = 12;
|
||||
|
||||
private static final int OFFSET_size = 0;
|
||||
private static final int OFFSET_id = 8;
|
||||
private static final int OFFSET_data = 12;
|
||||
}
|
@ -115,7 +115,7 @@ public abstract class CommonHeader extends ZipHeader {
|
||||
putShort(offsetGeneralPurpose + 2, value);
|
||||
}
|
||||
public long getDosTime(){
|
||||
return getUnsignedLong(offsetGeneralPurpose + 4);
|
||||
return getIntegerUnsigned(offsetGeneralPurpose + 4);
|
||||
}
|
||||
public void setDosTime(long value){
|
||||
putInteger(offsetGeneralPurpose + 4, value);
|
||||
@ -130,19 +130,19 @@ public abstract class CommonHeader extends ZipHeader {
|
||||
setDosTime(javaToDosTime(date));
|
||||
}
|
||||
public long getCrc(){
|
||||
return getUnsignedLong(offsetGeneralPurpose + 8);
|
||||
return getIntegerUnsigned(offsetGeneralPurpose + 8);
|
||||
}
|
||||
public void setCrc(long value){
|
||||
putInteger(offsetGeneralPurpose + 8, value);
|
||||
}
|
||||
public long getCompressedSize(){
|
||||
return getUnsignedLong(offsetGeneralPurpose + 12);
|
||||
return getIntegerUnsigned(offsetGeneralPurpose + 12);
|
||||
}
|
||||
public void setCompressedSize(long value){
|
||||
putInteger(offsetGeneralPurpose + 12, value);
|
||||
}
|
||||
public long getSize(){
|
||||
return getUnsignedLong(offsetGeneralPurpose + 16);
|
||||
return getIntegerUnsigned(offsetGeneralPurpose + 16);
|
||||
}
|
||||
public void setSize(long value){
|
||||
putInteger(offsetGeneralPurpose + 16, value);
|
||||
|
@ -22,19 +22,19 @@ public class DataDescriptor extends ZipHeader{
|
||||
super(MIN_LENGTH, ZipSignature.DATA_DESCRIPTOR);
|
||||
}
|
||||
public long getCrc(){
|
||||
return getUnsignedLong(OFFSET_crc);
|
||||
return getIntegerUnsigned(OFFSET_crc);
|
||||
}
|
||||
public void setCrc(long value){
|
||||
putInteger(OFFSET_crc, value);
|
||||
}
|
||||
public long getCompressedSize(){
|
||||
return getUnsignedLong(OFFSET_compressed_size);
|
||||
return getIntegerUnsigned(OFFSET_compressed_size);
|
||||
}
|
||||
public void setCompressedSize(long value){
|
||||
putInteger(OFFSET_compressed_size, value);
|
||||
}
|
||||
public long getSize(){
|
||||
return getUnsignedLong(OFFSET_size);
|
||||
return getIntegerUnsigned(OFFSET_size);
|
||||
}
|
||||
public void setSize(long value){
|
||||
putInteger(OFFSET_size, value);
|
||||
|
@ -46,13 +46,13 @@ public class EndRecord extends ZipHeader{
|
||||
putShort(OFFSET_totalNumberOfDirectories, value);
|
||||
}
|
||||
public long getLengthOfCentralDirectory(){
|
||||
return getUnsignedLong(OFFSET_lengthOfCentralDirectory);
|
||||
return getIntegerUnsigned(OFFSET_lengthOfCentralDirectory);
|
||||
}
|
||||
public void setLengthOfCentralDirectory(long value){
|
||||
putInteger(OFFSET_lengthOfCentralDirectory, value);
|
||||
}
|
||||
public long getOffsetOfCentralDirectory(){
|
||||
return getUnsignedLong(OFFSET_offsetOfCentralDirectory);
|
||||
return getIntegerUnsigned(OFFSET_offsetOfCentralDirectory);
|
||||
}
|
||||
public void setOffsetOfCentralDirectory(int value){
|
||||
putInteger(OFFSET_offsetOfCentralDirectory, value);
|
||||
|
40
src/main/java/com/reandroid/archive2/block/LongBlock.java
Normal file
40
src/main/java/com/reandroid/archive2/block/LongBlock.java
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.reandroid.archive2.block;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class LongBlock extends ZipBlock{
|
||||
public LongBlock() {
|
||||
super(8);
|
||||
}
|
||||
@Override
|
||||
public int readBytes(InputStream inputStream) throws IOException {
|
||||
byte[] bytes = getBytesInternal();
|
||||
return inputStream.read(bytes, 0, bytes.length);
|
||||
}
|
||||
public long get(){
|
||||
return getLong(0);
|
||||
}
|
||||
public void set(long value){
|
||||
putLong(0, value);
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return String.valueOf(get());
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2022 github.com/REAndroid
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.reandroid.archive2.block;
|
||||
|
||||
import com.reandroid.arsc.item.ByteArray;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class SignatureFooter extends ZipBlock{
|
||||
public SignatureFooter() {
|
||||
super(MIN_SIZE);
|
||||
setMagic(APK_SIG_BLOCK_MAGIC);
|
||||
}
|
||||
@Override
|
||||
public int readBytes(InputStream inputStream) throws IOException {
|
||||
setBytesLength(MIN_SIZE, false);
|
||||
byte[] bytes = getBytesInternal();
|
||||
return inputStream.read(bytes, 0, bytes.length);
|
||||
}
|
||||
public long getSigBlockSizeInFooter(){
|
||||
return getLong(OFFSET_size);
|
||||
}
|
||||
public void setSigBlockSizeInFooter(long size){
|
||||
putLong(OFFSET_size, size);
|
||||
}
|
||||
public byte[] getMagic() {
|
||||
return getBytes(OFFSET_magic, APK_SIG_BLOCK_MAGIC.length, false);
|
||||
}
|
||||
public void setMagic(byte[] magic){
|
||||
putBytes(magic, 0, OFFSET_magic, magic.length);
|
||||
}
|
||||
public boolean isValid(){
|
||||
return getSigBlockSizeInFooter() > MIN_SIZE
|
||||
&& ByteArray.equals(APK_SIG_BLOCK_MAGIC, getMagic());
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return getSigBlockSizeInFooter() + " ["+new String(getMagic())+"]";
|
||||
}
|
||||
|
||||
public static final int MIN_SIZE = 24;
|
||||
|
||||
private static final int OFFSET_size = 0;
|
||||
private static final int OFFSET_magic = 8;
|
||||
|
||||
private static final byte[] APK_SIG_BLOCK_MAGIC =
|
||||
new byte[]{'A', 'P', 'K', ' ', 'S', 'i', 'g', ' ', 'B', 'l', 'o', 'c', 'k', ' ', '4', '2'};
|
||||
}
|
@ -43,7 +43,34 @@ public abstract class ZipBlock extends BlockItem {
|
||||
this.readBytes((InputStream) blockReader);
|
||||
}
|
||||
|
||||
long getUnsignedLong(int offset){
|
||||
byte[] getBytes(int offset, int length, boolean strict){
|
||||
byte[] bytes = getBytesInternal();
|
||||
if(strict){
|
||||
if(offset<0 || offset>=bytes.length || (offset + length)>bytes.length){
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if(offset < 0){
|
||||
offset = 0;
|
||||
}
|
||||
int available = bytes.length - offset;
|
||||
if(length<=0 || available <=0){
|
||||
return new byte[0];
|
||||
}
|
||||
if(length > available){
|
||||
length = available;
|
||||
}
|
||||
byte[] result = new byte[length];
|
||||
System.arraycopy(getBytesInternal(), offset, result, 0, length);
|
||||
return result;
|
||||
}
|
||||
long getLong(int offset){
|
||||
return getLong(getBytesInternal(), offset);
|
||||
}
|
||||
void putLong(int offset, long value){
|
||||
putLong(getBytesInternal(), offset, value);
|
||||
}
|
||||
long getIntegerUnsigned(int offset){
|
||||
return getInteger(offset) & 0x00000000ffffffffL;
|
||||
}
|
||||
void putBit(int offset, int bitIndex, boolean bit){
|
||||
@ -71,4 +98,29 @@ public abstract class ZipBlock extends BlockItem {
|
||||
putShort(getBytesInternal(), offset, (short) value);
|
||||
}
|
||||
|
||||
public static long getLong(byte[] bytes, int offset){
|
||||
if((offset + 8)>bytes.length){
|
||||
return 0;
|
||||
}
|
||||
long result = 0;
|
||||
int index = offset + 7;
|
||||
while (index>=offset){
|
||||
result = result << 8;
|
||||
result |= (bytes[index] & 0xff);
|
||||
index --;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public static void putLong(byte[] bytes, int offset, long value){
|
||||
if((offset + 8) > bytes.length){
|
||||
return;
|
||||
}
|
||||
int index = offset;
|
||||
offset = index + 8;
|
||||
while (index<offset){
|
||||
bytes[index] = (byte) (value & 0xff);
|
||||
value = value >>> 8;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,45 +15,56 @@
|
||||
*/
|
||||
package com.reandroid.archive2.model;
|
||||
|
||||
import com.reandroid.archive2.block.ApkSignature;
|
||||
import com.reandroid.archive2.block.LongBlock;
|
||||
import com.reandroid.archive2.block.SignatureFooter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ApkSigBlock {
|
||||
public ApkSigBlock(){
|
||||
private final SignatureFooter signatureFooter;
|
||||
private final LongBlock mSize;
|
||||
private final List<ApkSignature> mSignatures;
|
||||
public ApkSigBlock(SignatureFooter signatureFooter){
|
||||
this.signatureFooter = signatureFooter;
|
||||
this.mSize = new LongBlock();
|
||||
this.mSignatures = new ArrayList<>();
|
||||
}
|
||||
|
||||
//TODO : implement all
|
||||
public void parse(byte[] bytes) throws IOException {
|
||||
int offset = findSignature(bytes);
|
||||
if(offset<0){
|
||||
return;
|
||||
}
|
||||
offset += APK_SIGNING_BLOCK_MAGIC.length;
|
||||
int length = bytes.length - offset;
|
||||
public LongBlock getTotalSize(){
|
||||
return mSize;
|
||||
}
|
||||
private int findSignature(byte[] bytes){
|
||||
for(int i=0;i<bytes.length;i++){
|
||||
if (matchesSignature(bytes, i)){
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
public List<ApkSignature> getSignatures() {
|
||||
return mSignatures;
|
||||
}
|
||||
private boolean matchesSignature(byte[] bytes, int offset){
|
||||
byte[] sig = APK_SIGNING_BLOCK_MAGIC;
|
||||
int length = bytes.length - offset;
|
||||
if (length<sig.length){
|
||||
return false;
|
||||
|
||||
public void readBytes(InputStream inputStream) throws IOException {
|
||||
mSize.readBytes(inputStream);
|
||||
ApkSignature apkSignature;
|
||||
while ((apkSignature = readNext(inputStream))!=null){
|
||||
mSignatures.add(apkSignature);
|
||||
}
|
||||
for(int i=0;i<sig.length;i++){
|
||||
int j = offset+i;
|
||||
if(sig[i] != bytes[j]){
|
||||
return false;
|
||||
}
|
||||
if(bytes[j] ==0){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private ApkSignature readNext(InputStream inputStream) throws IOException {
|
||||
ApkSignature apkSignature = new ApkSignature();
|
||||
apkSignature.readBytes(inputStream);
|
||||
if(apkSignature.isValid()){
|
||||
return apkSignature;
|
||||
}
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
public void writeSigData(File dir) throws IOException{
|
||||
for(ApkSignature apkSignature:mSignatures){
|
||||
apkSignature.writeToDir(dir);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String toString(){
|
||||
return "size=" + getTotalSize() + ", count=" + getSignatures().size();
|
||||
}
|
||||
|
||||
private static final long CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
|
||||
|
@ -18,6 +18,7 @@ package com.reandroid.archive2.model;
|
||||
import com.reandroid.archive2.block.CentralEntryHeader;
|
||||
import com.reandroid.archive2.block.EndRecord;
|
||||
import com.reandroid.archive2.block.LocalFileHeader;
|
||||
import com.reandroid.archive2.block.SignatureFooter;
|
||||
import com.reandroid.archive2.io.ZipSource;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@ -30,6 +31,7 @@ import java.util.Objects;
|
||||
public class CentralFileDirectory {
|
||||
private final List<CentralEntryHeader> headerList;
|
||||
private EndRecord endRecord;
|
||||
private SignatureFooter signatureFooter;
|
||||
public CentralFileDirectory(){
|
||||
this.headerList = new ArrayList<>();
|
||||
}
|
||||
@ -64,20 +66,25 @@ public class CentralFileDirectory {
|
||||
public List<CentralEntryHeader> getHeaderList() {
|
||||
return headerList;
|
||||
}
|
||||
|
||||
public SignatureFooter getSignatureFooter() {
|
||||
return signatureFooter;
|
||||
}
|
||||
public EndRecord getEndRecord() {
|
||||
return endRecord;
|
||||
}
|
||||
public void visit(ZipSource zipSource) throws IOException {
|
||||
byte[] footer = zipSource.getFooter(EndRecord.MAX_LENGTH);
|
||||
byte[] footer = zipSource.getFooter(SignatureFooter.MIN_SIZE + EndRecord.MAX_LENGTH);
|
||||
EndRecord endRecord = findEndRecord(footer);
|
||||
int length = (int) endRecord.getLengthOfCentralDirectory();
|
||||
int endLength = endRecord.countBytes();
|
||||
if(footer.length < (length + endLength)){
|
||||
footer = zipSource.getFooter(length + endLength);
|
||||
footer = zipSource.getFooter(SignatureFooter.MIN_SIZE + length + endLength);
|
||||
}
|
||||
int offset = footer.length - length - endLength;
|
||||
this.endRecord = endRecord;
|
||||
loadCentralFileHeaders(footer, offset, length);
|
||||
this.signatureFooter = tryFindSignatureFooter(footer, endRecord);
|
||||
}
|
||||
private void loadCentralFileHeaders(byte[] footer, int offset, int length) throws IOException {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(footer, offset, length);
|
||||
@ -107,4 +114,20 @@ public class CentralFileDirectory {
|
||||
}
|
||||
throw new IOException("Failed to find end record");
|
||||
}
|
||||
private SignatureFooter tryFindSignatureFooter(byte[] footer, EndRecord endRecord) throws IOException {
|
||||
int lenCd = (int) endRecord.getLengthOfCentralDirectory();
|
||||
int endLength = endRecord.countBytes();
|
||||
int length = SignatureFooter.MIN_SIZE;
|
||||
int offset = footer.length - endLength - lenCd - length;
|
||||
if(offset < 0){
|
||||
return null;
|
||||
}
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(footer, offset, length);
|
||||
SignatureFooter signatureFooter = new SignatureFooter();
|
||||
signatureFooter.readBytes(inputStream);
|
||||
if(signatureFooter.isValid()){
|
||||
return signatureFooter;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,7 @@
|
||||
*/
|
||||
package com.reandroid.archive2.model;
|
||||
|
||||
import com.reandroid.archive2.block.CentralEntryHeader;
|
||||
import com.reandroid.archive2.block.DataDescriptor;
|
||||
import com.reandroid.archive2.block.EndRecord;
|
||||
import com.reandroid.archive2.block.LocalFileHeader;
|
||||
import com.reandroid.archive2.block.*;
|
||||
import com.reandroid.archive2.io.ZipSource;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -41,12 +38,12 @@ public class LocalFileDirectory {
|
||||
public void visit(ZipSource zipSource) throws IOException {
|
||||
getCentralFileDirectory().visit(zipSource);
|
||||
visitLocalFile(zipSource);
|
||||
visitApkSigBlock(zipSource);
|
||||
}
|
||||
private void visitLocalFile(ZipSource zipSource) throws IOException {
|
||||
EndRecord endRecord = getCentralFileDirectory().getEndRecord();
|
||||
InputStream inputStream = zipSource.getInputStream(0, endRecord.getOffsetOfCentralDirectory());
|
||||
visitLocalFile(inputStream);
|
||||
visitApkSigBlock(inputStream);
|
||||
inputStream.close();
|
||||
}
|
||||
private void visitLocalFile(InputStream inputStream) throws IOException {
|
||||
@ -82,19 +79,23 @@ public class LocalFileDirectory {
|
||||
}
|
||||
mTotalDataLength = offset;
|
||||
}
|
||||
private void visitApkSigBlock(InputStream inputStream) throws IOException{
|
||||
int blockSize = (int) (getCentralFileDirectory().getEndRecord().getOffsetOfCentralDirectory()
|
||||
- getTotalDataLength());
|
||||
if(blockSize<=0){
|
||||
private void visitApkSigBlock(ZipSource zipSource) throws IOException{
|
||||
CentralFileDirectory cfd = getCentralFileDirectory();
|
||||
SignatureFooter footer = cfd.getSignatureFooter();
|
||||
if(footer == null || !footer.isValid()){
|
||||
return;
|
||||
}
|
||||
byte[] bytes = new byte[blockSize];
|
||||
inputStream.read(bytes, 0, bytes.length);
|
||||
ApkSigBlock apkSigBlock = new ApkSigBlock();
|
||||
apkSigBlock.parse(bytes);
|
||||
EndRecord endRecord = cfd.getEndRecord();
|
||||
long length = footer.getSigBlockSizeInFooter() + 8;
|
||||
long offset = endRecord.getOffsetOfCentralDirectory()
|
||||
- length;
|
||||
ApkSigBlock apkSigBlock = new ApkSigBlock(footer);
|
||||
apkSigBlock.readBytes(zipSource.getInputStream(offset, length));
|
||||
this.apkSigBlock = apkSigBlock;
|
||||
}
|
||||
|
||||
public ApkSigBlock getApkSigBlock() {
|
||||
return apkSigBlock;
|
||||
}
|
||||
public CentralFileDirectory getCentralFileDirectory() {
|
||||
return centralFileDirectory;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user