Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fba0cf29 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

AAPT2: Fix processing of quotes in XML

When processing attributes in XML, quotes can't be used to mark a
section as whitespace preserving, so the assumption should be that the
entire string is whitespace preserving, which makes quote characters
literals.

Bug: 62840718
Bug: 62840406
Test: make aapt2_tests
Change-Id: I4afff02148b5b8e78833abf1f323c2f5325d6155
parent 66ea8400
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ class BasicStringPiece {
 public:
  using const_iterator = const TChar*;
  using difference_type = size_t;
  using size_type = size_t;

  // End of string marker.
  constexpr static const size_t npos = static_cast<size_t>(-1);
+4 −2
Original line number Diff line number Diff line
@@ -257,9 +257,11 @@ class XmlFlattenerVisitor : public xml::Visitor {

      // Process plain strings to make sure they get properly escaped.
      StringPiece raw_value = xml_attr->value;
      util::StringBuilder str_builder;
      if (!options_.keep_raw_values) {

      util::StringBuilder str_builder(true /*preserve_spaces*/);
      str_builder.Append(xml_attr->value);

      if (!options_.keep_raw_values) {
        raw_value = str_builder.ToString();
      }

+102 −123
Original line number Diff line number Diff line
@@ -23,7 +23,13 @@
#include "util/BigBuffer.h"
#include "util/Util.h"

using android::StringPiece16;
using ::aapt::test::StrEq;
using ::android::StringPiece16;
using ::testing::Eq;
using ::testing::Ge;
using ::testing::IsNull;
using ::testing::Ne;
using ::testing::NotNull;

namespace aapt {

@@ -72,163 +78,138 @@ class XmlFlattenerTest : public ::testing::Test {
};

TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
            <View xmlns:test="http://com.test"
                  attr="hey">
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
      <View xmlns:test="http://com.test" attr="hey">
          <Layout test:hello="hi" />
          <Layout>Some text\\</Layout>
            </View>)EOF");
      </View>)");

  android::ResXMLTree tree;
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  ASSERT_EQ(android::ResXMLTree::START_NAMESPACE, tree.next());
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));

  size_t len;
  const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
  EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len));

  const char16_t* namespace_uri = tree.getNamespaceUri(&len);
  ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len));

  ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next());

  ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
  const char16_t* tag_name = tree.getElementName(&len);
  EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len));

  ASSERT_EQ(1u, tree.getAttributeCount());
  ASSERT_EQ(nullptr, tree.getAttributeNamespace(0, &len));
  const char16_t* attr_name = tree.getAttributeName(0, &len);
  EXPECT_EQ(StringPiece16(u"attr"), StringPiece16(attr_name, len));

  EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size()));
  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));

  ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next());
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));

  ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
  tag_name = tree.getElementName(&len);
  EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len));
  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
  EXPECT_THAT(tree.getAttributeNamespace(0, &len), IsNull());
  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"attr"));

  ASSERT_EQ(1u, tree.getAttributeCount());
  const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len);
  EXPECT_EQ(StringPiece16(u"http://com.test"), StringPiece16(attr_namespace, len));
  const StringPiece16 kAttr(u"attr");
  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kAttr.data(), kAttr.size()), Eq(0));

  attr_name = tree.getAttributeName(0, &len);
  EXPECT_EQ(StringPiece16(u"hello"), StringPiece16(attr_name, len));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));
  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));

  ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next());
  ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next());
  ASSERT_THAT(tree.getAttributeCount(), Eq(1u));
  EXPECT_THAT(tree.getAttributeNamespace(0, &len), StrEq(u"http://com.test"));
  EXPECT_THAT(tree.getAttributeName(0, &len), StrEq(u"hello"));

  ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
  tag_name = tree.getElementName(&len);
  EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len));
  ASSERT_EQ(0u, tree.getAttributeCount());
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));

  ASSERT_EQ(android::ResXMLTree::TEXT, tree.next());
  const char16_t* text = tree.getText(&len);
  EXPECT_EQ(StringPiece16(u"Some text\\"), StringPiece16(text, len));
  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));
  ASSERT_THAT(tree.getAttributeCount(), Eq(0u));

  ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next());
  ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
  tag_name = tree.getElementName(&len);
  EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
  EXPECT_THAT(tree.getText(&len), StrEq(u"Some text\\"));

  ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next());
  ASSERT_EQ(nullptr, tree.getElementNamespace(&len));
  tag_name = tree.getElementName(&len);
  EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
  EXPECT_THAT(tree.getElementName(&len), StrEq(u"Layout"));

  ASSERT_EQ(android::ResXMLTree::END_NAMESPACE, tree.next());
  namespace_prefix = tree.getNamespacePrefix(&len);
  EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_TAG));
  EXPECT_THAT(tree.getElementNamespace(&len), IsNull());
  EXPECT_THAT(tree.getElementName(&len), StrEq(u"View"));

  namespace_uri = tree.getNamespaceUri(&len);
  ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_NAMESPACE));
  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"test"));
  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://com.test"));

  ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next());
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::END_DOCUMENT));
}

TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) {
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
      <View xmlns:tools="http://schemas.android.com/tools"
          xmlns:foo="http://schemas.android.com/foo"
          foo:bar="Foo"
                tools:ignore="MissingTranslation"/>)EOF");
          tools:ignore="MissingTranslation"/>)");

  android::ResXMLTree tree;
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE);
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_NAMESPACE));

  size_t len;
  const char16_t* namespace_prefix = tree.getNamespacePrefix(&len);
  EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo");

  const char16_t* namespace_uri = tree.getNamespaceUri(&len);
  ASSERT_EQ(StringPiece16(namespace_uri, len),
            u"http://schemas.android.com/foo");

  ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG);
  EXPECT_THAT(tree.getNamespacePrefix(&len), StrEq(u"foo"));
  EXPECT_THAT(tree.getNamespaceUri(&len), StrEq(u"http://schemas.android.com/foo"));
  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::START_TAG));

  EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
            android::NAME_NOT_FOUND);
  EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0);
  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"),
              Eq(android::NAME_NOT_FOUND));
  EXPECT_THAT(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), Ge(0));
}

TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF(
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(
      <View xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@id/id"
          class="str"
                  style="@id/id"/>)EOF");
          style="@id/id"/>)");

  android::ResXMLTree tree;
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  while (tree.next() != android::ResXMLTree::START_TAG) {
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
  }

  EXPECT_EQ(tree.indexOfClass(), 0);
  EXPECT_EQ(tree.indexOfStyle(), 1);
  EXPECT_THAT(tree.indexOfClass(), Eq(0));
  EXPECT_THAT(tree.indexOfStyle(), Eq(1));
}

// The device ResXMLParser in libandroidfw differentiates between empty namespace and null
// namespace.
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"android\"/>");
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package="android"/>)");

  android::ResXMLTree tree;
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  while (tree.next() != android::ResXMLTree::START_TAG) {
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
  }

  const StringPiece16 kPackage = u"package";
  EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0);
  EXPECT_THAT(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), Ge(0));
}

TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) {
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>");
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<View package=""/>)");

  android::ResXMLTree tree;
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  while (tree.next() != android::ResXMLTree::START_TAG) {
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
  }

  const StringPiece16 kPackage = u"package";
  ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size());
  ASSERT_GE(idx, 0);
  ASSERT_THAT(idx, Ge(0));

  size_t len;
  EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len));
  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), NotNull());
}

TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
@@ -236,11 +217,11 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
  context_->SetPackageId(0x80);
  context_->SetNameManglerPolicy({"com.app.test.feature"});

  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF(
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"(
      <View xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@id/foo"
            app:foo="@id/foo" />)EOF");
            app:foo="@id/foo" />)");

  XmlReferenceLinker linker;
  ASSERT_TRUE(linker.Consume(context_.get(), doc.get()));
@@ -253,59 +234,57 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) {
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  while (tree.next() != android::ResXMLTree::START_TAG) {
    ASSERT_NE(android::ResXMLTree::BAD_DOCUMENT, tree.getEventType());
    ASSERT_NE(android::ResXMLTree::END_DOCUMENT, tree.getEventType());
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
  }

  ssize_t idx;

  idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id");
  ASSERT_GE(idx, 0);
  EXPECT_EQ(idx, tree.indexOfID());
  EXPECT_EQ(ResourceId(0x010100d0), ResourceId(tree.getAttributeNameResID(idx)));
  ASSERT_THAT(idx, Ge(0));
  EXPECT_THAT(tree.indexOfID(), Eq(idx));
  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x010100d0u));

  idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo");
  ASSERT_GE(idx, 0);
  EXPECT_EQ(ResourceId(0x80010000), ResourceId(tree.getAttributeNameResID(idx)));
  EXPECT_EQ(android::Res_value::TYPE_REFERENCE, tree.getAttributeDataType(idx));
  EXPECT_EQ(ResourceId(0x80020000), tree.getAttributeData(idx));
  ASSERT_THAT(idx, Ge(0));
  EXPECT_THAT(tree.getAttributeNameResID(idx), Eq(0x80010000u));
  EXPECT_THAT(tree.getAttributeDataType(idx), Eq(android::Res_value::TYPE_REFERENCE));
  EXPECT_THAT(tree.getAttributeData(idx), Eq(int32_t(0x80020000)));
}

TEST_F(XmlFlattenerTest, ProcessEscapedStrings) {
  std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(
      R"EOF(<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF");
      R"(<element value="\?hello" pattern="\\d{5}" other="&quot;">\\d{5}</element>)");

  android::ResXMLTree tree;
  ASSERT_TRUE(Flatten(doc.get(), &tree));

  while (tree.next() != android::ResXMLTree::START_TAG) {
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
    ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT);
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::BAD_DOCUMENT));
    ASSERT_THAT(tree.getEventType(), Ne(android::ResXMLTree::END_DOCUMENT));
  }

  const StringPiece16 kValue = u"value";
  const StringPiece16 kPattern = u"pattern";
  const StringPiece16 kOther = u"other";

  size_t len;
  ssize_t idx;
  const char16_t* str16;

  idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size());
  ASSERT_GE(idx, 0);
  str16 = tree.getAttributeStringValue(idx, &len);
  ASSERT_NE(nullptr, str16);
  EXPECT_EQ(StringPiece16(u"?hello"), StringPiece16(str16, len));
  ASSERT_THAT(idx, Ge(0));
  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"?hello"));

  idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size());
  ASSERT_GE(idx, 0);
  str16 = tree.getAttributeStringValue(idx, &len);
  ASSERT_NE(nullptr, str16);
  EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len));

  ASSERT_EQ(android::ResXMLTree::TEXT, tree.next());
  str16 = tree.getText(&len);
  ASSERT_NE(nullptr, str16);
  EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len));
  ASSERT_THAT(idx, Ge(0));
  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\\d{5}"));

  idx = tree.indexOfAttribute(nullptr, 0, kOther.data(), kOther.size());
  ASSERT_THAT(idx, Ge(0));
  EXPECT_THAT(tree.getAttributeStringValue(idx, &len), StrEq(u"\""));

  ASSERT_THAT(tree.next(), Eq(android::ResXMLTree::TEXT));
  EXPECT_THAT(tree.getText(&len), StrEq(u"\\d{5}"));
}

}  // namespace aapt
+2 −0
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@
  the set of Proguard keep rules. (bug 62216174)
- Automatically version XML `<adaptive-icon>` resources to v26. (bug 62316340)
- Fixed issue where escaped unicode characters would generate malformed UTF-8. (bug 62839202)
- Fixed issue where apostrophes or quotes used in XML attribute values were ignored.
  (bug 62840406, 62840718)

## Version 2.17
### `aapt2 ...`
+6 −0
Original line number Diff line number Diff line
@@ -136,6 +136,12 @@ void PrintTo(const Maybe<T>& value, std::ostream* out) {

namespace test {

MATCHER_P(StrEq, a,
          std::string(negation ? "isn't" : "is") + " equal to " +
              ::testing::PrintToString(android::StringPiece16(a))) {
  return android::StringPiece16(arg) == a;
}

MATCHER_P(ValueEq, a,
          std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) {
  return arg.Equals(&a);
Loading