diff --git a/brut.apktool/apktool-cli/proguard-rules.pro b/brut.apktool/apktool-cli/proguard-rules.pro index 27594dfb..5b6b4e34 100644 --- a/brut.apktool/apktool-cli/proguard-rules.pro +++ b/brut.apktool/apktool-cli/proguard-rules.pro @@ -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 diff --git a/brut.apktool/apktool-lib/build.gradle.kts b/brut.apktool/apktool-lib/build.gradle.kts index 6a0c8030..ed25d5e3 100644 --- a/brut.apktool/apktool-lib/build.gradle.kts +++ b/brut.apktool/apktool-lib/build.gradle.kts @@ -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) diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java index a1749e33..4f8e9818 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/ResourcesDecoder.java @@ -21,13 +21,12 @@ import brut.androlib.apk.ApkInfo; 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; @@ -75,7 +74,8 @@ public class ResourcesDecoder { } 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; @@ -157,7 +157,8 @@ public class ResourcesDecoder { 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; @@ -170,7 +171,6 @@ public class ResourcesDecoder { throw new AndrolibException(ex); } - ExtMXSerializer xmlSerializer = getResXmlSerializer(); for (ResPackage pkg : mResTable.listMainPackages()) { LOGGER.info("Decoding file-resources..."); @@ -191,20 +191,24 @@ public class ResourcesDecoder { } } - 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"); @@ -216,7 +220,6 @@ public class ResourcesDecoder { } serial.endTag(null, "resources"); - serial.newLine(); serial.endDocument(); serial.flush(); outStream.close(); diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java index 5bbe0567..b8deb587 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AXmlResourceParser.java @@ -611,12 +611,12 @@ public class AXmlResourceParser implements XmlResourceParser { } @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); } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AndroidManifestPullStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AndroidManifestPullStreamDecoder.java index 34b97271..d3235015 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AndroidManifestPullStreamDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/AndroidManifestPullStreamDecoder.java @@ -20,122 +20,28 @@ import brut.androlib.exceptions.AndrolibException; 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; - } - } - - super.event(pp); - } - - 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; - } - } - } - } - - 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; - } - } - } - } - }; - - par.setInput(in, null); - ser.setOutput(out, null); - - while (par.nextToken() != XmlPullParser.END_DOCUMENT) { - ser.event(par); - } - ser.flush(); + 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) { @@ -143,6 +49,90 @@ public class AndroidManifestPullStreamDecoder implements ResStreamDecoder { } } - private final AXmlResourceParser mParser; - private final ExtXmlSerializer mSerial; + 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(); + + if (name.equals("uses-sdk")) { + if (mHideSdkInfo) { + return true; + } + } + } + + 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; + } + } + } + } + + 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); + + 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; + } + } + } + } + } } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResXmlPullStreamDecoder.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResXmlPullStreamDecoder.java index 1719b20c..1e90d176 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResXmlPullStreamDecoder.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/decoder/ResXmlPullStreamDecoder.java @@ -19,45 +19,31 @@ package brut.androlib.res.decoder; import brut.androlib.exceptions.AndrolibException; import brut.androlib.exceptions.AXmlDecodingException; import brut.androlib.exceptions.RawXmlEncounteredException; -import brut.androlib.res.util.ExtXmlSerializer; -import org.xmlpull.v1.XmlPullParser; +import brut.xmlpull.XmlPullUtils; 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 ResXmlPullStreamDecoder implements ResStreamDecoder { - public ResXmlPullStreamDecoder(AXmlResourceParser parser, - ExtXmlSerializer serializer) { - this.mParser = parser; - this.mSerial = serializer; + private final AXmlResourceParser mParser; + private final XmlSerializer mSerial; + + public ResXmlPullStreamDecoder(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); - XmlSerializerWrapper ser = new StaticXmlSerializerWrapper(mSerial, factory); - - par.setInput(in, null); - ser.setOutput(out, null); - - while (par.nextToken() != XmlPullParser.END_DOCUMENT) { - ser.event(par); - } - ser.flush(); + mParser.setInput(in, null); + mSerial.setOutput(out, null); + XmlPullUtils.copy(mParser, mSerial); } 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; } diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java deleted file mode 100644 index 7114dae4..00000000 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtMXSerializer.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * 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 - * - * https://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 brut.androlib.res.util; - -import org.xmlpull.renamed.MXSerializer; - -import java.io.*; - -public class ExtMXSerializer extends MXSerializer implements ExtXmlSerializer { - @Override - public void startDocument(String encoding, Boolean standalone) - throws IOException, IllegalArgumentException, IllegalStateException { - super.startDocument(encoding != null ? encoding : mDefaultEncoding, standalone); - this.newLine(); - } - - @Override - protected void writeAttributeValue(String value, Writer out) throws IOException { - if (mIsDisabledAttrEscape) { - out.write(value == null ? "" : value); - return; - } - super.writeAttributeValue(value, out); - } - - @Override - public void setOutput(OutputStream os, String encoding) throws IOException { - super.setOutput(os, encoding != null ? encoding : mDefaultEncoding); - } - - @Override - public Object getProperty(String name) throws IllegalArgumentException { - if (PROPERTY_DEFAULT_ENCODING.equals(name)) { - return mDefaultEncoding; - } - return super.getProperty(name); - } - - @Override - public void setProperty(String name, Object value) throws IllegalArgumentException, IllegalStateException { - if (PROPERTY_DEFAULT_ENCODING.equals(name)) { - mDefaultEncoding = (String) value; - } else { - super.setProperty(name, value); - } - } - - @Override - public ExtXmlSerializer newLine() throws IOException { - super.out.write(lineSeparator); - return this; - } - - @Override - public void setDisabledAttrEscape(boolean disabled) { - mIsDisabledAttrEscape = disabled; - } - - private String mDefaultEncoding; - private boolean mIsDisabledAttrEscape = false; - -} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java deleted file mode 100644 index 3e4592ef..00000000 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/util/ExtXmlSerializer.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2010 Ryszard Wiśniewski - * Copyright (C) 2010 Connor Tumbleson - * - * 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 - * - * https://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 brut.androlib.res.util; - -import org.xmlpull.v1.XmlSerializer; - -import java.io.IOException; - -public interface ExtXmlSerializer extends XmlSerializer { - - ExtXmlSerializer newLine() throws IOException; - - void setDisabledAttrEscape(boolean disabled); - - String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; - String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; - String PROPERTY_DEFAULT_ENCODING = "DEFAULT_ENCODING"; -} diff --git a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlPatcher.java b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlPatcher.java index 5f236c39..ec868901 100644 --- a/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlPatcher.java +++ b/brut.apktool/apktool-lib/src/main/java/brut/androlib/res/xml/ResXmlPatcher.java @@ -23,12 +23,15 @@ import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.xpath.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.io.*; import java.util.*; import java.util.logging.Logger; @@ -436,11 +439,19 @@ public final class ResXmlPatcher { private static void saveDocument(File file, Document doc) throws IOException, SAXException, ParserConfigurationException, TransformerException { - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - Transformer transformer = transformerFactory.newTransformer(); - DOMSource source = new DOMSource(doc); - StreamResult result = new StreamResult(file); - transformer.transform(source, result); + TransformerFactory factory = TransformerFactory.newInstance(); + Transformer transformer = factory.newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + + byte[] xmlDecl = "".getBytes(StandardCharsets.US_ASCII); + byte[] newLine = System.getProperty("line.separator").getBytes(StandardCharsets.US_ASCII); + + try (OutputStream output = Files.newOutputStream(file.toPath())) { + output.write(xmlDecl); + output.write(newLine); + transformer.transform(new DOMSource(doc), new StreamResult(output)); + output.write(newLine); + } } private static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD"; diff --git a/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java b/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java index 855326ff..e59768de 100644 --- a/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java +++ b/brut.apktool/apktool-lib/src/test/java/brut/androlib/TestUtils.java @@ -25,12 +25,16 @@ import brut.directory.Directory; import brut.directory.FileDirectory; import brut.util.OS; import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParserFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; import java.io.*; import java.net.URL; import java.net.URLDecoder; @@ -40,43 +44,24 @@ import java.util.Map; public abstract class TestUtils { - public static Map parseStringsXml(File file) - throws BrutException { + public static Map parseStringsXml(File file) throws BrutException { try { - XmlPullParser xpp = XmlPullParserFactory.newInstance().newPullParser(); - xpp.setInput(new FileReader(file)); + Document doc = getDocumentFromFile(file); + XPath xPath = XPathFactory.newInstance().newXPath(); + String expression = "/resources/string[@name]"; + NodeList nodes = (NodeList) xPath.evaluate(expression, doc, XPathConstants.NODESET); - int eventType; - String key = null; Map map = new HashMap<>(); - while ((eventType = xpp.next()) != XmlPullParser.END_DOCUMENT) { - switch (eventType) { - case XmlPullParser.START_TAG: - if ("string".equals(xpp.getName())) { - int attrCount = xpp.getAttributeCount(); - for (int i = 0; i < attrCount; i++) { - if ("name".equals(xpp.getAttributeName(i))) { - key = xpp.getAttributeValue(i); - break; - } - } - } - break; - case XmlPullParser.END_TAG: - if ("string".equals(xpp.getName())) { - key = null; - } - break; - case XmlPullParser.TEXT: - if (key != null) { - map.put(key, xpp.getText()); - } - break; - } + + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + NamedNodeMap attrs = node.getAttributes(); + Node nameAttr = attrs.getNamedItem("name"); + map.put(nameAttr.getNodeValue(), node.getTextContent()); } return map; - } catch (IOException | XmlPullParserException ex) { + } catch (XPathExpressionException ex) { throw new BrutException(ex); } } @@ -84,7 +69,7 @@ public abstract class TestUtils { public static Document getDocumentFromFile(File file) throws BrutException { try { return ResXmlPatcher.loadDocument(file); - } catch (ParserConfigurationException | SAXException | IOException ex) { + } catch (IOException | SAXException | ParserConfigurationException ex) { throw new BrutException(ex); } } diff --git a/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml index 7a6c57e5..b5020e46 100644 --- a/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml +++ b/brut.apktool/apktool-lib/src/test/resources/aapt2/testapp/AndroidManifest.xml @@ -11,5 +11,5 @@ - + diff --git a/brut.j.dir/build.gradle.kts b/brut.j.dir/build.gradle.kts index d4865c80..d6157bc6 100644 --- a/brut.j.dir/build.gradle.kts +++ b/brut.j.dir/build.gradle.kts @@ -1,5 +1,5 @@ dependencies { - implementation(project(":brut.j.common")) - implementation(project(":brut.j.util")) - implementation(libs.commons.io) + implementation(project(":brut.j.common")) + implementation(project(":brut.j.util")) + implementation(libs.commons.io) } diff --git a/brut.j.xml/build.gradle.kts b/brut.j.xml/build.gradle.kts new file mode 100644 index 00000000..96159031 --- /dev/null +++ b/brut.j.xml/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + api(libs.xmlpull) +} diff --git a/brut.apktool/apktool-lib/src/main/java/org/xmlpull/renamed/MXSerializer.java b/brut.j.xml/src/main/java/brut/xmlpull/MXSerializer.java similarity index 63% rename from brut.apktool/apktool-lib/src/main/java/org/xmlpull/renamed/MXSerializer.java rename to brut.j.xml/src/main/java/brut/xmlpull/MXSerializer.java index 96c5f41f..68096dca 100644 --- a/brut.apktool/apktool-lib/src/main/java/org/xmlpull/renamed/MXSerializer.java +++ b/brut.j.xml/src/main/java/brut/xmlpull/MXSerializer.java @@ -14,14 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.xmlpull.renamed; +package brut.xmlpull; import org.xmlpull.v1.XmlSerializer; import java.io.*; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; +import java.util.*; /** * Implementation of XmlSerializer interface from XmlPull V1 API. This @@ -30,72 +28,71 @@ import java.util.Set; *

* Implemented features: *

    + *
  • FEATURE_ATTR_VALUE_NO_ESCAPE *
  • FEATURE_NAMES_INTERNED - when enabled all returned names (namespaces, * prefixes) will be interned and it is required that all names passed as * arguments MUST be interned - *
  • FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE *
*

* Implemented properties: *

    - *
  • PROPERTY_SERIALIZER_INDENTATION - *
  • PROPERTY_SERIALIZER_LINE_SEPARATOR + *
  • PROPERTY_DEFAULT_ENCODING + *
  • PROPERTY_INDENTATION + *
  • PROPERTY_LINE_SEPARATOR + *
  • PROPERTY_LOCATION *
* */ public class MXSerializer implements XmlSerializer { - protected final static String XML_URI = "http://www.w3.org/XML/1998/namespace"; - protected final static String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; + public static final String FEATURE_ATTR_VALUE_NO_ESCAPE = "http://xmlpull.org/v1/doc/features.html#attr-value-no-escape"; + public static final String FEATURE_NAMES_INTERNED = "http://xmlpull.org/v1/doc/features.html#names-interned"; + public static final String PROPERTY_DEFAULT_ENCODING = "http://xmlpull.org/v1/doc/properties.html#default-encoding"; + public static final String PROPERTY_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#indentation"; + public static final String PROPERTY_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#line-separator"; + public static final String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location"; + private static final boolean TRACE_SIZING = false; private static final boolean TRACE_ESCAPING = false; - - protected final String FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE = "http://xmlpull.org/v1/doc/features.html#serializer-attvalue-use-apostrophe"; - protected final String FEATURE_NAMES_INTERNED = "http://xmlpull.org/v1/doc/features.html#names-interned"; - protected final String PROPERTY_SERIALIZER_INDENTATION = "http://xmlpull.org/v1/doc/properties.html#serializer-indentation"; - protected final String PROPERTY_SERIALIZER_LINE_SEPARATOR = "http://xmlpull.org/v1/doc/properties.html#serializer-line-separator"; - protected final static String PROPERTY_LOCATION = "http://xmlpull.org/v1/doc/properties.html#location"; + private static final String XML_URI = "http://www.w3.org/XML/1998/namespace"; + private static final String XMLNS_URI = "http://www.w3.org/2000/xmlns/"; // properties/features - protected boolean namesInterned; - protected boolean attributeUseApostrophe; - protected String indentationString = null; // " "; - protected String lineSeparator = "\n"; + private boolean namesInterned; + private boolean attrValueNoEscape; + private String defaultEncoding; + private String indentationString; + private String lineSeparator; - protected String location; - protected Writer out; + private String location; + private Writer writer; - protected int autoDeclaredPrefixes; + private int autoDeclaredPrefixes; - protected int depth = 0; + private int depth = 0; // element stack - protected String[] elNamespace = new String[2]; - protected String[] elName = new String[elNamespace.length]; - protected String[] elPrefix = new String[elNamespace.length]; - protected int[] elNamespaceCount = new int[elNamespace.length]; + private String[] elNamespace = new String[2]; + private String[] elName = new String[elNamespace.length]; + private String[] elPrefix = new String[elNamespace.length]; + private int[] elNamespaceCount = new int[elNamespace.length]; // namespace stack - protected int namespaceEnd = 0; - protected String[] namespacePrefix = new String[8]; - protected String[] namespaceUri = new String[namespacePrefix.length]; + private int namespaceEnd = 0; + private String[] namespacePrefix = new String[8]; + private String[] namespaceUri = new String[namespacePrefix.length]; - protected boolean finished; - protected boolean pastRoot; - protected boolean setPrefixCalled; - protected boolean startTagIncomplete; + private boolean finished; + private boolean pastRoot; + private boolean setPrefixCalled; + private boolean startTagIncomplete; - protected boolean doIndent; - protected boolean seenTag; + private boolean doIndent; + private boolean seenTag; - protected boolean seenBracket; - protected boolean seenBracketBracket; - - // buffer output if needed to write escaped String see text(String) - private static final int BUF_LEN = Runtime.getRuntime().freeMemory() > 1000000L ? 8 * 1024 : 256; - protected char[] buf = new char[BUF_LEN]; - - protected static final String[] precomputedPrefixes; + private boolean seenBracket; + private boolean seenBracketBracket; + private static final String[] precomputedPrefixes; static { precomputedPrefixes = new String[32]; // arbitrary number ... for (int i = 0; i < precomputedPrefixes.length; i++) { @@ -112,9 +109,267 @@ public class MXSerializer implements XmlSerializer { } } - protected void reset() { + private String getLocation() { + return location != null ? " @" + location : ""; + } + + private void ensureElementsCapacity() { + int elStackSize = elName.length; + int newSize = (depth >= 7 ? 2 * depth : 8) + 2; + + if (TRACE_SIZING) { + System.err.println(getClass().getName() + " elStackSize " + + elStackSize + " ==> " + newSize); + } + boolean needsCopying = elStackSize > 0; + String[] arr; + // reuse arr local variable slot + arr = new String[newSize]; + if (needsCopying) { + System.arraycopy(elName, 0, arr, 0, elStackSize); + } + elName = arr; + + arr = new String[newSize]; + if (needsCopying) { + System.arraycopy(elPrefix, 0, arr, 0, elStackSize); + } + elPrefix = arr; + + arr = new String[newSize]; + if (needsCopying) { + System.arraycopy(elNamespace, 0, arr, 0, elStackSize); + } + elNamespace = arr; + + int[] iarr = new int[newSize]; + if (needsCopying) { + System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize); + } else { + // special initialization + iarr[0] = 0; + } + elNamespaceCount = iarr; + } + + private void ensureNamespacesCapacity() { + int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8; + if (TRACE_SIZING) { + System.err.println(getClass().getName() + " namespaceSize " + namespacePrefix.length + " ==> " + newSize); + } + String[] newNamespacePrefix = new String[newSize]; + String[] newNamespaceUri = new String[newSize]; + if (namespacePrefix != null) { + System.arraycopy(namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd); + System.arraycopy(namespaceUri, 0, newNamespaceUri, 0, namespaceEnd); + } + namespacePrefix = newNamespacePrefix; + namespaceUri = newNamespaceUri; + } + + // use buffer to optimize writing + private static final int BUFFER_LEN = 8192; + private final char[] buffer = new char[BUFFER_LEN]; + private int bufidx; + + private void flushBuffer() throws IOException { + if (bufidx > 0) { + writer.write(buffer, 0, bufidx); + writer.flush(); + bufidx = 0; + } + } + + private void write(char ch) throws IOException { + if (bufidx >= BUFFER_LEN) { + flushBuffer(); + } + buffer[bufidx++] = ch; + } + + private void write(char[] buf, int i, int length) throws IOException { + while (length > 0) { + if (bufidx == BUFFER_LEN) { + flushBuffer(); + } + int batch = BUFFER_LEN - bufidx; + if (batch > length) { + batch = length; + } + System.arraycopy(buf, i, buffer, bufidx, batch); + i += batch; + length -= batch; + bufidx += batch; + } + } + + private void write(String str) throws IOException { + write(str, 0, str.length()); + } + + private void write(String str, int i, int length) throws IOException { + while (length > 0) { + if (bufidx == BUFFER_LEN) { + flushBuffer(); + } + int batch = BUFFER_LEN - bufidx; + if (batch > length) { + batch = length; + } + str.getChars(i, i + batch, buffer, bufidx); + i += batch; + length -= batch; + bufidx += batch; + } + } + + // precomputed variables to simplify writing indentation + private static final int MAX_INDENT = 65; + private int offsetNewLine; + private int indentationJump; + private char[] indentationBuf; + private int maxIndentLevel; + private boolean writeLineSeparator; // should end-of-line be written + private boolean writeIndentation; // is indentation used? + + /** + * For maximum efficiency when writing indents the required output is + * pre-computed This is internal function that recomputes buffer after user + * requested changes. + */ + private void rebuildIndentationBuf() { + if (!doIndent) { + return; + } + int bufSize = 0; + offsetNewLine = 0; + if (writeLineSeparator) { + offsetNewLine = lineSeparator.length(); + bufSize += offsetNewLine; + } + maxIndentLevel = 0; + if (writeIndentation) { + indentationJump = indentationString.length(); + maxIndentLevel = MAX_INDENT / indentationJump; + bufSize += maxIndentLevel * indentationJump; + } + if (indentationBuf == null || indentationBuf.length < bufSize) { + indentationBuf = new char[bufSize + 8]; + } + int bufPos = 0; + if (writeLineSeparator) { + for (int i = 0; i < lineSeparator.length(); i++) { + indentationBuf[bufPos++] = lineSeparator.charAt(i); + } + } + if (writeIndentation) { + for (int i = 0; i < maxIndentLevel; i++) { + for (int j = 0; j < indentationString.length(); j++) { + indentationBuf[bufPos++] = indentationString.charAt(j); + } + } + } + } + + private void writeIndent() throws IOException { + int start = writeLineSeparator ? 0 : offsetNewLine; + int level = Math.min(depth, maxIndentLevel); + + write(indentationBuf, start, ((level - 1) * indentationJump) + offsetNewLine); + } + + // --- public API methods + + @Override + public void setFeature(String name, boolean state) + throws IllegalArgumentException, IllegalStateException { + if (name == null) { + throw new IllegalArgumentException("feature name can not be null"); + } + switch (name) { + case FEATURE_ATTR_VALUE_NO_ESCAPE: + attrValueNoEscape = state; + break; + case FEATURE_NAMES_INTERNED: + namesInterned = state; + break; + default: + throw new IllegalStateException("unsupported feature: " + name); + } + } + + @Override + public boolean getFeature(String name) throws IllegalArgumentException { + if (name == null) { + throw new IllegalArgumentException("feature name can not be null"); + } + switch (name) { + case FEATURE_ATTR_VALUE_NO_ESCAPE: + return attrValueNoEscape; + case FEATURE_NAMES_INTERNED: + return namesInterned; + default: + return false; + } + } + + @Override + public void setProperty(String name, Object value) + throws IllegalArgumentException, IllegalStateException { + if (name == null) { + throw new IllegalArgumentException("property name can not be null"); + } + switch (name) { + case PROPERTY_DEFAULT_ENCODING: + defaultEncoding = (String) value; + break; + case PROPERTY_INDENTATION: + indentationString = (String) value; + break; + case PROPERTY_LINE_SEPARATOR: + lineSeparator = (String) value; + break; + case PROPERTY_LOCATION: + location = (String) value; + break; + default: + throw new IllegalStateException("unsupported property: " + name); + } + writeLineSeparator = lineSeparator != null && !lineSeparator.isEmpty(); + writeIndentation = indentationString != null && !indentationString.isEmpty(); + // optimize - do not write when nothing to write ... + doIndent = indentationString != null && (writeLineSeparator || writeIndentation); + // NOTE: when indentationString == null there is no indentation + // (even though writeLineSeparator may be true ...) + rebuildIndentationBuf(); + seenTag = false; // for consistency + } + + @Override + public Object getProperty(String name) throws IllegalArgumentException { + if (name == null) { + throw new IllegalArgumentException("property name can not be null"); + } + switch (name) { + case PROPERTY_DEFAULT_ENCODING: + return defaultEncoding; + case PROPERTY_INDENTATION: + return indentationString; + case PROPERTY_LINE_SEPARATOR: + return lineSeparator; + case PROPERTY_LOCATION: + return location; + default: + return null; + } + } + + @Override + public void setOutput(Writer writer) { + this.writer = writer; + + // reset state location = null; - out = null; autoDeclaredPrefixes = 0; depth = 0; @@ -148,241 +403,43 @@ public class MXSerializer implements XmlSerializer { seenBracketBracket = false; } - protected void ensureElementsCapacity() { - final int elStackSize = elName.length; - final int newSize = (depth >= 7 ? 2 * depth : 8) + 2; - - if (TRACE_SIZING) { - System.err.println(getClass().getName() + " elStackSize " - + elStackSize + " ==> " + newSize); - } - final boolean needsCopying = elStackSize > 0; - String[] arr; - // reuse arr local variable slot - arr = new String[newSize]; - if (needsCopying) - System.arraycopy(elName, 0, arr, 0, elStackSize); - elName = arr; - - arr = new String[newSize]; - if (needsCopying) - System.arraycopy(elPrefix, 0, arr, 0, elStackSize); - elPrefix = arr; - - arr = new String[newSize]; - if (needsCopying) - System.arraycopy(elNamespace, 0, arr, 0, elStackSize); - elNamespace = arr; - - final int[] iarr = new int[newSize]; - if (needsCopying) { - System.arraycopy(elNamespaceCount, 0, iarr, 0, elStackSize); - } else { - // special initialization - iarr[0] = 0; - } - elNamespaceCount = iarr; - } - - protected void ensureNamespacesCapacity() { // int size) { - final int newSize = namespaceEnd > 7 ? 2 * namespaceEnd : 8; - if (TRACE_SIZING) { - System.err.println(getClass().getName() + " namespaceSize " + namespacePrefix.length + " ==> " + newSize); - } - final String[] newNamespacePrefix = new String[newSize]; - final String[] newNamespaceUri = new String[newSize]; - if (namespacePrefix != null) { - System.arraycopy(namespacePrefix, 0, newNamespacePrefix, 0, namespaceEnd); - System.arraycopy(namespaceUri, 0, newNamespaceUri, 0, namespaceEnd); - } - namespacePrefix = newNamespacePrefix; - namespaceUri = newNamespaceUri; - } - - @Override - public void setFeature(String name, boolean state) - throws IllegalArgumentException, IllegalStateException { - if (name == null) { - throw new IllegalArgumentException("feature name can not be null"); - } - if (FEATURE_NAMES_INTERNED.equals(name)) { - namesInterned = state; - } else if (FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) { - attributeUseApostrophe = state; - } else { - throw new IllegalStateException("unsupported feature " + name); - } - } - - @Override - public boolean getFeature(String name) throws IllegalArgumentException { - if (name == null) { - throw new IllegalArgumentException("feature name can not be null"); - } - if (FEATURE_NAMES_INTERNED.equals(name)) { - return namesInterned; - } else if (FEATURE_SERIALIZER_ATTVALUE_USE_APOSTROPHE.equals(name)) { - return attributeUseApostrophe; - } else { - return false; - } - } - - // precomputed variables to simplify writing indentation - protected int offsetNewLine; - protected int indentationJump; - protected char[] indentationBuf; - protected int maxIndentLevel; - protected boolean writeLineSepartor; // should end-of-line be written - protected boolean writeIndentation; // is indentation used? - - /** - * For maximum efficiency when writing indents the required output is - * pre-computed This is internal function that recomputes buffer after user - * requested chnages. - */ - protected void rebuildIndentationBuf() { - if (!doIndent) - return; - final int maxIndent = 65; // hardcoded maximum indentation size in characters - int bufSize = 0; - offsetNewLine = 0; - if (writeLineSepartor) { - offsetNewLine = lineSeparator.length(); - bufSize += offsetNewLine; - } - maxIndentLevel = 0; - if (writeIndentation) { - indentationJump = indentationString.length(); - maxIndentLevel = maxIndent / indentationJump; - bufSize += maxIndentLevel * indentationJump; - } - if (indentationBuf == null || indentationBuf.length < bufSize) { - indentationBuf = new char[bufSize + 8]; - } - int bufPos = 0; - if (writeLineSepartor) { - for (int i = 0; i < lineSeparator.length(); i++) { - indentationBuf[bufPos++] = lineSeparator.charAt(i); - } - } - if (writeIndentation) { - for (int i = 0; i < maxIndentLevel; i++) { - for (int j = 0; j < indentationString.length(); j++) { - indentationBuf[bufPos++] = indentationString.charAt(j); - } - } - } - } - - protected void writeIndent() throws IOException { - final int start = writeLineSepartor ? 0 : offsetNewLine; - final int level = Math.min(depth, maxIndentLevel); - - out.write(indentationBuf, start, ((level - 1) * indentationJump) + offsetNewLine); - } - - @Override - public void setProperty(String name, Object value) - throws IllegalArgumentException, IllegalStateException { - if (name == null) { - throw new IllegalArgumentException("property name can not be null"); - } - switch (name) { - case PROPERTY_SERIALIZER_INDENTATION: - indentationString = (String) value; - break; - case PROPERTY_SERIALIZER_LINE_SEPARATOR: - lineSeparator = (String) value; - break; - case PROPERTY_LOCATION: - location = (String) value; - break; - default: - throw new IllegalStateException("unsupported property " + name); - } - writeLineSepartor = lineSeparator != null && lineSeparator.length() > 0; - writeIndentation = indentationString != null - && indentationString.length() > 0; - // optimize - do not write when nothing to write ... - doIndent = indentationString != null - && (writeLineSepartor || writeIndentation); - // NOTE: when indentationString == null there is no indentation - // (even though writeLineSeparator may be true ...) - rebuildIndentationBuf(); - seenTag = false; // for consistency - } - - @Override - public Object getProperty(String name) throws IllegalArgumentException { - if (name == null) { - throw new IllegalArgumentException("property name can not be null"); - } - switch (name) { - case PROPERTY_SERIALIZER_INDENTATION: - return indentationString; - case PROPERTY_SERIALIZER_LINE_SEPARATOR: - return lineSeparator; - case PROPERTY_LOCATION: - return location; - default: - return null; - } - } - - private String getLocation() { - return location != null ? " @" + location : ""; - } - - // this is special method that can be accessed directly to retrieve Writer - // serializer is using - public Writer getWriter() { - return out; - } - - @Override - public void setOutput(Writer writer) { - reset(); - out = writer; - } - @Override public void setOutput(OutputStream os, String encoding) throws IOException { - if (os == null) + if (os == null) { throw new IllegalArgumentException("output stream can not be null"); - reset(); - if (encoding != null) { - out = new OutputStreamWriter(os, encoding); - } else { - out = new OutputStreamWriter(os); } + if (encoding == null) { + encoding = defaultEncoding; + } + setOutput(encoding != null + ? new OutputStreamWriter(os, encoding) + : new OutputStreamWriter(os)); } @Override - public void startDocument(String encoding, Boolean standalone) - throws IOException { - if (attributeUseApostrophe) { - out.write(""); + if (writeLineSeparator) { + write(lineSeparator); } - out.write("?>"); } @Override @@ -391,15 +448,18 @@ public class MXSerializer implements XmlSerializer { while (depth > 0) { endTag(elNamespace[depth], elName[depth]); } + if (writeLineSeparator) { + write(lineSeparator); + } + flushBuffer(); finished = pastRoot = startTagIncomplete = true; - out.flush(); } @Override public void setPrefix(String prefix, String namespace) throws IOException { - if (startTagIncomplete) + if (startTagIncomplete) { closeStartTag(); - + } if (prefix == null) { prefix = ""; } @@ -428,17 +488,12 @@ public class MXSerializer implements XmlSerializer { setPrefixCalled = true; } - protected String lookupOrDeclarePrefix(String namespace) { - return getPrefix(namespace, true); - } - @Override public String getPrefix(String namespace, boolean generatePrefix) { return getPrefix(namespace, generatePrefix, false); } - protected String getPrefix(String namespace, boolean generatePrefix, - boolean nonEmpty) { + private String getPrefix(String namespace, boolean generatePrefix, boolean nonEmpty) { if (!namesInterned) { // when String is interned we can do much faster namespace stack lookups ... namespace = namespace.intern(); @@ -447,15 +502,15 @@ public class MXSerializer implements XmlSerializer { } if (namespace == null) { throw new IllegalArgumentException("namespace must be not null" + getLocation()); - } else if (namespace.length() == 0) { + } else if (namespace.isEmpty()) { throw new IllegalArgumentException("default namespace cannot have prefix" + getLocation()); } // first check if namespace is already in scope for (int i = namespaceEnd - 1; i >= 0; --i) { if (namespace.equals(namespaceUri[i])) { - final String prefix = namespacePrefix[i]; - if (nonEmpty && prefix.length() == 0) { + String prefix = namespacePrefix[i]; + if (nonEmpty && prefix.isEmpty()) { continue; } @@ -470,24 +525,6 @@ public class MXSerializer implements XmlSerializer { return generatePrefix(namespace); } - private String generatePrefix(String namespace) { - ++autoDeclaredPrefixes; - // fast lookup uses table that was pre-initialized in static{} .... - final String prefix = autoDeclaredPrefixes < precomputedPrefixes.length - ? precomputedPrefixes[autoDeclaredPrefixes] - : ("n" + autoDeclaredPrefixes).intern(); - - // declare prefix - if (namespaceEnd >= namespacePrefix.length) { - ensureNamespacesCapacity(); - } - namespacePrefix[namespaceEnd] = prefix; - namespaceUri[namespaceEnd] = namespace; - ++namespaceEnd; - - return prefix; - } - @Override public int getDepth() { return depth; @@ -504,8 +541,7 @@ public class MXSerializer implements XmlSerializer { } @Override - public XmlSerializer startTag(String namespace, String name) - throws IOException { + public XmlSerializer startTag(String namespace, String name) throws IOException { if (startTagIncomplete) { closeStartTag(); } @@ -521,20 +557,22 @@ public class MXSerializer implements XmlSerializer { ensureElementsCapacity(); } - if (checkNamesInterned && namesInterned) + if (checkNamesInterned && namesInterned) { checkInterning(namespace); + } elNamespace[depth] = (namesInterned || namespace == null) ? namespace : namespace.intern(); - if (checkNamesInterned && namesInterned) + if (checkNamesInterned && namesInterned) { checkInterning(name); + } elName[depth] = (namesInterned || name == null) ? name : name.intern(); - if (out == null) { + if (writer == null) { throw new IllegalStateException("setOutput() must called set before serialization can start"); } - out.write('<'); + write('<'); if (namespace != null) { - if (namespace.length() > 0) { + if (!namespace.isEmpty()) { // in future make this algo a feature on serializer String prefix = null; if (depth > 0 && (namespaceEnd - elNamespaceCount[depth - 1]) == 1) { @@ -557,13 +595,13 @@ public class MXSerializer implements XmlSerializer { } } if (prefix == null) { - prefix = lookupOrDeclarePrefix(namespace); + prefix = getPrefix(namespace, true, false); } // make sure that default ("") namespace to not print ":" - if (prefix.length() > 0) { + if (!prefix.isEmpty()) { elPrefix[depth] = prefix; - out.write(prefix); - out.write(':'); + write(prefix); + write(':'); } else { elPrefix[depth] = ""; } @@ -571,10 +609,10 @@ public class MXSerializer implements XmlSerializer { // make sure that default namespace can be declared for (int i = namespaceEnd - 1; i >= 0; --i) { if (namespacePrefix[i] == "") { - final String uri = namespaceUri[i]; + String uri = namespaceUri[i]; if (uri == null) { setPrefix("", ""); - } else if (uri.length() > 0) { + } else if (!uri.isEmpty()) { throw new IllegalStateException("start tag can not be written in empty default namespace " + "as default namespace is currently bound to '" + uri + "'" + getLocation()); @@ -587,18 +625,40 @@ public class MXSerializer implements XmlSerializer { } else { elPrefix[depth] = ""; } - out.write(name); + write(name); return this; } + private void closeStartTag() throws IOException { + if (finished) { + throw new IllegalArgumentException("trying to write past already finished output" + getLocation()); + } + if (seenBracket) { + seenBracket = seenBracketBracket = false; + } + if (startTagIncomplete || setPrefixCalled) { + if (setPrefixCalled) { + throw new IllegalArgumentException("startTag() must be called immediately after setPrefix()" + getLocation()); + } + if (!startTagIncomplete) { + throw new IllegalArgumentException("trying to close start tag that is not opened" + getLocation()); + } + + // write all namespace declarations! + writeNamespaceDeclarations(); + write('>'); + elNamespaceCount[depth] = namespaceEnd; + startTagIncomplete = false; + } + } + @Override - public XmlSerializer attribute(String namespace, String name, String value) - throws IOException { + public XmlSerializer attribute(String namespace, String name, String value) throws IOException { if (!startTagIncomplete) { throw new IllegalArgumentException("startTag() must be called before attribute()" + getLocation()); } - out.write(' '); - if (namespace != null && namespace.length() > 0) { + write(' '); + if (namespace != null && !namespace.isEmpty()) { if (!namesInterned) { namespace = namespace.intern(); } else if (checkNamesInterned) { @@ -610,79 +670,18 @@ public class MXSerializer implements XmlSerializer { // NOTE: attributes such as a='b' are in NO namespace prefix = generatePrefix(namespace); } - out.write(prefix); - out.write(':'); + write(prefix); + write(':'); } - out.write(name); - out.write('='); - out.write(attributeUseApostrophe ? '\'' : '"'); - writeAttributeValue(value, out); - out.write(attributeUseApostrophe ? '\'' : '"'); + write(name); + write("=\""); + writeAttributeValue(value); + write('"'); return this; } - protected void closeStartTag() throws IOException { - if (finished) { - throw new IllegalArgumentException("trying to write past already finished output" - + getLocation()); - } - if (seenBracket) { - seenBracket = seenBracketBracket = false; - } - if (startTagIncomplete || setPrefixCalled) { - if (setPrefixCalled) { - throw new IllegalArgumentException("startTag() must be called immediately after setPrefix()" - + getLocation()); - } - if (!startTagIncomplete) { - throw new IllegalArgumentException("trying to close start tag that is not opened" - + getLocation()); - } - - // write all namespace declarations! - writeNamespaceDeclarations(); - out.write('>'); - elNamespaceCount[depth] = namespaceEnd; - startTagIncomplete = false; - } - } - - protected void writeNamespaceDeclarations() throws IOException { - Set uniqueNamespaces = new HashSet<>(); - for (int i = elNamespaceCount[depth - 1]; i < namespaceEnd; i++) { - String prefix = namespacePrefix[i]; - String uri = namespaceUri[i]; - - // Some applications as seen in #2664 have duplicated namespaces. - // AOSP doesn't care, but the parser does. So we filter them out. - if (uniqueNamespaces.contains(prefix + uri)) { - continue; - } - - if (doIndent && uri.length() > 40) { - writeIndent(); - out.write(" "); - } - if (prefix != "") { - out.write(" xmlns:"); - out.write(prefix); - out.write('='); - } else { - out.write(" xmlns="); - } - out.write(attributeUseApostrophe ? '\'' : '"'); - - // NOTE: escaping of namespace value the same way as attributes!!!! - writeAttributeValue(uri, out); - out.write(attributeUseApostrophe ? '\'' : '"'); - - uniqueNamespaces.add(prefix + uri); - } - } - @Override - public XmlSerializer endTag(String namespace, String name) - throws IOException { + public XmlSerializer endTag(String namespace, String name) throws IOException { seenBracket = seenBracketBracket = false; if (namespace != null) { if (!namesInterned) { @@ -700,19 +699,19 @@ public class MXSerializer implements XmlSerializer { } if (startTagIncomplete) { writeNamespaceDeclarations(); - out.write(" />"); // space is added to make it easier to work in XHTML!!! + write(" />"); // space is added to make it easier to work in XHTML!!! } else { if (doIndent && seenTag) { writeIndent(); } - out.write(" 0) { - out.write(startTagPrefix); - out.write(':'); + if (!startTagPrefix.isEmpty()) { + write(startTagPrefix); + write(':'); } - out.write(name); - out.write('>'); + write(name); + write('>'); } --depth; namespaceEnd = elNamespaceCount[depth]; @@ -723,142 +722,209 @@ public class MXSerializer implements XmlSerializer { @Override public XmlSerializer text(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled) + if (startTagIncomplete || setPrefixCalled) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - writeElementContent(text, out); + } + writeElementContent(text); return this; } @Override - public XmlSerializer text(char[] buf, int start, int len) - throws IOException { - if (startTagIncomplete || setPrefixCalled) + public XmlSerializer text(char[] buf, int start, int len) throws IOException { + if (startTagIncomplete || setPrefixCalled) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - writeElementContent(buf, start, len, out); + } + writeElementContent(buf, start, len); return this; } @Override public void cdsect(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled || seenBracket) + if (startTagIncomplete || setPrefixCalled || seenBracket) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - out.write(""); + } + write(""); } @Override public void entityRef(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled || seenBracket) + if (startTagIncomplete || setPrefixCalled || seenBracket) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - out.write('&'); - out.write(text); // escape? - out.write(';'); + } + write('&'); + write(text); + write(';'); } @Override public void processingInstruction(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled || seenBracket) + if (startTagIncomplete || setPrefixCalled || seenBracket) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - out.write(""); + } + write(""); } @Override public void comment(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled || seenBracket) + if (startTagIncomplete || setPrefixCalled || seenBracket) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - out.write(""); + } + write(""); } @Override public void docdecl(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled || seenBracket) + if (startTagIncomplete || setPrefixCalled || seenBracket) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - out.write(""); + } + write(""); } @Override public void ignorableWhitespace(String text) throws IOException { - if (startTagIncomplete || setPrefixCalled || seenBracket) + if (startTagIncomplete || setPrefixCalled || seenBracket) { closeStartTag(); - if (doIndent && seenTag) + } + if (doIndent && seenTag) { seenTag = false; - if (text.length() == 0) { + } + if (text.isEmpty()) { throw new IllegalArgumentException("empty string is not allowed for ignorable whitespace" + getLocation()); } - out.write(text); // no escape? + write(text); } @Override public void flush() throws IOException { - if (!finished && startTagIncomplete) + if (!finished && startTagIncomplete) { closeStartTag(); - out.flush(); + } + flushBuffer(); } // --- utility methods - protected void writeAttributeValue(String value, Writer out) - throws IOException { - // .[apostrophe and <, & escaped], - final char quot = attributeUseApostrophe ? '\'' : '"'; - final String quotEntity = attributeUseApostrophe ? "'" : """; + private String generatePrefix(String namespace) { + ++autoDeclaredPrefixes; + // fast lookup uses table that was pre-initialized in static{} .... + String prefix = autoDeclaredPrefixes < precomputedPrefixes.length + ? precomputedPrefixes[autoDeclaredPrefixes] + : ("n" + autoDeclaredPrefixes).intern(); + // declare prefix + if (namespaceEnd >= namespacePrefix.length) { + ensureNamespacesCapacity(); + } + namespacePrefix[namespaceEnd] = prefix; + namespaceUri[namespaceEnd] = namespace; + ++namespaceEnd; + + return prefix; + } + + private void writeNamespaceDeclarations() throws IOException { + Set uniqueNamespaces = new HashSet<>(); + for (int i = elNamespaceCount[depth - 1]; i < namespaceEnd; i++) { + String prefix = namespacePrefix[i]; + String uri = namespaceUri[i]; + + // Some applications as seen in #2664 have duplicated namespaces. + // AOSP doesn't care, but the parser does. So we filter them writer. + if (uniqueNamespaces.contains(prefix + uri)) { + continue; + } + + if (doIndent && uri.length() > 40) { + writeIndent(); + write(' '); + } + write(" xmlns"); + if (prefix != "") { + write(':'); + write(prefix); + } + write("=\""); + writeAttributeValue(uri); + write('"'); + + uniqueNamespaces.add(prefix + uri); + } + } + + private void writeAttributeValue(String value) throws IOException { + if (attrValueNoEscape) { + write(value); + return; + } + // .[&, < and " escaped], int pos = 0; for (int i = 0; i < value.length(); i++) { char ch = value.charAt(i); if (ch == '&') { - if (i > pos) - out.write(value.substring(pos, i)); - out.write("&"); + if (i > pos) { + write(value.substring(pos, i)); + } + write("&"); pos = i + 1; } if (ch == '<') { - if (i > pos) - out.write(value.substring(pos, i)); - out.write("<"); + if (i > pos) { + write(value.substring(pos, i)); + } + write("<"); pos = i + 1; - } else if (ch == quot) { - if (i > pos) - out.write(value.substring(pos, i)); - out.write(quotEntity); + } else if (ch == '"') { + if (i > pos) { + write(value.substring(pos, i)); + } + write("""); pos = i + 1; } else if (ch < 32) { // in XML 1.0 only legal character are #x9 | #xA | #xD // and they must be escaped otherwise in attribute value they // are normalized to spaces if (ch == 13 || ch == 10 || ch == 9) { - if (i > pos) - out.write(value.substring(pos, i)); - out.write("&#"); - out.write(Integer.toString(ch)); - out.write(';'); + if (i > pos) { + write(value.substring(pos, i)); + } + write("&#"); + write(Integer.toString(ch)); + write(';'); pos = i + 1; } else { - if (TRACE_ESCAPING) + if (TRACE_ESCAPING) { System.err.println(getClass().getName() + " DEBUG ATTR value.len=" + value.length() + " " + printable(value)); - + } throw new IllegalStateException( "character " + printable(ch) + " (" + Integer.toString(ch) + ") is not allowed in output" + getLocation() + " (attr value=" @@ -866,17 +932,11 @@ public class MXSerializer implements XmlSerializer { } } } - if (pos > 0) { - out.write(value.substring(pos)); - } else { - out.write(value); // this is shortcut to the most common case - } + write(pos > 0 ? value.substring(pos) : value); } - protected void writeElementContent(String text, Writer out) - throws IOException { - - // For some reason, some non-empty, empty characters are surviving this far and getting filtered out + private void writeElementContent(String text) throws IOException { + // For some reason, some non-empty, empty characters are surviving this far and getting filtered writer // So we are left with null, which causes an NPE if (text == null) { return; @@ -898,29 +958,33 @@ public class MXSerializer implements XmlSerializer { if (ch == '&') { if (!(i < text.length() - 3 && text.charAt(i+1) == 'l' && text.charAt(i+2) == 't' && text.charAt(i+3) == ';')) { - if (i > pos) - out.write(text.substring(pos, i)); - out.write("&"); + if (i > pos) { + write(text.substring(pos, i)); + } + write("&"); pos = i + 1; } } else if (ch == '<') { - if (i > pos) - out.write(text.substring(pos, i)); - out.write("<"); + if (i > pos) { + write(text.substring(pos, i)); + } + write("<"); pos = i + 1; } else if (seenBracketBracket && ch == '>') { - if (i > pos) - out.write(text.substring(pos, i)); - out.write(">"); + if (i > pos) { + write(text.substring(pos, i)); + } + write(">"); pos = i + 1; } else if (ch < 32) { // in XML 1.0 only legal character are #x9 | #xA | #xD if (ch == 9 || ch == 10 || ch == 13) { // pass through } else { - if (TRACE_ESCAPING) + if (TRACE_ESCAPING) { System.err.println(getClass().getName() + " DEBUG TEXT value.len=" + text.length() + " " + printable(text)); + } throw new IllegalStateException("character " + Integer.toString(ch) + " is not allowed in output" + getLocation() + " (text value=" + printable(text) + ")"); @@ -929,24 +993,17 @@ public class MXSerializer implements XmlSerializer { if (seenBracket) { seenBracketBracket = seenBracket = false; } - } } - if (pos > 0) { - out.write(text.substring(pos)); - } else { - out.write(text); // this is shortcut to the most common case - } - + write(pos > 0 ? text.substring(pos) : text); } - protected void writeElementContent(char[] buf, int off, int len, Writer out) - throws IOException { + private void writeElementContent(char[] buf, int off, int len) throws IOException { // escape '<', '&', ']]>' - final int end = off + len; + int end = off + len; int pos = off; for (int i = off; i < end; i++) { - final char ch = buf[i]; + char ch = buf[i]; if (ch == ']') { if (seenBracket) { seenBracketBracket = true; @@ -956,31 +1013,32 @@ public class MXSerializer implements XmlSerializer { } else { if (ch == '&') { if (i > pos) { - out.write(buf, pos, i - pos); + write(buf, pos, i - pos); } - out.write("&"); + write("&"); pos = i + 1; } else if (ch == '<') { if (i > pos) { - out.write(buf, pos, i - pos); + write(buf, pos, i - pos); } - out.write("<"); + write("<"); pos = i + 1; } else if (seenBracketBracket && ch == '>') { if (i > pos) { - out.write(buf, pos, i - pos); + write(buf, pos, i - pos); } - out.write(">"); + write(">"); pos = i + 1; } else if (ch < 32) { // in XML 1.0 only legal character are #x9 | #xA | #xD if (ch == 9 || ch == 10 || ch == 13) { // pass through } else { - if (TRACE_ESCAPING) + if (TRACE_ESCAPING) { System.err.println(getClass().getName() + " DEBUG TEXT value.len=" + len + " " + printable(new String(buf, off, len))); + } throw new IllegalStateException("character " + printable(ch) + " (" + Integer.toString(ch) + ") is not allowed in output" + getLocation()); @@ -992,24 +1050,24 @@ public class MXSerializer implements XmlSerializer { } } if (end > pos) { - out.write(buf, pos, end - pos); + write(buf, pos, end - pos); } } - protected static String printable(String s) { - if (s == null) { + private static String printable(String str) { + if (str == null) { return "null"; } - StringBuffer retval = new StringBuffer(s.length() + 16); + StringBuffer retval = new StringBuffer(str.length() + 16); retval.append("'"); - for (int i = 0; i < s.length(); i++) { - addPrintable(retval, s.charAt(i)); + for (int i = 0; i < str.length(); i++) { + addPrintable(retval, str.charAt(i)); } retval.append("'"); return retval.toString(); } - protected static String printable(char ch) { + private static String printable(char ch) { StringBuffer retval = new StringBuffer(); addPrintable(retval, ch); return retval.toString(); @@ -1017,37 +1075,38 @@ public class MXSerializer implements XmlSerializer { private static void addPrintable(StringBuffer retval, char ch) { switch (ch) { - case '\b': - retval.append("\\b"); - break; - case '\t': - retval.append("\\t"); - break; - case '\n': - retval.append("\\n"); - break; - case '\f': - retval.append("\\f"); - break; - case '\r': - retval.append("\\r"); - break; - case '\"': - retval.append("\\\""); - break; - case '\'': - retval.append("\\'"); - break; - case '\\': - retval.append("\\\\"); - break; - default: - if (ch < 0x20 || ch > 0x7e) { - final String ss = "0000" + Integer.toString(ch, 16); - retval.append("\\u").append(ss.substring(ss.length() - 4)); - } else { - retval.append(ch); - } + case '\b': + retval.append("\\b"); + break; + case '\t': + retval.append("\\t"); + break; + case '\n': + retval.append("\\n"); + break; + case '\f': + retval.append("\\f"); + break; + case '\r': + retval.append("\\r"); + break; + case '\"': + retval.append("\\\""); + break; + case '\'': + retval.append("\\'"); + break; + case '\\': + retval.append("\\\\"); + break; + default: + if (ch < 0x20 || ch > 0x7e) { + String str = "0000" + Integer.toString(ch, 16); + retval.append("\\u").append(str.substring(str.length() - 4)); + } else { + retval.append(ch); + } + break; } } } diff --git a/brut.j.xml/src/main/java/brut/xmlpull/XmlPullUtils.java b/brut.j.xml/src/main/java/brut/xmlpull/XmlPullUtils.java new file mode 100644 index 00000000..921cf6d0 --- /dev/null +++ b/brut.j.xml/src/main/java/brut/xmlpull/XmlPullUtils.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2010 Ryszard Wiśniewski + * Copyright (C) 2010 Connor Tumbleson + * + * 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 + * + * https://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 brut.xmlpull; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.*; + +public class XmlPullUtils { + private static final String PROPERTY_XMLDECL_STANDALONE + = "http://xmlpull.org/v1/doc/properties.html#xmldecl-standalone"; + + public interface EventHandler { + boolean onEvent(XmlPullParser in, XmlSerializer out) throws XmlPullParserException; + } + + public static void copy(XmlPullParser in, XmlSerializer out) + throws XmlPullParserException, IOException { + copy(in, out, null); + } + + public static void copy(XmlPullParser in, XmlSerializer out, EventHandler handler) + throws XmlPullParserException, IOException { + Boolean standalone = (Boolean) in.getProperty(PROPERTY_XMLDECL_STANDALONE); + + // Some parsers may have already consumed the event that starts the + // document, so we manually emit that event here for consistency + if (in.getEventType() == XmlPullParser.START_DOCUMENT) { + out.startDocument(in.getInputEncoding(), standalone); + } + + while (true) { + int event = in.nextToken(); + if (event == XmlPullParser.START_DOCUMENT) { + out.startDocument(in.getInputEncoding(), standalone); + continue; + } + if (event == XmlPullParser.END_DOCUMENT) { + out.endDocument(); + break; + } + if (handler != null && handler.onEvent(in, out)) { + continue; + } + switch (event) { + case XmlPullParser.START_TAG: + if (!in.getFeature(XmlPullParser.FEATURE_REPORT_NAMESPACE_ATTRIBUTES)) { + int nsStart = in.getNamespaceCount(in.getDepth() - 1); + int nsEnd = in.getNamespaceCount(in.getDepth()); + for (int i = nsStart; i < nsEnd; i++) { + String prefix = in.getNamespacePrefix(i); + String ns = in.getNamespaceUri(i); + out.setPrefix(prefix, ns); + } + } + out.startTag(normalizeNamespace(in.getNamespace()), in.getName()); + for (int i = 0; i < in.getAttributeCount(); i++) { + String ns = normalizeNamespace(in.getAttributeNamespace(i)); + String name = in.getAttributeName(i); + String value = in.getAttributeValue(i); + out.attribute(ns, name, value); + } + break; + case XmlPullParser.END_TAG: + out.endTag(normalizeNamespace(in.getNamespace()), in.getName()); + break; + case XmlPullParser.TEXT: + out.text(in.getText()); + break; + case XmlPullParser.CDSECT: + out.cdsect(in.getText()); + break; + case XmlPullParser.ENTITY_REF: + out.entityRef(in.getName()); + break; + case XmlPullParser.IGNORABLE_WHITESPACE: + out.ignorableWhitespace(in.getText()); + break; + case XmlPullParser.PROCESSING_INSTRUCTION: + out.processingInstruction(in.getText()); + break; + case XmlPullParser.COMMENT: + out.comment(in.getText()); + break; + case XmlPullParser.DOCDECL: + out.docdecl(in.getText()); + break; + default: + throw new IllegalStateException("Unknown event: " + event); + } + } + } + + /** + * Some parsers may return an empty string when a namespace in unsupported, + * which can confuse serializers. This method normalizes empty strings to + * be null. + */ + private static String normalizeNamespace(String namespace) { + if (namespace == null || namespace.isEmpty()) { + return null; + } else { + return namespace; + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 0ad1cf69..44d71637 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -83,7 +83,7 @@ subprojects { targetCompatibility = JavaVersion.VERSION_1_8 } - val mavenProjects = arrayOf("apktool-lib", "apktool-cli", "brut.j.common", "brut.j.util", "brut.j.dir") + val mavenProjects = arrayOf("brut.j.common", "brut.j.util", "brut.j.dir", "brut.j.xml", "apktool-lib", "apktool-cli") if (project.name in mavenProjects) { apply(plugin = "maven-publish") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7ce26123..efa0bf51 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ guava = "33.3.1-jre" junit = "4.13.2" r8 = "8.5.35" smali = "3.0.8" -xmlpull = "1.1.6" +xmlpull = "1.1.3.1" xmlunit = "2.10.0" [libraries] @@ -21,5 +21,5 @@ guava = { module = "com.google.guava:guava", version.ref = "guava" } junit = { module = "junit:junit", version.ref = "junit" } r8 = { module = "com.android.tools:r8", version.ref = "r8" } smali = { module = "com.android.tools.smali:smali", version.ref = "smali" } -xmlpull = { module = "org.ogce:xpp3", version.ref = "xmlpull" } +xmlpull = { module = "xmlpull:xmlpull", version.ref = "xmlpull" } xmlunit = { module = "org.xmlunit:xmlunit-legacy", version.ref = "xmlunit" } diff --git a/settings.gradle.kts b/settings.gradle.kts index db82a26b..619f8094 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,5 @@ rootProject.name = "apktool-cli" -include("brut.j.common", "brut.j.util", "brut.j.dir", "brut.apktool:apktool-lib", "brut.apktool:apktool-cli") +include("brut.j.common", "brut.j.util", "brut.j.dir", "brut.j.xml", "brut.apktool:apktool-lib", "brut.apktool:apktool-cli") dependencyResolutionManagement { versionCatalogs {