All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy