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 
LoadApkFromPath(const StringPiece & path,IDiagnostics * diag)38 std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
39   Source source(path);
40   std::string error;
41   std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
42   if (apk == nullptr) {
43     diag->Error(DiagMessage(path) << "failed opening zip: " << error);
44     return {};
45   }
46 
47   ApkFormat apkFormat = DetermineApkFormat(apk.get());
48   switch (apkFormat) {
49     case ApkFormat::kBinary:
50       return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
51     case ApkFormat::kProto:
52       return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
53     default:
54       diag->Error(DiagMessage(path) << "could not identify format of APK");
55       return {};
56   }
57 }
58 
LoadProtoApkFromFileCollection(const Source & source,unique_ptr<io::IFileCollection> collection,IDiagnostics * diag)59 std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
60     const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
61   std::unique_ptr<ResourceTable> table;
62 
63   io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
64   if (table_file != nullptr) {
65     pb::ResourceTable pb_table;
66     std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
67     if (in == nullptr) {
68       diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
69       return {};
70     }
71 
72     io::ZeroCopyInputAdaptor adaptor(in.get());
73     if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
74       diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
75       return {};
76     }
77 
78     std::string error;
79     table = util::make_unique<ResourceTable>();
80     if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
81       diag->Error(DiagMessage(source)
82                   << "failed to deserialize " << kProtoResourceTablePath << ": " << error);
83       return {};
84     }
85   }
86 
87   io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
88   if (manifest_file == nullptr) {
89     diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
90     return {};
91   }
92 
93   std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
94   if (manifest_in == nullptr) {
95     diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
96     return {};
97   }
98 
99   pb::XmlNode pb_node;
100   io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
101   if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
102     diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
103     return {};
104   }
105 
106   std::string error;
107   std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
108   if (manifest == nullptr) {
109     diag->Error(DiagMessage(source)
110                 << "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
111     return {};
112   }
113   return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
114                                       std::move(manifest), ApkFormat::kProto);
115 }
116 
LoadBinaryApkFromFileCollection(const Source & source,unique_ptr<io::IFileCollection> collection,IDiagnostics * diag)117 std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
118     const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
119   std::unique_ptr<ResourceTable> table;
120 
121   io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
122   if (table_file != nullptr) {
123     table = util::make_unique<ResourceTable>();
124     std::unique_ptr<io::IData> data = table_file->OpenAsData();
125     if (data == nullptr) {
126       diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
127       return {};
128     }
129     BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
130                                 collection.get());
131     if (!parser.Parse()) {
132       return {};
133     }
134   }
135 
136   io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
137   if (manifest_file == nullptr) {
138     diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
139     return {};
140   }
141 
142   std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
143   if (manifest_data == nullptr) {
144     diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
145     return {};
146   }
147 
148   std::string error;
149   std::unique_ptr<xml::XmlResource> manifest =
150       xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
151   if (manifest == nullptr) {
152     diag->Error(DiagMessage(source)
153                 << "failed to parse binary " << kAndroidManifestPath << ": " << error);
154     return {};
155   }
156   return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
157                                       std::move(manifest), ApkFormat::kBinary);
158 }
159 
WriteToArchive(IAaptContext * context,const TableFlattenerOptions & options,IArchiveWriter * writer)160 bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
161                                IArchiveWriter* writer) {
162   FilterChain empty;
163   return WriteToArchive(context, table_.get(), options, &empty, writer);
164 }
165 
WriteToArchive(IAaptContext * context,ResourceTable * split_table,const TableFlattenerOptions & options,FilterChain * filters,IArchiveWriter * writer,XmlResource * manifest)166 bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
167                                const TableFlattenerOptions& options, FilterChain* filters,
168                                IArchiveWriter* writer, XmlResource* manifest) {
169   std::set<std::string> referenced_resources;
170   // List the files being referenced in the resource table.
171   for (auto& pkg : split_table->packages) {
172     for (auto& type : pkg->types) {
173       for (auto& entry : type->entries) {
174         for (auto& config_value : entry->values) {
175           FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
176           if (file_ref) {
177             referenced_resources.insert(*file_ref->path);
178           }
179         }
180       }
181     }
182   }
183 
184   std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
185   while (iterator->HasNext()) {
186     io::IFile* file = iterator->Next();
187 
188     std::string path = file->GetSource().path;
189     // The name of the path has the format "<zip-file-name>@<path-to-file>".
190     path = path.substr(path.find('@') + 1);
191 
192     // Skip resources that are not referenced if requested.
193     if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) {
194       if (context->IsVerbose()) {
195         context->GetDiagnostics()->Note(DiagMessage()
196                                         << "Removing resource '" << path << "' from APK.");
197       }
198       continue;
199     }
200 
201     if (!filters->Keep(path)) {
202       if (context->IsVerbose()) {
203         context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
204       }
205       continue;
206     }
207 
208     // The resource table needs to be re-serialized since it might have changed.
209     if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) {
210       BigBuffer buffer(4096);
211       // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
212       // with sparse entries) b/35389232.
213       TableFlattener flattener(options, &buffer);
214       if (!flattener.Consume(context, split_table)) {
215         return false;
216       }
217 
218       io::BigBufferInputStream input_stream(&buffer);
219       if (!io::CopyInputStreamToArchive(context,
220                                         &input_stream,
221                                         path,
222                                         ArchiveEntry::kAlign,
223                                         writer)) {
224         return false;
225       }
226     } else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
227       pb::ResourceTable pb_table;
228       SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics());
229       if (!io::CopyProtoToArchive(context,
230                                   &pb_table,
231                                   path,
232                                   ArchiveEntry::kAlign, writer)) {
233         return false;
234       }
235     } else if (manifest != nullptr && path == "AndroidManifest.xml") {
236       BigBuffer buffer(8192);
237       XmlFlattenerOptions xml_flattener_options;
238       xml_flattener_options.use_utf16 = true;
239       XmlFlattener xml_flattener(&buffer, xml_flattener_options);
240       if (!xml_flattener.Consume(context, manifest)) {
241         context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
242         return false;
243       }
244 
245       uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
246       io::BigBufferInputStream manifest_buffer_in(&buffer);
247       if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
248                                         writer)) {
249         return false;
250       }
251     } else {
252       if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
253         return false;
254       }
255     }
256   }
257   return true;
258 }
259 
DetermineApkFormat(io::IFileCollection * apk)260 ApkFormat LoadedApk::DetermineApkFormat(io::IFileCollection* apk) {
261   if (apk->FindFile(kApkResourceTablePath) != nullptr) {
262     return ApkFormat::kBinary;
263   } else if (apk->FindFile(kProtoResourceTablePath) != nullptr) {
264     return ApkFormat::kProto;
265   } else {
266     // If the resource table is not present, attempt to read the manifest.
267     io::IFile* manifest_file = apk->FindFile(kAndroidManifestPath);
268     if (manifest_file == nullptr) {
269       return ApkFormat::kUnknown;
270     }
271 
272     // First try in proto format.
273     std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
274     if (manifest_in != nullptr) {
275       pb::XmlNode pb_node;
276       io::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
277       if (pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
278         return ApkFormat::kProto;
279       }
280     }
281 
282     // If it didn't work, try in binary format.
283     std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
284     if (manifest_data != nullptr) {
285       std::string error;
286       std::unique_ptr<xml::XmlResource> manifest =
287           xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
288       if (manifest != nullptr) {
289         return ApkFormat::kBinary;
290       }
291     }
292 
293     return ApkFormat::kUnknown;
294   }
295 }
296 
297 }  // namespace aapt
298