You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
358 lines
13 KiB
358 lines
13 KiB
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "LoadedApk.h"
|
|
|
|
#include "ResourceValues.h"
|
|
#include "ValueVisitor.h"
|
|
#include "format/Archive.h"
|
|
#include "format/binary/TableFlattener.h"
|
|
#include "format/binary/XmlFlattener.h"
|
|
#include "format/proto/ProtoDeserialize.h"
|
|
#include "format/proto/ProtoSerialize.h"
|
|
#include "io/BigBufferStream.h"
|
|
#include "io/Util.h"
|
|
#include "xml/XmlDom.h"
|
|
|
|
using ::aapt::io::IFile;
|
|
using ::aapt::io::IFileCollection;
|
|
using ::aapt::xml::XmlResource;
|
|
using ::android::StringPiece;
|
|
using ::std::unique_ptr;
|
|
|
|
namespace aapt {
|
|
|
|
static ApkFormat DetermineApkFormat(io::IFileCollection* apk) {
|
|
if (apk->FindFile(kApkResourceTablePath) != nullptr) {
|
|
return ApkFormat::kBinary;
|
|
} else if (apk->FindFile(kProtoResourceTablePath) != nullptr) {
|
|
return ApkFormat::kProto;
|
|
} else {
|
|
// If the resource table is not present, attempt to read the manifest.
|
|
io::IFile* manifest_file = apk->FindFile(kAndroidManifestPath);
|
|
if (manifest_file == nullptr) {
|
|
return ApkFormat::kUnknown;
|
|
}
|
|
|
|
// First try in proto format.
|
|
std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
|
|
if (manifest_in != nullptr) {
|
|
pb::XmlNode pb_node;
|
|
io::ProtoInputStreamReader proto_reader(manifest_in.get());
|
|
if (proto_reader.ReadMessage(&pb_node)) {
|
|
return ApkFormat::kProto;
|
|
}
|
|
}
|
|
|
|
// If it didn't work, try in binary format.
|
|
std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
|
|
if (manifest_data != nullptr) {
|
|
std::string error;
|
|
std::unique_ptr<xml::XmlResource> manifest =
|
|
xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
|
|
if (manifest != nullptr) {
|
|
return ApkFormat::kBinary;
|
|
}
|
|
}
|
|
|
|
return ApkFormat::kUnknown;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
|
|
Source source(path);
|
|
std::string error;
|
|
std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
|
|
if (apk == nullptr) {
|
|
diag->Error(DiagMessage(path) << "failed opening zip: " << error);
|
|
return {};
|
|
}
|
|
|
|
ApkFormat apkFormat = DetermineApkFormat(apk.get());
|
|
switch (apkFormat) {
|
|
case ApkFormat::kBinary:
|
|
return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
|
|
case ApkFormat::kProto:
|
|
return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
|
|
default:
|
|
diag->Error(DiagMessage(path) << "could not identify format of APK");
|
|
return {};
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
|
|
const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
|
|
std::unique_ptr<ResourceTable> table;
|
|
|
|
io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
|
|
if (table_file != nullptr) {
|
|
pb::ResourceTable pb_table;
|
|
std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
|
|
if (in == nullptr) {
|
|
diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
|
|
return {};
|
|
}
|
|
|
|
io::ProtoInputStreamReader proto_reader(in.get());
|
|
if (!proto_reader.ReadMessage(&pb_table)) {
|
|
diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
|
|
return {};
|
|
}
|
|
|
|
std::string error;
|
|
table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
|
|
if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
|
|
diag->Error(DiagMessage(source)
|
|
<< "failed to deserialize " << kProtoResourceTablePath << ": " << error);
|
|
return {};
|
|
}
|
|
}
|
|
|
|
io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
|
|
if (manifest_file == nullptr) {
|
|
diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
|
|
return {};
|
|
}
|
|
|
|
std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
|
|
if (manifest_in == nullptr) {
|
|
diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
|
|
return {};
|
|
}
|
|
|
|
pb::XmlNode pb_node;
|
|
io::ProtoInputStreamReader proto_reader(manifest_in.get());
|
|
if (!proto_reader.ReadMessage(&pb_node)) {
|
|
diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
|
|
return {};
|
|
}
|
|
|
|
std::string error;
|
|
std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
|
|
if (manifest == nullptr) {
|
|
diag->Error(DiagMessage(source)
|
|
<< "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
|
|
return {};
|
|
}
|
|
return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
|
|
std::move(manifest), ApkFormat::kProto);
|
|
}
|
|
|
|
std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
|
|
const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
|
|
std::unique_ptr<ResourceTable> table;
|
|
|
|
io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
|
|
if (table_file != nullptr) {
|
|
table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
|
|
std::unique_ptr<io::IData> data = table_file->OpenAsData();
|
|
if (data == nullptr) {
|
|
diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
|
|
return {};
|
|
}
|
|
BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
|
|
collection.get());
|
|
if (!parser.Parse()) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
|
|
if (manifest_file == nullptr) {
|
|
diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
|
|
return {};
|
|
}
|
|
|
|
std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
|
|
if (manifest_data == nullptr) {
|
|
diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
|
|
return {};
|
|
}
|
|
|
|
std::string error;
|
|
std::unique_ptr<xml::XmlResource> manifest =
|
|
xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
|
|
if (manifest == nullptr) {
|
|
diag->Error(DiagMessage(source)
|
|
<< "failed to parse binary " << kAndroidManifestPath << ": " << error);
|
|
return {};
|
|
}
|
|
return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
|
|
std::move(manifest), ApkFormat::kBinary);
|
|
}
|
|
|
|
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
|
|
IArchiveWriter* writer) {
|
|
FilterChain empty;
|
|
return WriteToArchive(context, table_.get(), options, &empty, writer);
|
|
}
|
|
|
|
bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
|
|
const TableFlattenerOptions& options, FilterChain* filters,
|
|
IArchiveWriter* writer, XmlResource* manifest) {
|
|
std::set<std::string> referenced_resources;
|
|
// List the files being referenced in the resource table.
|
|
for (auto& pkg : split_table->packages) {
|
|
for (auto& type : pkg->types) {
|
|
for (auto& entry : type->entries) {
|
|
for (auto& config_value : entry->values) {
|
|
FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
|
|
if (file_ref) {
|
|
referenced_resources.insert(*file_ref->path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
|
|
while (iterator->HasNext()) {
|
|
io::IFile* file = iterator->Next();
|
|
std::string path = file->GetSource().path;
|
|
|
|
std::string output_path = path;
|
|
bool is_resource = path.find("res/") == 0;
|
|
if (is_resource) {
|
|
auto it = options.shortened_path_map.find(path);
|
|
if (it != options.shortened_path_map.end()) {
|
|
output_path = it->second;
|
|
}
|
|
}
|
|
|
|
// Skip resources that are not referenced if requested.
|
|
if (is_resource && referenced_resources.find(output_path) == referenced_resources.end()) {
|
|
if (context->IsVerbose()) {
|
|
context->GetDiagnostics()->Note(DiagMessage()
|
|
<< "Removing resource '" << path << "' from APK.");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!filters->Keep(path)) {
|
|
if (context->IsVerbose()) {
|
|
context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// The resource table needs to be re-serialized since it might have changed.
|
|
if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) {
|
|
BigBuffer buffer(4096);
|
|
// TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
|
|
// with sparse entries) b/35389232.
|
|
TableFlattener flattener(options, &buffer);
|
|
if (!flattener.Consume(context, split_table)) {
|
|
return false;
|
|
}
|
|
|
|
io::BigBufferInputStream input_stream(&buffer);
|
|
if (!io::CopyInputStreamToArchive(context,
|
|
&input_stream,
|
|
path,
|
|
ArchiveEntry::kAlign,
|
|
writer)) {
|
|
return false;
|
|
}
|
|
} else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
|
|
SerializeTableOptions proto_serialize_options;
|
|
proto_serialize_options.collapse_key_stringpool =
|
|
options.collapse_key_stringpool;
|
|
proto_serialize_options.name_collapse_exemptions =
|
|
options.name_collapse_exemptions;
|
|
pb::ResourceTable pb_table;
|
|
SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(),
|
|
proto_serialize_options);
|
|
if (!io::CopyProtoToArchive(context,
|
|
&pb_table,
|
|
path,
|
|
ArchiveEntry::kAlign, writer)) {
|
|
return false;
|
|
}
|
|
} else if (manifest != nullptr && path == "AndroidManifest.xml") {
|
|
BigBuffer buffer(8192);
|
|
XmlFlattenerOptions xml_flattener_options;
|
|
xml_flattener_options.use_utf16 = true;
|
|
XmlFlattener xml_flattener(&buffer, xml_flattener_options);
|
|
if (!xml_flattener.Consume(context, manifest)) {
|
|
context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
|
|
return false;
|
|
}
|
|
|
|
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
|
|
io::BigBufferInputStream manifest_buffer_in(&buffer);
|
|
if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
|
|
writer)) {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (!io::CopyFileToArchivePreserveCompression(
|
|
context, file, output_path, writer)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<xml::XmlResource> LoadedApk::LoadXml(const std::string& file_path,
|
|
IDiagnostics* diag) const {
|
|
io::IFile* file = apk_->FindFile(file_path);
|
|
if (file == nullptr) {
|
|
diag->Error(DiagMessage() << "failed to find file");
|
|
return nullptr;
|
|
}
|
|
|
|
std::unique_ptr<xml::XmlResource> doc;
|
|
if (format_ == ApkFormat::kProto) {
|
|
std::unique_ptr<io::InputStream> in = file->OpenInputStream();
|
|
if (!in) {
|
|
diag->Error(DiagMessage() << "failed to open file");
|
|
return nullptr;
|
|
}
|
|
|
|
pb::XmlNode pb_node;
|
|
io::ProtoInputStreamReader proto_reader(in.get());
|
|
if (!proto_reader.ReadMessage(&pb_node)) {
|
|
diag->Error(DiagMessage() << "failed to parse file as proto XML");
|
|
return nullptr;
|
|
}
|
|
|
|
std::string err;
|
|
doc = DeserializeXmlResourceFromPb(pb_node, &err);
|
|
if (!doc) {
|
|
diag->Error(DiagMessage() << "failed to deserialize proto XML: " << err);
|
|
return nullptr;
|
|
}
|
|
} else if (format_ == ApkFormat::kBinary) {
|
|
std::unique_ptr<io::IData> data = file->OpenAsData();
|
|
if (!data) {
|
|
diag->Error(DiagMessage() << "failed to open file");
|
|
return nullptr;
|
|
}
|
|
|
|
std::string err;
|
|
doc = xml::Inflate(data->data(), data->size(), &err);
|
|
if (!doc) {
|
|
diag->Error(DiagMessage() << "failed to parse file as binary XML: " << err);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return doc;
|
|
}
|
|
|
|
} // namespace aapt
|