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

Commit 96ea08f1 authored by Adam Lesinski's avatar Adam Lesinski
Browse files

AAPT2: Differentiate between Android and Java package names

Android package names are more strict (ASCII only) than Java package names.
Also fixed an issue where trailing underscores were disallowed in Android
package names.

Bug: 68468089
Test: make aapt2_tests
Change-Id: I1052e9e82b6617db6065ce448d9bf7972bb68d59
parent b54be8de
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -127,9 +127,9 @@ static bool VerifyManifest(xml::Element* el, SourcePathDiagnostics* diag) {
    diag->Error(DiagMessage(el->line_number)
    diag->Error(DiagMessage(el->line_number)
                << "attribute 'package' in <manifest> tag must not be a reference");
                << "attribute 'package' in <manifest> tag must not be a reference");
    return false;
    return false;
  } else if (!util::IsJavaPackageName(attr->value)) {
  } else if (!util::IsAndroidPackageName(attr->value)) {
    diag->Error(DiagMessage(el->line_number)
    diag->Error(DiagMessage(el->line_number)
                << "attribute 'package' in <manifest> tag is not a valid Java package name: '"
                << "attribute 'package' in <manifest> tag is not a valid Android package name: '"
                << attr->value << "'");
                << attr->value << "'");
    return false;
    return false;
  }
  }
+2 −1
Original line number Original line Diff line number Diff line
@@ -85,7 +85,8 @@ bool IsJavaIdentifier(const StringPiece& str) {
    return false;
    return false;
  }
  }


  if (!IsXidStart(iter.Next())) {
  const char32_t first_codepoint = iter.Next();
  if (!IsXidStart(first_codepoint) && first_codepoint != U'_' && first_codepoint != U'$') {
    return false;
    return false;
  }
  }


+3 −2
Original line number Original line Diff line number Diff line
@@ -44,10 +44,11 @@ TEST(UnicodeTest, IsXidContinue) {
TEST(UnicodeTest, IsJavaIdentifier) {
TEST(UnicodeTest, IsJavaIdentifier) {
  EXPECT_TRUE(IsJavaIdentifier("FøøBar_12"));
  EXPECT_TRUE(IsJavaIdentifier("FøøBar_12"));
  EXPECT_TRUE(IsJavaIdentifier("Føø$Bar"));
  EXPECT_TRUE(IsJavaIdentifier("Føø$Bar"));
  EXPECT_TRUE(IsJavaIdentifier("_FøøBar"));
  EXPECT_TRUE(IsJavaIdentifier("$Føø$Bar"));


  EXPECT_FALSE(IsJavaIdentifier("12FøøBar"));
  EXPECT_FALSE(IsJavaIdentifier("12FøøBar"));
  EXPECT_FALSE(IsJavaIdentifier("_FøøBar"));
  EXPECT_FALSE(IsJavaIdentifier(".Hello"));
  EXPECT_FALSE(IsJavaIdentifier("$Føø$Bar"));
}
}


TEST(UnicodeTest, IsValidResourceEntryName) {
TEST(UnicodeTest, IsValidResourceEntryName) {
+34 −50
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@
#include "androidfw/StringPiece.h"
#include "androidfw/StringPiece.h"
#include "utils/Unicode.h"
#include "utils/Unicode.h"


#include "text/Unicode.h"
#include "text/Utf8Iterator.h"
#include "text/Utf8Iterator.h"
#include "util/BigBuffer.h"
#include "util/BigBuffer.h"
#include "util/Maybe.h"
#include "util/Maybe.h"
@@ -94,72 +95,55 @@ StringPiece TrimWhitespace(const StringPiece& str) {
  return StringPiece(start, end - start);
  return StringPiece(start, end - start);
}
}


StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
static int IsJavaNameImpl(const StringPiece& str) {
    const StringPiece& str, const StringPiece& allowed_chars) {
  int pieces = 0;
  const auto end_iter = str.end();
  for (const StringPiece& piece : Tokenize(str, '.')) {
  for (auto iter = str.begin(); iter != end_iter; ++iter) {
    pieces++;
    char c = *iter;
    if (!text::IsJavaIdentifier(piece)) {
    if ((c >= u'a' && c <= u'z') || (c >= u'A' && c <= u'Z') ||
      return -1;
        (c >= u'0' && c <= u'9')) {
      continue;
    }
    }

    bool match = false;
    for (char i : allowed_chars) {
      if (c == i) {
        match = true;
        break;
  }
  }
  return pieces;
}
}


    if (!match) {
bool IsJavaClassName(const StringPiece& str) {
      return iter;
  return IsJavaNameImpl(str) >= 2;
    }
}
}
  return end_iter;

bool IsJavaPackageName(const StringPiece& str) {
  return IsJavaNameImpl(str) >= 1;
}
}


bool IsJavaClassName(const StringPiece& str) {
static int IsAndroidNameImpl(const StringPiece& str) {
  size_t pieces = 0;
  int pieces = 0;
  for (const StringPiece& piece : Tokenize(str, '.')) {
  for (const StringPiece& piece : Tokenize(str, '.')) {
    pieces++;
    if (piece.empty()) {
    if (piece.empty()) {
      return false;
      return -1;
    }
    }


    // Can't have starting or trailing $ character.
    const char first_character = piece.data()[0];
    if (piece.data()[0] == '$' || piece.data()[piece.size() - 1] == '$') {
    if (!::isalpha(first_character)) {
      return false;
      return -1;
    }
    }


    if (FindNonAlphaNumericAndNotInSet(piece, "$_") != piece.end()) {
    bool valid = std::all_of(piece.begin() + 1, piece.end(), [](const char c) -> bool {
      return false;
      return ::isalnum(c) || c == '_';
    }
    });
  }
  return pieces >= 2;
}


bool IsJavaPackageName(const StringPiece& str) {
    if (!valid) {
  if (str.empty()) {
      return -1;
    return false;
    }
    }

  size_t pieces = 0;
  for (const StringPiece& piece : Tokenize(str, '.')) {
    pieces++;
    pieces++;
    if (piece.empty()) {
      return false;
  }
  }

  return pieces;
    if (piece.data()[0] == '_' || piece.data()[piece.size() - 1] == '_') {
      return false;
}
}


    if (FindNonAlphaNumericAndNotInSet(piece, "_") != piece.end()) {
bool IsAndroidPackageName(const StringPiece& str) {
      return false;
  return IsAndroidNameImpl(str) > 1 || str == "android";
    }
}
}
  return pieces >= 1;

bool IsAndroidSplitName(const StringPiece& str) {
  return IsAndroidNameImpl(str) > 0;
}
}


Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
@@ -176,7 +160,7 @@ Maybe<std::string> GetFullyQualifiedClassName(const StringPiece& package,
    return {};
    return {};
  }
  }


  std::string result(package.data(), package.size());
  std::string result = package.to_string();
  if (classname.data()[0] != '.') {
  if (classname.data()[0] != '.') {
    result += '.';
    result += '.';
  }
  }
+84 −105
Original line number Original line Diff line number Diff line
@@ -53,48 +53,40 @@ struct Range {
std::vector<std::string> Split(const android::StringPiece& str, char sep);
std::vector<std::string> Split(const android::StringPiece& str, char sep);
std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);


/**
// Returns true if the string starts with prefix.
 * Returns true if the string starts with prefix.
 */
bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);
bool StartsWith(const android::StringPiece& str, const android::StringPiece& prefix);


/**
// Returns true if the string ends with suffix.
 * Returns true if the string ends with suffix.
 */
bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);
bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffix);


/**
// Creates a new StringPiece16 that points to a substring of the original string without leading or
 * Creates a new StringPiece16 that points to a substring
// trailing whitespace.
 * of the original string without leading or trailing whitespace.
 */
android::StringPiece TrimWhitespace(const android::StringPiece& str);
android::StringPiece TrimWhitespace(const android::StringPiece& str);


/**
// Tests that the string is a valid Java class name.
 * Returns an iterator to the first character that is not alpha-numeric and that
 * is not in the allowedChars set.
 */
android::StringPiece::const_iterator FindNonAlphaNumericAndNotInSet(
    const android::StringPiece& str, const android::StringPiece& allowed_chars);

/**
 * Tests that the string is a valid Java class name.
 */
bool IsJavaClassName(const android::StringPiece& str);
bool IsJavaClassName(const android::StringPiece& str);


/**
// Tests that the string is a valid Java package name.
 * Tests that the string is a valid Java package name.
 */
bool IsJavaPackageName(const android::StringPiece& str);
bool IsJavaPackageName(const android::StringPiece& str);


/**
// Tests that the string is a valid Android package name. More strict than a Java package name.
 * Converts the class name to a fully qualified class name from the given
// - First character of each component (separated by '.') must be an ASCII letter.
 * `package`. Ex:
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
 *
// - Package must contain at least two components, unless it is 'android'.
 * asdf         --> package.asdf
bool IsAndroidPackageName(const android::StringPiece& str);
 * .asdf        --> package.asdf

 * .a.b         --> package.a.b
// Tests that the string is a valid Android split name.
 * asdf.adsf    --> asdf.adsf
// - First character of each component (separated by '.') must be an ASCII letter.
 */
// - Subsequent characters of a component can be ASCII alphanumeric or an underscore.
bool IsAndroidSplitName(const android::StringPiece& str);

// Converts the class name to a fully qualified class name from the given
// `package`. Ex:
//
// asdf         --> package.asdf
// .asdf        --> package.asdf
// .a.b         --> package.a.b
// asdf.adsf    --> asdf.adsf
Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package,
                                              const android::StringPiece& class_name);
                                              const android::StringPiece& class_name);


@@ -108,23 +100,17 @@ typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T
  return 0;
  return 0;
}
}


/**
// Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
 * Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
// This will be present in C++14 and can be removed then.
 * This will be present in C++14 and can be removed then.
 */
template <typename T, class... Args>
template <typename T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
std::unique_ptr<T> make_unique(Args&&... args) {
  return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
  return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
}
}


/**
// Writes a set of items to the std::ostream, joining the times with the provided separator.
 * Writes a set of items to the std::ostream, joining the times with the
 * provided
 * separator.
 */
template <typename Container>
template <typename Container>
::std::function<::std::ostream&(::std::ostream&)> Joiner(
::std::function<::std::ostream&(::std::ostream&)> Joiner(const Container& container,
    const Container& container, const char* sep) {
                                                         const char* sep) {
  using std::begin;
  using std::begin;
  using std::end;
  using std::end;
  const auto begin_iter = begin(container);
  const auto begin_iter = begin(container);
@@ -140,32 +126,19 @@ template <typename Container>
  };
  };
}
}


/**
// Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8,
 * Helper method to extract a UTF-16 string from a StringPool. If the string is
// the conversion to UTF-16 happens within ResStringPool.
 * stored as UTF-8,
 * the conversion to UTF-16 happens within ResStringPool.
 */
android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx);
android::StringPiece16 GetString16(const android::ResStringPool& pool, size_t idx);


/**
// Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16,
 * Helper method to extract a UTF-8 string from a StringPool. If the string is
// the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method,
 * stored as UTF-16,
// which maintains no state or cache. This means we must return an std::string copy.
 * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is
 * done by this method,
 * which maintains no state or cache. This means we must return an std::string
 * copy.
 */
std::string GetString(const android::ResStringPool& pool, size_t idx);
std::string GetString(const android::ResStringPool& pool, size_t idx);


/**
// Checks that the Java string format contains no non-positional arguments (arguments without
 * Checks that the Java string format contains no non-positional arguments
// explicitly specifying an index) when there are more than one argument. This is an error
 * (arguments without
// because translations may rearrange the order of the arguments in the string, which will
 * explicitly specifying an index) when there are more than one argument. This
// break the string interpolation.
 * is an error
 * because translations may rearrange the order of the arguments in the string,
 * which will
 * break the string interpolation.
 */
bool VerifyJavaStringFormat(const android::StringPiece& str);
bool VerifyJavaStringFormat(const android::StringPiece& str);


class StringBuilder {
class StringBuilder {
@@ -194,36 +167,38 @@ class StringBuilder {
  std::string error_;
  std::string error_;
};
};


inline const std::string& StringBuilder::ToString() const { return str_; }
inline const std::string& StringBuilder::ToString() const {
  return str_;
}


inline const std::string& StringBuilder::Error() const { return error_; }
inline const std::string& StringBuilder::Error() const {
  return error_;
}


inline bool StringBuilder::IsEmpty() const { return str_.empty(); }
inline bool StringBuilder::IsEmpty() const {
  return str_.empty();
}


inline size_t StringBuilder::Utf16Len() const { return utf16_len_; }
inline size_t StringBuilder::Utf16Len() const {
  return utf16_len_;
}


inline StringBuilder::operator bool() const { return error_.empty(); }
inline StringBuilder::operator bool() const {
  return error_.empty();
}


/**
// Converts a UTF8 string to a UTF16 string.
 * Converts a UTF8 string to a UTF16 string.
 */
std::u16string Utf8ToUtf16(const android::StringPiece& utf8);
std::u16string Utf8ToUtf16(const android::StringPiece& utf8);
std::string Utf16ToUtf8(const android::StringPiece16& utf16);
std::string Utf16ToUtf8(const android::StringPiece16& utf16);


/**
// Writes the entire BigBuffer to the output stream.
 * Writes the entire BigBuffer to the output stream.
 */
bool WriteAll(std::ostream& out, const BigBuffer& buffer);
bool WriteAll(std::ostream& out, const BigBuffer& buffer);


/*
// Copies the entire BigBuffer into a single buffer.
 * Copies the entire BigBuffer into a single buffer.
 */
std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer);
std::unique_ptr<uint8_t[]> Copy(const BigBuffer& buffer);


/**
// A Tokenizer implemented as an iterable collection. It does not allocate any memory on the heap
 * A Tokenizer implemented as an iterable collection. It does not allocate
// nor use standard containers.
 * any memory on the heap nor use standard containers.
 */
class Tokenizer {
class Tokenizer {
 public:
 public:
  class iterator {
  class iterator {
@@ -269,37 +244,41 @@ class Tokenizer {
  const iterator end_;
  const iterator end_;
};
};


inline Tokenizer Tokenize(const android::StringPiece& str, char sep) { return Tokenizer(str, sep); }
inline Tokenizer Tokenize(const android::StringPiece& str, char sep) {
  return Tokenizer(str, sep);
}


inline uint16_t HostToDevice16(uint16_t value) { return htods(value); }
inline uint16_t HostToDevice16(uint16_t value) {
  return htods(value);
}


inline uint32_t HostToDevice32(uint32_t value) { return htodl(value); }
inline uint32_t HostToDevice32(uint32_t value) {
  return htodl(value);
}


inline uint16_t DeviceToHost16(uint16_t value) { return dtohs(value); }
inline uint16_t DeviceToHost16(uint16_t value) {
  return dtohs(value);
}


inline uint32_t DeviceToHost32(uint32_t value) { return dtohl(value); }
inline uint32_t DeviceToHost32(uint32_t value) {
  return dtohl(value);
}


/**
// Given a path like: res/xml-sw600dp/foo.xml
 * Given a path like: res/xml-sw600dp/foo.xml
//
 *
// Extracts "res/xml-sw600dp/" into outPrefix.
 * Extracts "res/xml-sw600dp/" into outPrefix.
// Extracts "foo" into outEntry.
 * Extracts "foo" into outEntry.
// Extracts ".xml" into outSuffix.
 * Extracts ".xml" into outSuffix.
//
 *
// Returns true if successful.
 * Returns true if successful.
 */
bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
bool ExtractResFilePathParts(const android::StringPiece& path, android::StringPiece* out_prefix,
                             android::StringPiece* out_entry, android::StringPiece* out_suffix);
                             android::StringPiece* out_entry, android::StringPiece* out_suffix);


}  // namespace util
}  // namespace util


/**
// Stream operator for functions. Calls the function with the stream as an argument.
 * Stream operator for functions. Calls the function with the stream as an
// In the aapt namespace for lookup.
 * argument.
inline ::std::ostream& operator<<(::std::ostream& out,
 * In the aapt namespace for lookup.
 */
inline ::std::ostream& operator<<(
    ::std::ostream& out,
                                  const ::std::function<::std::ostream&(::std::ostream&)>& f) {
                                  const ::std::function<::std::ostream&(::std::ostream&)>& f) {
  return f(out);
  return f(out);
}
}
Loading