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