blob: 20a9f417228c43c9c3d9aa98daa4627cca1363a9 [file] [log] [blame]
Pierre Lecesneff759e62017-02-01 00:29:25 +00001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "LoadedApk.h"
18
Pierre Lecesne2599aa42017-02-01 22:47:03 +000019#include "ResourceValues.h"
20#include "ValueVisitor.h"
Adam Lesinski46708052017-09-29 14:49:15 -070021#include "format/Archive.h"
22#include "format/binary/TableFlattener.h"
23#include "format/binary/XmlFlattener.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070024#include "format/proto/ProtoDeserialize.h"
Adam Lesinski00451162017-10-03 07:44:08 -070025#include "io/BigBufferStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070026#include "io/Util.h"
Shane Farmer3edd4722017-09-01 14:34:22 -070027#include "xml/XmlDom.h"
Pierre Lecesne2599aa42017-02-01 22:47:03 +000028
Adam Lesinski8780eb62017-10-31 17:44:39 -070029using ::aapt::io::IFile;
30using ::aapt::io::IFileCollection;
31using ::aapt::xml::XmlResource;
32using ::android::StringPiece;
33using ::std::unique_ptr;
34
Pierre Lecesneff759e62017-02-01 00:29:25 +000035namespace aapt {
36
Adam Lesinski8780eb62017-10-31 17:44:39 -070037std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
Pierre Lecesneff759e62017-02-01 00:29:25 +000038 Source source(path);
39 std::string error;
Adam Lesinski06460ef2017-03-14 18:52:13 -070040 std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
Adam Lesinski8780eb62017-10-31 17:44:39 -070041 if (apk == nullptr) {
42 diag->Error(DiagMessage(path) << "failed opening zip: " << error);
Pierre Lecesneff759e62017-02-01 00:29:25 +000043 return {};
44 }
45
Pierre Lecesnef267a402017-12-01 11:39:01 +000046 ApkFormat apkFormat = DetermineApkFormat(apk.get());
47 switch (apkFormat) {
48 case ApkFormat::kBinary:
49 return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
50 case ApkFormat::kProto:
51 return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
52 default:
53 diag->Error(DiagMessage(path) << "could not identify format of APK");
54 return {};
Adam Lesinski8780eb62017-10-31 17:44:39 -070055 }
Adam Lesinski8780eb62017-10-31 17:44:39 -070056}
57
58std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
59 const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
Tom Dobek725fb122017-12-08 14:19:01 +000060 std::unique_ptr<ResourceTable> table;
61
Adam Lesinski8780eb62017-10-31 17:44:39 -070062 io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +000063 if (table_file != nullptr) {
64 pb::ResourceTable pb_table;
65 std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
66 if (in == nullptr) {
67 diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
68 return {};
69 }
Pierre Lecesneff759e62017-02-01 00:29:25 +000070
Tom Dobek725fb122017-12-08 14:19:01 +000071 io::ZeroCopyInputAdaptor adaptor(in.get());
72 if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
73 diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
74 return {};
75 }
Adam Lesinski8780eb62017-10-31 17:44:39 -070076
Tom Dobek725fb122017-12-08 14:19:01 +000077 std::string error;
78 table = util::make_unique<ResourceTable>();
79 if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
80 diag->Error(DiagMessage(source)
81 << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
82 return {};
83 }
Adam Lesinski8780eb62017-10-31 17:44:39 -070084 }
85
86 io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
87 if (manifest_file == nullptr) {
88 diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
89 return {};
90 }
91
92 std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
93 if (manifest_in == nullptr) {
94 diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
95 return {};
96 }
97
98 pb::XmlNode pb_node;
99 io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
100 if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
101 diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
102 return {};
103 }
104
Tom Dobek725fb122017-12-08 14:19:01 +0000105 std::string error;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700106 std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
107 if (manifest == nullptr) {
108 diag->Error(DiagMessage(source)
109 << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
110 return {};
111 }
112 return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
113 std::move(manifest));
114}
115
116std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
117 const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
Tom Dobek725fb122017-12-08 14:19:01 +0000118 std::unique_ptr<ResourceTable> table;
119
Adam Lesinski8780eb62017-10-31 17:44:39 -0700120 io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +0000121 if (table_file != nullptr) {
122 table = util::make_unique<ResourceTable>();
123 std::unique_ptr<io::IData> data = table_file->OpenAsData();
124 if (data == nullptr) {
125 diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
126 return {};
127 }
128 BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
129 collection.get());
130 if (!parser.Parse()) {
131 return {};
132 }
Pierre Lecesneff759e62017-02-01 00:29:25 +0000133 }
Shane Farmer3edd4722017-09-01 14:34:22 -0700134
Adam Lesinski8780eb62017-10-31 17:44:39 -0700135 io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
136 if (manifest_file == nullptr) {
137 diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
138 return {};
139 }
140
141 std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
142 if (manifest_data == nullptr) {
143 diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
144 return {};
145 }
146
147 std::string error;
148 std::unique_ptr<xml::XmlResource> manifest =
149 xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
150 if (manifest == nullptr) {
151 diag->Error(DiagMessage(source)
152 << "failed to parse binary " << kAndroidManifestPath << ": " << error);
153 return {};
154 }
155 return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
156 std::move(manifest));
Pierre Lecesneff759e62017-02-01 00:29:25 +0000157}
158
Adam Lesinskid48944a2017-02-21 14:22:30 -0800159bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
160 IArchiveWriter* writer) {
Shane Farmer57669432017-06-19 12:52:04 -0700161 FilterChain empty;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700162 return WriteToArchive(context, table_.get(), options, &empty, writer);
Shane Farmer57669432017-06-19 12:52:04 -0700163}
164
Shane Farmer0a5b2012017-06-22 12:24:12 -0700165bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
166 const TableFlattenerOptions& options, FilterChain* filters,
Shane Farmer3edd4722017-09-01 14:34:22 -0700167 IArchiveWriter* writer, XmlResource* manifest) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000168 std::set<std::string> referenced_resources;
169 // List the files being referenced in the resource table.
Shane Farmer0a5b2012017-06-22 12:24:12 -0700170 for (auto& pkg : split_table->packages) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000171 for (auto& type : pkg->types) {
172 for (auto& entry : type->entries) {
173 for (auto& config_value : entry->values) {
174 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
175 if (file_ref) {
176 referenced_resources.insert(*file_ref->path);
177 }
178 }
179 }
180 }
181 }
182
183 std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
184 while (iterator->HasNext()) {
185 io::IFile* file = iterator->Next();
186
187 std::string path = file->GetSource().path;
188 // The name of the path has the format "<zip-file-name>@<path-to-file>".
Chih-Hung Hsieh4dc58122017-08-03 16:28:10 -0700189 path = path.substr(path.find('@') + 1);
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000190
191 // Skip resources that are not referenced if requested.
192 if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) {
193 if (context->IsVerbose()) {
194 context->GetDiagnostics()->Note(DiagMessage()
Pierre Lecesnefa131d52017-02-03 19:15:03 +0000195 << "Removing resource '" << path << "' from APK.");
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000196 }
197 continue;
198 }
199
Shane Farmer57669432017-06-19 12:52:04 -0700200 if (!filters->Keep(path)) {
201 if (context->IsVerbose()) {
202 context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
203 }
204 continue;
205 }
206
Adam Lesinski06460ef2017-03-14 18:52:13 -0700207 // The resource table needs to be re-serialized since it might have changed.
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000208 if (path == "resources.arsc") {
Adam Lesinski06460ef2017-03-14 18:52:13 -0700209 BigBuffer buffer(4096);
Adam Lesinskic8f71aa2017-02-08 07:03:50 -0800210 // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
211 // with sparse entries) b/35389232.
Adam Lesinskid48944a2017-02-21 14:22:30 -0800212 TableFlattener flattener(options, &buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700213 if (!flattener.Consume(context, split_table)) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000214 return false;
215 }
216
Adam Lesinski06460ef2017-03-14 18:52:13 -0700217 io::BigBufferInputStream input_stream(&buffer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700218 if (!io::CopyInputStreamToArchive(context, &input_stream, path, ArchiveEntry::kAlign,
219 writer)) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000220 return false;
221 }
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000222
Shane Farmer3edd4722017-09-01 14:34:22 -0700223 } else if (manifest != nullptr && path == "AndroidManifest.xml") {
224 BigBuffer buffer(8192);
225 XmlFlattener xml_flattener(&buffer, {});
226 if (!xml_flattener.Consume(context, manifest)) {
227 context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
228 return false;
229 }
230
231 uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
232 io::BigBufferInputStream manifest_buffer_in(&buffer);
233 if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
234 writer)) {
235 return false;
236 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700237 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000238 if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700239 return false;
240 }
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000241 }
242 }
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000243 return true;
244}
245
Pierre Lecesnef267a402017-12-01 11:39:01 +0000246ApkFormat LoadedApk::DetermineApkFormat(io::IFileCollection* apk) {
247 if (apk->FindFile("resources.arsc") != nullptr) {
248 return ApkFormat::kBinary;
249 } else if (apk->FindFile("resources.pb") != nullptr) {
250 return ApkFormat::kProto;
251 } else {
252 // If the resource table is not present, attempt to read the manifest.
253 io::IFile* manifest_file = apk->FindFile(kAndroidManifestPath);
254 if (manifest_file == nullptr) {
255 return ApkFormat::kUnknown;
256 }
257
258 // First try in proto format.
259 std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
260 if (manifest_in != nullptr) {
261 pb::XmlNode pb_node;
262 io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
263 if (pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
264 return ApkFormat::kProto;
265 }
266 }
267
268 // If it didn't work, try in binary format.
269 std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
270 if (manifest_data != nullptr) {
271 std::string error;
272 std::unique_ptr<xml::XmlResource> manifest =
273 xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
274 if (manifest != nullptr) {
275 return ApkFormat::kBinary;
276 }
277 }
278
279 return ApkFormat::kUnknown;
280 }
281}
282
Pierre Lecesneff759e62017-02-01 00:29:25 +0000283} // namespace aapt