• 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 <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:
IApkSerializer(IAaptContext * context,const Source & source)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 
ConvertApk(IAaptContext * context,unique_ptr<LoadedApk> apk,IApkSerializer * serializer,IArchiveWriter * writer)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:
BinaryApkSerializer(IAaptContext * context,const Source & source,const TableFlattenerOptions & options)133   BinaryApkSerializer(IAaptContext* context, const Source& source,
134                    const TableFlattenerOptions& options)
135       : IApkSerializer(context, source), tableFlattenerOptions_(options) {}
136 
SerializeXml(const xml::XmlResource * xml,const std::string & path,bool utf16,IArchiveWriter * writer)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 
SerializeTable(ResourceTable * table,IArchiveWriter * writer)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 
SerializeFile(FileReference * file,IArchiveWriter * writer)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:
ProtoApkSerializer(IAaptContext * context,const Source & source)217   ProtoApkSerializer(IAaptContext* context, const Source& source)
218       : IApkSerializer(context, source) {}
219 
SerializeXml(const xml::XmlResource * xml,const std::string & path,bool utf16,IArchiveWriter * writer)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 
SerializeTable(ResourceTable * table,IArchiveWriter * writer)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 
SerializeFile(FileReference * file,IArchiveWriter * writer)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:
Context()275   Context() : mangler_({}), symbols_(&mangler_) {
276   }
277 
GetPackageType()278   PackageType GetPackageType() override {
279     return PackageType::kApp;
280   }
281 
GetExternalSymbols()282   SymbolTable* GetExternalSymbols() override {
283     return &symbols_;
284   }
285 
GetDiagnostics()286   IDiagnostics* GetDiagnostics() override {
287     return &diag_;
288   }
289 
GetCompilationPackage()290   const std::string& GetCompilationPackage() override {
291     return package_;
292   }
293 
GetPackageId()294   uint8_t GetPackageId() override {
295     // Nothing should call this.
296     UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
297     return 0;
298   }
299 
GetNameMangler()300   NameMangler* GetNameMangler() override {
301     UNIMPLEMENTED(FATAL);
302     return nullptr;
303   }
304 
IsVerbose()305   bool IsVerbose() override {
306     return verbose_;
307   }
308 
GetMinSdkVersion()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 
Convert(const vector<StringPiece> & args)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