blob: 86b1f4c54debc61f32ff6ddf765052ea03a4a229 [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,
49 IArchiveWriter* writer) = 0;
50 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 +000060bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer,
61 IArchiveWriter* writer) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000062 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
63 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
64 << "failed to serialize AndroidManifest.xml");
Adam Lesinski8780eb62017-10-31 17:44:39 -070065 return false;
66 }
67
Tom Dobek725fb122017-12-08 14:19:01 +000068 if (apk->GetResourceTable() != nullptr) {
David Chaloupkab66db4e2018-01-15 12:35:41 +000069 // The table might be modified by below code.
70 auto converted_table = apk->GetResourceTable();
Tom Dobek725fb122017-12-08 14:19:01 +000071
72 // Resources
David Chaloupkab66db4e2018-01-15 12:35:41 +000073 for (const auto& package : converted_table->packages) {
Tom Dobek725fb122017-12-08 14:19:01 +000074 for (const auto& type : package->types) {
75 for (const auto& entry : type->entries) {
76 for (const auto& config_value : entry->values) {
David Chaloupkab66db4e2018-01-15 12:35:41 +000077 FileReference* file = ValueCast<FileReference>(config_value->value.get());
Tom Dobek725fb122017-12-08 14:19:01 +000078 if (file != nullptr) {
79 if (file->file == nullptr) {
80 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
81 << "no file associated with " << *file);
82 return false;
83 }
84
85 if (!serializer->SerializeFile(file, writer)) {
86 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
87 << "failed to serialize file " << *file->path);
88 return false;
89 }
90 } // file
91 } // config_value
92 } // entry
93 } // type
94 } // package
David Chaloupkab66db4e2018-01-15 12:35:41 +000095
96 // Converted resource table
97 if (!serializer->SerializeTable(converted_table, writer)) {
98 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
99 << "failed to serialize the resource table");
100 return false;
101 }
Adam Lesinski8780eb62017-10-31 17:44:39 -0700102 }
103
Pierre Lecesned55bef72017-11-10 22:31:01 +0000104 // Other files
105 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
106 while (iterator->HasNext()) {
107 io::IFile* file = iterator->Next();
Pierre Lecesned55bef72017-11-10 22:31:01 +0000108 std::string path = file->GetSource().path;
Pierre Lecesned55bef72017-11-10 22:31:01 +0000109
110 // Manifest, resource table and resources have already been taken care of.
111 if (path == kAndroidManifestPath ||
112 path == kApkResourceTablePath ||
113 path == kProtoResourceTablePath ||
114 path.find("res/") == 0) {
115 continue;
116 }
117
118 if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
119 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
120 << "failed to copy file " << path);
121 return false;
122 }
123 }
124
Adam Lesinski8780eb62017-10-31 17:44:39 -0700125 return true;
126}
127
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000128
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000129class BinaryApkSerializer : public IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000130 public:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000131 BinaryApkSerializer(IAaptContext* context, const Source& source,
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000132 const TableFlattenerOptions& options)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000133 : IApkSerializer(context, source), tableFlattenerOptions_(options) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000134
135 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000136 IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000137 BigBuffer buffer(4096);
138 XmlFlattenerOptions options = {};
139 options.use_utf16 = utf16;
Adam Lesinskibbf42972018-02-14 13:36:09 -0800140 options.keep_raw_values = true;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000141 XmlFlattener flattener(&buffer, options);
142 if (!flattener.Consume(context_, xml)) {
143 return false;
144 }
145
146 io::BigBufferInputStream input_stream(&buffer);
147 return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
148 writer);
149 }
150
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000151 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000152 BigBuffer buffer(4096);
153 TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
154 if (!table_flattener.Consume(context_, table)) {
155 return false;
156 }
157
158 io::BigBufferInputStream input_stream(&buffer);
159 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
160 ArchiveEntry::kAlign, writer);
161 }
162
David Chaloupkab66db4e2018-01-15 12:35:41 +0000163 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000164 if (file->type == ResourceFile::Type::kProtoXml) {
165 unique_ptr<io::InputStream> in = file->file->OpenInputStream();
166 if (in == nullptr) {
167 context_->GetDiagnostics()->Error(DiagMessage(source_)
168 << "failed to open file " << *file->path);
169 return false;
170 }
171
172 pb::XmlNode pb_node;
173 io::ZeroCopyInputAdaptor adaptor(in.get());
174 if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
175 context_->GetDiagnostics()->Error(DiagMessage(source_)
176 << "failed to parse proto XML " << *file->path);
177 return false;
178 }
179
180 std::string error;
181 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
182 if (xml == nullptr) {
183 context_->GetDiagnostics()->Error(DiagMessage(source_)
184 << "failed to deserialize proto XML "
185 << *file->path << ": " << error);
186 return false;
187 }
188
189 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
190 context_->GetDiagnostics()->Error(DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000191 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000192 return false;
193 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000194
195 file->type = ResourceFile::Type::kBinaryXml;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000196 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000197 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
198 context_->GetDiagnostics()->Error(DiagMessage(source_)
199 << "failed to copy file " << *file->path);
200 return false;
201 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000202 }
203
204 return true;
205 }
206
207 private:
208 TableFlattenerOptions tableFlattenerOptions_;
209
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000210 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
211};
212
213class ProtoApkSerializer : public IApkSerializer {
214 public:
215 ProtoApkSerializer(IAaptContext* context, const Source& source)
216 : IApkSerializer(context, source) {}
217
218 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
219 IArchiveWriter* writer) override {
220 pb::XmlNode pb_node;
221 SerializeXmlResourceToPb(*xml, &pb_node);
222 return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer);
223 }
224
225 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
226 pb::ResourceTable pb_table;
Ryan Mitchella15c2a82018-03-26 11:05:31 -0700227 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000228 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
229 ArchiveEntry::kCompress, writer);
230 }
231
David Chaloupkab66db4e2018-01-15 12:35:41 +0000232 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000233 if (file->type == ResourceFile::Type::kBinaryXml) {
234 std::unique_ptr<io::IData> data = file->file->OpenAsData();
235 if (!data) {
236 context_->GetDiagnostics()->Error(DiagMessage(source_)
237 << "failed to open file " << *file->path);
238 return false;
239 }
240
241 std::string error;
242 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
243 if (xml == nullptr) {
244 context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: "
245 << error);
246 return false;
247 }
248
249 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
250 context_->GetDiagnostics()->Error(DiagMessage(source_)
251 << "failed to serialize to proto XML: " << *file->path);
252 return false;
253 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000254
255 file->type = ResourceFile::Type::kProtoXml;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000256 } else {
257 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
258 context_->GetDiagnostics()->Error(DiagMessage(source_)
259 << "failed to copy file " << *file->path);
260 return false;
261 }
262 }
263
264 return true;
265 }
266
267 private:
268 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000269};
270
Adam Lesinski8780eb62017-10-31 17:44:39 -0700271class Context : public IAaptContext {
272 public:
273 Context() : mangler_({}), symbols_(&mangler_) {
274 }
275
276 PackageType GetPackageType() override {
277 return PackageType::kApp;
278 }
279
280 SymbolTable* GetExternalSymbols() override {
281 return &symbols_;
282 }
283
284 IDiagnostics* GetDiagnostics() override {
285 return &diag_;
286 }
287
288 const std::string& GetCompilationPackage() override {
289 return package_;
290 }
291
292 uint8_t GetPackageId() override {
293 // Nothing should call this.
294 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
295 return 0;
296 }
297
298 NameMangler* GetNameMangler() override {
299 UNIMPLEMENTED(FATAL);
300 return nullptr;
301 }
302
303 bool IsVerbose() override {
304 return verbose_;
305 }
306
307 int GetMinSdkVersion() override {
308 return 0u;
309 }
310
311 bool verbose_ = false;
312 std::string package_;
313
314 private:
315 DISALLOW_COPY_AND_ASSIGN(Context);
316
317 NameMangler mangler_;
318 SymbolTable symbols_;
319 StdErrDiagnostics diag_;
320};
321
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700322const char* ConvertCommand::kOutputFormatProto = "proto";
323const char* ConvertCommand::kOutputFormatBinary = "binary";
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000324
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700325int ConvertCommand::Action(const std::vector<std::string>& args) {
326 if (args.size() != 1) {
327 std::cerr << "must supply a single proto APK\n";
328 Usage(&std::cerr);
329 return 1;
330 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000331
Adam Lesinski8780eb62017-10-31 17:44:39 -0700332 Context context;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700333 const StringPiece& path = args[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700334 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
335 if (apk == nullptr) {
336 context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
337 return 1;
338 }
339
340 Maybe<AppInfo> app_info =
341 ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
342 if (!app_info) {
343 return 1;
344 }
345
346 context.package_ = app_info.value().package;
347
348 unique_ptr<IArchiveWriter> writer =
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700349 CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700350 if (writer == nullptr) {
351 return 1;
352 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000353
354 unique_ptr<IApkSerializer> serializer;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700355 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
356
357 serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options_));
358 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000359 serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
360 } else {
361 context.GetDiagnostics()->Error(DiagMessage(path)
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700362 << "Invalid value for flag --output-format: "
363 << output_format_.value());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000364 return 1;
365 }
366
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000367 return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700368}
369
370} // namespace aapt