Skip to content

Commit

Permalink
refactor: clean up external pull parser and introduce brut.j.xml (#3709)
Browse files Browse the repository at this point in the history
* refactor: clean up external pull parser and introduce brut.j.xml

We have no need for an XML pull parser in the project,
it was only used for testing, which is now done with XPath.

The external xpp3 library from org.ogce is obsolete and has
the issue of including javax.xml.namespace.QName which conflicts
with the JRE implementation that exists for a very long time now.
This makes direct usages of QName produce very obscure NPEs that
took me hours to figure out. This patch will allow further
optimization that is WIP.
The external library was replaced by the basic xmlpull API.

The MXSerializer has been cleaned and the features used by apktool
have been integrated into the custom implementation, now part of
a separate module called brut.j.xml.
Writing has been optimized by buffering write operations, inspired
by KXmlSerializer used by Android itself.

A class XmlPullUtils also written that allows copying from a
XmlPullParser into a XmlSerializer with or without an EventHandler.
We use it for AndroidManifestPullStreamDecoder (with EventHandler,
to allow omitting the uses-sdk tag), and for ResXmlPullStreamDecoder
(direct copy, without EventHandler).

saveDocument in ResXmlPatcher was tweaked to output proper output -
a new line after declaration and a new line after root element's
end tag.

TL;DR mostly behind the scene refactor, no end user changes.
  • Loading branch information
IgorEisberg authored Oct 15, 2024
1 parent 7033f4e commit b49e770
Show file tree
Hide file tree
Showing 18 changed files with 783 additions and 735 deletions.
3 changes: 0 additions & 3 deletions brut.apktool/apktool-cli/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
static ** valueOf(java.lang.String);
}

# https://github.com/iBotPeaches/Apktool/issues/3602#issuecomment-2117317880
-dontwarn org.xmlpull.mxp1**

# https://github.com/iBotPeaches/Apktool/pull/3670#issuecomment-2296326878
-dontwarn com.google.j2objc.annotations.Weak
-dontwarn com.google.j2objc.annotations.RetainedWith
6 changes: 3 additions & 3 deletions brut.apktool/apktool-lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ tasks {
}

dependencies {
api(project(":brut.j.dir"))
api(project(":brut.j.util"))
api(project(":brut.j.common"))
api(project(":brut.j.util"))
api(project(":brut.j.dir"))
api(project(":brut.j.xml"))

implementation(libs.baksmali)
implementation(libs.smali)
implementation(libs.xmlpull)
implementation(libs.guava)
implementation(libs.commons.lang3)
implementation(libs.commons.io)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
import brut.androlib.exceptions.AndrolibException;
import brut.androlib.res.data.*;
import brut.androlib.res.decoder.*;
import brut.androlib.res.util.ExtMXSerializer;
import brut.androlib.res.util.ExtXmlSerializer;
import brut.androlib.res.xml.ResValuesXmlSerializable;
import brut.androlib.res.xml.ResXmlPatcher;
import brut.directory.Directory;
import brut.directory.DirectoryException;
import brut.directory.FileDirectory;
import brut.xmlpull.MXSerializer;
import org.apache.commons.io.IOUtils;
import org.xmlpull.v1.XmlSerializer;

Expand Down Expand Up @@ -75,7 +74,8 @@ public void decodeManifest(File outDir) throws AndrolibException {
}

AXmlResourceParser axmlParser = new AndroidManifestResourceParser(mResTable);
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, getResXmlSerializer());
XmlSerializer xmlSerializer = newXmlSerializer();
ResStreamDecoder fileDecoder = new AndroidManifestPullStreamDecoder(axmlParser, xmlSerializer);

Directory inApk, out;
InputStream inputStream = null;
Expand Down Expand Up @@ -157,7 +157,8 @@ public void decodeResources(File outDir) throws AndrolibException {
decoders.setDecoder("9patch", new Res9patchStreamDecoder());

AXmlResourceParser axmlParser = new AXmlResourceParser(mResTable);
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, getResXmlSerializer()));
XmlSerializer xmlSerializer = newXmlSerializer();
decoders.setDecoder("xml", new ResXmlPullStreamDecoder(axmlParser, xmlSerializer));

ResFileDecoder fileDecoder = new ResFileDecoder(decoders);
Directory in, out, outRes;
Expand All @@ -170,7 +171,6 @@ public void decodeResources(File outDir) throws AndrolibException {
throw new AndrolibException(ex);
}

ExtMXSerializer xmlSerializer = getResXmlSerializer();
for (ResPackage pkg : mResTable.listMainPackages()) {

LOGGER.info("Decoding file-resources...");
Expand All @@ -191,20 +191,24 @@ public void decodeResources(File outDir) throws AndrolibException {
}
}

private ExtMXSerializer getResXmlSerializer() {
ExtMXSerializer serial = new ExtMXSerializer();
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_INDENTATION, " ");
serial.setProperty(ExtXmlSerializer.PROPERTY_SERIALIZER_LINE_SEPARATOR, System.getProperty("line.separator"));
serial.setProperty(ExtXmlSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8");
serial.setDisabledAttrEscape(true);
return serial;
private XmlSerializer newXmlSerializer() throws AndrolibException {
try {
XmlSerializer serial = new MXSerializer();
serial.setFeature(MXSerializer.FEATURE_ATTR_VALUE_NO_ESCAPE, true);
serial.setProperty(MXSerializer.PROPERTY_DEFAULT_ENCODING, "utf-8");
serial.setProperty(MXSerializer.PROPERTY_INDENTATION, " ");
serial.setProperty(MXSerializer.PROPERTY_LINE_SEPARATOR, System.getProperty("line.separator"));
return serial;
} catch (IllegalArgumentException | IllegalStateException ex) {
throw new AndrolibException(ex);
}
}

private void generateValuesFile(ResValuesFile valuesFile, Directory out,
ExtXmlSerializer serial) throws AndrolibException {
XmlSerializer serial) throws AndrolibException {
try {
OutputStream outStream = out.getFileOutput(valuesFile.getPath());
serial.setOutput((outStream), null);
serial.setOutput(outStream, null);
serial.startDocument(null, null);
serial.startTag(null, "resources");

Expand All @@ -216,7 +220,6 @@ private void generateValuesFile(ResValuesFile valuesFile, Directory out,
}

serial.endTag(null, "resources");
serial.newLine();
serial.endDocument();
serial.flush();
outStream.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,12 +611,12 @@ public void setProperty(String name, Object value) throws XmlPullParserException
}

@Override
public boolean getFeature(String feature) {
public boolean getFeature(String name) {
return false;
}

@Override
public void setFeature(String name, boolean value) throws XmlPullParserException {
public void setFeature(String name, boolean state) throws XmlPullParserException {
throw new XmlPullParserException(E_NOT_SUPPORTED);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,129 +20,119 @@
import brut.androlib.exceptions.AXmlDecodingException;
import brut.androlib.exceptions.RawXmlEncounteredException;
import brut.androlib.res.data.ResTable;
import brut.androlib.res.util.ExtXmlSerializer;
import brut.xmlpull.XmlPullUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.wrapper.XmlPullParserWrapper;
import org.xmlpull.v1.wrapper.XmlPullWrapperFactory;
import org.xmlpull.v1.wrapper.XmlSerializerWrapper;
import org.xmlpull.v1.wrapper.classic.StaticXmlSerializerWrapper;
import org.xmlpull.v1.XmlSerializer;

import java.io.*;

public class AndroidManifestPullStreamDecoder implements ResStreamDecoder {
public AndroidManifestPullStreamDecoder(AXmlResourceParser parser,
ExtXmlSerializer serializer) {
this.mParser = parser;
this.mSerial = serializer;
private final AXmlResourceParser mParser;
private final XmlSerializer mSerial;

public AndroidManifestPullStreamDecoder(AXmlResourceParser parser, XmlSerializer serial) {
mParser = parser;
mSerial = serial;
}

@Override
public void decode(InputStream in, OutputStream out)
throws AndrolibException {
public void decode(InputStream in, OutputStream out) throws AndrolibException {
try {
XmlPullWrapperFactory factory = XmlPullWrapperFactory.newInstance();
XmlPullParserWrapper par = factory.newPullParserWrapper(mParser);
final ResTable resTable = mParser.getResTable();

XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory) {
final boolean hideSdkInfo = !resTable.getAnalysisMode();

@Override
public void event(XmlPullParser pp)
throws XmlPullParserException, IOException {
int type = pp.getEventType();

if (type == XmlPullParser.START_TAG) {
if ("manifest".equals(pp.getName())) {
try {
parseManifest(pp);
} catch (AndrolibException ignored) {}
} else if ("uses-sdk".equals(pp.getName())) {
try {
parseUsesSdk(pp);
} catch (AndrolibException ignored) {}
if (hideSdkInfo) {
return;
}
}
} else if (type == XmlPullParser.END_TAG
&& "uses-sdk".equals(pp.getName())) {
if (hideSdkInfo) {
return;
}
}
mParser.setInput(in, null);
mSerial.setOutput(out, null);
XmlPullUtils.copy(mParser, mSerial, new EventHandler(mParser.getResTable()));
} catch (XmlPullParserException ex) {
throw new AXmlDecodingException("Could not decode XML", ex);
} catch (IOException ex) {
throw new RawXmlEncounteredException("Could not decode XML", ex);
}
}

super.event(pp);
private static class EventHandler implements XmlPullUtils.EventHandler {
private final ResTable mResTable;
private final boolean mHideSdkInfo;

public EventHandler(ResTable resTable) {
mResTable = resTable;
mHideSdkInfo = !resTable.getAnalysisMode();
}

@Override
public boolean onEvent(XmlPullParser in, XmlSerializer out) throws XmlPullParserException {
int type = in.getEventType();

if (type == XmlPullParser.START_TAG) {
String name = in.getName();

if (name.equals("manifest")) {
parseManifest(in);
} else if (name.equals("uses-sdk")) {
parseUsesSdk(in);

if (mHideSdkInfo) {
return true;
}
}
} else if (type == XmlPullParser.END_TAG) {
String name = in.getName();

private void parseManifest(XmlPullParser pp)
throws AndrolibException {
for (int i = 0; i < pp.getAttributeCount(); i++) {
String ns = pp.getAttributeNamespace(i);
String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i);

if (value.isEmpty()) {
continue;
}

if (ns.isEmpty()) {
if (name.equals("package")) {
resTable.setPackageRenamed(value);
}
} else if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "versionCode":
resTable.setVersionCode(value);
break;
case "versionName":
resTable.setVersionName(value);
break;
}
}
if (name.equals("uses-sdk")) {
if (mHideSdkInfo) {
return true;
}
}
}

private void parseUsesSdk(XmlPullParser pp)
throws AndrolibException {
for (int i = 0; i < pp.getAttributeCount(); i++) {
String ns = pp.getAttributeNamespace(i);
String name = pp.getAttributeName(i);
String value = pp.getAttributeValue(i);

if (value.isEmpty()) {
continue;
}

if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "minSdkVersion":
case "targetSdkVersion":
case "maxSdkVersion":
case "compileSdkVersion":
resTable.addSdkInfo(name, value);
break;
}
}
return false;
}

private void parseManifest(XmlPullParser in) {
for (int i = 0; i < in.getAttributeCount(); i++) {
String ns = in.getAttributeNamespace(i);
String name = in.getAttributeName(i);
String value = in.getAttributeValue(i);

if (value.isEmpty()) {
continue;
}
if (ns.isEmpty()) {
if (name.equals("package")) {
mResTable.setPackageRenamed(value);
}
} else if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "versionCode":
mResTable.setVersionCode(value);
break;
case "versionName":
mResTable.setVersionName(value);
break;
}
}
};
}
}

par.setInput(in, null);
ser.setOutput(out, null);
private void parseUsesSdk(XmlPullParser in) {
for (int i = 0; i < in.getAttributeCount(); i++) {
String ns = in.getAttributeNamespace(i);
String name = in.getAttributeName(i);
String value = in.getAttributeValue(i);

while (par.nextToken() != XmlPullParser.END_DOCUMENT) {
ser.event(par);
if (value.isEmpty()) {
continue;
}
if (ns.equals(AXmlResourceParser.ANDROID_RES_NS)) {
switch (name) {
case "minSdkVersion":
case "targetSdkVersion":
case "maxSdkVersion":
case "compileSdkVersion":
mResTable.addSdkInfo(name, value);
break;
}
}
}
ser.flush();
} catch (XmlPullParserException ex) {
throw new AXmlDecodingException("Could not decode XML", ex);
} catch (IOException ex) {
throw new RawXmlEncounteredException("Could not decode XML", ex);
}
}

private final AXmlResourceParser mParser;
private final ExtXmlSerializer mSerial;
}
Loading

0 comments on commit b49e770

Please sign in to comment.