1 /*
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
19 #include "ResourceValues.h"
20 #include "ValueVisitor.h"
21 #include "format/Archive.h"
22 #include "format/binary/TableFlattener.h"
23 #include "format/binary/XmlFlattener.h"
24 #include "format/proto/ProtoDeserialize.h"
25 #include "format/proto/ProtoSerialize.h"
26 #include "io/BigBufferStream.h"
27 #include "io/Util.h"
28 #include "xml/XmlDom.h"
29
30 using ::aapt::io::IFile;
31 using ::aapt::io::IFileCollection;
32 using ::aapt::xml::XmlResource;
33 using ::android::StringPiece;
34 using ::std::unique_ptr;
35
36 namespace aapt {
37
DetermineApkFormat(io::IFileCollection * apk)38 static 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
LoadApkFromPath(const StringPiece & path,IDiagnostics * diag)75 std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
76 Source source(path);
77 std::string error;
78 std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
79 if (apk == nullptr) {
80 diag->Error(DiagMessage(path) << "failed opening zip: " << error);
81 return {};
82 }
83
84 ApkFormat apkFormat = DetermineApkFormat(apk.get());
85 switch (apkFormat) {
86 case ApkFormat::kBinary:
87 return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
88 case ApkFormat::kProto:
89 return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
90 default:
91 diag->Error(DiagMessage(path) << "could not identify format of APK");
92 return {};
93 }
94 }
95
LoadProtoApkFromFileCollection(const Source & source,unique_ptr<io::IFileCollection> collection,IDiagnostics * diag)96 std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
97 const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
98 std::unique_ptr<ResourceTable> table;
99
100 io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
101 if (table_file != nullptr) {
102 pb::ResourceTable pb_table;
103 std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
104 if (in == nullptr) {
105 diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
106 return {};
107 }
108
109 io::ProtoInputStreamReader proto_reader(in.get());
110 if (!proto_reader.ReadMessage(&pb_table)) {
111 diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
112 return {};
113 }
114
115 std::string error;
116 table = util::make_unique<ResourceTable>(/** validate_resources **/ false);
117 if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
118 diag->Error(DiagMessage(source)
119 << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
120 return {};
121 }
122 }
123
124 io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
125 if (manifest_file == nullptr) {
126 diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
127 return {};
128 }
129
130 std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
131 if (manifest_in == nullptr) {
132 diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
133 return {};
134 }
135
136 pb::XmlNode pb_node;
137 io::ProtoInputStreamReader proto_reader(manifest_in.get());
138 if (!proto_reader.ReadMessage(&pb_node)) {
139 diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
140 return {};
141 }
142
143 std::string error;
144 std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
145 if (manifest == nullptr) {
146 diag->Error(DiagMessage(source)
147 << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
148 return {};
149 }
150 return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
151 std::move(manifest), ApkFormat::kProto);
152 }
153
LoadBinaryApkFromFileCollection(const Source & source,unique_ptr<io::IFileCollection> collection,IDiagnostics * diag)154 std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
155 const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
156 std::unique_ptr<ResourceTable> table;
157
158 io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
159 if (table_file != nullptr) {
160 table = util::make_unique<ResourceTable>(/** validate_resources **/ false);
161 std::unique_ptr<io::IData> data = table_file->OpenAsData();
162 if (data == nullptr) {
163 diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
164 return {};
165 }
166 BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
167 collection.get());
168 if (!parser.Parse()) {
169 return {};
170 }
171 }
172
173 io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
174 if (manifest_file == nullptr) {
175 diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
176 return {};
177 }
178
179 std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
180 if (manifest_data == nullptr) {
181 diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
182 return {};
183 }
184
185 std::string error;
186 std::unique_ptr<xml::XmlResource> manifest =
187 xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
188 if (manifest == nullptr) {
189 diag->Error(DiagMessage(source)
190 << "failed to parse binary " << kAndroidManifestPath << ": " << error);
191 return {};
192 }
193 return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
194 std::move(manifest), ApkFormat::kBinary);
195 }
196
WriteToArchive(IAaptContext * context,const TableFlattenerOptions & options,IArchiveWriter * writer)197 bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
198 IArchiveWriter* writer) {
199 FilterChain empty;
200 return WriteToArchive(context, table_.get(), options, &empty, writer);
201 }
202
WriteToArchive(IAaptContext * context,ResourceTable * split_table,const TableFlattenerOptions & options,FilterChain * filters,IArchiveWriter * writer,XmlResource * manifest)203 bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
204 const TableFlattenerOptions& options, FilterChain* filters,
205 IArchiveWriter* writer, XmlResource* manifest) {
206 std::set<std::string> referenced_resources;
207 // List the files being referenced in the resource table.
208 for (auto& pkg : split_table->packages) {
209 for (auto& type : pkg->types) {
210 for (auto& entry : type->entries) {
211 for (auto& config_value : entry->values) {
212 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
213 if (file_ref) {
214 referenced_resources.insert(*file_ref->path);
215 }
216 }
217 }
218 }
219 }
220
221 std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
222 while (iterator->HasNext()) {
223 io::IFile* file = iterator->Next();
224 std::string path = file->GetSource().path;
225
226 std::string output_path = path;
227 bool is_resource = path.find("res/") == 0;
228 if (is_resource) {
229 auto it = options.shortened_path_map.find(path);
230 if (it != options.shortened_path_map.end()) {
231 output_path = it->second;
232 }
233 }
234
235 // Skip resources that are not referenced if requested.
236 if (is_resource && referenced_resources.find(output_path) == referenced_resources.end()) {
237 if (context->IsVerbose()) {
238 context->GetDiagnostics()->Note(DiagMessage()
239 << "Removing resource '" << path << "' from APK.");
240 }
241 continue;
242 }
243
244 if (!filters->Keep(path)) {
245 if (context->IsVerbose()) {
246 context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
247 }
248 continue;
249 }
250
251 // The resource table needs to be re-serialized since it might have changed.
252 if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) {
253 BigBuffer buffer(4096);
254 // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
255 // with sparse entries) b/35389232.
256 TableFlattener flattener(options, &buffer);
257 if (!flattener.Consume(context, split_table)) {
258 return false;
259 }
260
261 io::BigBufferInputStream input_stream(&buffer);
262 if (!io::CopyInputStreamToArchive(context,
263 &input_stream,
264 path,
265 ArchiveEntry::kAlign,
266 writer)) {
267 return false;
268 }
269 } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
270 pb::ResourceTable pb_table;
271 SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics());
272 if (!io::CopyProtoToArchive(context,
273 &pb_table,
274 path,
275 ArchiveEntry::kAlign, writer)) {
276 return false;
277 }
278 } else if (manifest != nullptr && path == "AndroidManifest.xml") {
279 BigBuffer buffer(8192);
280 XmlFlattenerOptions xml_flattener_options;
281 xml_flattener_options.use_utf16 = true;
282 XmlFlattener xml_flattener(&buffer, xml_flattener_options);
283 if (!xml_flattener.Consume(context, manifest)) {
284 context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
285 return false;
286 }
287
288 uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
289 io::BigBufferInputStream manifest_buffer_in(&buffer);
290 if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
291 writer)) {
292 return false;
293 }
294 } else {
295 if (!io::CopyFileToArchivePreserveCompression(
296 context, file, output_path, writer)) {
297 return false;
298 }
299 }
300 }
301 return true;
302 }
303
LoadXml(const std::string & file_path,IDiagnostics * diag) const304 std::unique_ptr<xml::XmlResource> LoadedApk::LoadXml(const std::string& file_path,
305 IDiagnostics* diag) const {
306 io::IFile* file = apk_->FindFile(file_path);
307 if (file == nullptr) {
308 diag->Error(DiagMessage() << "failed to find file");
309 return nullptr;
310 }
311
312 std::unique_ptr<xml::XmlResource> doc;
313 if (format_ == ApkFormat::kProto) {
314 std::unique_ptr<io::InputStream> in = file->OpenInputStream();
315 if (!in) {
316 diag->Error(DiagMessage() << "failed to open file");
317 return nullptr;
318 }
319
320 pb::XmlNode pb_node;
321 io::ProtoInputStreamReader proto_reader(in.get());
322 if (!proto_reader.ReadMessage(&pb_node)) {
323 diag->Error(DiagMessage() << "failed to parse file as proto XML");
324 return nullptr;
325 }
326
327 std::string err;
328 doc = DeserializeXmlResourceFromPb(pb_node, &err);
329 if (!doc) {
330 diag->Error(DiagMessage() << "failed to deserialize proto XML: " << err);
331 return nullptr;
332 }
333 } else if (format_ == ApkFormat::kBinary) {
334 std::unique_ptr<io::IData> data = file->OpenAsData();
335 if (!data) {
336 diag->Error(DiagMessage() << "failed to open file");
337 return nullptr;
338 }
339
340 std::string err;
341 doc = xml::Inflate(data->data(), data->size(), &err);
342 if (!doc) {
343 diag->Error(DiagMessage() << "failed to parse file as binary XML: " << err);
344 return nullptr;
345 }
346 }
347
348 return doc;
349 }
350
351 } // namespace aapt
352