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