diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-04-10 19:43:55 -0700 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-04-15 19:56:59 -0700 |
commit | 769de98f2dd41bfe39a1c9f76aefd1ad58942733 (patch) | |
tree | 3d79143b08f02dfb61158689f51e01eeb1bb371e | |
parent | 9310e4285b3fc951c3524d040726d1161015562c (diff) | |
download | frameworks_base-769de98f2dd41bfe39a1c9f76aefd1ad58942733.zip frameworks_base-769de98f2dd41bfe39a1c9f76aefd1ad58942733.tar.gz frameworks_base-769de98f2dd41bfe39a1c9f76aefd1ad58942733.tar.bz2 |
AAPT2: Add library support
Change-Id: I307f56d9631784ab29ee4156d94886f9b2f25b30
45 files changed, 4178 insertions, 729 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index 0622dc6..05034c3 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -27,6 +27,7 @@ main := Main.cpp sources := \ BigBuffer.cpp \ BinaryResourceParser.cpp \ + BinaryXmlPullParser.cpp \ BindingXmlPullParser.cpp \ ConfigDescription.cpp \ Files.cpp \ @@ -51,7 +52,9 @@ sources := \ ScopedXmlPullParser.cpp \ SourceXmlPullParser.cpp \ XliffXmlPullParser.cpp \ - XmlFlattener.cpp + XmlFlattener.cpp \ + ZipEntry.cpp \ + ZipFile.cpp testSources := \ BigBuffer_test.cpp \ @@ -63,6 +66,7 @@ testSources := \ Locale_test.cpp \ ManifestParser_test.cpp \ Maybe_test.cpp \ + NameMangler_test.cpp \ ResourceParser_test.cpp \ Resource_test.cpp \ ResourceTable_test.cpp \ diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp index 3eb96bc..71016c1 100644 --- a/tools/aapt2/BinaryResourceParser.cpp +++ b/tools/aapt2/BinaryResourceParser.cpp @@ -17,6 +17,7 @@ #include "BinaryResourceParser.h" #include "Logger.h" #include "ResChunkPullParser.h" +#include "Resolver.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceTypeExtensions.h" @@ -33,28 +34,14 @@ namespace aapt { using namespace android; -template <typename T> -inline static const T* convertTo(const ResChunk_header* chunk) { - if (chunk->headerSize < sizeof(T)) { - return nullptr; - } - return reinterpret_cast<const T*>(chunk); -} - -inline static const uint8_t* getChunkData(const ResChunk_header& chunk) { - return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize; -} - -inline static size_t getChunkDataLen(const ResChunk_header& chunk) { - return chunk.size - chunk.headerSize; -} - /* * Visitor that converts a reference's resource ID to a resource name, * given a mapping from resource ID to resource name. */ struct ReferenceIdToNameVisitor : ValueVisitor { - ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>& cache) : mCache(cache) { + ReferenceIdToNameVisitor(const std::shared_ptr<Resolver>& resolver, + std::map<ResourceId, ResourceName>* cache) : + mResolver(resolver), mCache(cache) { } void visit(Reference& reference, ValueVisitorArgs&) override { @@ -104,24 +91,39 @@ private: return; } - auto cacheIter = mCache.find(reference.id); - if (cacheIter == std::end(mCache)) { - Logger::note() << "failed to find " << reference.id << std::endl; - } else { + auto cacheIter = mCache->find(reference.id); + if (cacheIter != mCache->end()) { reference.name = cacheIter->second; reference.id = 0; + } else { + const android::ResTable& table = mResolver->getResTable(); + android::ResTable::resource_name resourceName; + if (table.getResourceName(reference.id.id, false, &resourceName)) { + const ResourceType* type = parseResourceType(StringPiece16(resourceName.type, + resourceName.typeLen)); + assert(type); + reference.name.package.assign(resourceName.package, resourceName.packageLen); + reference.name.type = *type; + reference.name.entry.assign(resourceName.name, resourceName.nameLen); + reference.id = 0; + + // Add to cache. + mCache->insert({reference.id, reference.name}); + } } } - const std::map<ResourceId, ResourceName>& mCache; + std::shared_ptr<Resolver> mResolver; + std::map<ResourceId, ResourceName>* mCache; }; -BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> table, +BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver, const Source& source, const void* data, size_t len) : - mTable(table), mSource(source), mData(data), mDataLen(len) { + mTable(table), mResolver(resolver), mSource(source), mData(data), mDataLen(len) { } bool BinaryResourceParser::parse() { @@ -421,7 +423,7 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { // Now go through the table and change resource ID references to // symbolic references. - ReferenceIdToNameVisitor visitor(mIdIndex); + ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex); for (auto& type : *mTable) { for (auto& entry : type->entries) { for (auto& configValue : entry->values) { @@ -676,7 +678,8 @@ std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0; + std::unique_ptr<Style> style = util::make_unique<Style>(isWeak); if (map->parent.ident == 0) { // The parent is either not set or it is an unresolved symbol. // Check to see if it is a symbol. @@ -759,7 +762,17 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa const ResTable_map_entry* map) { std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); for (const ResTable_map& mapEntry : map) { - styleable->entries.emplace_back(mapEntry.name.ident); + if (mapEntry.name.ident == 0) { + // The map entry's key (attribute) is not set. This must be + // a symbol reference, so resolve it. + ResourceNameRef symbol; + bool result = getSymbol(&mapEntry.name.ident, &symbol); + assert(result); + styleable->entries.emplace_back(symbol); + } else { + // The map entry's key (attribute) is a regular reference. + styleable->entries.emplace_back(mapEntry.name.ident); + } } return styleable; } diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h index 9268078..f95a0c8 100644 --- a/tools/aapt2/BinaryResourceParser.h +++ b/tools/aapt2/BinaryResourceParser.h @@ -17,6 +17,7 @@ #ifndef AAPT_BINARY_RESOURCE_PARSER_H #define AAPT_BINARY_RESOURCE_PARSER_H +#include "Resolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Source.h" @@ -41,7 +42,9 @@ public: * Creates a parser, which will read `len` bytes from `data`, and * add any resources parsed to `table`. `source` is for logging purposes. */ - BinaryResourceParser(std::shared_ptr<ResourceTable> table, const Source& source, + BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver, + const Source& source, const void* data, size_t len); BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. @@ -89,6 +92,8 @@ private: std::shared_ptr<ResourceTable> mTable; + std::shared_ptr<Resolver> mResolver; + const Source mSource; const void* mData; diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp new file mode 100644 index 0000000..7a07c06 --- /dev/null +++ b/tools/aapt2/BinaryXmlPullParser.cpp @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2015 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 "BinaryXmlPullParser.h" + +#include <androidfw/ResourceTypes.h> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { + +static XmlPullParser::Event codeToEvent(android::ResXMLParser::event_code_t code) { + switch (code) { + case android::ResXMLParser::START_DOCUMENT: + return XmlPullParser::Event::kStartDocument; + case android::ResXMLParser::END_DOCUMENT: + return XmlPullParser::Event::kEndDocument; + case android::ResXMLParser::START_NAMESPACE: + return XmlPullParser::Event::kStartNamespace; + case android::ResXMLParser::END_NAMESPACE: + return XmlPullParser::Event::kEndNamespace; + case android::ResXMLParser::START_TAG: + return XmlPullParser::Event::kStartElement; + case android::ResXMLParser::END_TAG: + return XmlPullParser::Event::kEndElement; + case android::ResXMLParser::TEXT: + return XmlPullParser::Event::kText; + default: + break; + } + return XmlPullParser::Event::kBadDocument; +} + +BinaryXmlPullParser::BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser) + : mParser(parser), mEvent(Event::kStartDocument), mHasComment(false), sEmpty(), sEmpty8(), + mDepth(0) { +} + +XmlPullParser::Event BinaryXmlPullParser::next() { + mStr1.clear(); + mStr2.clear(); + mAttributes.clear(); + + android::ResXMLParser::event_code_t code; + if (mHasComment) { + mHasComment = false; + code = mParser->getEventType(); + } else { + code = mParser->next(); + if (code != android::ResXMLParser::BAD_DOCUMENT) { + size_t len; + const char16_t* comment = mParser->getComment(&len); + if (comment) { + mHasComment = true; + mStr1.assign(comment, len); + return XmlPullParser::Event::kComment; + } + } + } + + size_t len; + const char16_t* data; + mEvent = codeToEvent(code); + switch (mEvent) { + case Event::kStartNamespace: + case Event::kEndNamespace: + data = mParser->getNamespacePrefix(&len); + mStr1.assign(data, len); + data = mParser->getNamespaceUri(&len); + mStr2.assign(data, len); + break; + + case Event::kStartElement: + copyAttributes(); + // fallthrough + + case Event::kEndElement: + data = mParser->getElementNamespace(&len); + mStr1.assign(data, len); + data = mParser->getElementName(&len); + mStr2.assign(data, len); + break; + + case Event::kText: + data = mParser->getText(&len); + mStr1.assign(data, len); + break; + + default: + break; + } + return mEvent; +} + +XmlPullParser::Event BinaryXmlPullParser::getEvent() const { + if (mHasComment) { + return XmlPullParser::Event::kComment; + } + return mEvent; +} + +const std::string& BinaryXmlPullParser::getLastError() const { + return sEmpty8; +} + +const std::u16string& BinaryXmlPullParser::getComment() const { + if (mHasComment) { + return mStr1; + } + return sEmpty; +} + +size_t BinaryXmlPullParser::getLineNumber() const { + return mParser->getLineNumber(); +} + +size_t BinaryXmlPullParser::getDepth() const { + return mDepth; +} + +const std::u16string& BinaryXmlPullParser::getText() const { + if (!mHasComment && mEvent == XmlPullParser::Event::kText) { + return mStr1; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getNamespacePrefix() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace || + mEvent == XmlPullParser::Event::kEndNamespace)) { + return mStr1; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getNamespaceUri() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace || + mEvent == XmlPullParser::Event::kEndNamespace)) { + return mStr2; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getElementNamespace() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || + mEvent == XmlPullParser::Event::kEndElement)) { + return mStr1; + } + return sEmpty; +} + +const std::u16string& BinaryXmlPullParser::getElementName() const { + if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement || + mEvent == XmlPullParser::Event::kEndElement)) { + return mStr2; + } + return sEmpty; +} + +size_t BinaryXmlPullParser::getAttributeCount() const { + return mAttributes.size(); +} + +XmlPullParser::const_iterator BinaryXmlPullParser::beginAttributes() const { + return mAttributes.begin(); +} + +XmlPullParser::const_iterator BinaryXmlPullParser::endAttributes() const { + return mAttributes.end(); +} + +void BinaryXmlPullParser::copyAttributes() { + const size_t attrCount = mParser->getAttributeCount(); + if (attrCount > 0) { + mAttributes.reserve(attrCount); + for (size_t i = 0; i < attrCount; i++) { + XmlPullParser::Attribute attr; + size_t len; + const char16_t* str = mParser->getAttributeNamespace(i, &len); + attr.namespaceUri.assign(str, len); + str = mParser->getAttributeName(i, &len); + attr.name.assign(str, len); + str = mParser->getAttributeStringValue(i, &len); + attr.value.assign(str, len); + mAttributes.push_back(std::move(attr)); + } + } +} + +} // namespace aapt diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h new file mode 100644 index 0000000..2d4256a --- /dev/null +++ b/tools/aapt2/BinaryXmlPullParser.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef AAPT_BINARY_XML_PULL_PARSER_H +#define AAPT_BINARY_XML_PULL_PARSER_H + +#include "XmlPullParser.h" + +#include <androidfw/ResourceTypes.h> +#include <memory> +#include <string> +#include <vector> + +namespace aapt { + +/** + * Wraps a ResTable into the canonical XmlPullParser interface. + */ +class BinaryXmlPullParser : public XmlPullParser { +public: + BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser); + BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete; + + Event getEvent() const; + const std::string& getLastError() const; + Event next(); + + const std::u16string& getComment() const; + size_t getLineNumber() const; + size_t getDepth() const; + + const std::u16string& getText() const; + + const std::u16string& getNamespacePrefix() const; + const std::u16string& getNamespaceUri() const; + + const std::u16string& getElementNamespace() const; + const std::u16string& getElementName() const; + + const_iterator beginAttributes() const; + const_iterator endAttributes() const; + size_t getAttributeCount() const; + +private: + void copyAttributes(); + + std::shared_ptr<android::ResXMLTree> mParser; + std::u16string mStr1; + std::u16string mStr2; + std::vector<Attribute> mAttributes; + Event mEvent; + bool mHasComment; + const std::u16string sEmpty; + const std::string sEmpty8; + size_t mDepth; +}; + +} // namespace aapt + +#endif // AAPT_BINARY_XML_PULL_PARSER_H diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp index b1ee8e7..a563bfc 100644 --- a/tools/aapt2/Flag.cpp +++ b/tools/aapt2/Flag.cpp @@ -39,7 +39,7 @@ void optionalSwitch(const StringPiece& name, const StringPiece& description, boo Flag{ name.toString(), description.toString(), {}, false, result, false }); } -static void usageAndDie(const StringPiece& command) { +void usageAndDie(const StringPiece& command) { std::cerr << command << " [options]"; for (const Flag& flag : sFlags) { if (flag.required) { diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h index 32f5f2c..4cadfc4 100644 --- a/tools/aapt2/Flag.h +++ b/tools/aapt2/Flag.h @@ -18,6 +18,8 @@ void optionalFlag(const StringPiece& name, const StringPiece& description, void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result); +void usageAndDie(const StringPiece& command); + void parse(int argc, char** argv, const StringPiece& command); const std::vector<std::string>& getArgs(); diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp index 779a346..3f92f18 100644 --- a/tools/aapt2/JavaClassGenerator.cpp +++ b/tools/aapt2/JavaClassGenerator.cpp @@ -15,6 +15,7 @@ */ #include "JavaClassGenerator.h" +#include "NameMangler.h" #include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -31,7 +32,7 @@ namespace aapt { // The number of attributes to emit per line in a Styleable array. constexpr size_t kAttribsPerLine = 4; -JavaClassGenerator::JavaClassGenerator(std::shared_ptr<const ResourceTable> table, +JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options) : mTable(table), mOptions(options) { } @@ -79,42 +80,18 @@ static std::u16string transform(const StringPiece16& symbol) { return output; } -bool JavaClassGenerator::generateType(std::ostream& out, const ResourceTableType& type, - size_t packageId) { - const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - - for (const auto& entry : type.entries) { - ResourceId id = { packageId, type.typeId, entry->entryId }; - assert(id.isValid()); - - if (!isValidSymbol(entry->name)) { - std::stringstream err; - err << "invalid symbol name '" - << StringPiece16(entry->name) - << "'"; - mError = err.str(); - return false; - } - - out << " " - << "public static" << finalModifier - << " int " << transform(entry->name) << " = " << id << ";" << std::endl; - } - return true; -} - struct GenArgs : ValueVisitorArgs { - GenArgs(std::ostream& o, const ResourceEntry& e) : out(o), entry(e) { + GenArgs(std::ostream* o, std::u16string* e) : out(o), entryName(e) { } - std::ostream& out; - const ResourceEntry& entry; + std::ostream* out; + std::u16string* entryName; }; void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) { const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; - std::ostream& out = static_cast<GenArgs&>(a).out; - const ResourceEntry& entry = static_cast<GenArgs&>(a).entry; + std::ostream* out = static_cast<GenArgs&>(a).out; + std::u16string* entryName = static_cast<GenArgs&>(a).entryName; // This must be sorted by resource ID. std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes; @@ -127,59 +104,86 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) std::sort(sortedAttributes.begin(), sortedAttributes.end()); // First we emit the array containing the IDs of each attribute. - out << " " - << "public static final int[] " << transform(entry.name) << " = {"; + *out << " " + << "public static final int[] " << transform(*entryName) << " = {"; const size_t attrCount = sortedAttributes.size(); for (size_t i = 0; i < attrCount; i++) { if (i % kAttribsPerLine == 0) { - out << std::endl << " "; + *out << std::endl << " "; } - out << sortedAttributes[i].first; + *out << sortedAttributes[i].first; if (i != attrCount - 1) { - out << ", "; + *out << ", "; } } - out << std::endl << " };" << std::endl; + *out << std::endl << " };" << std::endl; // Now we emit the indices into the array. for (size_t i = 0; i < attrCount; i++) { - out << " " - << "public static" << finalModifier - << " int " << transform(entry.name) << "_" << transform(sortedAttributes[i].second) - << " = " << i << ";" << std::endl; + *out << " " + << "public static" << finalModifier + << " int " << transform(*entryName) << "_" << transform(sortedAttributes[i].second) + << " = " << i << ";" << std::endl; } } -bool JavaClassGenerator::generate(std::ostream& out) { +bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId, + const ResourceTableType& type, std::ostream& out) { + const StringPiece finalModifier = mOptions.useFinal ? " final" : ""; + + std::u16string unmangledPackage; + std::u16string unmangledName; + for (const auto& entry : type.entries) { + ResourceId id = { packageId, type.typeId, entry->entryId }; + assert(id.isValid()); + + unmangledName = entry->name; + if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) { + // The entry name was mangled, and we successfully unmangled it. + // Check that we want to emit this symbol. + if (package != unmangledPackage) { + // Skip the entry if it doesn't belong to the package we're writing. + continue; + } + } else { + if (package != mTable->getPackage()) { + // We are processing a mangled package name, + // but this is a non-mangled resource. + continue; + } + } + + if (!isValidSymbol(unmangledName)) { + ResourceNameRef resourceName = { package, type.type, unmangledName }; + std::stringstream err; + err << "invalid symbol name '" << resourceName << "'"; + mError = err.str(); + return false; + } + + if (type.type == ResourceType::kStyleable) { + assert(!entry->values.empty()); + entry->values.front().value->accept(*this, GenArgs{ &out, &unmangledName }); + } else { + out << " " << "public static" << finalModifier + << " int " << transform(unmangledName) << " = " << id << ";" << std::endl; + } + } + return true; +} + +bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) { const size_t packageId = mTable->getPackageId(); - generateHeader(out, mTable->getPackage()); + generateHeader(out, package); out << "public final class R {" << std::endl; for (const auto& type : *mTable) { out << " public static final class " << type->type << " {" << std::endl; - bool result; - if (type->type == ResourceType::kStyleable) { - for (const auto& entry : type->entries) { - assert(!entry->values.empty()); - if (!isValidSymbol(entry->name)) { - std::stringstream err; - err << "invalid symbol name '" - << StringPiece16(entry->name) - << "'"; - mError = err.str(); - return false; - } - entry->values.front().value->accept(*this, GenArgs{ out, *entry }); - } - } else { - result = generateType(out, *type, packageId); - } - - if (!result) { + if (!generateType(package, packageId, *type, out)) { return false; } out << " }" << std::endl; diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h index 5b8e500..f8b9ee3 100644 --- a/tools/aapt2/JavaClassGenerator.h +++ b/tools/aapt2/JavaClassGenerator.h @@ -41,12 +41,16 @@ public: bool useFinal = true; }; - JavaClassGenerator(std::shared_ptr<const ResourceTable> table, Options options); + JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options); /* - * Writes the R.java file to `out`. Returns true on success. + * Writes the R.java file to `out`. Only symbols belonging to `package` are written. + * All symbols technically belong to a single package, but linked libraries will + * have their names mangled, denoting that they came from a different package. + * We need to generate these symbols in a separate file. + * Returns true on success. */ - bool generate(std::ostream& out); + bool generate(const std::u16string& package, std::ostream& out); /* * ConstValueVisitor implementation. @@ -56,7 +60,8 @@ public: const std::string& getError() const; private: - bool generateType(std::ostream& out, const ResourceTableType& type, size_t packageId); + bool generateType(const std::u16string& package, size_t packageId, + const ResourceTableType& type, std::ostream& out); std::shared_ptr<const ResourceTable> mTable; Options mOptions; diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp index 32050e3..96bb10b 100644 --- a/tools/aapt2/JavaClassGenerator_test.cpp +++ b/tools/aapt2/JavaClassGenerator_test.cpp @@ -15,6 +15,8 @@ */ #include "JavaClassGenerator.h" +#include "Linker.h" +#include "Resolver.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Util.h" @@ -47,7 +49,7 @@ TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) { JavaClassGenerator generator(mTable, {}); std::stringstream out; - EXPECT_FALSE(generator.generate(out)); + EXPECT_FALSE(generator.generate(mTable->getPackage(), out)); } TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { @@ -69,7 +71,7 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { JavaClassGenerator generator(mTable, {}); std::stringstream out; - EXPECT_TRUE(generator.generate(out)); + EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); std::string output = out.str(); EXPECT_NE(std::string::npos, @@ -82,4 +84,33 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { output.find("public static final int hey_dude_cool_attr = 0;")); } +TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) { + ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" }, + ResourceId{ 0x01, 0x02, 0x0000 })); + ResourceTable table; + table.setPackage(u"com.lib"); + ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {}, + SourceLine{ "lib.xml", 33 }, util::make_unique<Id>())); + ASSERT_TRUE(mTable->merge(std::move(table))); + + std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(mTable, + std::make_shared<const android::AssetManager>()); + Linker linker(mTable, resolver); + ASSERT_TRUE(linker.linkAndValidate()); + + JavaClassGenerator generator(mTable, {}); + + std::stringstream out; + EXPECT_TRUE(generator.generate(mTable->getPackage(), out)); + std::string output = out.str(); + EXPECT_NE(std::string::npos, output.find("int foo =")); + EXPECT_EQ(std::string::npos, output.find("int test =")); + + out.str(""); + EXPECT_TRUE(generator.generate(u"com.lib", out)); + output = out.str(); + EXPECT_NE(std::string::npos, output.find("int test =")); + EXPECT_EQ(std::string::npos, output.find("int foo =")); +} + } // namespace aapt diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp index 1cfb297..4346c8b 100644 --- a/tools/aapt2/Linker.cpp +++ b/tools/aapt2/Linker.cpp @@ -128,6 +128,20 @@ const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const { void Linker::visit(Reference& reference, ValueVisitorArgs& a) { Args& args = static_cast<Args&>(a); + if (!reference.name.isValid()) { + // We can't have a completely bad reference. + assert(reference.id.isValid()); + + // This reference has no name but has an ID. + // It is a really bad error to have no name and have the same + // package ID. + assert(reference.id.packageId() != mTable->getPackageId()); + + // The reference goes outside this package, let it stay as a + // resource ID because it will not change. + return; + } + Maybe<ResourceId> result = mResolver->findId(reference.name); if (!result) { addUnresolvedSymbol(reference.name, args.source); @@ -206,7 +220,7 @@ void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine void Linker::visit(Style& style, ValueVisitorArgs& a) { Args& args = static_cast<Args&>(a); - if (style.parent.name.isValid()) { + if (style.parent.name.isValid() || style.parent.id.isValid()) { visit(style.parent, a); } diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp index b1e201b..4d2d360 100644 --- a/tools/aapt2/Linker_test.cpp +++ b/tools/aapt2/Linker_test.cpp @@ -30,6 +30,7 @@ struct LinkerTest : public ::testing::Test { virtual void SetUp() override { mTable = std::make_shared<ResourceTable>(); mTable->setPackage(u"android"); + mTable->setPackageId(0x01); mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>( mTable, std::make_shared<android::AssetManager>())); @@ -75,7 +76,7 @@ TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) { } TEST_F(LinkerTest, EscapeAndConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123")) @@ -91,7 +92,7 @@ TEST_F(LinkerTest, EscapeAndConvertRawString) { } TEST_F(LinkerTest, FailToConvertRawString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?")) @@ -103,7 +104,7 @@ TEST_F(LinkerTest, FailToConvertRawString) { } TEST_F(LinkerTest, ConvertRawStringToString) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"string" }, util::make_unique<RawString>( @@ -122,7 +123,7 @@ TEST_F(LinkerTest, ConvertRawStringToString) { } TEST_F(LinkerTest, ConvertRawStringToFlags) { - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); style->entries.push_back(Style::Entry{ ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" }, util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple")) @@ -140,4 +141,12 @@ TEST_F(LinkerTest, ConvertRawStringToFlags) { EXPECT_EQ(bin->value.data, 1u | 2u); } +TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) { + ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" }, + util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 }))); + + ASSERT_TRUE(mLinker->linkAndValidate()); + EXPECT_TRUE(mLinker->getUnresolvedReferences().empty()); +} + } // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index 87127fd..b3e2768 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -17,6 +17,7 @@ #include "AppInfo.h" #include "BigBuffer.h" #include "BinaryResourceParser.h" +#include "BinaryXmlPullParser.h" #include "BindingXmlPullParser.h" #include "Files.h" #include "Flag.h" @@ -34,6 +35,7 @@ #include "TableFlattener.h" #include "Util.h" #include "XmlFlattener.h" +#include "ZipFile.h" #include <algorithm> #include <androidfw/AssetManager.h> @@ -44,6 +46,7 @@ #include <iostream> #include <sstream> #include <sys/stat.h> +#include <unordered_set> #include <utils/Errors.h> using namespace aapt; @@ -96,17 +99,6 @@ void printStringPool(const StringPool& pool) { } } -std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename, - ResourceType type, const ConfigDescription& config) { - std::stringstream path; - path << "res/" << type; - if (config != ConfigDescription{}) { - path << "-" << config; - } - path << "/" << filename; - return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str()))); -} - /** * Collect files from 'root', filtering out any files that do not * match the FileFilter 'filter'. @@ -148,30 +140,6 @@ bool walkTree(const Source& root, const FileFilter& filter, return !error; } -bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) { - std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); - if (!ifs) { - Logger::error(source) << strerror(errno) << std::endl; - return false; - } - - std::streampos fsize = ifs.tellg(); - ifs.seekg(0, std::ios::end); - fsize = ifs.tellg() - fsize; - ifs.seekg(0, std::ios::beg); - - assert(fsize >= 0); - size_t dataSize = static_cast<size_t>(fsize); - char* buf = new char[dataSize]; - ifs.read(buf, dataSize); - - BinaryResourceParser parser(table, source, buf, dataSize); - bool result = parser.parse(); - - delete [] buf; - return result; -} - bool loadResTable(android::ResTable* table, const Source& source) { std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary); if (!ifs) { @@ -195,7 +163,7 @@ bool loadResTable(android::ResTable* table, const Source& source) { return result; } -void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { +void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) { for (auto& type : *table) { if (type->type != ResourceType::kStyle) { continue; @@ -251,10 +219,12 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { {}, // Create a copy of the original style. - std::unique_ptr<Value>(configValue.value->clone()) + std::unique_ptr<Value>(configValue.value->clone( + &table->getValueStringPool())) }; Style& newStyle = static_cast<Style&>(*value.value); + newStyle.weak = true; // Move the recorded stripped attributes into this new style. std::move(stripped.begin(), stripped.end(), @@ -285,88 +255,98 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) { } } -bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source, - const ResourceName& name, const ConfigDescription& config) { - std::ifstream in(source.path, std::ifstream::binary); - if (!in) { - Logger::error(source) << strerror(errno) << std::endl; - return false; +struct CompileItem { + Source source; + ResourceName name; + ConfigDescription config; + std::string extension; +}; + +struct LinkItem { + Source source; + std::string apkPath; +}; + +std::string buildFileReference(const CompileItem& item) { + std::stringstream path; + path << "res/" << item.name.type; + if (item.config != ConfigDescription{}) { + path << "-" << item.config; } + path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension; + return path.str(); +} - std::set<size_t> sdkLevels; +bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) { + StringPool& pool = table->getValueStringPool(); + StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item))); + return table->addResource(item.name, item.config, item.source.line(0), + util::make_unique<FileReference>(ref)); +} - SourceXmlPullParser parser(in); - while (XmlPullParser::isGoodEvent(parser.next())) { - if (parser.getEvent() != XmlPullParser::Event::kStartElement) { - continue; - } +struct AaptOptions { + enum class Phase { + Link, + Compile, + }; - const auto endIter = parser.endAttributes(); - for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) { - if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") { - size_t sdkLevel = findAttributeSdkLevel(iter->name); - if (sdkLevel > 1) { - sdkLevels.insert(sdkLevel); - } - } + // The phase to process. + Phase phase; - ResourceNameRef refName; - bool create = false; - bool privateRef = false; - if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) && - create) { - table->addResource(refName, {}, source.line(parser.getLineNumber()), - util::make_unique<Id>()); - } - } - } + // Details about the app. + AppInfo appInfo; - for (size_t level : sdkLevels) { - Logger::note(source) - << "creating v" << level << " versioned file." - << std::endl; - ConfigDescription newConfig = config; - newConfig.sdkVersion = level; - - std::unique_ptr<FileReference> fileResource = makeFileReference( - table->getValueStringPool(), - util::utf16ToUtf8(name.entry) + ".xml", - name.type, - newConfig); - table->addResource(name, newConfig, source.line(0), std::move(fileResource)); - } - return true; -} + // The location of the manifest file. + Source manifest; -struct CompileItem { - Source source; - ResourceName name; - ConfigDescription config; - std::string extension; + // The APK files to link. + std::vector<Source> input; + + // The libraries these files may reference. + std::vector<Source> libraries; + + // Output path. This can be a directory or file + // depending on the phase. + Source output; + + // Directory in which to write binding xml files. + Source bindingOutput; + + // Directory to in which to generate R.java. + Maybe<Source> generateJavaClass; + + // Whether to output verbose details about + // compilation. + bool verbose = false; }; -bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, - const Source& outputSource, std::queue<CompileItem>* queue) { +bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, + const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) { std::ifstream in(item.source.path, std::ifstream::binary); if (!in) { Logger::error(item.source) << strerror(errno) << std::endl; return false; } - std::shared_ptr<BindingXmlPullParser> binding; - std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); - if (item.name.type == ResourceType::kLayout) { - binding = std::make_shared<BindingXmlPullParser>(xmlParser); - xmlParser = binding; - } - BigBuffer outBuffer(1024); - XmlFlattener flattener(resolver); + + // No resolver, since we are not compiling attributes here. + XmlFlattener flattener(table, {}); // We strip attributes that do not belong in this version of the resource. // Non-version qualified resources have an implicit version 1 requirement. - XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 }; - Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options); + XmlFlattener::Options xmlOptions; + xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1; + + std::shared_ptr<BindingXmlPullParser> binding; + std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in); + if (item.name.type == ResourceType::kLayout) { + // Layouts may have defined bindings, so we need to make sure they get processed. + binding = std::make_shared<BindingXmlPullParser>(parser); + parser = binding; + } + + Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions); if (!minStrippedSdk) { return false; } @@ -376,24 +356,29 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, // with the version of the smallest SDK version stripped. CompileItem newWork = item; newWork.config.sdkVersion = minStrippedSdk.value(); - queue->push(newWork); + outQueue->push(newWork); } - std::ofstream out(outputSource.path, std::ofstream::binary); - if (!out) { - Logger::error(outputSource) << strerror(errno) << std::endl; + // Write the resulting compiled XML file to the output APK. + if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, + nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk." + << std::endl; return false; } - if (!util::writeAll(out, outBuffer)) { - Logger::error(outputSource) << strerror(errno) << std::endl; - return false; - } + if (binding && !options.bindingOutput.path.empty()) { + // We generated a binding xml file, write it out. + Source bindingOutput = options.bindingOutput; + appendPath(&bindingOutput.path, buildFileReference(item)); + + if (!mkdirs(bindingOutput.path)) { + Logger::error(bindingOutput) << strerror(errno) << std::endl; + return false; + } + + appendPath(&bindingOutput.path, "bind.xml"); - if (binding) { - // We generated a binding xml file, write it out beside the output file. - Source bindingOutput = outputSource; - bindingOutput.path += ".bind.xml"; std::ofstream bout(bindingOutput.path); if (!bout) { Logger::error(bindingOutput) << strerror(errno) << std::endl; @@ -408,100 +393,68 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item, return true; } -bool compilePng(const Source& source, const Source& output) { - std::ifstream in(source.path, std::ifstream::binary); - if (!in) { - Logger::error(source) << strerror(errno) << std::endl; +bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver, + const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) { + std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>(); + if (tree->setTo(data, dataLen, false) != android::NO_ERROR) { return false; } - std::ofstream out(output.path, std::ofstream::binary); - if (!out) { - Logger::error(output) << strerror(errno) << std::endl; + std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree); + + BigBuffer outBuffer(1024); + XmlFlattener flattener({}, resolver); + if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) { return false; } - std::string err; - Png png; - if (!png.process(source, in, out, {}, &err)) { - Logger::error(source) << err << std::endl; + if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) != + android::NO_ERROR) { + Logger::error(options.output) << "failed to write linked file '" << item.source + << "' to apk." << std::endl; return false; } return true; } -bool copyFile(const Source& source, const Source& output) { - std::ifstream in(source.path, std::ifstream::binary); +bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { + std::ifstream in(item.source.path, std::ifstream::binary); if (!in) { - Logger::error(source) << strerror(errno) << std::endl; + Logger::error(item.source) << strerror(errno) << std::endl; return false; } - std::ofstream out(output.path, std::ofstream::binary); - if (!out) { - Logger::error(output) << strerror(errno) << std::endl; + BigBuffer outBuffer(4096); + std::string err; + Png png; + if (!png.process(item.source, in, &outBuffer, {}, &err)) { + Logger::error(item.source) << err << std::endl; return false; } - if (out << in.rdbuf()) { - Logger::error(output) << strerror(errno) << std::endl; - return true; + if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored, + nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write compiled '" << item.source + << "' to apk." << std::endl; + return false; } - return false; + return true; } -struct AaptOptions { - enum class Phase { - Full, - Collect, - Link, - Compile, - Manifest - }; - - // The phase to process. - Phase phase; - - // Details about the app. - AppInfo appInfo; - - // The location of the manifest file. - Source manifest; - - // The source directories to walk and find resource files. - std::vector<Source> sourceDirs; - - // The resource files to process and collect. - std::vector<Source> collectFiles; - - // The binary table files to link. - std::vector<Source> linkFiles; - - // The resource files to compile. - std::vector<Source> compileFiles; - - // The libraries these files may reference. - std::vector<Source> libraries; - - // Output path. This can be a directory or file - // depending on the phase. - Source output; - - // Directory to in which to generate R.java. - Maybe<Source> generateJavaClass; - - // Whether to output verbose details about - // compilation. - bool verbose = false; -}; - -bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver, - const AaptOptions& options) { - Source outSource = options.output; - appendPath(&outSource.path, "AndroidManifest.xml"); +bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) { + if (outApk->add(item.source.path.data(), buildFileReference(item).data(), + ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk." + << std::endl; + return false; + } + return true; +} +bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver, + ZipFile* outApk) { if (options.verbose) { - Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl; + Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl; } std::ifstream in(options.manifest.path, std::ifstream::binary); @@ -512,23 +465,16 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver, BigBuffer outBuffer(1024); std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in); - XmlFlattener flattener(resolver); + XmlFlattener flattener({}, resolver); - Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer, - XmlFlattener::Options{}); - if (!result) { + if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) { return false; } - std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]); - uint8_t* p = data.get(); - for (const auto& b : outBuffer) { - memcpy(p, b.buffer.get(), b.size); - p += b.size; - } + std::unique_ptr<uint8_t[]> data = util::copy(outBuffer); android::ResXMLTree tree; - if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) { + if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) { return false; } @@ -537,14 +483,10 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver, return false; } - std::ofstream out(outSource.path, std::ofstream::binary); - if (!out) { - Logger::error(outSource) << strerror(errno) << std::endl; - return false; - } - - if (!util::writeAll(out, outBuffer)) { - Logger::error(outSource) << strerror(errno) << std::endl; + if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml", + ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk." + << std::endl; return false; } return true; @@ -562,10 +504,20 @@ bool loadAppInfo(const Source& source, AppInfo* outInfo) { return parser.parse(source, pullParser, outInfo); } +static void printCommandsAndDie() { + std::cerr << "The following commands are supported:" << std::endl << std::endl; + std::cerr << "compile compiles a subset of resources" << std::endl; + std::cerr << "link links together compiled resources and libraries" << std::endl; + std::cerr << std::endl; + std::cerr << "run aapt2 with one of the commands and the -h flag for extra details." + << std::endl; + exit(1); +} + static AaptOptions prepareArgs(int argc, char** argv) { if (argc < 2) { - std::cerr << "no command specified." << std::endl; - exit(1); + std::cerr << "no command specified." << std::endl << std::endl; + printCommandsAndDie(); } const StringPiece command(argv[1]); @@ -574,32 +526,27 @@ static AaptOptions prepareArgs(int argc, char** argv) { AaptOptions options; - StringPiece outputDescription = "place output in file"; - if (command == "package") { - options.phase = AaptOptions::Phase::Full; - outputDescription = "place output in directory"; - } else if (command == "collect") { - options.phase = AaptOptions::Phase::Collect; - } else if (command == "link") { + if (command == "link") { options.phase = AaptOptions::Phase::Link; } else if (command == "compile") { options.phase = AaptOptions::Phase::Compile; - outputDescription = "place output in directory"; - } else if (command == "manifest") { - options.phase = AaptOptions::Phase::Manifest; - outputDescription = "place AndroidManifest.xml in directory"; } else { - std::cerr << "invalid command '" << command << "'." << std::endl; - exit(1); + std::cerr << "invalid command '" << command << "'." << std::endl << std::endl; + printCommandsAndDie(); } - if (options.phase == AaptOptions::Phase::Full) { - flag::requiredFlag("-S", "add a directory in which to find resources", + if (options.phase == AaptOptions::Phase::Compile) { + flag::requiredFlag("--package", "Android package name", [&options](const StringPiece& arg) { - options.sourceDirs.push_back(Source{ arg.toString() }); + options.appInfo.package = util::utf8ToUtf16(arg); + }); + flag::optionalFlag("--binding", "Output directory for binding XML files", + [&options](const StringPiece& arg) { + options.bindingOutput = Source{ arg.toString() }; }); - flag::requiredFlag("-M", "path to AndroidManifest.xml", + } else if (options.phase == AaptOptions::Phase::Link) { + flag::requiredFlag("--manifest", "AndroidManifest.xml of your app", [&options](const StringPiece& arg) { options.manifest = Source{ arg.toString() }; }); @@ -613,35 +560,16 @@ static AaptOptions prepareArgs(int argc, char** argv) { [&options](const StringPiece& arg) { options.generateJavaClass = Source{ arg.toString() }; }); - - } else { - if (options.phase != AaptOptions::Phase::Manifest) { - flag::requiredFlag("--package", "Android package name", - [&options](const StringPiece& arg) { - options.appInfo.package = util::utf8ToUtf16(arg); - }); - } - - if (options.phase != AaptOptions::Phase::Collect) { - flag::optionalFlag("-I", "add an Android APK to link against", - [&options](const StringPiece& arg) { - options.libraries.push_back(Source{ arg.toString() }); - }); - } - - if (options.phase == AaptOptions::Phase::Link) { - flag::optionalFlag("--java", "directory in which to generate R.java", - [&options](const StringPiece& arg) { - options.generateJavaClass = Source{ arg.toString() }; - }); - } } // Common flags for all steps. - flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) { + flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) { options.output = Source{ arg.toString() }; }); + + bool help = false; flag::optionalSwitch("-v", "enables verbose logging", &options.verbose); + flag::optionalSwitch("-h", "displays this help menu", &help); // Build the command string for output (eg. "aapt2 compile"). std::string fullCommand = "aapt2"; @@ -651,28 +579,18 @@ static AaptOptions prepareArgs(int argc, char** argv) { // Actually read the command line flags. flag::parse(argc, argv, fullCommand); + if (help) { + flag::usageAndDie(fullCommand); + } + // Copy all the remaining arguments. - if (options.phase == AaptOptions::Phase::Collect) { - for (const std::string& arg : flag::getArgs()) { - options.collectFiles.push_back(Source{ arg }); - } - } else if (options.phase == AaptOptions::Phase::Compile) { - for (const std::string& arg : flag::getArgs()) { - options.compileFiles.push_back(Source{ arg }); - } - } else if (options.phase == AaptOptions::Phase::Link) { - for (const std::string& arg : flag::getArgs()) { - options.linkFiles.push_back(Source{ arg }); - } - } else if (options.phase == AaptOptions::Phase::Manifest) { - if (!flag::getArgs().empty()) { - options.manifest = Source{ flag::getArgs()[0] }; - } + for (const std::string& arg : flag::getArgs()) { + options.input.push_back(Source{ arg }); } return options; } -static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source, +static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source, const ConfigDescription& config) { std::ifstream in(source.path, std::ifstream::binary); if (!in) { @@ -738,115 +656,91 @@ static Maybe<ResourcePathData> extractResourcePathData(const Source& source) { }; } -bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table, - const std::shared_ptr<Resolver>& resolver) { - const bool versionStyles = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Link); - const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Link); - const bool compileFiles = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Compile); - const bool flattenTable = (options->phase == AaptOptions::Phase::Full || - options->phase == AaptOptions::Phase::Collect || - options->phase == AaptOptions::Phase::Link); - const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect; - - // Build the output table path. - Source outputTable = options->output; - if (options->phase == AaptOptions::Phase::Full) { - appendPath(&outputTable.path, "resources.arsc"); - } - - bool error = false; - std::queue<CompileItem> compileQueue; +bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, + const TableFlattener::Options& flattenerOptions, ZipFile* outApk) { + if (table->begin() != table->end()) { + BigBuffer buffer(1024); + TableFlattener flattener(flattenerOptions); + if (!flattener.flatten(&buffer, *table)) { + Logger::error() << "failed to flatten resource table." << std::endl; + return false; + } - // If source directories were specified, walk them looking for resource files. - if (!options->sourceDirs.empty()) { - const char* customIgnore = getenv("ANDROID_AAPT_IGNORE"); - FileFilter fileFilter; - if (customIgnore && customIgnore[0]) { - fileFilter.setPattern(customIgnore); - } else { - fileFilter.setPattern( - "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~"); + if (options.verbose) { + Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) + << std::endl; } - for (const Source& source : options->sourceDirs) { - if (!walkTree(source, fileFilter, &options->collectFiles)) { - return false; - } + if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) != + android::NO_ERROR) { + Logger::note(options.output) << "failed to store resource table." << std::endl; + return false; } } + return true; +} - // Load all binary resource tables. - for (const Source& source : options->linkFiles) { - error |= !loadBinaryResourceTable(table, source); - } +static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate | + ZipFile::kOpenReadWrite; - if (error) { - return false; - } +bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable, + const std::shared_ptr<Resolver>& resolver) { + std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles; + std::unordered_set<std::u16string> linkedPackages; - // Collect all the resource files. - // Need to parse the resource type/config/filename. - for (const Source& source : options->collectFiles) { - Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); - if (!maybePathData) { + // Populate the linkedPackages with our own. + linkedPackages.insert(options.appInfo.package); + + // Load all APK files. + for (const Source& source : options.input) { + std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>(); + if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) { + Logger::error(source) << "failed to open: " << strerror(errno) << std::endl; return false; } - const ResourcePathData& pathData = maybePathData.value(); - if (pathData.resourceDir == u"values") { - if (options->verbose) { - Logger::note(source) << "collecting values..." << std::endl; - } - - error |= !collectValues(table, source, pathData.config); - continue; - } + std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>(); - const ResourceType* type = parseResourceType(pathData.resourceDir); - if (!type) { - Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." - << std::endl; + ZipEntry* entry = zipFile->getEntryByName("resources.arsc"); + if (!entry) { + Logger::error(source) << "missing 'resources.arsc'." << std::endl; return false; } - ResourceName resourceName = { table->getPackage(), *type, pathData.name }; + void* uncompressedData = zipFile->uncompress(entry); + assert(uncompressedData); - // Add the file name to the resource table. - std::unique_ptr<FileReference> fileReference = makeFileReference( - table->getValueStringPool(), - util::utf16ToUtf8(pathData.name) + "." + pathData.extension, - *type, pathData.config); - error |= !table->addResource(resourceName, pathData.config, source.line(0), - std::move(fileReference)); - - if (pathData.extension == "xml") { - error |= !collectXml(table, source, resourceName, pathData.config); + BinaryResourceParser parser(table, resolver, source, uncompressedData, + entry->getUncompressedLen()); + if (!parser.parse()) { + free(uncompressedData); + return false; } + free(uncompressedData); - compileQueue.push( - CompileItem{ source, resourceName, pathData.config, pathData.extension }); - } + // Keep track of where this table came from. + apkFiles[table] = std::move(zipFile); - if (error) { - return false; + // Add the package to the set of linked packages. + linkedPackages.insert(table->getPackage()); } - // Version all styles referencing attributes outside of their specified SDK version. - if (versionStyles) { - versionStylesForCompat(table); - } + for (auto& p : apkFiles) { + const std::shared_ptr<ResourceTable>& inTable = p.first; - // Verify that all references are valid. - Linker linker(table, resolver); - if (!linker.linkAndValidate()) { - return false; + if (!outTable->merge(std::move(*inTable))) { + return false; + } } - // Verify that all symbols exist. - if (verifyNoMissingSymbols) { + { + // Now that everything is merged, let's link it. + Linker linker(outTable, resolver); + if (!linker.linkAndValidate()) { + return false; + } + + // Verify that all symbols exist. const auto& unresolvedRefs = linker.getUnresolvedReferences(); if (!unresolvedRefs.empty()) { for (const auto& entry : unresolvedRefs) { @@ -859,143 +753,190 @@ bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table, } } - // Compile files. - if (compileFiles) { - // First process any input compile files. - for (const Source& source : options->compileFiles) { - Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); - if (!maybePathData) { - return false; - } + // Open the output APK file for writing. + ZipFile outApk; + if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { + Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; + return false; + } - const ResourcePathData& pathData = maybePathData.value(); - const ResourceType* type = parseResourceType(pathData.resourceDir); - if (!type) { - Logger::error(source) << "invalid resource type '" << pathData.resourceDir - << "'." << std::endl; - return false; + if (!compileManifest(options, resolver, &outApk)) { + return false; + } + + for (auto& p : apkFiles) { + std::unique_ptr<ZipFile>& zipFile = p.second; + + // TODO(adamlesinski): Get list of files to read when processing config filter. + + const int numEntries = zipFile->getNumEntries(); + for (int i = 0; i < numEntries; i++) { + ZipEntry* entry = zipFile->getEntryByIndex(i); + assert(entry); + + StringPiece filename = entry->getFileName(); + if (!util::stringStartsWith<char>(filename, "res/")) { + continue; } - ResourceName resourceName = { table->getPackage(), *type, pathData.name }; - compileQueue.push( - CompileItem{ source, resourceName, pathData.config, pathData.extension }); - } + if (util::stringEndsWith<char>(filename, ".xml")) { + void* uncompressedData = zipFile->uncompress(entry); + assert(uncompressedData); - // Now process the actual compile queue. - for (; !compileQueue.empty(); compileQueue.pop()) { - const CompileItem& item = compileQueue.front(); + LinkItem item = { Source{ filename.toString() }, filename.toString() }; - // Create the output directory path from the resource type and config. - std::stringstream outputPath; - outputPath << item.name.type; - if (item.config != ConfigDescription{}) { - outputPath << "-" << item.config.toString(); + if (!linkXml(options, resolver, item, uncompressedData, + entry->getUncompressedLen(), &outApk)) { + Logger::error(options.output) << "failed to link '" << filename << "'." + << std::endl; + return false; + } + } else { + if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) { + Logger::error(options.output) << "failed to copy '" << filename << "'." + << std::endl; + return false; + } } + } + } - Source outSource = options->output; - appendPath(&outSource.path, "res"); - appendPath(&outSource.path, outputPath.str()); + // Generate the Java class file. + if (options.generateJavaClass) { + JavaClassGenerator generator(outTable, {}); - // Make the directory. - if (!mkdirs(outSource.path)) { - Logger::error(outSource) << strerror(errno) << std::endl; - return false; + for (const std::u16string& package : linkedPackages) { + Source outPath = options.generateJavaClass.value(); + + // Build the output directory from the package name. + // Eg. com.android.app -> com/android/app + const std::string packageUtf8 = util::utf16ToUtf8(package); + for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { + appendPath(&outPath.path, part); } - // Add the file name to the directory path. - appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension); + if (!mkdirs(outPath.path)) { + Logger::error(outPath) << strerror(errno) << std::endl; + return false; + } - if (item.extension == "xml") { - if (options->verbose) { - Logger::note(outSource) << "compiling XML file." << std::endl; - } + appendPath(&outPath.path, "R.java"); - error |= !compileXml(resolver, item, outSource, &compileQueue); - } else if (item.extension == "png" || item.extension == "9.png") { - if (options->verbose) { - Logger::note(outSource) << "compiling png file." << std::endl; - } + if (options.verbose) { + Logger::note(outPath) << "writing Java symbols." << std::endl; + } - error |= !compilePng(item.source, outSource); - } else { - error |= !copyFile(item.source, outSource); + std::ofstream fout(outPath.path); + if (!fout) { + Logger::error(outPath) << strerror(errno) << std::endl; + return false; } - } - if (error) { - return false; + if (!generator.generate(package, fout)) { + Logger::error(outPath) << generator.getError() << "." << std::endl; + return false; + } } } - // Compile and validate the AndroidManifest.xml. - if (!options->manifest.path.empty()) { - if (!compileAndroidManifest(resolver, *options)) { - return false; - } + // Flatten the resource table. + TableFlattener::Options flattenerOptions; + flattenerOptions.useExtendedChunks = false; + if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) { + return false; } - // Generate the Java class file. - if (options->generateJavaClass) { - Source outPath = options->generateJavaClass.value(); - if (options->verbose) { - Logger::note() << "writing symbols to " << outPath << "." << std::endl; - } + outApk.flush(); + return true; +} - // Build the output directory from the package name. - // Eg. com.android.app -> com/android/app - const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage()); - for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) { - appendPath(&outPath.path, part); - } +bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver) { + std::queue<CompileItem> compileQueue; + bool error = false; - if (!mkdirs(outPath.path)) { - Logger::error(outPath) << strerror(errno) << std::endl; + // Compile all the resource files passed in on the command line. + for (const Source& source : options.input) { + // Need to parse the resource type/config/filename. + Maybe<ResourcePathData> maybePathData = extractResourcePathData(source); + if (!maybePathData) { return false; } - appendPath(&outPath.path, "R.java"); + const ResourcePathData& pathData = maybePathData.value(); + if (pathData.resourceDir == u"values") { + // The file is in the values directory, which means its contents will + // go into the resource table. + if (options.verbose) { + Logger::note(source) << "compiling values." << std::endl; + } - std::ofstream fout(outPath.path); - if (!fout) { - Logger::error(outPath) << strerror(errno) << std::endl; - return false; - } + error |= !compileValues(table, source, pathData.config); + } else { + // The file is in a directory like 'layout' or 'drawable'. Find out + // the type. + const ResourceType* type = parseResourceType(pathData.resourceDir); + if (!type) { + Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'." + << std::endl; + return false; + } - JavaClassGenerator generator(table, {}); - if (!generator.generate(fout)) { - Logger::error(outPath) << generator.getError() << "." << std::endl; - return false; + compileQueue.push(CompileItem{ + source, + ResourceName{ table->getPackage(), *type, pathData.name }, + pathData.config, + pathData.extension + }); } } - // Flatten the resource table. - if (flattenTable && table->begin() != table->end()) { - BigBuffer buffer(1024); - TableFlattener::Options tableOptions; - tableOptions.useExtendedChunks = useExtendedChunks; - TableFlattener flattener(tableOptions); - if (!flattener.flatten(&buffer, *table)) { - Logger::error() << "failed to flatten resource table." << std::endl; - return false; - } + if (error) { + return false; + } - if (options->verbose) { - Logger::note() << "Final resource table size=" << util::formatSize(buffer.size()) - << std::endl; - } + // Version all styles referencing attributes outside of their specified SDK version. + versionStylesForCompat(table); - std::ofstream fout(outputTable.path, std::ofstream::binary); - if (!fout) { - Logger::error(outputTable) << strerror(errno) << "." << std::endl; - return false; - } + // Open the output APK file for writing. + ZipFile outApk; + if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) { + Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl; + return false; + } - if (!util::writeAll(fout, buffer)) { - Logger::error(outputTable) << strerror(errno) << "." << std::endl; - return false; + // Compile each file. + for (; !compileQueue.empty(); compileQueue.pop()) { + const CompileItem& item = compileQueue.front(); + + // Add the file name to the resource table. + error |= !addFileReference(table, item); + + if (item.extension == "xml") { + error |= !compileXml(options, table, item, &compileQueue, &outApk); + } else if (item.extension == "png" || item.extension == "9.png") { + error |= !compilePng(options, item, &outApk); + } else { + error |= !copyFile(options, item, &outApk); } - fout.flush(); } + + if (error) { + return false; + } + + // Link and assign resource IDs. + Linker linker(table, resolver); + if (!linker.linkAndValidate()) { + return false; + } + + // Flatten the resource table. + if (!writeResourceTable(options, table, {}, &outApk)) { + return false; + } + + outApk.flush(); return true; } @@ -1057,10 +998,16 @@ int main(int argc, char** argv) { // Make the resolver that will cache IDs for us. std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries); - // Do the work. - if (!doAll(&options, table, resolver)) { - Logger::error() << "aapt exiting with failures." << std::endl; - return 1; + if (options.phase == AaptOptions::Phase::Compile) { + if (!compile(options, table, resolver)) { + Logger::error() << "aapt exiting with failures." << std::endl; + return 1; + } + } else if (options.phase == AaptOptions::Phase::Link) { + if (!link(options, table, resolver)) { + Logger::error() << "aapt exiting with failures." << std::endl; + return 1; + } } return 0; } diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h new file mode 100644 index 0000000..1e15e20 --- /dev/null +++ b/tools/aapt2/NameMangler.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 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. + */ + +#ifndef AAPT_NAME_MANGLER_H +#define AAPT_NAME_MANGLER_H + +#include <string> + +namespace aapt { + +struct NameMangler { + /** + * Mangles the name in `outName` with the `package` and stores the mangled + * result in `outName`. The mangled name should contain symbols that are + * illegal to define in XML, so that there will never be name mangling + * collisions. + */ + static void mangle(const std::u16string& package, std::u16string* outName) { + *outName = package + u"$" + *outName; + } + + /** + * Unmangles the name in `outName`, storing the correct name back in `outName` + * and the package in `outPackage`. Returns true if the name was unmangled or + * false if the name was never mangled to begin with. + */ + static bool unmangle(std::u16string* outName, std::u16string* outPackage) { + size_t pivot = outName->find(u'$'); + if (pivot == std::string::npos) { + return false; + } + + outPackage->assign(outName->data(), pivot); + outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1)); + return true; + } +}; + +} // namespace aapt + +#endif // AAPT_NAME_MANGLER_H diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp new file mode 100644 index 0000000..6103655 --- /dev/null +++ b/tools/aapt2/NameMangler_test.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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 "NameMangler.h" + +#include <gtest/gtest.h> +#include <string> + +namespace aapt { + +TEST(NameManglerTest, MangleName) { + std::u16string package = u"android.appcompat"; + std::u16string name = u"Platform.AppCompat"; + + NameMangler::mangle(package, &name); + EXPECT_EQ(name, u"android.appcompat$Platform.AppCompat"); + + std::u16string newPackage; + ASSERT_TRUE(NameMangler::unmangle(&name, &newPackage)); + EXPECT_EQ(name, u"Platform.AppCompat"); + EXPECT_EQ(newPackage, u"android.appcompat"); +} + +TEST(NameManglerTest, IgnoreUnmangledName) { + std::u16string package; + std::u16string name = u"foo_bar"; + + EXPECT_FALSE(NameMangler::unmangle(&name, &package)); + EXPECT_EQ(name, u"foo_bar"); +} + +} // namespace aapt diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp index 76120ac..4e9b68e 100644 --- a/tools/aapt2/Png.cpp +++ b/tools/aapt2/Png.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "BigBuffer.h" #include "Logger.h" #include "Png.h" #include "Source.h" @@ -85,17 +86,12 @@ static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t l } static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) { - std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr)); - if (!output->write(reinterpret_cast<const char*>(data), length)) { - png_error(writePtr, strerror(errno)); - } + BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); + png_bytep buf = outBuffer->nextBlock<png_byte>(length); + memcpy(buf, data, length); } -static void flushDataToStream(png_structp writePtr) { - std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr)); - if (!output->flush()) { - png_error(writePtr, strerror(errno)); - } +static void flushDataToStream(png_structp /*writePtr*/) { } static void logWarning(png_structp readPtr, png_const_charp warningMessage) { @@ -1196,7 +1192,7 @@ getout: } -bool Png::process(const Source& source, std::istream& input, std::ostream& output, +bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer, const Options& options, std::string* outError) { png_byte signature[kPngSignatureSize]; @@ -1262,7 +1258,7 @@ bool Png::process(const Source& source, std::istream& input, std::ostream& outpu png_set_error_fn(writePtr, nullptr, nullptr, logWarning); // Set the write function to write to std::ostream. - png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream); + png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger, outError)) { diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h index bc80754..4577ab8 100644 --- a/tools/aapt2/Png.h +++ b/tools/aapt2/Png.h @@ -17,6 +17,7 @@ #ifndef AAPT_PNG_H #define AAPT_PNG_H +#include "BigBuffer.h" #include "Source.h" #include <iostream> @@ -29,7 +30,7 @@ struct Png { int grayScaleTolerance = 0; }; - bool process(const Source& source, std::istream& input, std::ostream& output, + bool process(const Source& source, std::istream& input, BigBuffer* outBuffer, const Options& options, std::string* outError); }; diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/ResChunkPullParser.h index 7366c89..1426ed2 100644 --- a/tools/aapt2/ResChunkPullParser.h +++ b/tools/aapt2/ResChunkPullParser.h @@ -74,6 +74,22 @@ private: std::string mLastError; }; +template <typename T> +inline static const T* convertTo(const android::ResChunk_header* chunk) { + if (chunk->headerSize < sizeof(T)) { + return nullptr; + } + return reinterpret_cast<const T*>(chunk); +} + +inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) { + return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize; +} + +inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) { + return chunk.size - chunk.headerSize; +} + // // Implementation // diff --git a/tools/aapt2/Resolver.cpp b/tools/aapt2/Resolver.cpp index 93b5e98..ae006ab 100644 --- a/tools/aapt2/Resolver.cpp +++ b/tools/aapt2/Resolver.cpp @@ -15,6 +15,7 @@ */ #include "Maybe.h" +#include "NameMangler.h" #include "Resolver.h" #include "Resource.h" #include "ResourceTable.h" @@ -31,6 +32,12 @@ namespace aapt { Resolver::Resolver(std::shared_ptr<const ResourceTable> table, std::shared_ptr<const android::AssetManager> sources) : mTable(table), mSources(sources) { + const android::ResTable& resTable = mSources->getResources(false); + const size_t packageCount = resTable.getBasePackageCount(); + for (size_t i = 0; i < packageCount; i++) { + std::u16string packageName = resTable.getBasePackageName(i).string(); + mIncludedPackages.insert(std::move(packageName)); + } } Maybe<ResourceId> Resolver::findId(const ResourceName& name) { @@ -47,9 +54,31 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) { return Entry{ cacheIter->second.id, cacheIter->second.attr.get() }; } + ResourceName mangledName; + const ResourceName* nameToSearch = &name; + if (name.package != mTable->getPackage()) { + // This may be a reference to an included resource or + // to a mangled resource. + if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) { + // This is not in our included set, so mangle the name and + // check for that. + mangledName.entry = name.entry; + NameMangler::mangle(name.package, &mangledName.entry); + mangledName.package = mTable->getPackage(); + mangledName.type = name.type; + nameToSearch = &mangledName; + } else { + const CacheEntry* cacheEntry = buildCacheEntry(name); + if (cacheEntry) { + return Entry{ cacheEntry->id, cacheEntry->attr.get() }; + } + return {}; + } + } + const ResourceTableType* type; const ResourceEntry* entry; - std::tie(type, entry) = mTable->findResource(name); + std::tie(type, entry) = mTable->findResource(*nameToSearch); if (type && entry) { Entry result = {}; if (mTable->getPackageId() != ResourceTable::kUnsetPackageId && @@ -65,11 +94,6 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) { } return result; } - - const CacheEntry* cacheEntry = buildCacheEntry(name); - if (cacheEntry) { - return Entry{ cacheEntry->id, cacheEntry->attr.get() }; - } return {}; } diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h index 90a8cd9..cb2234d 100644 --- a/tools/aapt2/Resolver.h +++ b/tools/aapt2/Resolver.h @@ -26,6 +26,7 @@ #include <androidfw/ResourceTypes.h> #include <memory> #include <vector> +#include <unordered_set> namespace aapt { @@ -94,6 +95,7 @@ private: std::shared_ptr<const ResourceTable> mTable; std::shared_ptr<const android::AssetManager> mSources; std::map<ResourceName, CacheEntry> mCache; + std::unordered_set<std::u16string> mIncludedPackages; }; inline const std::u16string& Resolver::getDefaultPackage() const { diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 4d2c64c..f928acd 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -193,8 +193,7 @@ inline ::std::ostream& operator<<(::std::ostream& out, // ResourceType implementation. // -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceType& val) { +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) { return out << toString(val); } @@ -221,6 +220,14 @@ inline bool ResourceName::operator!=(const ResourceName& rhs) const { != std::tie(rhs.package, rhs.type, rhs.entry); } +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) { + if (!name.package.empty()) { + out << name.package << ":"; + } + return out << name.type << "/" << name.entry; +} + + // // ResourceNameRef implementation. // @@ -264,8 +271,7 @@ inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const { != std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, - const ResourceNameRef& name) { +inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) { if (!name.package.empty()) { out << name.package << ":"; } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 4c96187..943892d 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -22,6 +22,8 @@ #include "Util.h" #include "XliffXmlPullParser.h" +#include <sstream> + namespace aapt { void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage, @@ -107,6 +109,71 @@ bool ResourceParser::tryParseAttributeReference(const StringPiece16& str, return false; } +/* + * Style parent's are a bit different. We accept the following formats: + * + * @[package:]style/<entry> + * ?[package:]style/<entry> + * <package>:[style/]<entry> + * [package:style/]<entry> + */ +bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference, + std::string* outError) { + if (str.empty()) { + return true; + } + + StringPiece16 name = str; + + bool hasLeadingIdentifiers = false; + bool privateRef = false; + + // Skip over these identifiers. A style's parent is a normal reference. + if (name.data()[0] == u'@' || name.data()[0] == u'?') { + hasLeadingIdentifiers = true; + name = name.substr(1, name.size() - 1); + if (name.data()[0] == u'*') { + privateRef = true; + name = name.substr(1, name.size() - 1); + } + } + + ResourceNameRef ref; + ref.type = ResourceType::kStyle; + + StringPiece16 typeStr; + extractResourceName(name, &ref.package, &typeStr, &ref.entry); + if (!typeStr.empty()) { + // If we have a type, make sure it is a Style. + const ResourceType* parsedType = parseResourceType(typeStr); + if (!parsedType || *parsedType != ResourceType::kStyle) { + std::stringstream err; + err << "invalid resource type '" << typeStr << "' for parent of style"; + *outError = err.str(); + return false; + } + } else { + // No type was defined, this should not have a leading identifier. + if (hasLeadingIdentifiers) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return false; + } + } + + if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return false; + } + + outReference->name = ref.toResourceName(); + outReference->privateReference = privateRef; + return true; +} + std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str, const StringPiece16& defaultPackage, bool* outCreate) { @@ -885,15 +952,16 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) { const SourceLine source = mSource.line(parser->getLineNumber()); - std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false); + ResourceName actualName = resourceName.toResourceName(); + std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false); if (!attr) { return false; } - return mTable->addResource(resourceName, mConfig, source, std::move(attr)); + return mTable->addResource(actualName, mConfig, source, std::move(attr)); } std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, - const ResourceNameRef& resourceName, + ResourceName* resourceName, bool weak) { uint32_t typeMask = 0; @@ -911,6 +979,18 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser, } } + // If this is a declaration, the package name may be in the name. Separate these out. + // Eg. <attr name="android:text" /> + // No format attribute is allowed. + if (weak && formatAttrIter == endAttrIter) { + StringPiece16 package, type, name; + extractResourceName(resourceName->entry, &package, &type, &name); + if (type.empty() && !package.empty()) { + resourceName->package = package.toString(); + resourceName->entry = name.toString(); + } + } + std::vector<Attribute::Symbol> items; bool error = false; @@ -1079,31 +1159,15 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) { bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) { const SourceLine source = mSource.line(parser->getLineNumber()); - std::unique_ptr<Style> style = util::make_unique<Style>(); + std::unique_ptr<Style> style = util::make_unique<Style>(false); const auto endAttrIter = parser->endAttributes(); const auto parentAttrIter = parser->findAttribute(u"", u"parent"); if (parentAttrIter != endAttrIter) { - ResourceNameRef ref; - bool create = false; - bool privateRef = false; - if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) { - if (create) { - mLogger.error(source.line) - << "parent of style can not be an ID." - << std::endl; - return false; - } - style->parent.name = ref.toResourceName(); - style->parent.privateReference = privateRef; - } else if (tryParseAttributeReference(parentAttrIter->value, &ref)) { - style->parent.name = ref.toResourceName(); - } else { - // TODO(adamlesinski): Try parsing without the '@' or '?'. - // Also, make sure to check the entry name for weird symbols. - style->parent.name = ResourceName { - {}, ResourceType::kStyle, parentAttrIter->value - }; + std::string errStr; + if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) { + mLogger.error(source.line) << errStr << "." << std::endl; + return false; } if (style->parent.name.package.empty()) { @@ -1277,15 +1341,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, } // Copy because our iterator will be invalidated. - std::u16string attrName = attrIter->value; - - ResourceNameRef attrResourceName = { + ResourceName attrResourceName = { mTable->getPackage(), ResourceType::kAttr, - attrName + attrIter->value }; - std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true); + std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true); if (!attr) { success = false; continue; @@ -1293,9 +1355,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, styleable->entries.emplace_back(attrResourceName); - success &= mTable->addResource(attrResourceName, mConfig, - mSource.line(childParser.getLineNumber()), - std::move(attr)); + // The package may have been corrected to another package. If that is so, + // we don't add the declaration. + if (attrResourceName.package == mTable->getPackage()) { + success &= mTable->addResource(attrResourceName, mConfig, + mSource.line(childParser.getLineNumber()), + std::move(attr)); + } } else if (elementName != u"eat-comment" && elementName != u"skip") { mLogger.error(childParser.getLineNumber()) diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 96bba4f..52194bd 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -64,6 +64,17 @@ public: ResourceNameRef* outReference); /* + * Returns true if the string `str` was parsed as a valid reference to a style. + * The format for a style parent is slightly more flexible than a normal reference: + * + * @[package:]style/<entry> or + * ?[package:]style/<entry> or + * <package>:[style/]<entry> + */ + static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference, + std::string* outError); + + /* * Returns a Reference object if the string was parsed as a resource or attribute reference, * ( @[+][package:]type/name | ?[package:]type/name ) * assigning defaultPackage if the package was not present in the string, and setting @@ -166,7 +177,7 @@ private: bool parsePublic(XmlPullParser* parser, const StringPiece16& name); bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName); std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser, - const ResourceNameRef& resourceName, + ResourceName* resourceName, bool weak); bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag, Attribute::Symbol* outSymbol); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 5afbaf4..63352de 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -94,6 +94,31 @@ TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) { &privateRef)); } +TEST(ResourceParserReferenceTest, ParseStyleParentReference) { + Reference ref; + std::string errStr; + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" })); + + EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr)); + EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" })); +} + struct ResourceParserTest : public ::testing::Test { virtual void SetUp() override { mTable = std::make_shared<ResourceTable>(); @@ -283,7 +308,7 @@ TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { TEST_F(ResourceParserTest, ParseStyle) { std::stringstream input; - input << "<style name=\"foo\" parent=\"fu\">" << std::endl + input << "<style name=\"foo\" parent=\"@style/fu\">" << std::endl << " <item name=\"bar\">#ffffffff</item>" << std::endl << " <item name=\"bat\">@string/hey</item>" << std::endl << " <item name=\"baz\"><b>hey</b></item>" << std::endl @@ -304,6 +329,17 @@ TEST_F(ResourceParserTest, ParseStyle) { (ResourceName{ u"android", ResourceType::kAttr, u"baz" })); } +TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { + std::stringstream input; + input << "<style name=\"foo\" parent=\"com.app:Theme\"/>" << std::endl; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource<Style>( + ResourceName{ u"android", ResourceType::kStyle, u"foo" }); + ASSERT_NE(style, nullptr); + EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name); +} + TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { std::stringstream input; input << "<string name=\"foo\">@+id/bar</string>" << std::endl; diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 794090d..02be651 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -16,6 +16,7 @@ #include "ConfigDescription.h" #include "Logger.h" +#include "NameMangler.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "Util.h" @@ -311,6 +312,71 @@ bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId res return true; } +bool ResourceTable::merge(ResourceTable&& other) { + const bool mangleNames = mPackage != other.getPackage(); + std::u16string mangledName; + + for (auto& otherType : other) { + std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type); + if (type->publicStatus.isPublic && otherType->publicStatus.isPublic && + type->typeId != otherType->typeId) { + Logger::error() << "can not merge type '" << type->type << "': conflicting public IDs " + << "(" << type->typeId << " vs " << otherType->typeId << ")." + << std::endl; + return false; + } + + for (auto& otherEntry : otherType->entries) { + const std::u16string* nameToAdd = &otherEntry->name; + if (mangleNames) { + mangledName = otherEntry->name; + NameMangler::mangle(other.getPackage(), &mangledName); + nameToAdd = &mangledName; + } + + std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd); + if (entry->publicStatus.isPublic && otherEntry->publicStatus.isPublic && + entry->entryId != otherEntry->entryId) { + Logger::error() << "can not merge entry '" << type->type << "/" << entry->name + << "': conflicting public IDs " + << "(" << entry->entryId << " vs " << entry->entryId << ")." + << std::endl; + return false; + } + + for (ResourceConfigValue& otherValue : otherEntry->values) { + auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), + otherValue.config, compareConfigs); + if (iter != entry->values.end() && iter->config == otherValue.config) { + int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value); + if (collisionResult > 0) { + // Take the incoming value. + iter->source = std::move(otherValue.source); + iter->comment = std::move(otherValue.comment); + iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)); + } else if (collisionResult == 0) { + ResourceNameRef resourceName = { mPackage, type->type, entry->name }; + Logger::error(otherValue.source) + << "resource '" << resourceName << "' has a conflicting value for " + << "configuration (" << otherValue.config << ")." + << std::endl; + Logger::note(iter->source) << "originally defined here." << std::endl; + return false; + } + } else { + entry->values.insert(iter, ResourceConfigValue{ + otherValue.config, + std::move(otherValue.source), + std::move(otherValue.comment), + std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)), + }); + } + } + } + } + return true; +} + std::tuple<const ResourceTableType*, const ResourceEntry*> ResourceTable::findResource(const ResourceNameRef& name) const { if (name.package != mPackage) { diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 57b5213..3591d11 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -148,6 +148,12 @@ public: bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source); + /* + * Merges the resources from `other` into this table, mangling the names of the resources + * if `other` has a different package name. + */ + bool merge(ResourceTable&& other); + /** * Returns the string pool used by this ResourceTable. * Values that reference strings should use this pool to create diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 785ea15..06d8699 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -31,7 +31,7 @@ struct TestValue : public Value { TestValue(StringPiece16 str) : value(str.toString()) { } - TestValue* clone() const override { + TestValue* clone(StringPool* /*newPool*/) const override { return new TestValue(value); } @@ -48,7 +48,7 @@ struct TestWeakValue : public Value { return true; } - TestWeakValue* clone() const override { + TestWeakValue* clone(StringPool* /*newPool*/) const override { return new TestWeakValue(); } diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 60ef1a8..3a6d65d 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -39,8 +39,8 @@ bool Item::isItem() const { RawString::RawString(const StringPool::Ref& ref) : value(ref) { } -RawString* RawString::clone() const { - return new RawString(value); +RawString* RawString::clone(StringPool* newPool) const { + return new RawString(newPool->makeRef(*value)); } bool RawString::flatten(android::Res_value& outValue) const { @@ -71,7 +71,7 @@ bool Reference::flatten(android::Res_value& outValue) const { return true; } -Reference* Reference::clone() const { +Reference* Reference::clone(StringPool* /*newPool*/) const { Reference* ref = new Reference(); ref->referenceType = referenceType; ref->name = name; @@ -106,7 +106,7 @@ bool Id::flatten(android::Res_value& out) const { return true; } -Id* Id::clone() const { +Id* Id::clone(StringPool* /*newPool*/) const { return new Id(); } @@ -128,8 +128,8 @@ bool String::flatten(android::Res_value& outValue) const { return true; } -String* String::clone() const { - return new String(value); +String* String::clone(StringPool* newPool) const { + return new String(newPool->makeRef(*value)); } void String::print(std::ostream& out) const { @@ -149,8 +149,8 @@ bool StyledString::flatten(android::Res_value& outValue) const { return true; } -StyledString* StyledString::clone() const { - return new StyledString(value); +StyledString* StyledString::clone(StringPool* newPool) const { + return new StyledString(newPool->makeRef(value)); } void StyledString::print(std::ostream& out) const { @@ -170,8 +170,8 @@ bool FileReference::flatten(android::Res_value& outValue) const { return true; } -FileReference* FileReference::clone() const { - return new FileReference(path); +FileReference* FileReference::clone(StringPool* newPool) const { + return new FileReference(newPool->makeRef(*path)); } void FileReference::print(std::ostream& out) const { @@ -186,7 +186,7 @@ bool BinaryPrimitive::flatten(android::Res_value& outValue) const { return true; } -BinaryPrimitive* BinaryPrimitive::clone() const { +BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const { return new BinaryPrimitive(value); } @@ -227,7 +227,7 @@ bool Sentinel::flatten(android::Res_value& outValue) const { return true; } -Sentinel* Sentinel::clone() const { +Sentinel* Sentinel::clone(StringPool* /*newPool*/) const { return new Sentinel(); } @@ -243,7 +243,7 @@ bool Attribute::isWeak() const { return weak; } -Attribute* Attribute::clone() const { +Attribute* Attribute::clone(StringPool* /*newPool*/) const { Attribute* attr = new Attribute(weak); attr->typeMask = typeMask; std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols)); @@ -371,13 +371,20 @@ static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& return out << s.symbol.name.entry << "=" << s.value; } -Style* Style::clone() const { - Style* style = new Style(); +Style::Style(bool weak) : weak(weak) { +} + +bool Style::isWeak() const { + return weak; +} + +Style* Style::clone(StringPool* newPool) const { + Style* style = new Style(weak); style->parent = parent; for (auto& entry : entries) { style->entries.push_back(Entry{ entry.key, - std::unique_ptr<Item>(entry.value->clone()) + std::unique_ptr<Item>(entry.value->clone(newPool)) }); } return style; @@ -399,10 +406,10 @@ static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value return out; } -Array* Array::clone() const { +Array* Array::clone(StringPool* newPool) const { Array* array = new Array(); for (auto& item : items) { - array->items.emplace_back(std::unique_ptr<Item>(item->clone())); + array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool))); } return array; } @@ -413,12 +420,12 @@ void Array::print(std::ostream& out) const { << "]"; } -Plural* Plural::clone() const { +Plural* Plural::clone(StringPool* newPool) const { Plural* p = new Plural(); const size_t count = values.size(); for (size_t i = 0; i < count; i++) { if (values[i]) { - p->values[i] = std::unique_ptr<Item>(values[i]->clone()); + p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool)); } } return p; @@ -432,7 +439,7 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite return out << *item; } -Styleable* Styleable::clone() const { +Styleable* Styleable::clone(StringPool* /*newPool*/) const { Styleable* styleable = new Styleable(); std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries)); return styleable; diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index f25bcf0..e3352f3 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -63,7 +63,7 @@ struct Value { /** * Clone the value. */ - virtual Value* clone() const = 0; + virtual Value* clone(StringPool* newPool) const = 0; /** * Human readable printout of this value. @@ -92,7 +92,7 @@ struct Item : public Value { /** * Clone the Item. */ - virtual Item* clone() const override = 0; + virtual Item* clone(StringPool* newPool) const override = 0; /** * Fills in an android::Res_value structure with this Item's binary representation. @@ -132,7 +132,7 @@ struct Reference : public BaseItem<Reference> { Reference(const ResourceId& i, Type type = Type::kResource); bool flatten(android::Res_value& outValue) const override; - Reference* clone() const override; + Reference* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -142,7 +142,7 @@ struct Reference : public BaseItem<Reference> { struct Id : public BaseItem<Id> { bool isWeak() const override; bool flatten(android::Res_value& out) const override; - Id* clone() const override; + Id* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -157,7 +157,7 @@ struct RawString : public BaseItem<RawString> { RawString(const StringPool::Ref& ref); bool flatten(android::Res_value& outValue) const override; - RawString* clone() const override; + RawString* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -167,7 +167,7 @@ struct String : public BaseItem<String> { String(const StringPool::Ref& ref); bool flatten(android::Res_value& outValue) const override; - String* clone() const override; + String* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -177,7 +177,7 @@ struct StyledString : public BaseItem<StyledString> { StyledString(const StringPool::StyleRef& ref); bool flatten(android::Res_value& outValue) const override; - StyledString* clone() const override; + StyledString* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -188,7 +188,7 @@ struct FileReference : public BaseItem<FileReference> { FileReference(const StringPool::Ref& path); bool flatten(android::Res_value& outValue) const override; - FileReference* clone() const override; + FileReference* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -202,8 +202,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { BinaryPrimitive(const android::Res_value& val); bool flatten(android::Res_value& outValue) const override; - BinaryPrimitive* clone() const override; - void print(::std::ostream& out) const override; + BinaryPrimitive* clone(StringPool* newPool) const override; + void print(std::ostream& out) const override; }; /** @@ -214,8 +214,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { struct Sentinel : public BaseItem<Sentinel> { bool isWeak() const override; bool flatten(android::Res_value& outValue) const override; - Sentinel* clone() const override; - void print(::std::ostream& out) const override; + Sentinel* clone(StringPool* newPool) const override; + void print(std::ostream& out) const override; }; struct Attribute : public BaseValue<Attribute> { @@ -233,7 +233,7 @@ struct Attribute : public BaseValue<Attribute> { Attribute(bool w, uint32_t t = 0u); bool isWeak() const override; - virtual Attribute* clone() const override; + virtual Attribute* clone(StringPool* newPool) const override; virtual void print(std::ostream& out) const override; }; @@ -243,17 +243,20 @@ struct Style : public BaseValue<Style> { std::unique_ptr<Item> value; }; + bool weak; Reference parent; std::vector<Entry> entries; - Style* clone() const override; + Style(bool weak); + bool isWeak() const override; + Style* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; struct Array : public BaseValue<Array> { std::vector<std::unique_ptr<Item>> items; - Array* clone() const override; + Array* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; @@ -270,14 +273,14 @@ struct Plural : public BaseValue<Plural> { std::array<std::unique_ptr<Item>, Count> values; - Plural* clone() const override; + Plural* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; struct Styleable : public BaseValue<Styleable> { std::vector<Reference> entries; - Styleable* clone() const override; + Styleable* clone(StringPool* newPool) const override; void print(std::ostream& out) const override; }; diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index b159a00..b983a53 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -175,6 +175,25 @@ StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& return StyleRef(styleEntry); } +StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) { + Entry* entry = new Entry(); + entry->value = *ref.mEntry->str; + entry->context = ref.mEntry->str.mEntry->context; + entry->index = mStrings.size(); + entry->ref = 0; + mStrings.emplace_back(entry); + mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry)); + + StyleEntry* styleEntry = new StyleEntry(); + styleEntry->str = Ref(entry); + for (const Span& span : ref.mEntry->spans) { + styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar }); + } + styleEntry->ref = 0; + mStyles.emplace_back(styleEntry); + return StyleRef(styleEntry); +} + void StringPool::merge(StringPool&& pool) { mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end()); pool.mIndexedStrings.clear(); @@ -266,7 +285,7 @@ bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { header->stringCount = pool.size(); header->flags |= android::ResStringPool_header::UTF8_FLAG; - uint32_t* indices = out->nextBlock<uint32_t>(pool.size()); + uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr; uint32_t* styleIndices = nullptr; if (!pool.mStyles.empty()) { diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 2aa5b65..64772a4 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -158,6 +158,12 @@ public: StyleRef makeRef(const StyleString& str, const Context& context); /** + * Adds a style from another string pool. Returns a reference to the + * style in the string pool. + */ + StyleRef makeRef(const StyleRef& ref); + + /** * Moves pool into this one without coalescing strings. When this * function returns, pool will be empty. */ diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 85d101a..9552937 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -162,6 +162,16 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { EXPECT_NE(ref.getIndex(), styleRef.getIndex()); } +TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { + StringPool pool; + BigBuffer buffer(1024); + StringPool::flattenUtf8(&buffer, pool); + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + android::ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR); +} + constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。"; TEST(StringPoolTest, FlattenUtf8) { @@ -183,16 +193,10 @@ TEST(StringPoolTest, FlattenUtf8) { BigBuffer buffer(1024); StringPool::flattenUtf8(&buffer, pool); - uint8_t* data = new uint8_t[buffer.size()]; - uint8_t* p = data; - for (const auto& b : buffer) { - memcpy(p, b.buffer.get(), b.size); - p += b.size; - } - + std::unique_ptr<uint8_t[]> data = util::copy(buffer); { - ResStringPool test; - ASSERT_TRUE(test.setTo(data, buffer.size()) == NO_ERROR); + android::ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR); EXPECT_EQ(util::getString(test, 0), u"hello"); EXPECT_EQ(util::getString(test, 1), u"goodbye"); @@ -214,7 +218,6 @@ TEST(StringPoolTest, FlattenUtf8) { EXPECT_EQ(ResStringPool_span::END, span->name.index); } - delete[] data; } } // namespace aapt diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp index b6ca6d5..dd6f63a 100644 --- a/tools/aapt2/XmlFlattener.cpp +++ b/tools/aapt2/XmlFlattener.cpp @@ -35,6 +35,10 @@ namespace aapt { +constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; +constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; +constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/"; + struct AttributeValueFlattener : ValueVisitor { struct Args : ValueVisitorArgs { Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV, @@ -95,7 +99,9 @@ static bool lessAttributeId(const XmlAttribute& a, uint32_t id) { return a.resourceId < id; } -XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) { +XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver) : + mTable(table), mResolver(resolver) { } /** @@ -190,28 +196,50 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, uint32_t nextAttributeId = 0; const auto endAttrIter = parser->endAttributes(); for (auto attrIter = parser->beginAttributes(); - attrIter != endAttrIter; - ++attrIter) { + attrIter != endAttrIter; + ++attrIter) { uint32_t id; StringPool::Ref nameRef; const Attribute* attr = nullptr; - if (attrIter->namespaceUri.empty()) { + + if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) { + size_t sdkVersion = findAttributeSdkLevel(attrIter->name); + if (sdkVersion > options.maxSdkAttribute.value()) { + // We will silently omit this attribute + smallestStrippedAttributeSdk = + std::min(smallestStrippedAttributeSdk, sdkVersion); + continue; + } + } + + ResourceNameRef genIdName; + bool create = false; + bool privateRef = false; + if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName, + &create, &privateRef) && create) { + mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()), + util::make_unique<Id>()); + } + + + StringPiece16 package; + if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) { + StringPiece16 schemaPrefix = kSchemaPrefix; + package = attrIter->namespaceUri; + package = package.substr(schemaPrefix.size(), + package.size() - schemaPrefix.size()); + } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) { + package = mResolver->getDefaultPackage(); + } + + if (package.empty() || !mResolver) { // Attributes that have no resource ID (because they don't belong to a // package) should appear after those that do have resource IDs. Assign // them some/ integer value that will appear after. id = 0x80000000u | nextAttributeId++; nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); - } else { - StringPiece16 package; - if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") { - package = mResolver->getDefaultPackage(); - } else { - // TODO(adamlesinski): Extract package from namespace. - // The package name appears like so: - // http://schemas.android.com/apk/res/<package name> - package = u"android"; - } + } else { // Find the Attribute object via our Resolver. ResourceName attrName = { package.toString(), ResourceType::kAttr, attrIter->name }; @@ -236,16 +264,6 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source, continue; } - if (options.maxSdkAttribute && package == u"android") { - size_t sdkVersion = findAttributeSdkLevel(attrIter->name); - if (sdkVersion > options.maxSdkAttribute.value()) { - // We will silently omit this attribute - smallestStrippedAttributeSdk = - std::min(smallestStrippedAttributeSdk, sdkVersion); - continue; - } - } - id = result.value().id.id; attr = result.value().attr; diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h index abf64ab..540a5ef 100644 --- a/tools/aapt2/XmlFlattener.h +++ b/tools/aapt2/XmlFlattener.h @@ -45,7 +45,8 @@ public: * Creates a flattener with a Resolver to resolve references * and attributes. */ - XmlFlattener(const std::shared_ptr<Resolver>& resolver); + XmlFlattener(const std::shared_ptr<ResourceTable>& table, + const std::shared_ptr<Resolver>& resolver); XmlFlattener(const XmlFlattener&) = delete; // Not copyable. @@ -60,6 +61,7 @@ public: BigBuffer* outBuffer, Options options); private: + std::shared_ptr<ResourceTable> mTable; std::shared_ptr<Resolver> mResolver; }; diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp index 6e24847..a7d7ac6 100644 --- a/tools/aapt2/XmlFlattener_test.cpp +++ b/tools/aapt2/XmlFlattener_test.cpp @@ -47,7 +47,7 @@ public: table->addResource(ResourceName{ {}, ResourceType::kId, u"test" }, ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>()); - mFlattener = std::make_shared<XmlFlattener>( + mFlattener = std::make_shared<XmlFlattener>(nullptr, std::make_shared<Resolver>(table, std::make_shared<AssetManager>())); } diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp new file mode 100644 index 0000000..ad5d84a --- /dev/null +++ b/tools/aapt2/ZipEntry.cpp @@ -0,0 +1,739 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// Access to entries in a Zip archive. +// + +#define LOG_TAG "zip" + +#include "ZipEntry.h" +#include <utils/Log.h> + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +namespace aapt { + +using namespace android; + +/* + * Initialize a new ZipEntry structure from a FILE* positioned at a + * CentralDirectoryEntry. + * + * On exit, the file pointer will be at the start of the next CDE or + * at the EOCD. + */ +status_t ZipEntry::initFromCDE(FILE* fp) +{ + status_t result; + long posn; + bool hasDD; + + //ALOGV("initFromCDE ---\n"); + + /* read the CDE */ + result = mCDE.read(fp); + if (result != NO_ERROR) { + ALOGD("mCDE.read failed\n"); + return result; + } + + //mCDE.dump(); + + /* using the info in the CDE, go load up the LFH */ + posn = ftell(fp); + if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) { + ALOGD("local header seek failed (%ld)\n", + mCDE.mLocalHeaderRelOffset); + return UNKNOWN_ERROR; + } + + result = mLFH.read(fp); + if (result != NO_ERROR) { + ALOGD("mLFH.read failed\n"); + return result; + } + + if (fseek(fp, posn, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + //mLFH.dump(); + + /* + * We *might* need to read the Data Descriptor at this point and + * integrate it into the LFH. If this bit is set, the CRC-32, + * compressed size, and uncompressed size will be zero. In practice + * these seem to be rare. + */ + hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0; + if (hasDD) { + // do something clever + //ALOGD("+++ has data descriptor\n"); + } + + /* + * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr" + * flag is set, because the LFH is incomplete. (Not a problem, since we + * prefer the CDE values.) + */ + if (!hasDD && !compareHeaders()) { + ALOGW("warning: header mismatch\n"); + // keep going? + } + + /* + * If the mVersionToExtract is greater than 20, we may have an + * issue unpacking the record -- could be encrypted, compressed + * with something we don't support, or use Zip64 extensions. We + * can defer worrying about that to when we're extracting data. + */ + + return NO_ERROR; +} + +/* + * Initialize a new entry. Pass in the file name and an optional comment. + * + * Initializes the CDE and the LFH. + */ +void ZipEntry::initNew(const char* fileName, const char* comment) +{ + assert(fileName != NULL && *fileName != '\0'); // name required + + /* most fields are properly initialized by constructor */ + mCDE.mVersionMadeBy = kDefaultMadeBy; + mCDE.mVersionToExtract = kDefaultVersion; + mCDE.mCompressionMethod = kCompressStored; + mCDE.mFileNameLength = strlen(fileName); + if (comment != NULL) + mCDE.mFileCommentLength = strlen(comment); + mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does + + if (mCDE.mFileNameLength > 0) { + mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1]; + strcpy((char*) mCDE.mFileName, fileName); + } + if (mCDE.mFileCommentLength > 0) { + /* TODO: stop assuming null-terminated ASCII here? */ + mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1]; + strcpy((char*) mCDE.mFileComment, comment); + } + + copyCDEtoLFH(); +} + +/* + * Initialize a new entry, starting with the ZipEntry from a different + * archive. + * + * Initializes the CDE and the LFH. + */ +status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */, + const ZipEntry* pEntry) +{ + mCDE = pEntry->mCDE; + // Check whether we got all the memory needed. + if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) || + (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) || + (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) { + return NO_MEMORY; + } + + /* construct the LFH from the CDE */ + copyCDEtoLFH(); + + /* + * The LFH "extra" field is independent of the CDE "extra", so we + * handle it here. + */ + assert(mLFH.mExtraField == NULL); + mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength; + if (mLFH.mExtraFieldLength > 0) { + mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1]; + if (mLFH.mExtraField == NULL) + return NO_MEMORY; + memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField, + mLFH.mExtraFieldLength+1); + } + + return NO_ERROR; +} + +/* + * Insert pad bytes in the LFH by tweaking the "extra" field. This will + * potentially confuse something that put "extra" data in here earlier, + * but I can't find an actual problem. + */ +status_t ZipEntry::addPadding(int padding) +{ + if (padding <= 0) + return INVALID_OPERATION; + + //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n", + // padding, mLFH.mExtraFieldLength, mCDE.mFileName); + + if (mLFH.mExtraFieldLength > 0) { + /* extend existing field */ + unsigned char* newExtra; + + newExtra = new unsigned char[mLFH.mExtraFieldLength + padding]; + if (newExtra == NULL) + return NO_MEMORY; + memset(newExtra + mLFH.mExtraFieldLength, 0, padding); + memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength); + + delete[] mLFH.mExtraField; + mLFH.mExtraField = newExtra; + mLFH.mExtraFieldLength += padding; + } else { + /* create new field */ + mLFH.mExtraField = new unsigned char[padding]; + memset(mLFH.mExtraField, 0, padding); + mLFH.mExtraFieldLength = padding; + } + + return NO_ERROR; +} + +/* + * Set the fields in the LFH equal to the corresponding fields in the CDE. + * + * This does not touch the LFH "extra" field. + */ +void ZipEntry::copyCDEtoLFH(void) +{ + mLFH.mVersionToExtract = mCDE.mVersionToExtract; + mLFH.mGPBitFlag = mCDE.mGPBitFlag; + mLFH.mCompressionMethod = mCDE.mCompressionMethod; + mLFH.mLastModFileTime = mCDE.mLastModFileTime; + mLFH.mLastModFileDate = mCDE.mLastModFileDate; + mLFH.mCRC32 = mCDE.mCRC32; + mLFH.mCompressedSize = mCDE.mCompressedSize; + mLFH.mUncompressedSize = mCDE.mUncompressedSize; + mLFH.mFileNameLength = mCDE.mFileNameLength; + // the "extra field" is independent + + delete[] mLFH.mFileName; + if (mLFH.mFileNameLength > 0) { + mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1]; + strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName); + } else { + mLFH.mFileName = NULL; + } +} + +/* + * Set some information about a file after we add it. + */ +void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod) +{ + mCDE.mCompressionMethod = compressionMethod; + mCDE.mCRC32 = crc32; + mCDE.mCompressedSize = compLen; + mCDE.mUncompressedSize = uncompLen; + mCDE.mCompressionMethod = compressionMethod; + if (compressionMethod == kCompressDeflated) { + mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used + } + copyCDEtoLFH(); +} + +/* + * See if the data in mCDE and mLFH match up. This is mostly useful for + * debugging these classes, but it can be used to identify damaged + * archives. + * + * Returns "false" if they differ. + */ +bool ZipEntry::compareHeaders(void) const +{ + if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) { + ALOGV("cmp: VersionToExtract\n"); + return false; + } + if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) { + ALOGV("cmp: GPBitFlag\n"); + return false; + } + if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) { + ALOGV("cmp: CompressionMethod\n"); + return false; + } + if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) { + ALOGV("cmp: LastModFileTime\n"); + return false; + } + if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) { + ALOGV("cmp: LastModFileDate\n"); + return false; + } + if (mCDE.mCRC32 != mLFH.mCRC32) { + ALOGV("cmp: CRC32\n"); + return false; + } + if (mCDE.mCompressedSize != mLFH.mCompressedSize) { + ALOGV("cmp: CompressedSize\n"); + return false; + } + if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) { + ALOGV("cmp: UncompressedSize\n"); + return false; + } + if (mCDE.mFileNameLength != mLFH.mFileNameLength) { + ALOGV("cmp: FileNameLength\n"); + return false; + } +#if 0 // this seems to be used for padding, not real data + if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) { + ALOGV("cmp: ExtraFieldLength\n"); + return false; + } +#endif + if (mCDE.mFileName != NULL) { + if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) { + ALOGV("cmp: FileName\n"); + return false; + } + } + + return true; +} + + +/* + * Convert the DOS date/time stamp into a UNIX time stamp. + */ +time_t ZipEntry::getModWhen(void) const +{ + struct tm parts; + + parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1; + parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5; + parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11; + parts.tm_mday = (mCDE.mLastModFileDate & 0x001f); + parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1; + parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80; + parts.tm_wday = parts.tm_yday = 0; + parts.tm_isdst = -1; // DST info "not available" + + return mktime(&parts); +} + +/* + * Set the CDE/LFH timestamp from UNIX time. + */ +void ZipEntry::setModWhen(time_t when) +{ +#if !defined(_WIN32) + struct tm tmResult; +#endif + time_t even; + unsigned short zdate, ztime; + + struct tm* ptm; + + /* round up to an even number of seconds */ + even = (time_t)(((unsigned long)(when) + 1) & (~1)); + + /* expand */ +#if !defined(_WIN32) + ptm = localtime_r(&even, &tmResult); +#else + ptm = localtime(&even); +#endif + + int year; + year = ptm->tm_year; + if (year < 80) + year = 80; + + zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday; + ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1; + + mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime; + mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate; +} + + +/* + * =========================================================================== + * ZipEntry::LocalFileHeader + * =========================================================================== + */ + +/* + * Read a local file header. + * + * On entry, "fp" points to the signature at the start of the header. + * On exit, "fp" points to the start of data. + */ +status_t ZipEntry::LocalFileHeader::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kLFHLen]; + + assert(mFileName == NULL); + assert(mExtraField == NULL); + + if (fread(buf, 1, kLFHLen, fp) != kLFHLen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + ALOGD("whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]); + mCRC32 = ZipEntry::getLongLE(&buf[0x0e]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x12]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]); + + // TODO: validate sizes + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* grab extra field */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a local file header. + */ +status_t ZipEntry::LocalFileHeader::write(FILE* fp) +{ + unsigned char buf[kLFHLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x06], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x08], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x0e], mCRC32); + ZipEntry::putLongLE(&buf[0x12], mCompressedSize); + ZipEntry::putLongLE(&buf[0x16], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1a], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength); + + if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Dump the contents of a LocalFileHeader object. + */ +void ZipEntry::LocalFileHeader::dump(void) const +{ + ALOGD(" LocalFileHeader contents:\n"); + ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionToExtract, mGPBitFlag, mCompressionMethod); + ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + ALOGD(" filenameLen=%u extraLen=%u\n", + mFileNameLength, mExtraFieldLength); + if (mFileName != NULL) + ALOGD(" filename: '%s'\n", mFileName); +} + + +/* + * =========================================================================== + * ZipEntry::CentralDirEntry + * =========================================================================== + */ + +/* + * Read the central dir entry that appears next in the file. + * + * On entry, "fp" should be positioned on the signature bytes for the + * entry. On exit, "fp" will point at the signature word for the next + * entry or for the EOCD. + */ +status_t ZipEntry::CentralDirEntry::read(FILE* fp) +{ + status_t result = NO_ERROR; + unsigned char buf[kCDELen]; + + /* no re-use */ + assert(mFileName == NULL); + assert(mExtraField == NULL); + assert(mFileComment == NULL); + + if (fread(buf, 1, kCDELen, fp) != kCDELen) { + result = UNKNOWN_ERROR; + goto bail; + } + + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) { + ALOGD("Whoops: didn't find expected signature\n"); + result = UNKNOWN_ERROR; + goto bail; + } + + mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]); + mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]); + mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]); + mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]); + mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]); + mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]); + mCRC32 = ZipEntry::getLongLE(&buf[0x10]); + mCompressedSize = ZipEntry::getLongLE(&buf[0x14]); + mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]); + mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]); + mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]); + mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]); + mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]); + mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]); + mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]); + mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]); + + // TODO: validate sizes and offsets + + /* grab filename */ + if (mFileNameLength != 0) { + mFileName = new unsigned char[mFileNameLength+1]; + if (mFileName == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mFileName[mFileNameLength] = '\0'; + } + + /* read "extra field" */ + if (mExtraFieldLength != 0) { + mExtraField = new unsigned char[mExtraFieldLength+1]; + if (mExtraField == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) { + result = UNKNOWN_ERROR; + goto bail; + } + mExtraField[mExtraFieldLength] = '\0'; + } + + + /* grab comment, if any */ + if (mFileCommentLength != 0) { + mFileComment = new unsigned char[mFileCommentLength+1]; + if (mFileComment == NULL) { + result = NO_MEMORY; + goto bail; + } + if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + { + result = UNKNOWN_ERROR; + goto bail; + } + mFileComment[mFileCommentLength] = '\0'; + } + +bail: + return result; +} + +/* + * Write a central dir entry. + */ +status_t ZipEntry::CentralDirEntry::write(FILE* fp) +{ + unsigned char buf[kCDELen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy); + ZipEntry::putShortLE(&buf[0x06], mVersionToExtract); + ZipEntry::putShortLE(&buf[0x08], mGPBitFlag); + ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod); + ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime); + ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate); + ZipEntry::putLongLE(&buf[0x10], mCRC32); + ZipEntry::putLongLE(&buf[0x14], mCompressedSize); + ZipEntry::putLongLE(&buf[0x18], mUncompressedSize); + ZipEntry::putShortLE(&buf[0x1c], mFileNameLength); + ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength); + ZipEntry::putShortLE(&buf[0x20], mFileCommentLength); + ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart); + ZipEntry::putShortLE(&buf[0x24], mInternalAttrs); + ZipEntry::putLongLE(&buf[0x26], mExternalAttrs); + ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset); + + if (fwrite(buf, 1, kCDELen, fp) != kCDELen) + return UNKNOWN_ERROR; + + /* write filename */ + if (mFileNameLength != 0) { + if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength) + return UNKNOWN_ERROR; + } + + /* write "extra field" */ + if (mExtraFieldLength != 0) { + if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) + return UNKNOWN_ERROR; + } + + /* write comment */ + if (mFileCommentLength != 0) { + if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of a CentralDirEntry object. + */ +void ZipEntry::CentralDirEntry::dump(void) const +{ + ALOGD(" CentralDirEntry contents:\n"); + ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n", + mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod); + ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n", + mLastModFileTime, mLastModFileDate, mCRC32); + ALOGD(" compressedSize=%lu uncompressedSize=%lu\n", + mCompressedSize, mUncompressedSize); + ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n", + mFileNameLength, mExtraFieldLength, mFileCommentLength); + ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n", + mDiskNumberStart, mInternalAttrs, mExternalAttrs, + mLocalHeaderRelOffset); + + if (mFileName != NULL) + ALOGD(" filename: '%s'\n", mFileName); + if (mFileComment != NULL) + ALOGD(" comment: '%s'\n", mFileComment); +} + +/* + * Copy-assignment operator for CentralDirEntry. + */ +ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) { + if (this == &src) { + return *this; + } + + // Free up old data. + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + + // Copy scalars. + mVersionMadeBy = src.mVersionMadeBy; + mVersionToExtract = src.mVersionToExtract; + mGPBitFlag = src.mGPBitFlag; + mCompressionMethod = src.mCompressionMethod; + mLastModFileTime = src.mLastModFileTime; + mLastModFileDate = src.mLastModFileDate; + mCRC32 = src.mCRC32; + mCompressedSize = src.mCompressedSize; + mUncompressedSize = src.mUncompressedSize; + mFileNameLength = src.mFileNameLength; + mExtraFieldLength = src.mExtraFieldLength; + mFileCommentLength = src.mFileCommentLength; + mDiskNumberStart = src.mDiskNumberStart; + mInternalAttrs = src.mInternalAttrs; + mExternalAttrs = src.mExternalAttrs; + mLocalHeaderRelOffset = src.mLocalHeaderRelOffset; + + // Copy strings, if necessary. + if (mFileNameLength > 0) { + mFileName = new unsigned char[mFileNameLength + 1]; + if (mFileName != NULL) + strcpy((char*)mFileName, (char*)src.mFileName); + } else { + mFileName = NULL; + } + if (mFileCommentLength > 0) { + mFileComment = new unsigned char[mFileCommentLength + 1]; + if (mFileComment != NULL) + strcpy((char*)mFileComment, (char*)src.mFileComment); + } else { + mFileComment = NULL; + } + if (mExtraFieldLength > 0) { + /* we null-terminate this, though it may not be a string */ + mExtraField = new unsigned char[mExtraFieldLength + 1]; + if (mExtraField != NULL) + memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1); + } else { + mExtraField = NULL; + } + + return *this; +} + +} // namespace aapt diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h new file mode 100644 index 0000000..d048a3e --- /dev/null +++ b/tools/aapt2/ZipEntry.h @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> + +namespace aapt { + +using android::status_t; + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + CentralDirEntry& operator=(const CentralDirEntry& src); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace aapt + +#endif // __LIBS_ZIPENTRY_H diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp new file mode 100644 index 0000000..41e59cf --- /dev/null +++ b/tools/aapt2/ZipFile.cpp @@ -0,0 +1,1305 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// Access to Zip archives. +// + +#define LOG_TAG "zip" + +#include <androidfw/ZipUtils.h> +#include <utils/Log.h> + +#include "ZipFile.h" +#include "Util.h" + +#include <zlib.h> +#define DEF_MEM_LEVEL 8 // normally in zutil.h? + +#include <memory.h> +#include <sys/stat.h> +#include <errno.h> +#include <assert.h> + +namespace aapt { + +using namespace android; + +/* + * Some environments require the "b", some choke on it. + */ +#define FILE_OPEN_RO "rb" +#define FILE_OPEN_RW "r+b" +#define FILE_OPEN_RW_CREATE "w+b" + +/* should live somewhere else? */ +static status_t errnoToStatus(int err) +{ + if (err == ENOENT) + return NAME_NOT_FOUND; + else if (err == EACCES) + return PERMISSION_DENIED; + else + return UNKNOWN_ERROR; +} + +/* + * Open a file and parse its guts. + */ +status_t ZipFile::open(const char* zipFileName, int flags) +{ + bool newArchive = false; + + assert(mZipFp == NULL); // no reopen + + if ((flags & kOpenTruncate)) + flags |= kOpenCreate; // trunc implies create + + if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite)) + return INVALID_OPERATION; // not both + if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite))) + return INVALID_OPERATION; // not neither + if ((flags & kOpenCreate) && !(flags & kOpenReadWrite)) + return INVALID_OPERATION; // create requires write + + if (flags & kOpenTruncate) { + newArchive = true; + } else { + newArchive = (access(zipFileName, F_OK) != 0); + if (!(flags & kOpenCreate) && newArchive) { + /* not creating, must already exist */ + ALOGD("File %s does not exist", zipFileName); + return NAME_NOT_FOUND; + } + } + + /* open the file */ + const char* openflags; + if (flags & kOpenReadWrite) { + if (newArchive) + openflags = FILE_OPEN_RW_CREATE; + else + openflags = FILE_OPEN_RW; + } else { + openflags = FILE_OPEN_RO; + } + mZipFp = fopen(zipFileName, openflags); + if (mZipFp == NULL) { + int err = errno; + ALOGD("fopen failed: %d\n", err); + return errnoToStatus(err); + } + + status_t result; + if (!newArchive) { + /* + * Load the central directory. If that fails, then this probably + * isn't a Zip archive. + */ + result = readCentralDir(); + } else { + /* + * Newly-created. The EndOfCentralDir constructor actually + * sets everything to be the way we want it (all zeroes). We + * set mNeedCDRewrite so that we create *something* if the + * caller doesn't add any files. (We could also just unlink + * the file if it's brand new and nothing was added, but that's + * probably doing more than we really should -- the user might + * have a need for empty zip files.) + */ + mNeedCDRewrite = true; + result = NO_ERROR; + } + + if (flags & kOpenReadOnly) + mReadOnly = true; + else + assert(!mReadOnly); + + return result; +} + +/* + * Return the Nth entry in the archive. + */ +ZipEntry* ZipFile::getEntryByIndex(int idx) const +{ + if (idx < 0 || idx >= (int) mEntries.size()) + return NULL; + + return mEntries[idx]; +} + +/* + * Find an entry by name. + */ +ZipEntry* ZipFile::getEntryByName(const char* fileName) const +{ + /* + * Do a stupid linear string-compare search. + * + * There are various ways to speed this up, especially since it's rare + * to intermingle changes to the archive with "get by name" calls. We + * don't want to sort the mEntries vector itself, however, because + * it's used to recreate the Central Directory. + * + * (Hash table works, parallel list of pointers in sorted order is good.) + */ + int idx; + + for (idx = mEntries.size()-1; idx >= 0; idx--) { + ZipEntry* pEntry = mEntries[idx]; + if (!pEntry->getDeleted() && + strcmp(fileName, pEntry->getFileName()) == 0) + { + return pEntry; + } + } + + return NULL; +} + +/* + * Empty the mEntries vector. + */ +void ZipFile::discardEntries(void) +{ + int count = mEntries.size(); + + while (--count >= 0) + delete mEntries[count]; + + mEntries.clear(); +} + + +/* + * Find the central directory and read the contents. + * + * The fun thing about ZIP archives is that they may or may not be + * readable from start to end. In some cases, notably for archives + * that were written to stdout, the only length information is in the + * central directory at the end of the file. + * + * Of course, the central directory can be followed by a variable-length + * comment field, so we have to scan through it backwards. The comment + * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ +status_t ZipFile::readCentralDir(void) +{ + status_t result = NO_ERROR; + unsigned char* buf = NULL; + off_t fileLength, seekStart; + long readAmount; + int i; + + fseek(mZipFp, 0, SEEK_END); + fileLength = ftell(mZipFp); + rewind(mZipFp); + + /* too small to be a ZIP archive? */ + if (fileLength < EndOfCentralDir::kEOCDLen) { + ALOGD("Length is %ld -- too small\n", (long)fileLength); + result = INVALID_OPERATION; + goto bail; + } + + buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch]; + if (buf == NULL) { + ALOGD("Failure allocating %d bytes for EOCD search", + EndOfCentralDir::kMaxEOCDSearch); + result = NO_MEMORY; + goto bail; + } + + if (fileLength > EndOfCentralDir::kMaxEOCDSearch) { + seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch; + readAmount = EndOfCentralDir::kMaxEOCDSearch; + } else { + seekStart = 0; + readAmount = (long) fileLength; + } + if (fseek(mZipFp, seekStart, SEEK_SET) != 0) { + ALOGD("Failure seeking to end of zip at %ld", (long) seekStart); + result = UNKNOWN_ERROR; + goto bail; + } + + /* read the last part of the file into the buffer */ + if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) { + ALOGD("short file? wanted %ld\n", readAmount); + result = UNKNOWN_ERROR; + goto bail; + } + + /* find the end-of-central-dir magic */ + for (i = readAmount - 4; i >= 0; i--) { + if (buf[i] == 0x50 && + ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature) + { + ALOGV("+++ Found EOCD at buf+%d\n", i); + break; + } + } + if (i < 0) { + ALOGD("EOCD not found, not Zip\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* extract eocd values */ + result = mEOCD.readBuf(buf + i, readAmount - i); + if (result != NO_ERROR) { + ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i); + goto bail; + } + //mEOCD.dump(); + + if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 || + mEOCD.mNumEntries != mEOCD.mTotalNumEntries) + { + ALOGD("Archive spanning not supported\n"); + result = INVALID_OPERATION; + goto bail; + } + + /* + * So far so good. "mCentralDirSize" is the size in bytes of the + * central directory, so we can just seek back that far to find it. + * We can also seek forward mCentralDirOffset bytes from the + * start of the file. + * + * We're not guaranteed to have the rest of the central dir in the + * buffer, nor are we guaranteed that the central dir will have any + * sort of convenient size. We need to skip to the start of it and + * read the header, then the other goodies. + * + * The only thing we really need right now is the file comment, which + * we're hoping to preserve. + */ + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + ALOGD("Failure seeking to central dir offset %ld\n", + mEOCD.mCentralDirOffset); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Loop through and read the central dir entries. + */ + ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries); + int entry; + for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) { + ZipEntry* pEntry = new ZipEntry; + + result = pEntry->initFromCDE(mZipFp); + if (result != NO_ERROR) { + ALOGD("initFromCDE failed\n"); + delete pEntry; + goto bail; + } + + mEntries.push_back(pEntry); + } + + + /* + * If all went well, we should now be back at the EOCD. + */ + { + unsigned char checkBuf[4]; + if (fread(checkBuf, 1, 4, mZipFp) != 4) { + ALOGD("EOCD check read failed\n"); + result = INVALID_OPERATION; + goto bail; + } + if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) { + ALOGD("EOCD read check failed\n"); + result = UNKNOWN_ERROR; + goto bail; + } + ALOGV("+++ EOCD read check passed\n"); + } + +bail: + delete[] buf; + return result; +} + +status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod, + ZipEntry** ppEntry) { + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry); +} + + +/* + * Add a new file to the archive. + * + * This requires creating and populating a ZipEntry structure, and copying + * the data into the file at the appropriate position. The "appropriate + * position" is the current location of the central directory, which we + * casually overwrite (we can put it back later). + * + * If we were concerned about safety, we would want to make all changes + * in a temp file and then overwrite the original after everything was + * safely written. Not really a concern for us. + */ +status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result = NO_ERROR; + long lfhPosn, startPosn, endPosn, uncompressedLen; + FILE* inputFp = NULL; + unsigned long crc; + time_t modWhen; + + if (mReadOnly) + return INVALID_OPERATION; + + assert(compressionMethod == ZipEntry::kCompressDeflated || + compressionMethod == ZipEntry::kCompressStored); + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + /* make sure it doesn't already exist */ + if (getEntryByName(storageName) != NULL) + return ALREADY_EXISTS; + + if (!data) { + inputFp = fopen(fileName, FILE_OPEN_RO); + if (inputFp == NULL) + return errnoToStatus(errno); + } + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + pEntry->initNew(storageName, NULL); + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH, even though it's still mostly blank. We need it + * as a place-holder. In theory the LFH isn't necessary, but in + * practice some utilities demand it. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + startPosn = ftell(mZipFp); + + /* + * Copy the data in, possibly compressing it as we go. + */ + if (sourceType == ZipEntry::kCompressStored) { + if (compressionMethod == ZipEntry::kCompressDeflated) { + bool failed = false; + result = compressFpToFp(mZipFp, inputFp, data, size, &crc); + if (result != NO_ERROR) { + ALOGD("compression failed, storing\n"); + failed = true; + } else { + /* + * Make sure it has compressed "enough". This probably ought + * to be set through an API call, but I don't expect our + * criteria to change over time. + */ + long src = inputFp ? ftell(inputFp) : size; + long dst = ftell(mZipFp) - startPosn; + if (dst + (dst / 10) > src) { + ALOGD("insufficient compression (src=%ld dst=%ld), storing\n", + src, dst); + failed = true; + } + } + + if (failed) { + compressionMethod = ZipEntry::kCompressStored; + if (inputFp) rewind(inputFp); + fseek(mZipFp, startPosn, SEEK_SET); + /* fall through to kCompressStored case */ + } + } + /* handle "no compression" request, or failed compression from above */ + if (compressionMethod == ZipEntry::kCompressStored) { + if (inputFp) { + result = copyFpToFp(mZipFp, inputFp, &crc); + } else { + result = copyDataToFp(mZipFp, data, size, &crc); + } + if (result != NO_ERROR) { + // don't need to truncate; happens in CDE rewrite + ALOGD("failed copying data in\n"); + goto bail; + } + } + + // currently seeked to end of file + uncompressedLen = inputFp ? ftell(inputFp) : size; + } else if (sourceType == ZipEntry::kCompressDeflated) { + /* we should support uncompressed-from-compressed, but it's not + * important right now */ + assert(compressionMethod == ZipEntry::kCompressDeflated); + + bool scanResult; + int method; + long compressedLen; + + scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen, + &compressedLen, &crc); + if (!scanResult || method != ZipEntry::kCompressDeflated) { + ALOGD("this isn't a deflated gzip file?"); + result = UNKNOWN_ERROR; + goto bail; + } + + result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL); + if (result != NO_ERROR) { + ALOGD("failed copying gzip data in\n"); + goto bail; + } + } else { + assert(false); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * We could write the "Data Descriptor", but there doesn't seem to + * be any point since we're going to go back and write the LFH. + * + * Update file offsets. + */ + endPosn = ftell(mZipFp); // seeked to end of compressed data + + /* + * Success! Fill out new values. + */ + pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc, + compressionMethod); + modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp)); + pEntry->setModWhen(modWhen); + pEntry->setLFHOffset(lfhPosn); + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Go back and write the LFH. + */ + if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + pEntry->mLFH.write(mZipFp); + + /* + * Add pEntry to the list. + */ + mEntries.push_back(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + +bail: + if (inputFp != NULL) + fclose(inputFp); + delete pEntry; + return result; +} + +/* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ +status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry) +{ + ZipEntry* pEntry = NULL; + status_t result; + long lfhPosn, endPosn; + + if (mReadOnly) + return INVALID_OPERATION; + + /* make sure we're in a reasonable state */ + assert(mZipFp != NULL); + assert(mEntries.size() == mEOCD.mTotalNumEntries); + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) { + result = UNKNOWN_ERROR; + goto bail; + } + + pEntry = new ZipEntry; + if (pEntry == NULL) { + result = NO_MEMORY; + goto bail; + } + + result = pEntry->initFromExternal(pSourceZip, pSourceEntry); + if (result != NO_ERROR) + goto bail; + if (padding != 0) { + result = pEntry->addPadding(padding); + if (result != NO_ERROR) + goto bail; + } + + /* + * From here on out, failures are more interesting. + */ + mNeedCDRewrite = true; + + /* + * Write the LFH. Since we're not recompressing the data, we already + * have all of the fields filled out. + */ + lfhPosn = ftell(mZipFp); + pEntry->mLFH.write(mZipFp); + + /* + * Copy the data over. + * + * If the "has data descriptor" flag is set, we want to copy the DD + * fields as well. This is a fixed-size area immediately following + * the data. + */ + if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0) + { + result = UNKNOWN_ERROR; + goto bail; + } + + off_t copyLen; + copyLen = pSourceEntry->getCompressedLen(); + if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0) + copyLen += ZipEntry::kDataDescriptorLen; + + if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL) + != NO_ERROR) + { + ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName); + result = UNKNOWN_ERROR; + goto bail; + } + + /* + * Update file offsets. + */ + endPosn = ftell(mZipFp); + + /* + * Success! Fill out new values. + */ + pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset + mEOCD.mNumEntries++; + mEOCD.mTotalNumEntries++; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + mEOCD.mCentralDirOffset = endPosn; + + /* + * Add pEntry to the list. + */ + mEntries.push_back(pEntry); + if (ppEntry != NULL) + *ppEntry = pEntry; + pEntry = NULL; + + result = NO_ERROR; + +bail: + delete pEntry; + return result; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data. + */ +status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (1) { + count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp); + if (ferror(srcFp) || ferror(dstFp)) + return errnoToStatus(errno); + if (count == 0) + break; + + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + ALOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy all of the bytes in "src" to "dst". + * + * On exit, "dstFp" will be seeked immediately past the data. + */ +status_t ZipFile::copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + *pCRC32 = crc32(0L, Z_NULL, 0); + if (size > 0) { + *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size); + if (fwrite(data, 1, size, dstFp) != size) { + ALOGD("fwrite %d bytes failed\n", (int) size); + return UNKNOWN_ERROR; + } + } + + return NO_ERROR; +} + +/* + * Copy some of the bytes in "src" to "dst". + * + * If "pCRC32" is NULL, the CRC will not be computed. + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the data just written. + */ +status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32) +{ + unsigned char tmpBuf[32768]; + size_t count; + + if (pCRC32 != NULL) + *pCRC32 = crc32(0L, Z_NULL, 0); + + while (length) { + long readSize; + + readSize = sizeof(tmpBuf); + if (readSize > length) + readSize = length; + + count = fread(tmpBuf, 1, readSize, srcFp); + if ((long) count != readSize) { // error or unexpected EOF + ALOGD("fread %d bytes failed\n", (int) readSize); + return UNKNOWN_ERROR; + } + + if (pCRC32 != NULL) + *pCRC32 = crc32(*pCRC32, tmpBuf, count); + + if (fwrite(tmpBuf, 1, count, dstFp) != count) { + ALOGD("fwrite %d bytes failed\n", (int) count); + return UNKNOWN_ERROR; + } + + length -= readSize; + } + + return NO_ERROR; +} + +/* + * Compress all of the data in "srcFp" and write it to "dstFp". + * + * On exit, "srcFp" will be seeked to the end of the file, and "dstFp" + * will be seeked immediately past the compressed data. + */ +status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32) +{ + status_t result = NO_ERROR; + const size_t kBufSize = 32768; + unsigned char* inBuf = NULL; + unsigned char* outBuf = NULL; + z_stream zstream; + bool atEof = false; // no feof() aviailable yet + unsigned long crc; + int zerr; + + /* + * Create an input buffer and an output buffer. + */ + inBuf = new unsigned char[kBufSize]; + outBuf = new unsigned char[kBufSize]; + if (inBuf == NULL || outBuf == NULL) { + result = NO_MEMORY; + goto bail; + } + + /* + * Initialize the zlib stream. + */ + memset(&zstream, 0, sizeof(zstream)); + zstream.zalloc = Z_NULL; + zstream.zfree = Z_NULL; + zstream.opaque = Z_NULL; + zstream.next_in = NULL; + zstream.avail_in = 0; + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + zstream.data_type = Z_UNKNOWN; + + zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION, + Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY); + if (zerr != Z_OK) { + result = UNKNOWN_ERROR; + if (zerr == Z_VERSION_ERROR) { + ALOGE("Installed zlib is not compatible with linked version (%s)\n", + ZLIB_VERSION); + } else { + ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr); + } + goto bail; + } + + crc = crc32(0L, Z_NULL, 0); + + /* + * Loop while we have data. + */ + do { + size_t getSize; + int flush; + + /* only read if the input buffer is empty */ + if (zstream.avail_in == 0 && !atEof) { + ALOGV("+++ reading %d bytes\n", (int)kBufSize); + if (data) { + getSize = size > kBufSize ? kBufSize : size; + memcpy(inBuf, data, getSize); + data = ((const char*)data) + getSize; + size -= getSize; + } else { + getSize = fread(inBuf, 1, kBufSize, srcFp); + if (ferror(srcFp)) { + ALOGD("deflate read failed (errno=%d)\n", errno); + goto z_bail; + } + } + if (getSize < kBufSize) { + ALOGV("+++ got %d bytes, EOF reached\n", + (int)getSize); + atEof = true; + } + + crc = crc32(crc, inBuf, getSize); + + zstream.next_in = inBuf; + zstream.avail_in = getSize; + } + + if (atEof) + flush = Z_FINISH; /* tell zlib that we're done */ + else + flush = Z_NO_FLUSH; /* more to come! */ + + zerr = deflate(&zstream, flush); + if (zerr != Z_OK && zerr != Z_STREAM_END) { + ALOGD("zlib deflate call failed (zerr=%d)\n", zerr); + result = UNKNOWN_ERROR; + goto z_bail; + } + + /* write when we're full or when we're done */ + if (zstream.avail_out == 0 || + (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize)) + { + ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf)); + if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) != + (size_t)(zstream.next_out - outBuf)) + { + ALOGD("write %d failed in deflate\n", + (int) (zstream.next_out - outBuf)); + goto z_bail; + } + + zstream.next_out = outBuf; + zstream.avail_out = kBufSize; + } + } while (zerr == Z_OK); + + assert(zerr == Z_STREAM_END); /* other errors should've been caught */ + + *pCRC32 = crc; + +z_bail: + deflateEnd(&zstream); /* free up any allocated structures */ + +bail: + delete[] inBuf; + delete[] outBuf; + + return result; +} + +/* + * Mark an entry as deleted. + * + * We will eventually need to crunch the file down, but if several files + * are being removed (perhaps as part of an "update" process) we can make + * things considerably faster by deferring the removal to "flush" time. + */ +status_t ZipFile::remove(ZipEntry* pEntry) +{ + /* + * Should verify that pEntry is actually part of this archive, and + * not some stray ZipEntry from a different file. + */ + + /* mark entry as deleted, and mark archive as dirty */ + pEntry->setDeleted(); + mNeedCDRewrite = true; + return NO_ERROR; +} + +/* + * Flush any pending writes. + * + * In particular, this will crunch out deleted entries, and write the + * Central Directory and EOCD if we have stomped on them. + */ +status_t ZipFile::flush(void) +{ + status_t result = NO_ERROR; + long eocdPosn; + int i, count; + + if (mReadOnly) + return INVALID_OPERATION; + if (!mNeedCDRewrite) + return NO_ERROR; + + assert(mZipFp != NULL); + + result = crunchArchive(); + if (result != NO_ERROR) + return result; + + if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) + return UNKNOWN_ERROR; + + count = mEntries.size(); + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + pEntry->mCDE.write(mZipFp); + } + + eocdPosn = ftell(mZipFp); + mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset; + + mEOCD.write(mZipFp); + + /* + * If we had some stuff bloat up during compression and get replaced + * with plain files, or if we deleted some entries, there's a lot + * of wasted space at the end of the file. Remove it now. + */ + if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) { + ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno)); + // not fatal + } + + /* should we clear the "newly added" flag in all entries now? */ + + mNeedCDRewrite = false; + return NO_ERROR; +} + +/* + * Crunch deleted files out of an archive by shifting the later files down. + * + * Because we're not using a temp file, we do the operation inside the + * current file. + */ +status_t ZipFile::crunchArchive(void) +{ + status_t result = NO_ERROR; + int i, count; + long delCount, adjust; + +#if 0 + printf("CONTENTS:\n"); + for (i = 0; i < (int) mEntries.size(); i++) { + printf(" %d: lfhOff=%ld del=%d\n", + i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted()); + } + printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset); +#endif + + /* + * Roll through the set of files, shifting them as appropriate. We + * could probably get a slight performance improvement by sliding + * multiple files down at once (because we could use larger reads + * when operating on batches of small files), but it's not that useful. + */ + count = mEntries.size(); + delCount = adjust = 0; + for (i = 0; i < count; i++) { + ZipEntry* pEntry = mEntries[i]; + long span; + + if (pEntry->getLFHOffset() != 0) { + long nextOffset; + + /* Get the length of this entry by finding the offset + * of the next entry. Directory entries don't have + * file offsets, so we need to find the next non-directory + * entry. + */ + nextOffset = 0; + for (int ii = i+1; nextOffset == 0 && ii < count; ii++) + nextOffset = mEntries[ii]->getLFHOffset(); + if (nextOffset == 0) + nextOffset = mEOCD.mCentralDirOffset; + span = nextOffset - pEntry->getLFHOffset(); + + assert(span >= ZipEntry::LocalFileHeader::kLFHLen); + } else { + /* This is a directory entry. It doesn't have + * any actual file contents, so there's no need to + * move anything. + */ + span = 0; + } + + //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n", + // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count); + + if (pEntry->getDeleted()) { + adjust += span; + delCount++; + + delete pEntry; + mEntries.erase(mEntries.begin() + i); + + /* adjust loop control */ + count--; + i--; + } else if (span != 0 && adjust > 0) { + /* shuffle this entry back */ + //printf("+++ Shuffling '%s' back %ld\n", + // pEntry->getFileName(), adjust); + result = filemove(mZipFp, pEntry->getLFHOffset() - adjust, + pEntry->getLFHOffset(), span); + if (result != NO_ERROR) { + /* this is why you use a temp file */ + ALOGE("error during crunch - archive is toast\n"); + return result; + } + + pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust); + } + } + + /* + * Fix EOCD info. We have to wait until the end to do some of this + * because we use mCentralDirOffset to determine "span" for the + * last entry. + */ + mEOCD.mCentralDirOffset -= adjust; + mEOCD.mNumEntries -= delCount; + mEOCD.mTotalNumEntries -= delCount; + mEOCD.mCentralDirSize = 0; // mark invalid; set by flush() + + assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries); + assert(mEOCD.mNumEntries == count); + + return result; +} + +/* + * Works like memmove(), but on pieces of a file. + */ +status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n) +{ + if (dst == src || n <= 0) + return NO_ERROR; + + unsigned char readBuf[32768]; + + if (dst < src) { + /* shift stuff toward start of file; must read from start */ + while (n != 0) { + size_t getSize = sizeof(readBuf); + if (getSize > n) + getSize = n; + + if (fseek(fp, (long) src, SEEK_SET) != 0) { + ALOGD("filemove src seek %ld failed\n", (long) src); + return UNKNOWN_ERROR; + } + + if (fread(readBuf, 1, getSize, fp) != getSize) { + ALOGD("filemove read %ld off=%ld failed\n", + (long) getSize, (long) src); + return UNKNOWN_ERROR; + } + + if (fseek(fp, (long) dst, SEEK_SET) != 0) { + ALOGD("filemove dst seek %ld failed\n", (long) dst); + return UNKNOWN_ERROR; + } + + if (fwrite(readBuf, 1, getSize, fp) != getSize) { + ALOGD("filemove write %ld off=%ld failed\n", + (long) getSize, (long) dst); + return UNKNOWN_ERROR; + } + + src += getSize; + dst += getSize; + n -= getSize; + } + } else { + /* shift stuff toward end of file; must read from end */ + assert(false); // write this someday, maybe + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + + +/* + * Get the modification time from a file descriptor. + */ +time_t ZipFile::getModTime(int fd) +{ + struct stat sb; + + if (fstat(fd, &sb) < 0) { + ALOGD("HEY: fstat on fd %d failed\n", fd); + return (time_t) -1; + } + + return sb.st_mtime; +} + + +#if 0 /* this is a bad idea */ +/* + * Get a copy of the Zip file descriptor. + * + * We don't allow this if the file was opened read-write because we tend + * to leave the file contents in an uncertain state between calls to + * flush(). The duplicated file descriptor should only be valid for reads. + */ +int ZipFile::getZipFd(void) const +{ + if (!mReadOnly) + return INVALID_OPERATION; + assert(mZipFp != NULL); + + int fd; + fd = dup(fileno(mZipFp)); + if (fd < 0) { + ALOGD("didn't work, errno=%d\n", errno); + } + + return fd; +} +#endif + + +#if 0 +/* + * Expand data. + */ +bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const +{ + return false; +} +#endif + +// free the memory when you're done +void* ZipFile::uncompress(const ZipEntry* entry) +{ + size_t unlen = entry->getUncompressedLen(); + size_t clen = entry->getCompressedLen(); + + void* buf = malloc(unlen); + if (buf == NULL) { + return NULL; + } + + fseek(mZipFp, 0, SEEK_SET); + + off_t offset = entry->getFileOffset(); + if (fseek(mZipFp, offset, SEEK_SET) != 0) { + goto bail; + } + + switch (entry->getCompressionMethod()) + { + case ZipEntry::kCompressStored: { + ssize_t amt = fread(buf, 1, unlen, mZipFp); + if (amt != (ssize_t)unlen) { + goto bail; + } +#if 0 + printf("data...\n"); + const unsigned char* p = (unsigned char*)buf; + const unsigned char* end = p+unlen; + for (int i=0; i<32 && p < end; i++) { + printf("0x%08x ", (int)(offset+(i*0x10))); + for (int j=0; j<0x10 && p < end; j++) { + printf(" %02x", *p); + p++; + } + printf("\n"); + } +#endif + + } + break; + case ZipEntry::kCompressDeflated: { + if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) { + goto bail; + } + } + break; + default: + goto bail; + } + return buf; + +bail: + free(buf); + return NULL; +} + + +/* + * =========================================================================== + * ZipFile::EndOfCentralDir + * =========================================================================== + */ + +/* + * Read the end-of-central-dir fields. + * + * "buf" should be positioned at the EOCD signature, and should contain + * the entire EOCD area including the comment. + */ +status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len) +{ + /* don't allow re-use */ + assert(mComment == NULL); + + if (len < kEOCDLen) { + /* looks like ZIP file got truncated */ + ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n", + kEOCDLen, len); + return INVALID_OPERATION; + } + + /* this should probably be an assert() */ + if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) + return UNKNOWN_ERROR; + + mDiskNumber = ZipEntry::getShortLE(&buf[0x04]); + mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]); + mNumEntries = ZipEntry::getShortLE(&buf[0x08]); + mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]); + mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]); + mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]); + mCommentLen = ZipEntry::getShortLE(&buf[0x14]); + + // TODO: validate mCentralDirOffset + + if (mCommentLen > 0) { + if (kEOCDLen + mCommentLen > len) { + ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n", + kEOCDLen, mCommentLen, len); + return UNKNOWN_ERROR; + } + mComment = new unsigned char[mCommentLen]; + memcpy(mComment, buf + kEOCDLen, mCommentLen); + } + + return NO_ERROR; +} + +/* + * Write an end-of-central-directory section. + */ +status_t ZipFile::EndOfCentralDir::write(FILE* fp) +{ + unsigned char buf[kEOCDLen]; + + ZipEntry::putLongLE(&buf[0x00], kSignature); + ZipEntry::putShortLE(&buf[0x04], mDiskNumber); + ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir); + ZipEntry::putShortLE(&buf[0x08], mNumEntries); + ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries); + ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize); + ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset); + ZipEntry::putShortLE(&buf[0x14], mCommentLen); + + if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen) + return UNKNOWN_ERROR; + if (mCommentLen > 0) { + assert(mComment != NULL); + if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen) + return UNKNOWN_ERROR; + } + + return NO_ERROR; +} + +/* + * Dump the contents of an EndOfCentralDir object. + */ +void ZipFile::EndOfCentralDir::dump(void) const +{ + ALOGD(" EndOfCentralDir contents:\n"); + ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n", + mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries); + ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n", + mCentralDirSize, mCentralDirOffset, mCommentLen); +} + +} // namespace aapt diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h new file mode 100644 index 0000000..9cbd1fa --- /dev/null +++ b/tools/aapt2/ZipFile.h @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2006 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. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include "BigBuffer.h" +#include "ZipEntry.h" + +#include <stdio.h> +#include <utils/Errors.h> +#include <vector> + +namespace aapt { + +using android::status_t; + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + status_t add(const BigBuffer& data, const char* storageName, + int compressionMethod, ZipEntry** ppEntry); + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least <uncompressed len> bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + std::vector<ZipEntry*> mEntries; +}; + +}; // namespace aapt + +#endif // __LIBS_ZIPFILE_H diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile index f296dc1..5a2a1d1 100644 --- a/tools/aapt2/data/Makefile +++ b/tools/aapt2/data/Makefile @@ -14,6 +14,7 @@ FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediat LOCAL_PACKAGE := com.android.app LOCAL_RESOURCE_DIR := res +LOCAL_LIBS := lib/out/package.apk LOCAL_OUT := out LOCAL_GEN := out/gen @@ -21,13 +22,12 @@ LOCAL_GEN := out/gen # AAPT2 custom rules. ## -PRIVATE_ARSC := $(LOCAL_OUT)/resources.arsc PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk # Eg: framework.apk, etc. -PRIVATE_LIBS := $(FRAMEWORK) -$(info PRIVATE_LIBS = $(PRIVATE_LIBS)) +PRIVATE_INCLUDES := $(FRAMEWORK) +$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES)) # Eg: gen/com/android/app/R.java PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java @@ -42,56 +42,32 @@ PRIVATE_RESOURCE_TYPES := \ $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) $(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) -# Eg: drawable, drawable-xhdpi, layout -PRIVATE_NON_VALUE_RESOURCE_TYPES := $(filter-out values%,$(PRIVATE_RESOURCE_TYPES)) -$(info PRIVATE_NON_VALUE_RESOURCE_TYPES = $(PRIVATE_NON_VALUE_RESOURCE_TYPES)) - -# Eg: out/values-v4.table, out/drawable-xhdpi.table -PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.table,$(PRIVATE_RESOURCE_TYPES)) +# Eg: out/values-v4.apk, out/drawable-xhdpi.apk +PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) $(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) -# Eg: out/res/layout/main.xml, out/res/drawable/icon.png -PRIVATE_INTERMEDIATE_FILES := $(patsubst $(LOCAL_RESOURCE_DIR)/%,$(LOCAL_OUT)/res/%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES))) -$(info PRIVATE_INTERMEDIATE_FILES = $(PRIVATE_INTERMEDIATE_FILES)) - # Generates rules for collect phase. # $1: Resource type (values-v4) -# returns: out/values-v4.table: res/values-v4/styles.xml res/values-v4/colors.xml +# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml define make-collect-rule -$(LOCAL_OUT)/$1.table: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) - $(AAPT) collect --package $(LOCAL_PACKAGE) -o $$@ $$^ +$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) + $(AAPT) compile --package $(LOCAL_PACKAGE) --binding $(LOCAL_GEN) -o $$@ $$^ endef -# Collect: out/values-v4.table <- res/values-v4/styles.xml res/values-v4/colors.xml +# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml $(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) -# Link: out/resources.arsc <- out/values-v4.table out/drawable-v4.table -$(PRIVATE_ARSC): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) - $(AAPT) link --package $(LOCAL_PACKAGE) $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) - -# Compile Manifest: out/AndroidManifest.xml <- AndroidManifest.xml out/resources.arsc -$(LOCAL_OUT)/AndroidManifest.xml: AndroidManifest.xml $(PRIVATE_ARSC) $(PRIVATE_LIBS) - $(AAPT) manifest -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) AndroidManifest.xml - -# Generates rules for compile phase. -# $1: resource file (res/drawable/icon.png) -# returns: out/res/drawable/icon.png: res/drawable/icon.png out/resources.arsc -define make-compile-rule -$1: $(patsubst $(LOCAL_OUT)/res/%,$(LOCAL_RESOURCE_DIR)/%,$1) $(PRIVATE_ARSC) $(PRIVATE_LIBS) - $(AAPT) compile --package $(LOCAL_PACKAGE) -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) $$< -endef - -# Compile: out/res/drawable-xhdpi/icon.png <- res/drawable-xhdpi/icon.png -$(foreach f,$(PRIVATE_INTERMEDIATE_FILES),$(eval $(call make-compile-rule,$f))) +# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk +$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml + $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS) # R.java: gen/com/android/app/R.java <- out/resources.arsc # No action since R.java is generated when out/resources.arsc is. -$(PRIVATE_R_JAVA): $(PRIVATE_ARSC) +$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) # Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* -$(PRIVATE_APK_ALIGNED): $(PRIVATE_ARSC) $(PRIVATE_INTERMEDIATE_FILES) $(LOCAL_OUT)/AndroidManifest.xml - cd $(LOCAL_OUT); $(ZIP) $(patsubst $(LOCAL_OUT)/%,%,$(PRIVATE_APK_UNALIGNED)) $(patsubst $(LOCAL_OUT)/%,%,$^) - $(ZIPALIGN) $(PRIVATE_APK_UNALIGNED) $@ +$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) + $(ZIPALIGN) $< $@ # Create the out directory if needed. dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) @@ -100,7 +76,7 @@ dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) java: $(PRIVATE_R_JAVA) .PHONY: assemble -assemble: $(LOCAL_OUT)/package.apk +assemble: $(PRIVATE_APK_ALIGNED) .PHONY: all all: assemble java diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml new file mode 100644 index 0000000..c1612e5 --- /dev/null +++ b/tools/aapt2/data/lib/AndroidManifest.xml @@ -0,0 +1,3 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android.appcompat"/> diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile new file mode 100644 index 0000000..2897ff1 --- /dev/null +++ b/tools/aapt2/data/lib/Makefile @@ -0,0 +1,81 @@ +## +# Environment dependent variables +## + +AAPT := aapt2 +ZIPALIGN := zipalign 4 +FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk + +## +# Project depenedent variables +## + +LOCAL_PACKAGE := android.appcompat +LOCAL_RESOURCE_DIR := res +LOCAL_OUT := out +LOCAL_GEN := out/gen + +## +# AAPT2 custom rules. +## + +PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk +PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk + +# Eg: framework.apk, etc. +PRIVATE_LIBS := $(FRAMEWORK) +$(info PRIVATE_LIBS = $(PRIVATE_LIBS)) + +# Eg: gen/com/android/app/R.java +PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java +$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA)) + +# Eg: res/drawable/icon.png, res/values/styles.xml +PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f) +$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES)) + +# Eg: drawable, values, layouts +PRIVATE_RESOURCE_TYPES := \ + $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES)))) +$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES)) + +# Eg: out/values-v4.apk, out/drawable-xhdpi.apk +PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES)) +$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES)) + +# Generates rules for collect phase. +# $1: Resource type (values-v4) +# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml +define make-collect-rule +$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES)) + $(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^ +endef + +# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml +$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d))) + +# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk +$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml + $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) + +# R.java: gen/com/android/app/R.java <- out/resources.arsc +# No action since R.java is generated when out/resources.arsc is. +$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED) + +# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/* +$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED) + $(ZIPALIGN) $< $@ + +# Create the out directory if needed. +dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT)) + +.PHONY: java +java: $(PRIVATE_R_JAVA) + +.PHONY: assemble +assemble: $(PRIVATE_APK_ALIGNED) + +.PHONY: all +all: assemble java + +.DEFAULT_GOAL := all diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml new file mode 100644 index 0000000..adb5c4f --- /dev/null +++ b/tools/aapt2/data/lib/res/values/styles.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <style name="Platform.AppCompat" parent="@android:style/Theme"> + <item name="android:windowNoTitle">true</item> + </style> +</resources> diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml index 71ce388..c5dd276 100644 --- a/tools/aapt2/data/res/values/styles.xml +++ b/tools/aapt2/data/res/values/styles.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <style name="App" parent="android:Theme.Material"> + <style name="App" parent="android.appcompat:Platform.AppCompat"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> <item name="android:colorPrimaryDark">@color/primary_dark</item> diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot index a92405d..4741952 100644 --- a/tools/aapt2/process.dot +++ b/tools/aapt2/process.dot @@ -19,6 +19,13 @@ digraph aapt { res_layout_fr_main_xml [label="res/layout-fr/main.xml"]; res_values_fr_strings_xml [label="res/values-fr/strings.xml"]; + lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green]; + lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green]; + lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green]; + lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green]; + lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green]; + out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"]; + out_package -> package_default; out_fr_package -> package_fr; @@ -26,6 +33,7 @@ digraph aapt { package_default -> out_table_aligned; package_default -> out_res_layout_main_xml; package_default -> out_res_layout_v21_main_xml [color=red]; + package_default -> out_res_layout_lib_main_xml; package_fr [shape=box,label="Assemble",color=blue]; package_fr -> out_table_fr_aligned; @@ -44,6 +52,7 @@ digraph aapt { link_tables [shape=box,label="Link",color=blue]; link_tables -> out_values_table; link_tables -> out_layout_table; + link_tables -> lib_apk_resources_arsc; out_values_table -> compile_values; @@ -61,10 +70,11 @@ digraph aapt { link_fr_tables [shape=box,label="Link",color=blue]; link_fr_tables -> out_values_fr_table; link_fr_tables -> out_layout_fr_table; + link_fr_tables -> lib_apk_resources_arsc; out_values_fr_table -> compile_values_fr; - compile_values_fr [shape=box,label="Compile",color=blue]; + compile_values_fr [shape=box,label="Collect",color=blue]; compile_values_fr -> res_values_fr_strings_xml; out_layout_fr_table -> collect_xml_fr; @@ -89,4 +99,10 @@ digraph aapt { compile_res_layout_fr_main_xml -> res_layout_fr_main_xml; compile_res_layout_fr_main_xml -> out_table_fr_aligned; + + out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml; + + compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue]; + compile_res_layout_lib_main_xml -> out_table_aligned; + compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml; } |