Home | History | Annotate | Download | only in cmd
      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 <vector>
     18 
     19 #include "android-base/macros.h"
     20 #include "androidfw/StringPiece.h"
     21 
     22 #include "Flags.h"
     23 #include "LoadedApk.h"
     24 #include "ValueVisitor.h"
     25 #include "cmd/Util.h"
     26 #include "format/binary/TableFlattener.h"
     27 #include "format/binary/XmlFlattener.h"
     28 #include "format/proto/ProtoDeserialize.h"
     29 #include "format/proto/ProtoSerialize.h"
     30 #include "io/BigBufferStream.h"
     31 #include "io/Util.h"
     32 #include "process/IResourceTableConsumer.h"
     33 #include "process/SymbolTable.h"
     34 #include "util/Util.h"
     35 
     36 using ::android::StringPiece;
     37 using ::android::base::StringPrintf;
     38 using ::std::unique_ptr;
     39 using ::std::vector;
     40 
     41 namespace aapt {
     42 
     43 class IApkSerializer {
     44  public:
     45   IApkSerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {}
     46 
     47   virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
     48                             IArchiveWriter* writer) = 0;
     49   virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
     50   virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0;
     51 
     52   virtual ~IApkSerializer() = default;
     53 
     54  protected:
     55   IAaptContext* context_;
     56   Source source_;
     57 };
     58 
     59 bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer,
     60                 IArchiveWriter* writer) {
     61   if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
     62     context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
     63                                      << "failed to serialize AndroidManifest.xml");
     64     return false;
     65   }
     66 
     67   if (apk->GetResourceTable() != nullptr) {
     68     // The table might be modified by below code.
     69     auto converted_table = apk->GetResourceTable();
     70 
     71     // Resources
     72     for (const auto& package : converted_table->packages) {
     73       for (const auto& type : package->types) {
     74         for (const auto& entry : type->entries) {
     75           for (const auto& config_value : entry->values) {
     76             FileReference* file = ValueCast<FileReference>(config_value->value.get());
     77             if (file != nullptr) {
     78               if (file->file == nullptr) {
     79                 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
     80                                                  << "no file associated with " << *file);
     81                 return false;
     82               }
     83 
     84               if (!serializer->SerializeFile(file, writer)) {
     85                 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
     86                                                  << "failed to serialize file " << *file->path);
     87                 return false;
     88               }
     89             } // file
     90           } // config_value
     91         } // entry
     92       } // type
     93     } // package
     94 
     95     // Converted resource table
     96     if (!serializer->SerializeTable(converted_table, writer)) {
     97       context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
     98                                        << "failed to serialize the resource table");
     99       return false;
    100     }
    101   }
    102 
    103   // Other files
    104   std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
    105   while (iterator->HasNext()) {
    106     io::IFile* file = iterator->Next();
    107 
    108     std::string path = file->GetSource().path;
    109     // The name of the path has the format "<zip-file-name>@<path-to-file>".
    110     path = path.substr(path.find('@') + 1);
    111 
    112     // Manifest, resource table and resources have already been taken care of.
    113     if (path == kAndroidManifestPath ||
    114         path == kApkResourceTablePath ||
    115         path == kProtoResourceTablePath ||
    116         path.find("res/") == 0) {
    117       continue;
    118     }
    119 
    120     if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
    121       context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
    122                                        << "failed to copy file " << path);
    123       return false;
    124     }
    125   }
    126 
    127   return true;
    128 }
    129 
    130 
    131 class BinaryApkSerializer : public IApkSerializer {
    132  public:
    133   BinaryApkSerializer(IAaptContext* context, const Source& source,
    134                    const TableFlattenerOptions& options)
    135       : IApkSerializer(context, source), tableFlattenerOptions_(options) {}
    136 
    137   bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
    138                     IArchiveWriter* writer) override {
    139     BigBuffer buffer(4096);
    140     XmlFlattenerOptions options = {};
    141     options.use_utf16 = utf16;
    142     options.keep_raw_values = true;
    143     XmlFlattener flattener(&buffer, options);
    144     if (!flattener.Consume(context_, xml)) {
    145       return false;
    146     }
    147 
    148     io::BigBufferInputStream input_stream(&buffer);
    149     return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
    150                                         writer);
    151   }
    152 
    153   bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
    154     BigBuffer buffer(4096);
    155     TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
    156     if (!table_flattener.Consume(context_, table)) {
    157       return false;
    158     }
    159 
    160     io::BigBufferInputStream input_stream(&buffer);
    161     return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
    162                                         ArchiveEntry::kAlign, writer);
    163   }
    164 
    165   bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
    166     if (file->type == ResourceFile::Type::kProtoXml) {
    167       unique_ptr<io::InputStream> in = file->file->OpenInputStream();
    168       if (in == nullptr) {
    169         context_->GetDiagnostics()->Error(DiagMessage(source_)
    170                                           << "failed to open file " << *file->path);
    171         return false;
    172       }
    173 
    174       pb::XmlNode pb_node;
    175       io::ZeroCopyInputAdaptor adaptor(in.get());
    176       if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
    177         context_->GetDiagnostics()->Error(DiagMessage(source_)
    178                                           << "failed to parse proto XML " << *file->path);
    179         return false;
    180       }
    181 
    182       std::string error;
    183       unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
    184       if (xml == nullptr) {
    185         context_->GetDiagnostics()->Error(DiagMessage(source_)
    186                                           << "failed to deserialize proto XML "
    187                                           << *file->path << ": " << error);
    188         return false;
    189       }
    190 
    191       if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
    192         context_->GetDiagnostics()->Error(DiagMessage(source_)
    193                                           << "failed to serialize to binary XML: " << *file->path);
    194         return false;
    195       }
    196 
    197       file->type = ResourceFile::Type::kBinaryXml;
    198     } else {
    199       if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
    200         context_->GetDiagnostics()->Error(DiagMessage(source_)
    201                                           << "failed to copy file " << *file->path);
    202         return false;
    203       }
    204     }
    205 
    206     return true;
    207   }
    208 
    209  private:
    210   TableFlattenerOptions tableFlattenerOptions_;
    211 
    212   DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
    213 };
    214 
    215 class ProtoApkSerializer : public IApkSerializer {
    216  public:
    217   ProtoApkSerializer(IAaptContext* context, const Source& source)
    218       : IApkSerializer(context, source) {}
    219 
    220   bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
    221                     IArchiveWriter* writer) override {
    222     pb::XmlNode pb_node;
    223     SerializeXmlResourceToPb(*xml, &pb_node);
    224     return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer);
    225   }
    226 
    227   bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
    228     pb::ResourceTable pb_table;
    229     SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
    230     return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
    231                                   ArchiveEntry::kCompress, writer);
    232   }
    233 
    234   bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
    235     if (file->type == ResourceFile::Type::kBinaryXml) {
    236       std::unique_ptr<io::IData> data = file->file->OpenAsData();
    237       if (!data) {
    238         context_->GetDiagnostics()->Error(DiagMessage(source_)
    239                                          << "failed to open file " << *file->path);
    240         return false;
    241       }
    242 
    243       std::string error;
    244       std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
    245       if (xml == nullptr) {
    246         context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: "
    247                                           << error);
    248         return false;
    249       }
    250 
    251       if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
    252         context_->GetDiagnostics()->Error(DiagMessage(source_)
    253                                           << "failed to serialize to proto XML: " << *file->path);
    254         return false;
    255       }
    256 
    257       file->type = ResourceFile::Type::kProtoXml;
    258     } else {
    259       if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
    260         context_->GetDiagnostics()->Error(DiagMessage(source_)
    261                                           << "failed to copy file " << *file->path);
    262         return false;
    263       }
    264     }
    265 
    266     return true;
    267   }
    268 
    269  private:
    270   DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
    271 };
    272 
    273 class Context : public IAaptContext {
    274  public:
    275   Context() : mangler_({}), symbols_(&mangler_) {
    276   }
    277 
    278   PackageType GetPackageType() override {
    279     return PackageType::kApp;
    280   }
    281 
    282   SymbolTable* GetExternalSymbols() override {
    283     return &symbols_;
    284   }
    285 
    286   IDiagnostics* GetDiagnostics() override {
    287     return &diag_;
    288   }
    289 
    290   const std::string& GetCompilationPackage() override {
    291     return package_;
    292   }
    293 
    294   uint8_t GetPackageId() override {
    295     // Nothing should call this.
    296     UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
    297     return 0;
    298   }
    299 
    300   NameMangler* GetNameMangler() override {
    301     UNIMPLEMENTED(FATAL);
    302     return nullptr;
    303   }
    304 
    305   bool IsVerbose() override {
    306     return verbose_;
    307   }
    308 
    309   int GetMinSdkVersion() override {
    310     return 0u;
    311   }
    312 
    313   bool verbose_ = false;
    314   std::string package_;
    315 
    316  private:
    317   DISALLOW_COPY_AND_ASSIGN(Context);
    318 
    319   NameMangler mangler_;
    320   SymbolTable symbols_;
    321   StdErrDiagnostics diag_;
    322 };
    323 
    324 int Convert(const vector<StringPiece>& args) {
    325 
    326   static const char* kOutputFormatProto = "proto";
    327   static const char* kOutputFormatBinary = "binary";
    328 
    329   Context context;
    330   std::string output_path;
    331   Maybe<std::string> output_format;
    332   TableFlattenerOptions options;
    333   Flags flags =
    334       Flags()
    335           .RequiredFlag("-o", "Output path", &output_path)
    336           .OptionalFlag("--output-format", StringPrintf("Format of the output. Accepted values are "
    337                         "'%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto,
    338                         kOutputFormatBinary, kOutputFormatBinary), &output_format)
    339           .OptionalSwitch("--enable-sparse-encoding",
    340                           "Enables encoding sparse entries using a binary search tree.\n"
    341                           "This decreases APK size at the cost of resource retrieval performance.",
    342                           &options.use_sparse_entries)
    343           .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
    344   if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
    345     return 1;
    346   }
    347 
    348   if (flags.GetArgs().size() != 1) {
    349     std::cerr << "must supply a single proto APK\n";
    350     flags.Usage("aapt2 convert", &std::cerr);
    351     return 1;
    352   }
    353 
    354   const StringPiece& path = flags.GetArgs()[0];
    355   unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
    356   if (apk == nullptr) {
    357     context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
    358     return 1;
    359   }
    360 
    361   Maybe<AppInfo> app_info =
    362       ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
    363   if (!app_info) {
    364     return 1;
    365   }
    366 
    367   context.package_ = app_info.value().package;
    368 
    369   unique_ptr<IArchiveWriter> writer =
    370       CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
    371   if (writer == nullptr) {
    372     return 1;
    373   }
    374 
    375   unique_ptr<IApkSerializer> serializer;
    376   if (!output_format || output_format.value() == kOutputFormatBinary) {
    377     serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options));
    378   } else if (output_format.value() == kOutputFormatProto) {
    379     serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
    380   } else {
    381     context.GetDiagnostics()->Error(DiagMessage(path)
    382                                     << "Invalid value for flag --output-format: "
    383                                     << output_format.value());
    384     return 1;
    385   }
    386 
    387 
    388   return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
    389 }
    390 
    391 }  // namespace aapt
    392