blob: 9b9cde2f37da1de6538da6c4dc94df040bd2a7a0 [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"
corysmith@google.comf7db43e2018-03-13 12:04:10 -040025#include "format/proto/ProtoSerialize.h"
Adam Lesinski00451162017-10-03 07:44:08 -070026#include "io/BigBufferStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070027#include "io/Util.h"
Shane Farmer3edd4722017-09-01 14:34:22 -070028#include "xml/XmlDom.h"
Pierre Lecesne2599aa42017-02-01 22:47:03 +000029
Adam Lesinski8780eb62017-10-31 17:44:39 -070030using ::aapt::io::IFile;
31using ::aapt::io::IFileCollection;
32using ::aapt::xml::XmlResource;
33using ::android::StringPiece;
34using ::std::unique_ptr;
35
Pierre Lecesneff759e62017-02-01 00:29:25 +000036namespace aapt {
37
Ryan Mitchellec74f2f2018-10-17 09:30:01 -070038static ApkFormat DetermineApkFormat(io::IFileCollection* apk) {
39 if (apk->FindFile(kApkResourceTablePath) != nullptr) {
40 return ApkFormat::kBinary;
41 } else if (apk->FindFile(kProtoResourceTablePath) != nullptr) {
42 return ApkFormat::kProto;
43 } else {
44 // If the resource table is not present, attempt to read the manifest.
45 io::IFile* manifest_file = apk->FindFile(kAndroidManifestPath);
46 if (manifest_file == nullptr) {
47 return ApkFormat::kUnknown;
48 }
49
50 // First try in proto format.
51 std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
52 if (manifest_in != nullptr) {
53 pb::XmlNode pb_node;
54 io::ProtoInputStreamReader proto_reader(manifest_in.get());
55 if (proto_reader.ReadMessage(&pb_node)) {
56 return ApkFormat::kProto;
57 }
58 }
59
60 // If it didn't work, try in binary format.
61 std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
62 if (manifest_data != nullptr) {
63 std::string error;
64 std::unique_ptr<xml::XmlResource> manifest =
65 xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
66 if (manifest != nullptr) {
67 return ApkFormat::kBinary;
68 }
69 }
70
71 return ApkFormat::kUnknown;
72 }
73}
74
Jeremy Meyer56f36e82022-05-20 20:35:42 +000075std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path,
76 android::IDiagnostics* diag) {
77 android::Source source(path);
Pierre Lecesneff759e62017-02-01 00:29:25 +000078 std::string error;
Adam Lesinski06460ef2017-03-14 18:52:13 -070079 std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
Adam Lesinski8780eb62017-10-31 17:44:39 -070080 if (apk == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +000081 diag->Error(android::DiagMessage(path) << "failed opening zip: " << error);
Pierre Lecesneff759e62017-02-01 00:29:25 +000082 return {};
83 }
84
Pierre Lecesnef267a402017-12-01 11:39:01 +000085 ApkFormat apkFormat = DetermineApkFormat(apk.get());
86 switch (apkFormat) {
87 case ApkFormat::kBinary:
88 return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
89 case ApkFormat::kProto:
90 return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
91 default:
Jeremy Meyer56f36e82022-05-20 20:35:42 +000092 diag->Error(android::DiagMessage(path) << "could not identify format of APK");
Pierre Lecesnef267a402017-12-01 11:39:01 +000093 return {};
Adam Lesinski8780eb62017-10-31 17:44:39 -070094 }
Adam Lesinski8780eb62017-10-31 17:44:39 -070095}
96
97std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
Jeremy Meyer56f36e82022-05-20 20:35:42 +000098 const android::Source& source, unique_ptr<io::IFileCollection> collection,
99 android::IDiagnostics* diag) {
Tom Dobek725fb122017-12-08 14:19:01 +0000100 std::unique_ptr<ResourceTable> table;
101
Adam Lesinski8780eb62017-10-31 17:44:39 -0700102 io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +0000103 if (table_file != nullptr) {
104 pb::ResourceTable pb_table;
105 std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
106 if (in == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000107 diag->Error(android::DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +0000108 return {};
109 }
Pierre Lecesneff759e62017-02-01 00:29:25 +0000110
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700111 io::ProtoInputStreamReader proto_reader(in.get());
112 if (!proto_reader.ReadMessage(&pb_table)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000113 diag->Error(android::DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +0000114 return {};
115 }
Adam Lesinski8780eb62017-10-31 17:44:39 -0700116
Tom Dobek725fb122017-12-08 14:19:01 +0000117 std::string error;
Ryan Mitchell9634efb2021-03-19 14:53:17 -0700118 table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
Tom Dobek725fb122017-12-08 14:19:01 +0000119 if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000120 diag->Error(android::DiagMessage(source)
Tom Dobek725fb122017-12-08 14:19:01 +0000121 << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
122 return {};
123 }
Adam Lesinski8780eb62017-10-31 17:44:39 -0700124 }
125
126 io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
127 if (manifest_file == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000128 diag->Error(android::DiagMessage(source) << "failed to find " << kAndroidManifestPath);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700129 return {};
130 }
131
132 std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
133 if (manifest_in == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000134 diag->Error(android::DiagMessage(source) << "failed to open " << kAndroidManifestPath);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700135 return {};
136 }
137
138 pb::XmlNode pb_node;
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700139 io::ProtoInputStreamReader proto_reader(manifest_in.get());
140 if (!proto_reader.ReadMessage(&pb_node)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000141 diag->Error(android::DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700142 return {};
143 }
144
Tom Dobek725fb122017-12-08 14:19:01 +0000145 std::string error;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700146 std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
147 if (manifest == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000148 diag->Error(android::DiagMessage(source)
Adam Lesinski8780eb62017-10-31 17:44:39 -0700149 << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
150 return {};
151 }
152 return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400153 std::move(manifest), ApkFormat::kProto);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700154}
155
156std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000157 const android::Source& source, unique_ptr<io::IFileCollection> collection,
158 android::IDiagnostics* diag) {
Tom Dobek725fb122017-12-08 14:19:01 +0000159 std::unique_ptr<ResourceTable> table;
160
Adam Lesinski8780eb62017-10-31 17:44:39 -0700161 io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +0000162 if (table_file != nullptr) {
Ryan Mitchell9634efb2021-03-19 14:53:17 -0700163 table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
Tom Dobek725fb122017-12-08 14:19:01 +0000164 std::unique_ptr<io::IData> data = table_file->OpenAsData();
165 if (data == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000166 diag->Error(android::DiagMessage(source) << "failed to open " << kApkResourceTablePath);
Tom Dobek725fb122017-12-08 14:19:01 +0000167 return {};
168 }
169 BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
170 collection.get());
171 if (!parser.Parse()) {
172 return {};
173 }
Pierre Lecesneff759e62017-02-01 00:29:25 +0000174 }
Shane Farmer3edd4722017-09-01 14:34:22 -0700175
Adam Lesinski8780eb62017-10-31 17:44:39 -0700176 io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
177 if (manifest_file == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000178 diag->Error(android::DiagMessage(source) << "failed to find " << kAndroidManifestPath);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700179 return {};
180 }
181
182 std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
183 if (manifest_data == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000184 diag->Error(android::DiagMessage(source) << "failed to open " << kAndroidManifestPath);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700185 return {};
186 }
187
188 std::string error;
189 std::unique_ptr<xml::XmlResource> manifest =
190 xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
191 if (manifest == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000192 diag->Error(android::DiagMessage(source)
Adam Lesinski8780eb62017-10-31 17:44:39 -0700193 << "failed to parse binary " << kAndroidManifestPath << ": " << error);
194 return {};
195 }
196 return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400197 std::move(manifest), ApkFormat::kBinary);
Pierre Lecesneff759e62017-02-01 00:29:25 +0000198}
199
Adam Lesinskid48944a2017-02-21 14:22:30 -0800200bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
201 IArchiveWriter* writer) {
Shane Farmer57669432017-06-19 12:52:04 -0700202 FilterChain empty;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700203 return WriteToArchive(context, table_.get(), options, &empty, writer);
Shane Farmer57669432017-06-19 12:52:04 -0700204}
205
Shane Farmer0a5b2012017-06-22 12:24:12 -0700206bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
207 const TableFlattenerOptions& options, FilterChain* filters,
Shane Farmer3edd4722017-09-01 14:34:22 -0700208 IArchiveWriter* writer, XmlResource* manifest) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000209 std::set<std::string> referenced_resources;
210 // List the files being referenced in the resource table.
Shane Farmer0a5b2012017-06-22 12:24:12 -0700211 for (auto& pkg : split_table->packages) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000212 for (auto& type : pkg->types) {
213 for (auto& entry : type->entries) {
214 for (auto& config_value : entry->values) {
215 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
216 if (file_ref) {
217 referenced_resources.insert(*file_ref->path);
218 }
219 }
220 }
221 }
222 }
223
224 std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
225 while (iterator->HasNext()) {
226 io::IFile* file = iterator->Next();
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000227 std::string path = file->GetSource().path;
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000228
Mohamed Heikalc7694032018-11-07 16:49:02 -0500229 std::string output_path = path;
230 bool is_resource = path.find("res/") == 0;
231 if (is_resource) {
232 auto it = options.shortened_path_map.find(path);
233 if (it != options.shortened_path_map.end()) {
234 output_path = it->second;
235 }
236 }
237
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000238 // Skip resources that are not referenced if requested.
Mohamed Heikalc7694032018-11-07 16:49:02 -0500239 if (is_resource && referenced_resources.find(output_path) == referenced_resources.end()) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000240 if (context->IsVerbose()) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000241 context->GetDiagnostics()->Note(android::DiagMessage()
Pierre Lecesnefa131d52017-02-03 19:15:03 +0000242 << "Removing resource '" << path << "' from APK.");
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000243 }
244 continue;
245 }
246
Shane Farmer57669432017-06-19 12:52:04 -0700247 if (!filters->Keep(path)) {
248 if (context->IsVerbose()) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000249 context->GetDiagnostics()->Note(android::DiagMessage()
250 << "Filtered '" << path << "' from APK.");
Shane Farmer57669432017-06-19 12:52:04 -0700251 }
252 continue;
253 }
254
Adam Lesinski06460ef2017-03-14 18:52:13 -0700255 // The resource table needs to be re-serialized since it might have changed.
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400256 if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000257 android::BigBuffer buffer(4096);
Adam Lesinskic8f71aa2017-02-08 07:03:50 -0800258 // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
259 // with sparse entries) b/35389232.
Adam Lesinskid48944a2017-02-21 14:22:30 -0800260 TableFlattener flattener(options, &buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700261 if (!flattener.Consume(context, split_table)) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000262 return false;
263 }
264
Adam Lesinski06460ef2017-03-14 18:52:13 -0700265 io::BigBufferInputStream input_stream(&buffer);
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400266 if (!io::CopyInputStreamToArchive(context,
267 &input_stream,
268 path,
269 ArchiveEntry::kAlign,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700270 writer)) {
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000271 return false;
272 }
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400273 } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
Mohamed Heikale0388de2020-03-18 13:19:57 -0400274 SerializeTableOptions proto_serialize_options;
275 proto_serialize_options.collapse_key_stringpool =
276 options.collapse_key_stringpool;
277 proto_serialize_options.name_collapse_exemptions =
278 options.name_collapse_exemptions;
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400279 pb::ResourceTable pb_table;
Mohamed Heikale0388de2020-03-18 13:19:57 -0400280 SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(),
281 proto_serialize_options);
corysmith@google.comf7db43e2018-03-13 12:04:10 -0400282 if (!io::CopyProtoToArchive(context,
283 &pb_table,
284 path,
285 ArchiveEntry::kAlign, writer)) {
286 return false;
287 }
Shane Farmer3edd4722017-09-01 14:34:22 -0700288 } else if (manifest != nullptr && path == "AndroidManifest.xml") {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000289 android::BigBuffer buffer(8192);
Shane Farmerd05b9132018-02-14 15:40:35 -0800290 XmlFlattenerOptions xml_flattener_options;
291 xml_flattener_options.use_utf16 = true;
292 XmlFlattener xml_flattener(&buffer, xml_flattener_options);
Shane Farmer3edd4722017-09-01 14:34:22 -0700293 if (!xml_flattener.Consume(context, manifest)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000294 context->GetDiagnostics()->Error(android::DiagMessage(path) << "flattening failed");
Shane Farmer3edd4722017-09-01 14:34:22 -0700295 return false;
296 }
297
298 uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
299 io::BigBufferInputStream manifest_buffer_in(&buffer);
300 if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
301 writer)) {
302 return false;
303 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700304 } else {
Mohamed Heikalc7694032018-11-07 16:49:02 -0500305 if (!io::CopyFileToArchivePreserveCompression(
306 context, file, output_path, writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700307 return false;
308 }
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000309 }
310 }
Pierre Lecesne2599aa42017-02-01 22:47:03 +0000311 return true;
312}
313
Ryan Mitchell5d275512018-07-19 14:29:00 -0700314std::unique_ptr<xml::XmlResource> LoadedApk::LoadXml(const std::string& file_path,
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000315 android::IDiagnostics* diag) const {
Ryan Mitchell5d275512018-07-19 14:29:00 -0700316 io::IFile* file = apk_->FindFile(file_path);
317 if (file == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000318 diag->Error(android::DiagMessage() << "failed to find file");
Ryan Mitchell5d275512018-07-19 14:29:00 -0700319 return nullptr;
320 }
321
322 std::unique_ptr<xml::XmlResource> doc;
323 if (format_ == ApkFormat::kProto) {
324 std::unique_ptr<io::InputStream> in = file->OpenInputStream();
325 if (!in) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000326 diag->Error(android::DiagMessage() << "failed to open file");
Ryan Mitchell5d275512018-07-19 14:29:00 -0700327 return nullptr;
328 }
329
Ryan Mitchell5d275512018-07-19 14:29:00 -0700330 pb::XmlNode pb_node;
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700331 io::ProtoInputStreamReader proto_reader(in.get());
332 if (!proto_reader.ReadMessage(&pb_node)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000333 diag->Error(android::DiagMessage() << "failed to parse file as proto XML");
Ryan Mitchell5d275512018-07-19 14:29:00 -0700334 return nullptr;
335 }
336
337 std::string err;
338 doc = DeserializeXmlResourceFromPb(pb_node, &err);
339 if (!doc) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000340 diag->Error(android::DiagMessage() << "failed to deserialize proto XML: " << err);
Ryan Mitchell5d275512018-07-19 14:29:00 -0700341 return nullptr;
342 }
343 } else if (format_ == ApkFormat::kBinary) {
344 std::unique_ptr<io::IData> data = file->OpenAsData();
345 if (!data) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000346 diag->Error(android::DiagMessage() << "failed to open file");
Ryan Mitchell5d275512018-07-19 14:29:00 -0700347 return nullptr;
348 }
349
350 std::string err;
351 doc = xml::Inflate(data->data(), data->size(), &err);
352 if (!doc) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000353 diag->Error(android::DiagMessage() << "failed to parse file as binary XML: " << err);
Ryan Mitchell5d275512018-07-19 14:29:00 -0700354 return nullptr;
355 }
356 }
357
358 return doc;
359}
360
Pierre Lecesneff759e62017-02-01 00:29:25 +0000361} // namespace aapt