AAPT2: Add support to strip namespaces from XML
The --no-xml-namespaces flag will strip namespace information from
compiled binary XML files in res/* (excluding res/raw/*) and
AndroidManifest.xml. It will also strip URI information from compiled
binary XML files in res/* (excluding res/raw/* and AndroidManifest.xml).
AndroidManifest.xml URI information is retained due to PackageParser, which
requires the Android URI for intent filters.
Bug: 29115919
Change-Id: I90cad6ed39ce02a69776f55314c1d4f38ad1aabe
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 5a2bb6a..ea95dd1 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -74,6 +74,7 @@
bool generateNonFinalIds = false;
std::vector<std::string> javadocAnnotations;
bool outputToDirectory = false;
+ bool noXmlNamespaces = false;
bool autoAddOverlay = false;
bool doNotCompressAnything = false;
std::unordered_set<std::string> extensionsToNotCompress;
@@ -293,6 +294,7 @@
struct ResourceFileFlattenerOptions {
bool noAutoVersion = false;
bool noVersionVectors = false;
+ bool noXmlNamespaces = false;
bool keepRawValues = false;
bool doNotCompressAnything = false;
bool updateProguardSpec = false;
@@ -382,6 +384,13 @@
return false;
}
+ if (mOptions.noXmlNamespaces) {
+ XmlNamespaceRemover namespaceRemover;
+ if (!namespaceRemover.consume(mContext, outFileOp->xmlToFlatten.get())) {
+ return false;
+ }
+ }
+
if (!mOptions.noAutoVersion) {
if (mOptions.noVersionVectors) {
// Skip this if it is a vector or animated-vector.
@@ -1296,6 +1305,7 @@
fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
+ fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
fileFlattenerOptions.updateProguardSpec =
static_cast<bool>(mOptions.generateProguardRulesPath);
@@ -1594,6 +1604,14 @@
error = true;
}
}
+
+ if (mOptions.noXmlNamespaces) {
+ // PackageParser will fail if URIs are removed from AndroidManifest.xml.
+ XmlNamespaceRemover namespaceRemover(true /* keepUris */);
+ if (!namespaceRemover.consume(mContext, manifestXml.get())) {
+ error = true;
+ }
+ }
} else {
error = true;
}
@@ -1732,6 +1750,9 @@
.optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
"by -o",
&options.outputToDirectory)
+ .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI "
+ "information from AndroidManifest.xml\nand XML binaries in res/*.",
+ &options.noXmlNamespaces)
.optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
"AndroidManifest.xml",
&options.manifestFixerOptions.minSdkVersionDefault)
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 43b8fb4..82e2868 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -86,6 +86,23 @@
};
/**
+ * Removes namespace nodes and URI information from the XmlResource.
+ *
+ * Once an XmlResource is processed by this consumer, it is no longer able to have its attributes
+ * parsed. As such, this XmlResource must have already been processed by XmlReferenceLinker.
+ */
+class XmlNamespaceRemover : public IXmlResourceConsumer {
+private:
+ bool mKeepUris;
+
+public:
+ XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris) {
+ };
+
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
+};
+
+/**
* Resolves attributes in the XmlResource and compiles string values to resource values.
* Once an XmlResource is processed by this linker, it is ready to be flattened.
*/
diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp
new file mode 100644
index 0000000..9f95177
--- /dev/null
+++ b/tools/aapt2/link/XmlNamespaceRemover.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#include "ResourceTable.h"
+#include "link/Linkers.h"
+
+#include <algorithm>
+
+namespace aapt {
+
+namespace {
+
+/**
+ * Visits each xml Node, removing URI references and nested namespaces.
+ */
+class XmlVisitor : public xml::Visitor {
+public:
+ XmlVisitor(bool keepUris) : mKeepUris(keepUris) {
+ }
+
+ void visit(xml::Element* el) override {
+ // Strip namespaces
+ for (auto& child : el->children) {
+ while (child && xml::nodeCast<xml::Namespace>(child.get())) {
+ if (child->children.empty()) {
+ child = {};
+ } else {
+ child = std::move(child->children.front());
+ child->parent = el;
+ }
+ }
+ }
+ el->children.erase(std::remove_if(el->children.begin(), el->children.end(),
+ [](const std::unique_ptr<xml::Node>& child) -> bool {
+ return child == nullptr;
+ }), el->children.end());
+
+ if (!mKeepUris) {
+ for (xml::Attribute& attr : el->attributes) {
+ attr.namespaceUri = std::string();
+ }
+ el->namespaceUri = std::string();
+ }
+ xml::Visitor::visit(el);
+ }
+
+private:
+ bool mKeepUris;
+};
+
+} // namespace
+
+bool XmlNamespaceRemover::consume(IAaptContext* context, xml::XmlResource* resource) {
+ if (!resource->root) {
+ return false;
+ }
+ // Replace any root namespaces until the root is a non-namespace node
+ while (xml::nodeCast<xml::Namespace>(resource->root.get())) {
+ if (resource->root->children.empty()) {
+ break;
+ }
+ resource->root = std::move(resource->root->children.front());
+ resource->root->parent = nullptr;
+ }
+ XmlVisitor visitor(mKeepUris);
+ resource->root->accept(&visitor);
+ return true;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
new file mode 100644
index 0000000..e72ea439
--- /dev/null
+++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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
+ *
+ * http://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.
+ */
+
+#include "link/Linkers.h"
+#include "test/Test.h"
+
+namespace aapt {
+
+class XmlUriTestVisitor : public xml::Visitor {
+public:
+ void visit(xml::Element* el) override {
+ for (const auto& attr : el->attributes) {
+ EXPECT_EQ(std::string(), attr.namespaceUri);
+ }
+ EXPECT_EQ(std::string(), el->namespaceUri);
+ xml::Visitor::visit(el);
+ }
+
+ void visit(xml::Namespace* ns) override {
+ EXPECT_EQ(std::string(), ns->namespaceUri);
+ xml::Visitor::visit(ns);
+ }
+};
+
+class XmlNamespaceTestVisitor : public xml::Visitor {
+public:
+ void visit(xml::Namespace* ns) override {
+ ADD_FAILURE() << "Detected namespace: "
+ << ns->namespacePrefix << "=\"" << ns->namespaceUri << "\"";
+ xml::Visitor::visit(ns);
+ }
+};
+
+class XmlNamespaceRemoverTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ mContext = test::ContextBuilder()
+ .setCompilationPackage("com.app.test")
+ .build();
+ }
+
+protected:
+ std::unique_ptr<IAaptContext> mContext;
+};
+
+TEST_F(XmlNamespaceRemoverTest, RemoveUris) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="hello" />)EOF");
+
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
+
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
+
+ XmlUriTestVisitor visitor;
+ root->accept(&visitor);
+}
+
+TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:foo="http://schemas.android.com/apk/res/foo"
+ foo:bar="foobar"
+ android:text="hello" />)EOF");
+
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
+
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
+
+ XmlNamespaceTestVisitor visitor;
+ root->accept(&visitor);
+}
+
+TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:text="hello">
+ <View xmlns:foo="http://schemas.example.com/foo"
+ android:text="foo"/>
+ </View>)EOF");
+
+ XmlNamespaceRemover remover;
+ ASSERT_TRUE(remover.consume(mContext.get(), doc.get()));
+
+ xml::Node* root = doc.get()->root.get();
+ ASSERT_NE(root, nullptr);
+
+ XmlNamespaceTestVisitor visitor;
+ root->accept(&visitor);
+}
+
+} // namespace aapt