xref: /aosp_15_r20/frameworks/base/tools/aapt2/cmd/Convert.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
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 
SetVerbose(bool verbose)247   void SetVerbose(bool verbose) {
248     verbose_ = verbose;
249   }
250 
GetMinSdkVersion()251   int GetMinSdkVersion() override {
252     return min_sdk_;
253   }
254 
GetSplitNameDependencies()255   const std::set<std::string>& GetSplitNameDependencies() override {
256     UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
257     static std::set<std::string> empty;
258     return empty;
259   }
260 
261   bool verbose_ = false;
262   std::string package_;
263   int32_t min_sdk_ = 0;
264 
265  private:
266   DISALLOW_COPY_AND_ASSIGN(Context);
267 
268   NameMangler mangler_;
269   SymbolTable symbols_;
270   StdErrDiagnostics diag_;
271 };
272 
Convert(IAaptContext * context,LoadedApk * apk,IArchiveWriter * output_writer,ApkFormat output_format,TableFlattenerOptions table_flattener_options,XmlFlattenerOptions xml_flattener_options)273 int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
274             ApkFormat output_format, TableFlattenerOptions table_flattener_options,
275             XmlFlattenerOptions xml_flattener_options) {
276   unique_ptr<IApkSerializer> serializer;
277   if (output_format == ApkFormat::kBinary) {
278     serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), table_flattener_options,
279                                              xml_flattener_options));
280   } else if (output_format == ApkFormat::kProto) {
281     serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
282   } else {
283     context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
284                                      << "Cannot convert APK to unknown format");
285     return 1;
286   }
287 
288   io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
289   if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
290                                 output_writer, (manifest != nullptr && manifest->WasCompressed())
291                                                ? ArchiveEntry::kCompress : 0u)) {
292     context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
293                                      << "failed to serialize AndroidManifest.xml");
294     return 1;
295   }
296 
297   if (apk->GetResourceTable() != nullptr) {
298     // The table might be modified by below code.
299     auto converted_table = apk->GetResourceTable();
300 
301     std::unordered_set<std::string> files_written;
302 
303     // Resources
304     for (const auto& package : converted_table->packages) {
305       for (const auto& type : package->types) {
306         for (const auto& entry : type->entries) {
307           for (const auto& config_value : entry->values) {
308             FileReference* file = ValueCast<FileReference>(config_value->value.get());
309             if (file != nullptr) {
310               if (file->file == nullptr) {
311                 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
312                                                  << "no file associated with " << *file);
313                 return 1;
314               }
315 
316               // Only serialize if we haven't seen this file before
317               if (files_written.insert(*file->path).second) {
318                 if (!serializer->SerializeFile(file, output_writer)) {
319                   context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
320                                                    << "failed to serialize file " << *file->path);
321                   return 1;
322                 }
323               }
324             } // file
325           } // config_value
326         } // entry
327       } // type
328     } // package
329 
330     // Converted resource table
331     if (!serializer->SerializeTable(converted_table, output_writer)) {
332       context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
333                                        << "failed to serialize the resource table");
334       return 1;
335     }
336   }
337 
338   // Other files
339   std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
340   while (iterator->HasNext()) {
341     io::IFile* file = iterator->Next();
342     std::string path = file->GetSource().path;
343 
344     // Manifest, resource table and resources have already been taken care of.
345     if (path == kAndroidManifestPath ||
346         path == kApkResourceTablePath ||
347         path == kProtoResourceTablePath ||
348         path.find("res/") == 0) {
349       continue;
350     }
351 
352     if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
353       context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
354                                        << "failed to copy file " << path);
355       return 1;
356     }
357   }
358 
359   return 0;
360 }
361 
ExtractResourceConfig(const std::string & path,IAaptContext * context,TableFlattenerOptions & out_options)362 bool ExtractResourceConfig(const std::string& path, IAaptContext* context,
363                            TableFlattenerOptions& out_options) {
364   std::string content;
365   if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) {
366     context->GetDiagnostics()->Error(android::DiagMessage(path) << "failed reading config file");
367     return false;
368   }
369   std::unordered_set<ResourceName> resources_exclude_list;
370   bool result = ParseResourceConfig(content, context, resources_exclude_list,
371                                     out_options.name_collapse_exemptions,
372                                     out_options.path_shorten_exemptions);
373   if (!result) {
374     return false;
375   }
376   if (!resources_exclude_list.empty()) {
377     context->GetDiagnostics()->Error(android::DiagMessage(path)
378                                      << "Unsupported '#remove' directive in resource config.");
379     return false;
380   }
381   return true;
382 }
383 
384 const char* ConvertCommand::kOutputFormatProto = "proto";
385 const char* ConvertCommand::kOutputFormatBinary = "binary";
386 
Action(const std::vector<std::string> & args)387 int ConvertCommand::Action(const std::vector<std::string>& args) {
388   if (args.size() != 1) {
389     std::cerr << "must supply a single APK\n";
390     Usage(&std::cerr);
391     return 1;
392   }
393 
394   Context context;
395   context.SetVerbose(verbose_);
396 
397   StringPiece path = args[0];
398   unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
399   if (apk == nullptr) {
400     context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
401     return 1;
402   }
403 
404   auto app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
405   if (!app_info) {
406     return 1;
407   }
408 
409   context.package_ = app_info.value().package;
410   context.min_sdk_ = app_info.value().min_sdk_version.value_or(0);
411   unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
412                                                                  output_path_);
413   if (writer == nullptr) {
414     return 1;
415   }
416 
417   ApkFormat format;
418   if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
419     format = ApkFormat::kBinary;
420   } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
421     format = ApkFormat::kProto;
422   } else {
423     context.GetDiagnostics()->Error(android::DiagMessage(path)
424                                     << "Invalid value for flag --output-format: "
425                                     << output_format_.value());
426     return 1;
427   }
428   if (enable_sparse_encoding_) {
429     table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled;
430   }
431   if (force_sparse_encoding_) {
432     table_flattener_options_.sparse_entries = SparseEntriesMode::Forced;
433   }
434   table_flattener_options_.use_compact_entries = enable_compact_entries_;
435   if (resources_config_path_) {
436     if (!ExtractResourceConfig(*resources_config_path_, &context, table_flattener_options_)) {
437       return 1;
438     }
439   }
440 
441   return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
442                  xml_flattener_options_);
443 }
444 
445 }  // namespace aapt
446