blob: 4492f6b49cf02de34fb1c1b75fb68dd4c00b30b0 [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:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000046 IApkSerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000047
48 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070049 IArchiveWriter* writer, uint32_t compression_flags) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000050 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
David Chaloupkab66db4e2018-01-15 12:35:41 +000051 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000052
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000053 virtual ~IApkSerializer() = default;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000054
55 protected:
56 IAaptContext* context_;
57 Source source_;
58};
Adam Lesinski8780eb62017-10-31 17:44:39 -070059
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000060class BinaryApkSerializer : public IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000061 public:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000062 BinaryApkSerializer(IAaptContext* context, const Source& source,
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000063 const TableFlattenerOptions& options)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000064 : IApkSerializer(context, source), tableFlattenerOptions_(options) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000065
66 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070067 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000068 BigBuffer buffer(4096);
69 XmlFlattenerOptions options = {};
70 options.use_utf16 = utf16;
Adam Lesinskibbf42972018-02-14 13:36:09 -080071 options.keep_raw_values = true;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000072 XmlFlattener flattener(&buffer, options);
73 if (!flattener.Consume(context_, xml)) {
74 return false;
75 }
76
77 io::BigBufferInputStream input_stream(&buffer);
Ryan Mitchell05aebf42018-10-09 15:08:41 -070078 return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000079 }
80
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000081 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000082 BigBuffer buffer(4096);
83 TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
84 if (!table_flattener.Consume(context_, table)) {
85 return false;
86 }
87
88 io::BigBufferInputStream input_stream(&buffer);
89 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
90 ArchiveEntry::kAlign, writer);
91 }
92
David Chaloupkab66db4e2018-01-15 12:35:41 +000093 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000094 if (file->type == ResourceFile::Type::kProtoXml) {
95 unique_ptr<io::InputStream> in = file->file->OpenInputStream();
96 if (in == nullptr) {
97 context_->GetDiagnostics()->Error(DiagMessage(source_)
98 << "failed to open file " << *file->path);
99 return false;
100 }
101
102 pb::XmlNode pb_node;
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700103 io::ProtoInputStreamReader proto_reader(in.get());
104 if (!proto_reader.ReadMessage(&pb_node)) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000105 context_->GetDiagnostics()->Error(DiagMessage(source_)
106 << "failed to parse proto XML " << *file->path);
107 return false;
108 }
109
110 std::string error;
111 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
112 if (xml == nullptr) {
113 context_->GetDiagnostics()->Error(DiagMessage(source_)
114 << "failed to deserialize proto XML "
115 << *file->path << ": " << error);
116 return false;
117 }
118
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700119 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
120 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000121 context_->GetDiagnostics()->Error(DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000122 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000123 return false;
124 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000125
126 file->type = ResourceFile::Type::kBinaryXml;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000127 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000128 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
129 context_->GetDiagnostics()->Error(DiagMessage(source_)
130 << "failed to copy file " << *file->path);
131 return false;
132 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000133 }
134
135 return true;
136 }
137
138 private:
139 TableFlattenerOptions tableFlattenerOptions_;
140
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000141 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
142};
143
144class ProtoApkSerializer : public IApkSerializer {
145 public:
146 ProtoApkSerializer(IAaptContext* context, const Source& source)
147 : IApkSerializer(context, source) {}
148
149 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700150 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000151 pb::XmlNode pb_node;
152 SerializeXmlResourceToPb(*xml, &pb_node);
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700153 return io::CopyProtoToArchive(context_, &pb_node, path, compression_flags, writer);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000154 }
155
156 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
157 pb::ResourceTable pb_table;
Ryan Mitchella15c2a82018-03-26 11:05:31 -0700158 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000159 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
160 ArchiveEntry::kCompress, writer);
161 }
162
David Chaloupkab66db4e2018-01-15 12:35:41 +0000163 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000164 if (file->type == ResourceFile::Type::kBinaryXml) {
165 std::unique_ptr<io::IData> data = file->file->OpenAsData();
166 if (!data) {
167 context_->GetDiagnostics()->Error(DiagMessage(source_)
168 << "failed to open file " << *file->path);
169 return false;
170 }
171
172 std::string error;
173 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
174 if (xml == nullptr) {
175 context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: "
176 << error);
177 return false;
178 }
179
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700180 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
181 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000182 context_->GetDiagnostics()->Error(DiagMessage(source_)
183 << "failed to serialize to proto XML: " << *file->path);
184 return false;
185 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000186
187 file->type = ResourceFile::Type::kProtoXml;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000188 } else {
189 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
190 context_->GetDiagnostics()->Error(DiagMessage(source_)
191 << "failed to copy file " << *file->path);
192 return false;
193 }
194 }
195
196 return true;
197 }
198
199 private:
200 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000201};
202
Adam Lesinski8780eb62017-10-31 17:44:39 -0700203class Context : public IAaptContext {
204 public:
205 Context() : mangler_({}), symbols_(&mangler_) {
206 }
207
208 PackageType GetPackageType() override {
209 return PackageType::kApp;
210 }
211
212 SymbolTable* GetExternalSymbols() override {
213 return &symbols_;
214 }
215
216 IDiagnostics* GetDiagnostics() override {
217 return &diag_;
218 }
219
220 const std::string& GetCompilationPackage() override {
221 return package_;
222 }
223
224 uint8_t GetPackageId() override {
225 // Nothing should call this.
226 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
227 return 0;
228 }
229
230 NameMangler* GetNameMangler() override {
231 UNIMPLEMENTED(FATAL);
232 return nullptr;
233 }
234
235 bool IsVerbose() override {
236 return verbose_;
237 }
238
239 int GetMinSdkVersion() override {
240 return 0u;
241 }
242
243 bool verbose_ = false;
244 std::string package_;
245
246 private:
247 DISALLOW_COPY_AND_ASSIGN(Context);
248
249 NameMangler mangler_;
250 SymbolTable symbols_;
251 StdErrDiagnostics diag_;
252};
253
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800254int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
255 ApkFormat output_format, TableFlattenerOptions& options) {
256 // Do not change the ordering of strings in the values string pool
257 options.sort_stringpool_entries = false;
258
259 unique_ptr<IApkSerializer> serializer;
260 if (output_format == ApkFormat::kBinary) {
261 serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), options));
262 } else if (output_format == ApkFormat::kProto) {
263 serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
264 } else {
265 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
266 << "Cannot convert APK to unknown format");
267 return 1;
268 }
269
270 io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
271 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
272 output_writer, (manifest != nullptr && manifest->WasCompressed())
273 ? ArchiveEntry::kCompress : 0u)) {
274 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
275 << "failed to serialize AndroidManifest.xml");
276 return 1;
277 }
278
279 if (apk->GetResourceTable() != nullptr) {
280 // The table might be modified by below code.
281 auto converted_table = apk->GetResourceTable();
282
283 // Resources
284 for (const auto& package : converted_table->packages) {
285 for (const auto& type : package->types) {
286 for (const auto& entry : type->entries) {
287 for (const auto& config_value : entry->values) {
288 FileReference* file = ValueCast<FileReference>(config_value->value.get());
289 if (file != nullptr) {
290 if (file->file == nullptr) {
291 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
292 << "no file associated with " << *file);
293 return 1;
294 }
295
296 if (!serializer->SerializeFile(file, output_writer)) {
297 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
298 << "failed to serialize file " << *file->path);
299 return 1;
300 }
301 } // file
302 } // config_value
303 } // entry
304 } // type
305 } // package
306
307 // Converted resource table
308 if (!serializer->SerializeTable(converted_table, output_writer)) {
309 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
310 << "failed to serialize the resource table");
311 return 1;
312 }
313 }
314
315 // Other files
316 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
317 while (iterator->HasNext()) {
318 io::IFile* file = iterator->Next();
319 std::string path = file->GetSource().path;
320
321 // Manifest, resource table and resources have already been taken care of.
322 if (path == kAndroidManifestPath ||
323 path == kApkResourceTablePath ||
324 path == kProtoResourceTablePath ||
325 path.find("res/") == 0) {
326 continue;
327 }
328
329 if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
330 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
331 << "failed to copy file " << path);
332 return 1;
333 }
334 }
335
336 return 0;
337}
338
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700339const char* ConvertCommand::kOutputFormatProto = "proto";
340const char* ConvertCommand::kOutputFormatBinary = "binary";
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000341
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700342int ConvertCommand::Action(const std::vector<std::string>& args) {
343 if (args.size() != 1) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800344 std::cerr << "must supply a single APK\n";
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700345 Usage(&std::cerr);
346 return 1;
347 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000348
Adam Lesinski8780eb62017-10-31 17:44:39 -0700349 Context context;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700350 const StringPiece& path = args[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700351 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
352 if (apk == nullptr) {
353 context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
354 return 1;
355 }
356
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800357 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(),
358 context.GetDiagnostics());
Adam Lesinski8780eb62017-10-31 17:44:39 -0700359 if (!app_info) {
360 return 1;
361 }
362
363 context.package_ = app_info.value().package;
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800364 unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
365 output_path_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700366 if (writer == nullptr) {
367 return 1;
368 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000369
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800370 ApkFormat format;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700371 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800372 format = ApkFormat::kBinary;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700373 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800374 format = ApkFormat::kProto;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000375 } else {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800376 context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: "
377 << output_format_.value());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000378 return 1;
379 }
380
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800381 return Convert(&context, apk.get(), writer.get(), format, options_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700382}
383
384} // namespace aapt