Skip to content

Commit

Permalink
renmae ApkParser to ApkFile; add ApkParsers
Browse files Browse the repository at this point in the history
  • Loading branch information
Liu Dong committed Dec 28, 2016
1 parent 673c047 commit 3f215af
Show file tree
Hide file tree
Showing 14 changed files with 383 additions and 183 deletions.
61 changes: 38 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,38 @@
Apk parser with java, for decoding xml file and getting meta infos from apk file.

#### Features
* Retrieve basic apk metas, such as title, icon, package name, version, etc.

* Retrieve basic apk meta info, such as title, icon, package name, version, etc.
* Parse and convert binary xml file to text
* Classes from dex file
* Get certificate metas and verify apk signature
* Get classes names from dex file
* Get certificate meta info and verify apk signature

#### Get apk-parser
Apk-parser has been submited to maven central repo, maven, gradle, ivy and other build tools can be used to get this lib.
With maven, you can add apk-parser as dependency by:

Apk-parser has been submitted to maven central repo. With maven, you can add apk-parser as dependency by:
```xml
<dependency>
<groupId>net.dongliu</groupId>
<artifactId>apk-parser</artifactId>
<version>2.1.7</version>
<version>2.2.0</version>
</dependency>
```
From version 2.0, apk-parser requires java7. The last version support java6 is 1.7.4.

#### Usage
The easiest way is to use the ApkParser class, which contains convenient methods to get AndroidManifest.xml, apk meta infos, etc.

The ordinary way is using the ApkFile class, which contains convenient methods to get AndroidManifest.xml, apk meta info, etc.
There is also a ByteArrayApkFile class for reading apk file from byte array.
ApkFile need to be closed when no longer used. If you need to get info more than once for one apk file, you can reuse the same ApkFile instance.

If only want to get meta info or manifest xml file, you can use a utils class ApkParsers.

#####1. Apk meta info

ApkMeta contains name(label), packageName, version, sdk, used features, etc.

```java
try(ApkParser apkParser = new ApkParser(new File(filePath))) {
try (ApkFile apkFile = new ApkFile(new File(filePath))) {
System.out.println(apkMeta.getLabel());
System.out.println(apkMeta.getPackageName());
System.out.println(apkMeta.getVersionCode());
Expand All @@ -33,44 +42,50 @@ try(ApkParser apkParser = new ApkParser(new File(filePath))) {
}
```
#####2. Get binary xml and manifest xml file

```java
try(ApkParser apkParser = new ApkParser(new File(filePath))) {
String manifestXml = apkParser.getManifestXml();
String xml = apkParser.transBinaryXml("res/menu/main.xml");
try (ApkFile apkFile = new ApkFile(new File(filePath))) {
String manifestXml = apkFile.getManifestXml();
String xml = apkFile.transBinaryXml("res/menu/main.xml");
}
```

#####3. Get dex classes

```java
try(ApkParser apkParser = new ApkParser(new File(filePath))) {
DexClass[] classes = apkParser.getDexClasses();
try(ApkFile apkFile = new ApkFile(new File(filePath))) {
DexClass[] classes = apkFile.getDexClasses();
for (DexClass dexClass : classes) {
System.out.println(dexClass);
}
}
```

#####4. Get certificate and verify apk signature

```java
try(ApkParser apkParser = new ApkParser(new File(filePath))) {
ApkSignStatus signStatus = apkParser.verifyApk();
List<CertificateMeta> certs = apkParser.getCertificateMetas();
try(ApkFile apkFile = new ApkFile(new File(filePath))) {
ApkSignStatus signStatus = apkFile.verifyApk();
List<CertificateMeta> certs = apkFile.getCertificateMetas();
for (CertificateMeta certificateMeta : certs) {
System.out.println(certificateMeta.getSignAlgorithm());
}
}
```

#####5. Locales
Apk may appear different infos(title, icon, etc.) for different region and language, which is determined by Locales.
If locale is not set, the "en_US" locale(<code>Locale.US</code>) is used. You can set locale like this:

Apk may appear different info(title, icon, etc.) for different regions and languages——or we can called it Locales.
If locale is not set, the "en_US" locale(<code>Locale.US</code>) is used. You can set locale as blow:

```java
try(ApkParser apkParser = new ApkParser(new File(filePath))) {
apkParser.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
ApkMeta apkMeta = apkParser.getApkMeta();
try (ApkFile apkFile = new ApkFile(new File(filePath))) {
apkFile.setPreferredLocale(Locale.SIMPLIFIED_CHINESE);
ApkMeta apkMeta = apkFile.getApkMeta();
}
```
The PreferredLocale parameter work for getApkMeta, getManifestXml, and other binary xmls.

Apk parser will find best match languages with locale you specified.

If locale is set to null, ApkParser will not translate resource tag, just give the resource id.
If locale is set to null, ApkFile will not translate resource tag, just give the resource id.
For example, apk title will be '@string/app_name' instead of 'WeChat'.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<artifactId>apk-parser</artifactId>
<name>apk-parser</name>
<packaging>jar</packaging>
<version>2.1.7</version>
<version>2.2.0</version>
<url>https://github.com/xiaxiaocao/apk-parser</url>
<developers>
<developer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*
* @author Liu Dong
*/
public abstract class AbstractApkParser implements Closeable {
public abstract class AbstractApkFile implements Closeable {
private DexClass[] dexClasses;
private ResourceTable resourceTable;

Expand Down Expand Up @@ -88,7 +88,7 @@ private void parseCertificate() throws IOException, CertificateException {

byte[] data = getCertificateData();
if (data == null) {
throw new ParserException("ApkParser certificate not found");
throw new ParserException("ApkFile certificate not found");
}
CertificateParser parser = new CertificateParser(data);
parser.parse();
Expand Down
105 changes: 105 additions & 0 deletions src/main/java/net/dongliu/apk/parser/ApkFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package net.dongliu.apk.parser;

import net.dongliu.apk.parser.bean.ApkSignStatus;
import net.dongliu.apk.parser.utils.Utils;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


/**
* ApkFile, for parsing apk file info.
* This class is not thread-safe.
*
* @author dongliu
*/
public class ApkFile extends AbstractApkFile implements Closeable {

private final ZipFile zf;
private File apkFile;

public ApkFile(File apkFile) throws IOException {
this.apkFile = apkFile;
// create zip file cost time, use one zip file for apk parser life cycle
this.zf = new ZipFile(apkFile);
}

public ApkFile(String filePath) throws IOException {
this(new File(filePath));
}

@Override
protected byte[] getCertificateData() throws IOException {
ZipEntry entry = null;
Enumeration<? extends ZipEntry> enu = zf.entries();
while (enu.hasMoreElements()) {
ZipEntry ne = enu.nextElement();
if (ne.isDirectory()) {
continue;
}
if (ne.getName().toUpperCase().endsWith(".RSA") || ne.getName().toUpperCase().endsWith(".DSA")) {
entry = ne;
break;
}
}
if (entry == null) {
return null;
}
return Utils.toByteArray(zf.getInputStream(entry));
}

@Override
public byte[] getFileData(String path) throws IOException {
ZipEntry entry = zf.getEntry(path);
if (entry == null) {
return null;
}

InputStream inputStream = zf.getInputStream(entry);
return Utils.toByteArray(inputStream);
}


@Override
public ApkSignStatus verifyApk() throws IOException {
ZipEntry entry = zf.getEntry("META-INF/MANIFEST.MF");
if (entry == null) {
// apk is not signed;
return ApkSignStatus.notSigned;
}

JarFile jarFile = new JarFile(this.apkFile);
Enumeration<JarEntry> entries = jarFile.entries();
byte[] buffer = new byte[8192];

while (entries.hasMoreElements()) {
JarEntry e = entries.nextElement();
if (e.isDirectory()) {
continue;
}
try (InputStream in = jarFile.getInputStream(e)) {
// Read in each jar entry. A security exception will be thrown if a signature/digest check fails.
int count;
while ((count = in.read(buffer, 0, buffer.length)) != -1) {
// Don't care
}
} catch (SecurityException se) {
return ApkSignStatus.incorrect;
}
}
return ApkSignStatus.signed;
}

@Override
public void close() throws IOException {
super.close();
zf.close();
}
}
99 changes: 6 additions & 93 deletions src/main/java/net/dongliu/apk/parser/ApkParser.java
Original file line number Diff line number Diff line change
@@ -1,111 +1,24 @@
package net.dongliu.apk.parser;

import net.dongliu.apk.parser.bean.*;
import net.dongliu.apk.parser.exception.ParserException;
import net.dongliu.apk.parser.parser.*;
import net.dongliu.apk.parser.struct.AndroidConstants;
import net.dongliu.apk.parser.struct.resource.ResourceTable;
import net.dongliu.apk.parser.utils.Utils;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.cert.CertificateException;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;


/**
* ApkParser and result holder.
* ApkParse and result holder.
* This class is not thread-safe.
*
* @author dongliu
* @deprecated use {@link net.dongliu.apk.parser.ApkFile} instead
*/
public class ApkParser extends AbstractApkParser implements Closeable {

private final ZipFile zf;
private File apkFile;
@Deprecated
public class ApkParser extends ApkFile {

public ApkParser(File apkFile) throws IOException {
this.apkFile = apkFile;
// create zip file cost time, use one zip file for apk parser life cycle
this.zf = new ZipFile(apkFile);
super(apkFile);
}

public ApkParser(String filePath) throws IOException {
this(new File(filePath));
}

@Override
protected byte[] getCertificateData() throws IOException {
ZipEntry entry = null;
Enumeration<? extends ZipEntry> enu = zf.entries();
while (enu.hasMoreElements()) {
ZipEntry ne = enu.nextElement();
if (ne.isDirectory()) {
continue;
}
if (ne.getName().toUpperCase().endsWith(".RSA") || ne.getName().toUpperCase().endsWith(".DSA")) {
entry = ne;
break;
}
}
if (entry == null) {
return null;
}
return Utils.toByteArray(zf.getInputStream(entry));
}

@Override
public byte[] getFileData(String path) throws IOException {
ZipEntry entry = zf.getEntry(path);
if (entry == null) {
return null;
}

InputStream inputStream = zf.getInputStream(entry);
return Utils.toByteArray(inputStream);
}


@Override
public ApkSignStatus verifyApk() throws IOException {
ZipEntry entry = zf.getEntry("META-INF/MANIFEST.MF");
if (entry == null) {
// apk is not signed;
return ApkSignStatus.notSigned;
}

JarFile jarFile = new JarFile(this.apkFile);
Enumeration<JarEntry> entries = jarFile.entries();
byte[] buffer = new byte[8192];

while (entries.hasMoreElements()) {
JarEntry e = entries.nextElement();
if (e.isDirectory()) {
continue;
}
try (InputStream in = jarFile.getInputStream(e)) {
// Read in each jar entry. A security exception will be thrown if a signature/digest check fails.
int count;
while ((count = in.read(buffer, 0, buffer.length)) != -1) {
// Don't care
}
} catch (SecurityException se) {
return ApkSignStatus.incorrect;
}
}
return ApkSignStatus.signed;
}

@Override
public void close() throws IOException {
super.close();
zf.close();
super(filePath);
}
}
Loading

0 comments on commit 3f215af

Please sign in to comment.