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