blob: 7a74ba925ba0def680801a1fdb4e9050cd27d740 [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,
Ryan Mitchell479fa392019-01-02 17:15:39 -080063 const TableFlattenerOptions& table_flattener_options,
64 const XmlFlattenerOptions& xml_flattener_options)
65 : IApkSerializer(context, source),
66 table_flattener_options_(table_flattener_options),
67 xml_flattener_options_(xml_flattener_options) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000068
69 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070070 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000071 BigBuffer buffer(4096);
Ryan Mitchell479fa392019-01-02 17:15:39 -080072 xml_flattener_options_.use_utf16 = utf16;
73 XmlFlattener flattener(&buffer, xml_flattener_options_);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000074 if (!flattener.Consume(context_, xml)) {
75 return false;
76 }
77
78 io::BigBufferInputStream input_stream(&buffer);
Ryan Mitchell05aebf42018-10-09 15:08:41 -070079 return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000080 }
81
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000082 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000083 BigBuffer buffer(4096);
Ryan Mitchell479fa392019-01-02 17:15:39 -080084 TableFlattener table_flattener(table_flattener_options_, &buffer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000085 if (!table_flattener.Consume(context_, table)) {
86 return false;
87 }
88
89 io::BigBufferInputStream input_stream(&buffer);
90 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
91 ArchiveEntry::kAlign, writer);
92 }
93
David Chaloupkab66db4e2018-01-15 12:35:41 +000094 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000095 if (file->type == ResourceFile::Type::kProtoXml) {
96 unique_ptr<io::InputStream> in = file->file->OpenInputStream();
97 if (in == nullptr) {
98 context_->GetDiagnostics()->Error(DiagMessage(source_)
99 << "failed to open file " << *file->path);
100 return false;
101 }
102
103 pb::XmlNode pb_node;
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700104 io::ProtoInputStreamReader proto_reader(in.get());
105 if (!proto_reader.ReadMessage(&pb_node)) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000106 context_->GetDiagnostics()->Error(DiagMessage(source_)
107 << "failed to parse proto XML " << *file->path);
108 return false;
109 }
110
111 std::string error;
112 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
113 if (xml == nullptr) {
114 context_->GetDiagnostics()->Error(DiagMessage(source_)
115 << "failed to deserialize proto XML "
116 << *file->path << ": " << error);
117 return false;
118 }
119
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700120 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
121 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000122 context_->GetDiagnostics()->Error(DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000123 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000124 return false;
125 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000126
127 file->type = ResourceFile::Type::kBinaryXml;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000128 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000129 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
130 context_->GetDiagnostics()->Error(DiagMessage(source_)
131 << "failed to copy file " << *file->path);
132 return false;
133 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000134 }
135
136 return true;
137 }
138
139 private:
Ryan Mitchell479fa392019-01-02 17:15:39 -0800140 TableFlattenerOptions table_flattener_options_;
141 XmlFlattenerOptions xml_flattener_options_;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000142
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000143 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
144};
145
146class ProtoApkSerializer : public IApkSerializer {
147 public:
148 ProtoApkSerializer(IAaptContext* context, const Source& source)
149 : IApkSerializer(context, source) {}
150
151 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700152 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000153 pb::XmlNode pb_node;
154 SerializeXmlResourceToPb(*xml, &pb_node);
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700155 return io::CopyProtoToArchive(context_, &pb_node, path, compression_flags, writer);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000156 }
157
158 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
159 pb::ResourceTable pb_table;
Ryan Mitchella15c2a82018-03-26 11:05:31 -0700160 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000161 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
162 ArchiveEntry::kCompress, writer);
163 }
164
David Chaloupkab66db4e2018-01-15 12:35:41 +0000165 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000166 if (file->type == ResourceFile::Type::kBinaryXml) {
167 std::unique_ptr<io::IData> data = file->file->OpenAsData();
168 if (!data) {
169 context_->GetDiagnostics()->Error(DiagMessage(source_)
170 << "failed to open file " << *file->path);
171 return false;
172 }
173
174 std::string error;
175 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
176 if (xml == nullptr) {
177 context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: "
178 << error);
179 return false;
180 }
181
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700182 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
183 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000184 context_->GetDiagnostics()->Error(DiagMessage(source_)
185 << "failed to serialize to proto XML: " << *file->path);
186 return false;
187 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000188
189 file->type = ResourceFile::Type::kProtoXml;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000190 } else {
191 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
192 context_->GetDiagnostics()->Error(DiagMessage(source_)
193 << "failed to copy file " << *file->path);
194 return false;
195 }
196 }
197
198 return true;
199 }
200
201 private:
202 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000203};
204
Adam Lesinski8780eb62017-10-31 17:44:39 -0700205class Context : public IAaptContext {
206 public:
207 Context() : mangler_({}), symbols_(&mangler_) {
208 }
209
210 PackageType GetPackageType() override {
211 return PackageType::kApp;
212 }
213
214 SymbolTable* GetExternalSymbols() override {
215 return &symbols_;
216 }
217
218 IDiagnostics* GetDiagnostics() override {
219 return &diag_;
220 }
221
222 const std::string& GetCompilationPackage() override {
223 return package_;
224 }
225
226 uint8_t GetPackageId() override {
227 // Nothing should call this.
228 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
229 return 0;
230 }
231
232 NameMangler* GetNameMangler() override {
233 UNIMPLEMENTED(FATAL);
234 return nullptr;
235 }
236
237 bool IsVerbose() override {
238 return verbose_;
239 }
240
241 int GetMinSdkVersion() override {
242 return 0u;
243 }
244
245 bool verbose_ = false;
246 std::string package_;
247
248 private:
249 DISALLOW_COPY_AND_ASSIGN(Context);
250
251 NameMangler mangler_;
252 SymbolTable symbols_;
253 StdErrDiagnostics diag_;
254};
255
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800256int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
Ryan Mitchell479fa392019-01-02 17:15:39 -0800257 ApkFormat output_format, TableFlattenerOptions table_flattener_options,
258 XmlFlattenerOptions xml_flattener_options) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800259 // Do not change the ordering of strings in the values string pool
Ryan Mitchell479fa392019-01-02 17:15:39 -0800260 table_flattener_options.sort_stringpool_entries = false;
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800261
262 unique_ptr<IApkSerializer> serializer;
263 if (output_format == ApkFormat::kBinary) {
Ryan Mitchell479fa392019-01-02 17:15:39 -0800264 serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), table_flattener_options,
265 xml_flattener_options));
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800266 } else if (output_format == ApkFormat::kProto) {
267 serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
268 } else {
269 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
270 << "Cannot convert APK to unknown format");
271 return 1;
272 }
273
274 io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
275 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
276 output_writer, (manifest != nullptr && manifest->WasCompressed())
277 ? ArchiveEntry::kCompress : 0u)) {
278 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
279 << "failed to serialize AndroidManifest.xml");
280 return 1;
281 }
282
283 if (apk->GetResourceTable() != nullptr) {
284 // The table might be modified by below code.
285 auto converted_table = apk->GetResourceTable();
286
Winsonf54c9a12019-01-23 12:39:40 -0800287 std::unordered_set<std::string> files_written;
288
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800289 // Resources
290 for (const auto& package : converted_table->packages) {
291 for (const auto& type : package->types) {
292 for (const auto& entry : type->entries) {
293 for (const auto& config_value : entry->values) {
294 FileReference* file = ValueCast<FileReference>(config_value->value.get());
295 if (file != nullptr) {
296 if (file->file == nullptr) {
297 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
298 << "no file associated with " << *file);
299 return 1;
300 }
301
Winsonf54c9a12019-01-23 12:39:40 -0800302 // Only serialize if we haven't seen this file before
303 if (files_written.insert(*file->path).second) {
304 if (!serializer->SerializeFile(file, output_writer)) {
305 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
306 << "failed to serialize file "
307 << *file->path);
308 return 1;
309 }
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800310 }
311 } // file
312 } // config_value
313 } // entry
314 } // type
315 } // package
316
317 // Converted resource table
318 if (!serializer->SerializeTable(converted_table, output_writer)) {
319 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
320 << "failed to serialize the resource table");
321 return 1;
322 }
323 }
324
325 // Other files
326 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
327 while (iterator->HasNext()) {
328 io::IFile* file = iterator->Next();
329 std::string path = file->GetSource().path;
330
331 // Manifest, resource table and resources have already been taken care of.
332 if (path == kAndroidManifestPath ||
333 path == kApkResourceTablePath ||
334 path == kProtoResourceTablePath ||
335 path.find("res/") == 0) {
336 continue;
337 }
338
339 if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
340 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
341 << "failed to copy file " << path);
342 return 1;
343 }
344 }
345
346 return 0;
347}
348
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700349const char* ConvertCommand::kOutputFormatProto = "proto";
350const char* ConvertCommand::kOutputFormatBinary = "binary";
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000351
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700352int ConvertCommand::Action(const std::vector<std::string>& args) {
353 if (args.size() != 1) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800354 std::cerr << "must supply a single APK\n";
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700355 Usage(&std::cerr);
356 return 1;
357 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000358
Adam Lesinski8780eb62017-10-31 17:44:39 -0700359 Context context;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700360 const StringPiece& path = args[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700361 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
362 if (apk == nullptr) {
363 context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
364 return 1;
365 }
366
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800367 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(),
368 context.GetDiagnostics());
Adam Lesinski8780eb62017-10-31 17:44:39 -0700369 if (!app_info) {
370 return 1;
371 }
372
373 context.package_ = app_info.value().package;
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800374 unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
375 output_path_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700376 if (writer == nullptr) {
377 return 1;
378 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000379
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800380 ApkFormat format;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700381 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800382 format = ApkFormat::kBinary;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700383 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800384 format = ApkFormat::kProto;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000385 } else {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800386 context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: "
387 << output_format_.value());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000388 return 1;
389 }
390
Ryan Mitchell479fa392019-01-02 17:15:39 -0800391 return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
392 xml_flattener_options_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700393}
394
395} // namespace aapt