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