2019-10-16 07:49:58 +02:00
|
|
|
/*
|
2020-04-11 12:33:05 +02:00
|
|
|
* Copyright (C) 2010 Ryszard Wiśniewski <brut.alll@gmail.com>
|
|
|
|
* Copyright (C) 2010 Connor Tumbleson <connor.tumbleson@gmail.com>
|
2015-04-16 15:02:24 +02:00
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
2021-08-24 15:31:41 +02:00
|
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
2015-04-16 15:02:24 +02:00
|
|
|
*
|
|
|
|
* 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.xml;
|
|
|
|
|
2021-03-07 21:06:45 +01:00
|
|
|
import brut.androlib.AndrolibException;
|
2022-05-07 12:52:07 +02:00
|
|
|
import org.w3c.dom.*;
|
2021-03-07 21:06:45 +01:00
|
|
|
import org.xml.sax.SAXException;
|
2015-04-16 15:02:24 +02:00
|
|
|
|
|
|
|
import javax.xml.parsers.DocumentBuilder;
|
|
|
|
import javax.xml.parsers.DocumentBuilderFactory;
|
|
|
|
import javax.xml.parsers.ParserConfigurationException;
|
|
|
|
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;
|
2021-03-07 21:06:45 +01:00
|
|
|
import javax.xml.xpath.*;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.logging.Logger;
|
2015-04-16 15:02:24 +02:00
|
|
|
|
|
|
|
public final class ResXmlPatcher {
|
2016-04-28 14:31:36 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes "debug" tag from file
|
|
|
|
*
|
|
|
|
* @param file AndroidManifest file
|
2021-03-07 21:06:45 +01:00
|
|
|
* @throws AndrolibException Error reading Manifest file
|
2016-04-28 14:31:36 +02:00
|
|
|
*/
|
|
|
|
public static void removeApplicationDebugTag(File file) throws AndrolibException {
|
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
Node application = doc.getElementsByTagName("application").item(0);
|
|
|
|
|
|
|
|
// load attr
|
|
|
|
NamedNodeMap attr = application.getAttributes();
|
|
|
|
Node debugAttr = attr.getNamedItem("android:debuggable");
|
|
|
|
|
|
|
|
// remove application:debuggable
|
|
|
|
if (debugAttr != null) {
|
|
|
|
attr.removeNamedItem("android:debuggable");
|
|
|
|
}
|
|
|
|
|
|
|
|
saveDocument(file, doc);
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
2020-05-20 12:20:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets "debug" tag in the file to true
|
|
|
|
*
|
|
|
|
* @param file AndroidManifest file
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
public static void setApplicationDebugTagTrue(File file) {
|
2020-05-20 12:20:41 +02:00
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
Node application = doc.getElementsByTagName("application").item(0);
|
|
|
|
|
|
|
|
// load attr
|
|
|
|
NamedNodeMap attr = application.getAttributes();
|
|
|
|
Node debugAttr = attr.getNamedItem("android:debuggable");
|
|
|
|
|
|
|
|
if (debugAttr == null) {
|
|
|
|
debugAttr = doc.createAttribute("android:debuggable");
|
|
|
|
attr.setNamedItem(debugAttr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// set application:debuggable to 'true
|
|
|
|
debugAttr.setNodeValue("true");
|
|
|
|
|
|
|
|
saveDocument(file, doc);
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
2016-04-28 14:31:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-07 12:52:07 +02:00
|
|
|
/**
|
|
|
|
* Sets the value of the network security config in the AndroidManifest file
|
|
|
|
*
|
|
|
|
* @param file AndroidManifest file
|
|
|
|
*/
|
|
|
|
public static void setNetworkSecurityConfig(File file) {
|
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
Node application = doc.getElementsByTagName("application").item(0);
|
|
|
|
|
|
|
|
// load attr
|
|
|
|
NamedNodeMap attr = application.getAttributes();
|
|
|
|
Node netSecConfAttr = attr.getNamedItem("android:networkSecurityConfig");
|
|
|
|
|
|
|
|
if (netSecConfAttr == null) {
|
|
|
|
// there is not an already existing network security configuration
|
|
|
|
netSecConfAttr = doc.createAttribute("android:networkSecurityConfig");
|
|
|
|
attr.setNamedItem(netSecConfAttr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// whether it already existed or it was created now set it to the proper value
|
|
|
|
netSecConfAttr.setNodeValue("@xml/network_security_config");
|
|
|
|
|
|
|
|
saveDocument(file, doc);
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a modified network security config file that is more permissive
|
|
|
|
*
|
|
|
|
* @param file network security config file
|
2022-07-10 13:34:45 +02:00
|
|
|
* @throws TransformerException XML file could not be edited
|
|
|
|
* @throws IOException XML file could not be located
|
|
|
|
* @throws SAXException XML file could not be read
|
|
|
|
* @throws ParserConfigurationException XML nodes could be written
|
2022-05-07 12:52:07 +02:00
|
|
|
*/
|
|
|
|
public static void modNetworkSecurityConfig(File file)
|
|
|
|
throws ParserConfigurationException, TransformerException, IOException, SAXException {
|
|
|
|
|
|
|
|
DocumentBuilderFactory documentFactory = DocumentBuilderFactory.newInstance();
|
|
|
|
DocumentBuilder documentBuilder = documentFactory.newDocumentBuilder();
|
|
|
|
Document document = documentBuilder.newDocument();
|
|
|
|
|
|
|
|
Element root = document.createElement("network-security-config");
|
|
|
|
document.appendChild(root);
|
|
|
|
Element baseConfig = document.createElement("base-config");
|
|
|
|
root.appendChild(baseConfig);
|
|
|
|
Element trustAnchors = document.createElement("trust-anchors");
|
|
|
|
baseConfig.appendChild(trustAnchors);
|
|
|
|
|
|
|
|
Element certSystem = document.createElement("certificates");
|
|
|
|
Attr attrSystem = document.createAttribute("src");
|
|
|
|
attrSystem.setValue("system");
|
|
|
|
certSystem.setAttributeNode(attrSystem);
|
|
|
|
trustAnchors.appendChild(certSystem);
|
|
|
|
|
|
|
|
Element certUser = document.createElement("certificates");
|
|
|
|
Attr attrUser = document.createAttribute("src");
|
|
|
|
attrUser.setValue("user");
|
|
|
|
certUser.setAttributeNode(attrUser);
|
|
|
|
trustAnchors.appendChild(certUser);
|
|
|
|
|
|
|
|
saveDocument(file, document);
|
|
|
|
}
|
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
/**
|
2021-03-07 21:06:45 +01:00
|
|
|
* Any @string reference in a provider value in AndroidManifest.xml will break on
|
2015-04-16 15:02:24 +02:00
|
|
|
* build, thus preventing the application from installing. This is from a bug/error
|
|
|
|
* in AOSP where public resources cannot be part of an authorities attribute within
|
2021-03-07 21:06:45 +01:00
|
|
|
* a provider tag.
|
2015-04-16 15:02:24 +02:00
|
|
|
*
|
|
|
|
* This finds any reference and replaces it with the literal value found in the
|
|
|
|
* res/values/strings.xml file.
|
|
|
|
*
|
|
|
|
* @param file File for AndroidManifest.xml
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
public static void fixingPublicAttrsInProviderAttributes(File file) {
|
2016-08-06 14:31:58 +02:00
|
|
|
boolean saved = false;
|
2015-04-16 15:02:24 +02:00
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
|
|
|
XPathExpression expression = xPath.compile("/manifest/application/provider");
|
|
|
|
|
|
|
|
Object result = expression.evaluate(doc, XPathConstants.NODESET);
|
|
|
|
NodeList nodes = (NodeList) result;
|
|
|
|
|
|
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
|
|
Node node = nodes.item(i);
|
|
|
|
NamedNodeMap attrs = node.getAttributes();
|
2022-07-10 13:34:45 +02:00
|
|
|
Node provider = attrs.getNamedItem("android:authorities");
|
2015-04-16 15:02:24 +02:00
|
|
|
|
2022-07-10 13:34:45 +02:00
|
|
|
if (provider != null) {
|
|
|
|
saved = isSaved(file, saved, provider);
|
2015-04-16 15:02:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-06 14:31:58 +02:00
|
|
|
// android:scheme
|
|
|
|
xPath = XPathFactory.newInstance().newXPath();
|
|
|
|
expression = xPath.compile("/manifest/application/activity/intent-filter/data");
|
|
|
|
|
|
|
|
result = expression.evaluate(doc, XPathConstants.NODESET);
|
|
|
|
nodes = (NodeList) result;
|
|
|
|
|
|
|
|
for (int i = 0; i < nodes.getLength(); i++) {
|
|
|
|
Node node = nodes.item(i);
|
|
|
|
NamedNodeMap attrs = node.getAttributes();
|
2022-07-10 13:34:45 +02:00
|
|
|
Node provider = attrs.getNamedItem("android:scheme");
|
2016-08-06 14:31:58 +02:00
|
|
|
|
2022-07-10 13:34:45 +02:00
|
|
|
if (provider != null) {
|
|
|
|
saved = isSaved(file, saved, provider);
|
2016-08-06 14:31:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (saved) {
|
|
|
|
saveDocument(file, doc);
|
|
|
|
}
|
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
} catch (SAXException | ParserConfigurationException | IOException |
|
|
|
|
XPathExpressionException | TransformerException ignored) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-05 21:44:10 +02:00
|
|
|
/**
|
|
|
|
* Checks if the replacement was properly made to a node.
|
|
|
|
*
|
|
|
|
* @param file File we are searching for value
|
|
|
|
* @param saved boolean on whether we need to save
|
2017-07-06 16:15:44 +02:00
|
|
|
* @param provider Node we are attempting to replace
|
2017-07-05 21:44:10 +02:00
|
|
|
* @return boolean
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
private static boolean isSaved(File file, boolean saved, Node provider) {
|
2017-07-05 21:44:10 +02:00
|
|
|
String reference = provider.getNodeValue();
|
|
|
|
String replacement = pullValueFromStrings(file.getParentFile(), reference);
|
|
|
|
|
|
|
|
if (replacement != null) {
|
|
|
|
provider.setNodeValue(replacement);
|
|
|
|
saved = true;
|
|
|
|
}
|
|
|
|
return saved;
|
|
|
|
}
|
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
/**
|
|
|
|
* Finds key in strings.xml file and returns text value
|
|
|
|
*
|
|
|
|
* @param directory Root directory of apk
|
|
|
|
* @param key String reference (ie @string/foo)
|
|
|
|
* @return String|null
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
public static String pullValueFromStrings(File directory, String key) {
|
2016-06-08 13:24:27 +02:00
|
|
|
if (key == null || ! key.contains("@")) {
|
2015-04-19 14:44:57 +02:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
File file = new File(directory, "/res/values/strings.xml");
|
|
|
|
key = key.replace("@string/", "");
|
|
|
|
|
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
|
|
|
XPathExpression expression = xPath.compile("/resources/string[@name=" + '"' + key + "\"]/text()");
|
|
|
|
|
|
|
|
Object result = expression.evaluate(doc, XPathConstants.STRING);
|
|
|
|
|
|
|
|
if (result != null) {
|
|
|
|
return (String) result;
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-02-15 14:08:03 +01:00
|
|
|
/**
|
|
|
|
* Finds key in integers.xml file and returns text value
|
|
|
|
*
|
|
|
|
* @param directory Root directory of apk
|
|
|
|
* @param key Integer reference (ie @integer/foo)
|
|
|
|
* @return String|null
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
public static String pullValueFromIntegers(File directory, String key) {
|
2019-02-15 14:08:03 +01:00
|
|
|
if (key == null || ! key.contains("@")) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
File file = new File(directory, "/res/values/integers.xml");
|
|
|
|
key = key.replace("@integer/", "");
|
|
|
|
|
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
XPath xPath = XPathFactory.newInstance().newXPath();
|
|
|
|
XPathExpression expression = xPath.compile("/resources/integer[@name=" + '"' + key + "\"]/text()");
|
|
|
|
|
|
|
|
Object result = expression.evaluate(doc, XPathConstants.STRING);
|
|
|
|
|
|
|
|
if (result != null) {
|
|
|
|
return (String) result;
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | XPathExpressionException ignored) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
/**
|
|
|
|
* Removes attributes like "versionCode" and "versionName" from file.
|
|
|
|
*
|
|
|
|
* @param file File representing AndroidManifest.xml
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
public static void removeManifestVersions(File file) {
|
2015-04-16 15:02:24 +02:00
|
|
|
if (file.exists()) {
|
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
Node manifest = doc.getFirstChild();
|
|
|
|
NamedNodeMap attr = manifest.getAttributes();
|
|
|
|
Node vCode = attr.getNamedItem("android:versionCode");
|
|
|
|
Node vName = attr.getNamedItem("android:versionName");
|
|
|
|
|
|
|
|
if (vCode != null) {
|
|
|
|
attr.removeNamedItem("android:versionCode");
|
|
|
|
}
|
|
|
|
if (vName != null) {
|
|
|
|
attr.removeNamedItem("android:versionName");
|
|
|
|
}
|
|
|
|
saveDocument(file, doc);
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replaces package value with passed packageOriginal string
|
|
|
|
*
|
|
|
|
* @param file File for AndroidManifest.xml
|
|
|
|
* @param packageOriginal Package name to replace
|
|
|
|
*/
|
2021-03-07 21:06:45 +01:00
|
|
|
public static void renameManifestPackage(File file, String packageOriginal) {
|
2015-04-16 15:02:24 +02:00
|
|
|
try {
|
|
|
|
Document doc = loadDocument(file);
|
|
|
|
|
|
|
|
// Get the manifest line
|
|
|
|
Node manifest = doc.getFirstChild();
|
|
|
|
|
|
|
|
// update package attribute
|
|
|
|
NamedNodeMap attr = manifest.getAttributes();
|
|
|
|
Node nodeAttr = attr.getNamedItem("package");
|
|
|
|
nodeAttr.setNodeValue(packageOriginal);
|
|
|
|
saveDocument(file, doc);
|
|
|
|
|
|
|
|
} catch (SAXException | ParserConfigurationException | IOException | TransformerException ignored) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param file File to load into Document
|
|
|
|
* @return Document
|
|
|
|
* @throws IOException
|
|
|
|
* @throws SAXException
|
|
|
|
* @throws ParserConfigurationException
|
|
|
|
*/
|
|
|
|
private static Document loadDocument(File file)
|
|
|
|
throws IOException, SAXException, ParserConfigurationException {
|
|
|
|
|
|
|
|
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
|
2017-05-27 21:12:54 +02:00
|
|
|
docFactory.setFeature(FEATURE_DISABLE_DOCTYPE_DECL, true);
|
2017-07-05 21:36:54 +02:00
|
|
|
docFactory.setFeature(FEATURE_LOAD_DTD, false);
|
|
|
|
|
2017-07-17 20:12:14 +02:00
|
|
|
try {
|
|
|
|
docFactory.setAttribute(ACCESS_EXTERNAL_DTD, " ");
|
|
|
|
docFactory.setAttribute(ACCESS_EXTERNAL_SCHEMA, " ");
|
|
|
|
} catch (IllegalArgumentException ex) {
|
|
|
|
LOGGER.warning("JAXP 1.5 Support is required to validate XML");
|
|
|
|
}
|
2017-05-27 21:12:54 +02:00
|
|
|
|
2015-04-16 15:02:24 +02:00
|
|
|
DocumentBuilder docBuilder = docFactory.newDocumentBuilder();
|
2017-07-12 20:16:00 +02:00
|
|
|
// Not using the parse(File) method on purpose, so that we can control when
|
|
|
|
// to close it. Somehow parse(File) does not seem to close the file in all cases.
|
2021-08-26 19:00:37 +02:00
|
|
|
try (FileInputStream inputStream = new FileInputStream(file)) {
|
|
|
|
return docBuilder.parse(inputStream);
|
2017-07-12 20:16:00 +02:00
|
|
|
}
|
2015-04-16 15:02:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param file File to save Document to (ie AndroidManifest.xml)
|
|
|
|
* @param doc Document being saved
|
|
|
|
* @throws IOException
|
|
|
|
* @throws SAXException
|
|
|
|
* @throws ParserConfigurationException
|
|
|
|
* @throws TransformerException
|
|
|
|
*/
|
|
|
|
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);
|
|
|
|
}
|
2017-05-27 21:12:54 +02:00
|
|
|
|
2017-07-08 20:20:26 +02:00
|
|
|
private static final String ACCESS_EXTERNAL_DTD = "http://javax.xml.XMLConstants/property/accessExternalDTD";
|
|
|
|
private static final String ACCESS_EXTERNAL_SCHEMA = "http://javax.xml.XMLConstants/property/accessExternalSchema";
|
2017-07-05 21:36:54 +02:00
|
|
|
private static final String FEATURE_LOAD_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
|
2017-05-27 21:12:54 +02:00
|
|
|
private static final String FEATURE_DISABLE_DOCTYPE_DECL = "http://apache.org/xml/features/disallow-doctype-decl";
|
2017-07-17 20:12:14 +02:00
|
|
|
|
|
|
|
private static final Logger LOGGER = Logger.getLogger(ResXmlPatcher.class.getName());
|
2015-04-16 15:02:24 +02:00
|
|
|
}
|