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