blob: 0cf86ccdd59fc0e335d19ff164ca4b737ee3f478 [file] [log] [blame]
Adam Lesinski8780eb62017-10-31 17:44:39 -07001/*
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
Ryan Mitchell833a1a62018-07-10 13:51:36 -070017#include "Convert.h"
18
Adam Lesinski8780eb62017-10-31 17:44:39 -070019#include <vector>
20
21#include "android-base/macros.h"
22#include "androidfw/StringPiece.h"
23
Adam Lesinski8780eb62017-10-31 17:44:39 -070024#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"
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000030#include "format/proto/ProtoSerialize.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070031#include "io/BigBufferStream.h"
32#include "io/Util.h"
33#include "process/IResourceTableConsumer.h"
34#include "process/SymbolTable.h"
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000035#include "util/Util.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070036
37using ::android::StringPiece;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000038using ::android::base::StringPrintf;
Adam Lesinski8780eb62017-10-31 17:44:39 -070039using ::std::unique_ptr;
40using ::std::vector;
41
42namespace aapt {
43
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000044class IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000045 public:
Ryan Mitchell90b7a082019-02-15 17:39:58 +000046 IApkSerializer(IAaptContext* context, const Source& source) : context_(context),
47 source_(source) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000048
49 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070050 IArchiveWriter* writer, uint32_t compression_flags) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000051 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
David Chaloupkab66db4e2018-01-15 12:35:41 +000052 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000053
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000054 virtual ~IApkSerializer() = default;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000055
56 protected:
57 IAaptContext* context_;
58 Source source_;
59};
Adam Lesinski8780eb62017-10-31 17:44:39 -070060
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000061class BinaryApkSerializer : public IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000062 public:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000063 BinaryApkSerializer(IAaptContext* context, const Source& source,
Ryan Mitchell479fa392019-01-02 17:15:39 -080064 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) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000069
70 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070071 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000072 BigBuffer buffer(4096);
Ryan Mitchell479fa392019-01-02 17:15:39 -080073 xml_flattener_options_.use_utf16 = utf16;
74 XmlFlattener flattener(&buffer, xml_flattener_options_);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000075 if (!flattener.Consume(context_, xml)) {
76 return false;
77 }
78
79 io::BigBufferInputStream input_stream(&buffer);
Ryan Mitchell05aebf42018-10-09 15:08:41 -070080 return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000081 }
82
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000083 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000084 BigBuffer buffer(4096);
Ryan Mitchell479fa392019-01-02 17:15:39 -080085 TableFlattener table_flattener(table_flattener_options_, &buffer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000086 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
David Chaloupkab66db4e2018-01-15 12:35:41 +000095 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000096 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;
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700105 io::ProtoInputStreamReader proto_reader(in.get());
106 if (!proto_reader.ReadMessage(&pb_node)) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000107 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
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700121 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
122 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000123 context_->GetDiagnostics()->Error(DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000124 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000125 return false;
126 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000127
128 file->type = ResourceFile::Type::kBinaryXml;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000129 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000130 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 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000135 }
136
137 return true;
138 }
139
140 private:
Ryan Mitchell479fa392019-01-02 17:15:39 -0800141 TableFlattenerOptions table_flattener_options_;
142 XmlFlattenerOptions xml_flattener_options_;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000143
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000144 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
145};
146
147class 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,
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700153 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000154 pb::XmlNode pb_node;
155 SerializeXmlResourceToPb(*xml, &pb_node);
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700156 return io::CopyProtoToArchive(context_, &pb_node, path, compression_flags, writer);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000157 }
158
159 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
160 pb::ResourceTable pb_table;
Ryan Mitchella15c2a82018-03-26 11:05:31 -0700161 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000162 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
163 ArchiveEntry::kCompress, writer);
164 }
165
David Chaloupkab66db4e2018-01-15 12:35:41 +0000166 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000167 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_)
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000171 << "failed to open file " << *file->path);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000172 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: "
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000179 << error);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000180 return false;
181 }
182
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700183 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
184 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000185 context_->GetDiagnostics()->Error(DiagMessage(source_)
186 << "failed to serialize to proto XML: " << *file->path);
187 return false;
188 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000189
190 file->type = ResourceFile::Type::kProtoXml;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000191 } 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);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000204};
205
Adam Lesinski8780eb62017-10-31 17:44:39 -0700206class 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
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800257int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
Ryan Mitchell479fa392019-01-02 17:15:39 -0800258 ApkFormat output_format, TableFlattenerOptions table_flattener_options,
259 XmlFlattenerOptions xml_flattener_options) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800260 unique_ptr<IApkSerializer> serializer;
261 if (output_format == ApkFormat::kBinary) {
Ryan Mitchell479fa392019-01-02 17:15:39 -0800262 serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), table_flattener_options,
263 xml_flattener_options));
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800264 } 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())
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000275 ? ArchiveEntry::kCompress : 0u)) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800276 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
Winsonf54c9a12019-01-23 12:39:40 -0800285 std::unordered_set<std::string> files_written;
286
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800287 // 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
Winsonf54c9a12019-01-23 12:39:40 -0800300 // 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())
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000304 << "failed to serialize file " << *file->path);
Winsonf54c9a12019-01-23 12:39:40 -0800305 return 1;
306 }
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800307 }
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())
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000338 << "failed to copy file " << path);
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800339 return 1;
340 }
341 }
342
343 return 0;
344}
345
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700346const char* ConvertCommand::kOutputFormatProto = "proto";
347const char* ConvertCommand::kOutputFormatBinary = "binary";
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000348
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700349int ConvertCommand::Action(const std::vector<std::string>& args) {
350 if (args.size() != 1) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800351 std::cerr << "must supply a single APK\n";
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700352 Usage(&std::cerr);
353 return 1;
354 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000355
Adam Lesinski8780eb62017-10-31 17:44:39 -0700356 Context context;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700357 const StringPiece& path = args[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700358 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
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800364 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(),
365 context.GetDiagnostics());
Adam Lesinski8780eb62017-10-31 17:44:39 -0700366 if (!app_info) {
367 return 1;
368 }
369
370 context.package_ = app_info.value().package;
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800371 unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
372 output_path_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700373 if (writer == nullptr) {
374 return 1;
375 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000376
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800377 ApkFormat format;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700378 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800379 format = ApkFormat::kBinary;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700380 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800381 format = ApkFormat::kProto;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000382 } else {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800383 context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: "
384 << output_format_.value());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000385 return 1;
386 }
387
Ryan Mitchell479fa392019-01-02 17:15:39 -0800388 return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
389 xml_flattener_options_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700390}
391
392} // namespace aapt