diff --git a/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java b/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java index c1bbedd..19b41a0 100644 --- a/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java +++ b/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java @@ -3,9 +3,16 @@ package com.bartek.esa.core.archetype; import com.bartek.esa.core.xml.XmlHelper; import com.bartek.esa.file.matcher.GlobMatcher; import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import javax.xml.namespace.QName; import java.io.File; +import java.util.Arrays; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.String.format; public abstract class XmlPlugin extends BasePlugin { private final GlobMatcher globMatcher; @@ -32,4 +39,21 @@ public abstract class XmlPlugin extends BasePlugin { protected Object xPath(Document xml, String expression, QName returnType) { return xmlHelper.xPath(xml, expression, returnType); } + + protected Stream stream(NodeList nodeList) { + return xmlHelper.stream(nodeList); + } + + protected String tagString(Node node) { + Node[] attributes = new Node[node.getAttributes().getLength()]; + for(int i=0; i format("%s=\"%s\"", n.getNodeName(), n.getNodeValue())) + .collect(Collectors.joining(" ")); + + return format("<%s %s ...", node.getNodeName(), attributesString); + } } diff --git a/src/main/java/com/bartek/esa/core/di/PluginModule.java b/src/main/java/com/bartek/esa/core/di/PluginModule.java index a549835..e99daca 100644 --- a/src/main/java/com/bartek/esa/core/di/PluginModule.java +++ b/src/main/java/com/bartek/esa/core/di/PluginModule.java @@ -95,4 +95,10 @@ public class PluginModule { public Plugin suppressWarningsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { return new SuppressWarningsPlugin(globMatcher, xmlHelper); } + + @Provides + @IntoSet + public Plugin exportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { + return new ExportedComponentsPlugin(globMatcher, xmlHelper); + } } diff --git a/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java b/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java new file mode 100644 index 0000000..ed3e96b --- /dev/null +++ b/src/main/java/com/bartek/esa/core/plugin/ExportedComponentsPlugin.java @@ -0,0 +1,72 @@ +package com.bartek.esa.core.plugin; + +import com.bartek.esa.core.archetype.AndroidManifestPlugin; +import com.bartek.esa.core.model.enumeration.Severity; +import com.bartek.esa.core.xml.XmlHelper; +import com.bartek.esa.file.matcher.GlobMatcher; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.inject.Inject; +import javax.xml.xpath.XPathConstants; +import java.util.Optional; + +import static java.lang.String.format; + +public class ExportedComponentsPlugin extends AndroidManifestPlugin { + + @Inject + public ExportedComponentsPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) { + super(globMatcher, xmlHelper); + } + + @Override + protected void run(Document xml) { + findExportedComponents(xml, "activity"); + findExportedComponents(xml, "service"); + findExportedComponents(xml, "receiver"); + findExportedProviders(xml); + } + + private void findExportedComponents(Document xml, String component) { + NodeList exportedActivities = (NodeList) xPath(xml, format("/manifest/application/%s", component), XPathConstants.NODESET); + stream(exportedActivities) + .filter(this::isExported) + .filter(node -> doesNotHavePermission(node, "android:permission")) + .forEach(node -> addIssue(Severity.WARNING, format(".%s.NO_PERMISSION", component.toUpperCase()), null, tagString(node))); + } + + private void findExportedProviders(Document xml) { + NodeList exportedProviders = (NodeList) xPath(xml, "/manifest/application/provider", XPathConstants.NODESET); + stream(exportedProviders) + .filter(this::isExported) + .filter(node -> doesNotHavePermission(node, "android:writePermission") + || doesNotHavePermission(node, "android:readPermission")) + .forEach(node -> addIssue(Severity.WARNING, ".PROVIDER.NO_PERMISSION", null, tagString(node))); + } + + private boolean doesNotHavePermission(Node node, String permissionAttribute) { + Boolean doesHavePermission = Optional.ofNullable(node.getAttributes().getNamedItem(permissionAttribute)) + .map(Node::getNodeValue) + .map(s -> !s.isEmpty()) + .orElse(false); + return !doesHavePermission; + } + + private boolean isExported(Node node) { + return Optional.ofNullable(node.getAttributes().getNamedItem("android:exported")) + .map(Node::getNodeValue) + .map(v -> v.equals("true")) + .orElse(false); + } + + private String nodeToString(Node node) { + String nodeName = Optional.ofNullable(node.getAttributes().getNamedItem("android:name")) + .map(Node::getNodeValue) + .map(name -> format(" android:name=\"%s\"", name)) + .orElse(""); + + return format("<%s%s ...", node.getNodeName(), nodeName); + } +} diff --git a/src/main/java/com/bartek/esa/core/xml/XmlHelper.java b/src/main/java/com/bartek/esa/core/xml/XmlHelper.java index 7a9ceee..f533431 100644 --- a/src/main/java/com/bartek/esa/core/xml/XmlHelper.java +++ b/src/main/java/com/bartek/esa/core/xml/XmlHelper.java @@ -3,6 +3,8 @@ package com.bartek.esa.core.xml; import com.bartek.esa.error.EsaException; import io.vavr.control.Try; import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import javax.inject.Inject; import javax.xml.namespace.QName; @@ -10,6 +12,8 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPathFactory; import java.io.File; +import java.util.Arrays; +import java.util.stream.Stream; public class XmlHelper { @@ -28,4 +32,13 @@ public class XmlHelper { return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType)) .getOrElseThrow(EsaException::new); } + + public Stream stream(NodeList nodeList) { + Node[] nodes = new Node[nodeList.getLength()]; + for (int i=0; i