tinker.net.dongliu.apk.parser.AbstractApkParser Maven / Gradle / Ivy
The newest version!
package tinker.net.dongliu.apk.parser;
import tinker.net.dongliu.apk.parser.bean.ApkMeta;
import tinker.net.dongliu.apk.parser.bean.ApkSignStatus;
import tinker.net.dongliu.apk.parser.bean.CertificateMeta;
import tinker.net.dongliu.apk.parser.bean.DexClass;
import tinker.net.dongliu.apk.parser.bean.Icon;
import tinker.net.dongliu.apk.parser.exception.ParserException;
import tinker.net.dongliu.apk.parser.parser.BinaryXmlParser;
import tinker.net.dongliu.apk.parser.parser.CertificateParser;
import tinker.net.dongliu.apk.parser.parser.CompositeXmlStreamer;
import tinker.net.dongliu.apk.parser.parser.ResourceTableParser;
import tinker.net.dongliu.apk.parser.parser.XmlStreamer;
import tinker.net.dongliu.apk.parser.struct.AndroidConstants;
import tinker.net.dongliu.apk.parser.struct.resource.ResourceTable;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.security.cert.CertificateException;
import java.util.*;
import tinker.net.dongliu.apk.parser.parser.ApkMetaTranslator;
import tinker.net.dongliu.apk.parser.parser.DexParser;
import tinker.net.dongliu.apk.parser.parser.XmlTranslator;
/**
* Common Apk Parser methods.
* This Class is not thread-safe
*
* @author Liu Dong
*/
public abstract class AbstractApkParser implements Closeable {
private DexClass[] dexClasses;
private ResourceTable resourceTable;
private String manifestXml;
private ApkMeta apkMeta;
private Set locales;
private List certificateMetaList;
private static final Locale DEFAULT_LOCALE = Locale.US;
/**
* default use empty locale
*/
private Locale preferredLocale = DEFAULT_LOCALE;
/**
* return decoded AndroidManifest.xml
*
* @return decoded AndroidManifest.xml
* @throws IOException
*/
public String getManifestXml() throws IOException {
if (this.manifestXml == null) {
parseManifestXml();
}
return this.manifestXml;
}
/**
* return decoded AndroidManifest.xml
*
* @return decoded AndroidManifest.xml
* @throws IOException
*/
public ApkMeta getApkMeta() throws IOException {
if (this.apkMeta == null) {
parseApkMeta();
}
return this.apkMeta;
}
/**
* get locales supported from resource file
*
* @return decoded AndroidManifest.xml
* @throws IOException
*/
public Set getLocales() throws IOException {
if (this.locales == null) {
parseResourceTable();
}
return this.locales;
}
/**
* get the apk's certificates.
* @return
* @throws IOException
* @throws CertificateException
*/
public List getCertificateMetaList() throws IOException,
CertificateException {
if (this.certificateMetaList == null) {
parseCertificate();
}
return this.certificateMetaList;
}
protected abstract byte[] getCertificateData() throws IOException;
private void parseCertificate() throws IOException, CertificateException {
byte[] data = getCertificateData();
if (data == null) {
throw new ParserException("ApkParser certificate not found");
}
CertificateParser parser = new CertificateParser(data);
parser.parse();
this.certificateMetaList = parser.getCertificateMetas();
}
/**
* parse manifest.xml, get apkMeta.
*
* @throws IOException
*/
private void parseApkMeta() throws IOException {
if (this.manifestXml == null) {
parseManifestXml();
}
}
/**
* parse manifest.xml, get manifestXml as xml text.
*
* @throws IOException
*/
private void parseManifestXml() throws IOException {
XmlTranslator xmlTranslator = new XmlTranslator();
ApkMetaTranslator translator = new ApkMetaTranslator();
XmlStreamer xmlStreamer = new CompositeXmlStreamer(xmlTranslator, translator);
byte[] data = getFileData(AndroidConstants.MANIFEST_FILE);
if (data == null) {
throw new ParserException("Manifest file not found");
}
transBinaryXml(data, xmlStreamer);
this.manifestXml = xmlTranslator.getXml();
this.apkMeta = translator.getApkMeta();
}
/**
* read file in apk into bytes
* @param path
* @return
* @throws IOException
*/
public abstract byte[] getFileData(String path) throws IOException;
/**
* trans binary xml file to text xml file.
*
* @param path the xml file path in apk file
* @return the text. null if file not exists
* @throws IOException
*/
public String transBinaryXml(String path) throws IOException {
byte[] data = getFileData(path);
if (data == null) {
return null;
}
if (this.resourceTable == null) {
parseResourceTable();
}
XmlTranslator xmlTranslator = new XmlTranslator();
transBinaryXml(data, xmlTranslator);
return xmlTranslator.getXml();
}
private void transBinaryXml(byte[] data, XmlStreamer xmlStreamer) throws IOException {
if (this.resourceTable == null) {
parseResourceTable();
}
ByteBuffer buffer = ByteBuffer.wrap(data);
BinaryXmlParser binaryXmlParser = new BinaryXmlParser(buffer, resourceTable);
binaryXmlParser.setLocale(preferredLocale);
binaryXmlParser.setXmlStreamer(xmlStreamer);
binaryXmlParser.parse();
}
/**
* get the apk icon file as bytes.
*
* @return the apk icon data,null if icon not found
* @throws IOException
*/
public Icon getIconFile() throws IOException {
ApkMeta apkMeta = getApkMeta();
String iconPath = apkMeta.getIcon();
if (iconPath == null) {
return null;
}
return new Icon(iconPath, getFileData(iconPath));
}
/**
* get class infos form dex file. currently only class name
*/
public DexClass[] getDexClasses() throws IOException {
if (this.dexClasses == null) {
parseDexFile();
}
return this.dexClasses;
}
private void parseDexFile() throws IOException {
byte[] data = getFileData(AndroidConstants.DEX_FILE);
if (data == null) {
throw new ParserException("Dex file not found");
}
ByteBuffer buffer = ByteBuffer.wrap(data);
DexParser dexParser = new DexParser(buffer);
dexParser.parse();
this.dexClasses = dexParser.getDexClasses();
}
/**
* parse resource table.
*/
public void parseResourceTable() throws IOException {
byte[] data = getFileData(AndroidConstants.RESOURCE_FILE);
if (data == null) {
// if no resource entry has been found, we assume it is not needed by this APK
this.resourceTable = new ResourceTable();
this.locales = Collections.emptySet();
return;
}
this.resourceTable = new ResourceTable();
this.locales = Collections.emptySet();
ByteBuffer buffer = ByteBuffer.wrap(data);
ResourceTableParser resourceTableParser = new ResourceTableParser(buffer);
resourceTableParser.parse();
this.resourceTable = resourceTableParser.getResourceTable();
this.locales = resourceTableParser.getLocales();
}
public void parseResourceTable(File arscFile) throws IOException {
byte[] data = Files.readAllBytes(arscFile.toPath());
if (data == null) {
// if no resource entry has been found, we assume it is not needed by this APK
this.resourceTable = new ResourceTable();
this.locales = Collections.emptySet();
return;
}
this.resourceTable = new ResourceTable();
this.locales = Collections.emptySet();
ByteBuffer buffer = ByteBuffer.wrap(data);
ResourceTableParser resourceTableParser = new ResourceTableParser(buffer);
resourceTableParser.parse();
this.resourceTable = resourceTableParser.getResourceTable();
this.locales = resourceTableParser.getLocales();
}
public ResourceTable getResourceTable() {
return this.resourceTable;
}
/**
* check apk sign
*
* @throws IOException
*/
public abstract ApkSignStatus verifyApk() throws IOException;
@Override
public void close() throws IOException {
this.certificateMetaList = null;
this.resourceTable = null;
this.certificateMetaList = null;
}
public Locale getPreferredLocale() {
return preferredLocale;
}
/**
* The locale preferred. Will cause getManifestXml / getApkMeta to return different values.
* The default value is from os default locale setting.
*/
public void setPreferredLocale(Locale preferredLocale) {
if (!Objects.equals(this.preferredLocale, preferredLocale)) {
this.preferredLocale = preferredLocale;
this.manifestXml = null;
this.apkMeta = null;
}
}
}