1 /*
2 * Copyright (C) 2017 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 "Convert.h"
18
19 #include <vector>
20
21 #include "Diagnostics.h"
22 #include "LoadedApk.h"
23 #include "ValueVisitor.h"
24 #include "android-base/file.h"
25 #include "android-base/macros.h"
26 #include "android-base/stringprintf.h"
27 #include "androidfw/BigBufferStream.h"
28 #include "androidfw/StringPiece.h"
29 #include "cmd/Util.h"
30 #include "format/binary/TableFlattener.h"
31 #include "format/binary/XmlFlattener.h"
32 #include "format/proto/ProtoDeserialize.h"
33 #include "format/proto/ProtoSerialize.h"
34 #include "io/Util.h"
35 #include "process/IResourceTableConsumer.h"
36 #include "process/SymbolTable.h"
37 #include "util/Util.h"
38
39 using ::android::StringPiece;
40 using ::android::base::StringPrintf;
41 using ::std::unique_ptr;
42 using ::std::vector;
43
44 namespace aapt {
45
46 class IApkSerializer {
47 public:
IApkSerializer(IAaptContext * context,const android::Source & source)48 IApkSerializer(IAaptContext* context, const android::Source& source)
49 : context_(context), source_(source) {
50 }
51
52 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
53 IArchiveWriter* writer, uint32_t compression_flags) = 0;
54 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
55 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0;
56
57 virtual ~IApkSerializer() = default;
58
59 protected:
60 IAaptContext* context_;
61 android::Source source_;
62 };
63
64 class BinaryApkSerializer : public IApkSerializer {
65 public:
BinaryApkSerializer(IAaptContext * context,const android::Source & source,const TableFlattenerOptions & table_flattener_options,const XmlFlattenerOptions & xml_flattener_options)66 BinaryApkSerializer(IAaptContext* context, const android::Source& source,
67 const TableFlattenerOptions& table_flattener_options,
68 const XmlFlattenerOptions& xml_flattener_options)
69 : IApkSerializer(context, source),
70 table_flattener_options_(table_flattener_options),
71 xml_flattener_options_(xml_flattener_options) {
72 }
73
SerializeXml(const xml::XmlResource * xml,const std::string & path,bool utf16,IArchiveWriter * writer,uint32_t compression_flags)74 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
75 IArchiveWriter* writer, uint32_t compression_flags) override {
76 android::BigBuffer buffer(4096);
77 xml_flattener_options_.use_utf16 = utf16;
78 XmlFlattener flattener(&buffer, xml_flattener_options_);
79 if (!flattener.Consume(context_, xml)) {
80 return false;
81 }
82
83 android::BigBufferInputStream input_stream(&buffer);
84 return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
85 }
86
SerializeTable(ResourceTable * table,IArchiveWriter * writer)87 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
88 android::BigBuffer buffer(4096);
89 TableFlattener table_flattener(table_flattener_options_, &buffer);
90 if (!table_flattener.Consume(context_, table)) {
91 return false;
92 }
93
94 android::BigBufferInputStream input_stream(&buffer);
95 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
96 ArchiveEntry::kAlign, writer);
97 }
98
SerializeFile(FileReference * file,IArchiveWriter * writer)99 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
100 if (file->type == ResourceFile::Type::kProtoXml) {
101 unique_ptr<android::InputStream> in = file->file->OpenInputStream();
102 if (in == nullptr) {
103 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
104 << "failed to open file " << *file->path);
105 return false;
106 }
107
108 pb::XmlNode pb_node;
109 io::ProtoInputStreamReader proto_reader(in.get());
110 if (!proto_reader.ReadMessage(&pb_node)) {
111 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
112 << "failed to parse proto XML " << *file->path);
113 return false;
114 }
115
116 std::string error;
117 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
118 if (xml == nullptr) {
119 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
120 << "failed to deserialize proto XML " << *file->path
121 << ": " << error);
122 return false;
123 }
124
125 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
126 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
127 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
128 << "failed to serialize to binary XML: " << *file->path);
129 return false;
130 }
131
132 file->type = ResourceFile::Type::kBinaryXml;
133 } else {
134 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
135 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
136 << "failed to copy file " << *file->path);
137 return false;
138 }
139 }
140
141 return true;
142 }
143
144 private:
145 TableFlattenerOptions table_flattener_options_;
146 XmlFlattenerOptions xml_flattener_options_;
147
148 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
149 };
150
151 class ProtoApkSerializer : public IApkSerializer {
152 public:
ProtoApkSerializer(IAaptContext * context,const android::Source & source)153 ProtoApkSerializer(IAaptContext* context, const android::Source& source)
154 : IApkSerializer(context, source) {
155 }
156
SerializeXml(const xml::XmlResource * xml,const std::string & path,bool utf16,IArchiveWriter * writer,uint32_t compression_flags)157 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
158 IArchiveWriter* writer, uint32_t compression_flags) override {
159 pb::XmlNode pb_node;
160 SerializeXmlResourceToPb(*xml, &pb_node);
161 return io::CopyProtoToArchive(context_, &pb_node, path, compression_flags, writer);
162 }
163
SerializeTable(ResourceTable * table,IArchiveWriter * writer)164 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
165 pb::ResourceTable pb_table;
166 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
167 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
168 ArchiveEntry::kCompress, writer);
169 }
170
SerializeFile(FileReference * file,IArchiveWriter * writer)171 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
172 if (file->type == ResourceFile::Type::kBinaryXml) {
173 std::unique_ptr<io::IData> data = file->file->OpenAsData();
174 if (!data) {
175 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
176 << "failed to open file " << *file->path);
177 return false;
178 }
179
180 std::string error;
181 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
182 if (xml == nullptr) {
183 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
184 << "failed to parse binary XML: " << error);
185 return false;
186 }
187
188 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
189 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
190 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
191 << "failed to serialize to proto XML: " << *file->path);
192 return false;
193 }
194
195 file->type = ResourceFile::Type::kProtoXml;
196 } else {
197 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
198 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
199 << "failed to copy file " << *file->path);
200 return false;
201 }
202 }
203
204 return true;
205 }
206
207 private:
208 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
209 };
210
211 class Context : public IAaptContext {
212 public:
Context()213 Context() : mangler_({}), symbols_(&mangler_) {
214 }
215
GetPackageType()216 PackageType GetPackageType() override {
217 return PackageType::kApp;
218 }
219
GetExternalSymbols()220 SymbolTable* GetExternalSymbols() override {
221 return &symbols_;
222 }
223
GetDiagnostics()224 android::IDiagnostics* GetDiagnostics() override {
225 return &diag_;
226 }
227
GetCompilationPackage()228 const std::string& GetCompilationPackage() override {
229 return package_;
230 }
231
GetPackageId()232 uint8_t GetPackageId() override {
233 // Nothing should call this.
234 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
235 return 0;
236 }
237
GetNameMangler()238 NameMangler* GetNameMangler() override {
239 UNIMPLEMENTED(FATAL);
240 return nullptr;
241 }
242
IsVerbose()243 bool IsVerbose() override {
244 return verbose_;
245 }
246
GetMinSdkVersion()247 int GetMinSdkVersion() override {
248 return min_sdk_;
249 }
250
GetSplitNameDependencies()251 const std::set<std::string>& GetSplitNameDependencies() override {
252 UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
253 static std::set<std::string> empty;
254 return empty;
255 }
256
257 bool verbose_ = false;
258 std::string package_;
259 int32_t min_sdk_ = 0;
260
261 private:
262 DISALLOW_COPY_AND_ASSIGN(Context);
263
264 NameMangler mangler_;
265 SymbolTable symbols_;
266 StdErrDiagnostics diag_;
267 };
268
Convert(IAaptContext * context,LoadedApk * apk,IArchiveWriter * output_writer,ApkFormat output_format,TableFlattenerOptions table_flattener_options,XmlFlattenerOptions xml_flattener_options)269 int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
270 ApkFormat output_format, TableFlattenerOptions table_flattener_options,
271 XmlFlattenerOptions xml_flattener_options) {
272 unique_ptr<IApkSerializer> serializer;
273 if (output_format == ApkFormat::kBinary) {
274 serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), table_flattener_options,
275 xml_flattener_options));
276 } else if (output_format == ApkFormat::kProto) {
277 serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
278 } else {
279 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
280 << "Cannot convert APK to unknown format");
281 return 1;
282 }
283
284 io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
285 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
286 output_writer, (manifest != nullptr && manifest->WasCompressed())
287 ? ArchiveEntry::kCompress : 0u)) {
288 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
289 << "failed to serialize AndroidManifest.xml");
290 return 1;
291 }
292
293 if (apk->GetResourceTable() != nullptr) {
294 // The table might be modified by below code.
295 auto converted_table = apk->GetResourceTable();
296
297 std::unordered_set<std::string> files_written;
298
299 // Resources
300 for (const auto& package : converted_table->packages) {
301 for (const auto& type : package->types) {
302 for (const auto& entry : type->entries) {
303 for (const auto& config_value : entry->values) {
304 FileReference* file = ValueCast<FileReference>(config_value->value.get());
305 if (file != nullptr) {
306 if (file->file == nullptr) {
307 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
308 << "no file associated with " << *file);
309 return 1;
310 }
311
312 // Only serialize if we haven't seen this file before
313 if (files_written.insert(*file->path).second) {
314 if (!serializer->SerializeFile(file, output_writer)) {
315 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
316 << "failed to serialize file " << *file->path);
317 return 1;
318 }
319 }
320 } // file
321 } // config_value
322 } // entry
323 } // type
324 } // package
325
326 // Converted resource table
327 if (!serializer->SerializeTable(converted_table, output_writer)) {
328 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
329 << "failed to serialize the resource table");
330 return 1;
331 }
332 }
333
334 // Other files
335 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
336 while (iterator->HasNext()) {
337 io::IFile* file = iterator->Next();
338 std::string path = file->GetSource().path;
339
340 // Manifest, resource table and resources have already been taken care of.
341 if (path == kAndroidManifestPath ||
342 path == kApkResourceTablePath ||
343 path == kProtoResourceTablePath ||
344 path.find("res/") == 0) {
345 continue;
346 }
347
348 if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
349 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
350 << "failed to copy file " << path);
351 return 1;
352 }
353 }
354
355 return 0;
356 }
357
ExtractResourceConfig(const std::string & path,IAaptContext * context,TableFlattenerOptions & out_options)358 bool ExtractResourceConfig(const std::string& path, IAaptContext* context,
359 TableFlattenerOptions& out_options) {
360 std::string content;
361 if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
362 context->GetDiagnostics()->Error(android::DiagMessage(path) << "failed reading config file");
363 return false;
364 }
365 std::unordered_set<ResourceName> resources_exclude_list;
366 bool result = ParseResourceConfig(content, context, resources_exclude_list,
367 out_options.name_collapse_exemptions,
368 out_options.path_shorten_exemptions);
369 if (!result) {
370 return false;
371 }
372 if (!resources_exclude_list.empty()) {
373 context->GetDiagnostics()->Error(android::DiagMessage(path)
374 << "Unsupported '#remove' directive in resource config.");
375 return false;
376 }
377 return true;
378 }
379
380 const char* ConvertCommand::kOutputFormatProto = "proto";
381 const char* ConvertCommand::kOutputFormatBinary = "binary";
382
Action(const std::vector<std::string> & args)383 int ConvertCommand::Action(const std::vector<std::string>& args) {
384 if (args.size() != 1) {
385 std::cerr << "must supply a single APK\n";
386 Usage(&std::cerr);
387 return 1;
388 }
389
390 Context context;
391 StringPiece path = args[0];
392 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
393 if (apk == nullptr) {
394 context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
395 return 1;
396 }
397
398 auto app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
399 if (!app_info) {
400 return 1;
401 }
402
403 context.package_ = app_info.value().package;
404 context.min_sdk_ = app_info.value().min_sdk_version.value_or(0);
405 unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
406 output_path_);
407 if (writer == nullptr) {
408 return 1;
409 }
410
411 ApkFormat format;
412 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
413 format = ApkFormat::kBinary;
414 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
415 format = ApkFormat::kProto;
416 } else {
417 context.GetDiagnostics()->Error(android::DiagMessage(path)
418 << "Invalid value for flag --output-format: "
419 << output_format_.value());
420 return 1;
421 }
422 if (enable_sparse_encoding_) {
423 table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled;
424 }
425 if (force_sparse_encoding_) {
426 table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
427 }
428 table_flattener_options_.use_compact_entries = enable_compact_entries_;
429 if (resources_config_path_) {
430 if (!ExtractResourceConfig(*resources_config_path_, &context, table_flattener_options_)) {
431 return 1;
432 }
433 }
434
435 return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
436 xml_flattener_options_);
437 }
438
439 } // namespace aapt
440