• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
IApkSerializer(IAaptContext * context,const Source & source)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:
BinaryApkSerializer(IAaptContext * context,const Source & source,const TableFlattenerOptions & table_flattener_options,const XmlFlattenerOptions & xml_flattener_options)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 
SerializeXml(const xml::XmlResource * xml,const std::string & path,bool utf16,IArchiveWriter * writer,uint32_t compression_flags)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 
SerializeTable(ResourceTable * table,IArchiveWriter * writer)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 
SerializeFile(FileReference * file,IArchiveWriter * writer)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:
ProtoApkSerializer(IAaptContext * context,const Source & source)149   ProtoApkSerializer(IAaptContext* context, const Source& source)
150       : IApkSerializer(context, source) {}
151 
SerializeXml(const xml::XmlResource * xml,const std::string & path,bool utf16,IArchiveWriter * writer,uint32_t compression_flags)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 
SerializeTable(ResourceTable * table,IArchiveWriter * writer)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 
SerializeFile(FileReference * file,IArchiveWriter * writer)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:
Context()208   Context() : mangler_({}), symbols_(&mangler_) {
209   }
210 
GetPackageType()211   PackageType GetPackageType() override {
212     return PackageType::kApp;
213   }
214 
GetExternalSymbols()215   SymbolTable* GetExternalSymbols() override {
216     return &symbols_;
217   }
218 
GetDiagnostics()219   IDiagnostics* GetDiagnostics() override {
220     return &diag_;
221   }
222 
GetCompilationPackage()223   const std::string& GetCompilationPackage() override {
224     return package_;
225   }
226 
GetPackageId()227   uint8_t GetPackageId() override {
228     // Nothing should call this.
229     UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
230     return 0;
231   }
232 
GetNameMangler()233   NameMangler* GetNameMangler() override {
234     UNIMPLEMENTED(FATAL);
235     return nullptr;
236   }
237 
IsVerbose()238   bool IsVerbose() override {
239     return verbose_;
240   }
241 
GetMinSdkVersion()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 
Convert(IAaptContext * context,LoadedApk * apk,IArchiveWriter * output_writer,ApkFormat output_format,TableFlattenerOptions table_flattener_options,XmlFlattenerOptions xml_flattener_options)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 
Action(const std::vector<std::string> & args)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