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