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 "Convert.h" 18 19 #include <vector> 20 21 #include "android-base/macros.h" 22 #include "androidfw/StringPiece.h" 23 24 #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" 30 #include "format/proto/ProtoSerialize.h" 31 #include "io/BigBufferStream.h" 32 #include "io/Util.h" 33 #include "process/IResourceTableConsumer.h" 34 #include "process/SymbolTable.h" 35 #include "util/Util.h" 36 37 using ::android::StringPiece; 38 using ::android::base::StringPrintf; 39 using ::std::unique_ptr; 40 using ::std::vector; 41 42 namespace aapt { 43 44 class IApkSerializer { 45 public: 46 IApkSerializer(IAaptContext* context, const Source& source) : context_(context), 47 source_(source) {} 48 49 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, 50 IArchiveWriter* writer, uint32_t compression_flags) = 0; 51 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0; 52 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0; 53 54 virtual ~IApkSerializer() = default; 55 56 protected: 57 IAaptContext* context_; 58 Source source_; 59 }; 60 61 class BinaryApkSerializer : public IApkSerializer { 62 public: 63 BinaryApkSerializer(IAaptContext* context, const Source& source, 64 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) {} 69 70 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, 71 IArchiveWriter* writer, uint32_t compression_flags) override { 72 BigBuffer buffer(4096); 73 xml_flattener_options_.use_utf16 = utf16; 74 XmlFlattener flattener(&buffer, xml_flattener_options_); 75 if (!flattener.Consume(context_, xml)) { 76 return false; 77 } 78 79 io::BigBufferInputStream input_stream(&buffer); 80 return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer); 81 } 82 83 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { 84 BigBuffer buffer(4096); 85 TableFlattener table_flattener(table_flattener_options_, &buffer); 86 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 95 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override { 96 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; 105 io::ProtoInputStreamReader proto_reader(in.get()); 106 if (!proto_reader.ReadMessage(&pb_node)) { 107 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 121 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer, 122 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) { 123 context_->GetDiagnostics()->Error(DiagMessage(source_) 124 << "failed to serialize to binary XML: " << *file->path); 125 return false; 126 } 127 128 file->type = ResourceFile::Type::kBinaryXml; 129 } else { 130 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 } 135 } 136 137 return true; 138 } 139 140 private: 141 TableFlattenerOptions table_flattener_options_; 142 XmlFlattenerOptions xml_flattener_options_; 143 144 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer); 145 }; 146 147 class 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, 153 IArchiveWriter* writer, uint32_t compression_flags) override { 154 pb::XmlNode pb_node; 155 SerializeXmlResourceToPb(*xml, &pb_node); 156 return io::CopyProtoToArchive(context_, &pb_node, path, compression_flags, writer); 157 } 158 159 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { 160 pb::ResourceTable pb_table; 161 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics()); 162 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, 163 ArchiveEntry::kCompress, writer); 164 } 165 166 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override { 167 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_) 171 << "failed to open file " << *file->path); 172 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: " 179 << error); 180 return false; 181 } 182 183 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer, 184 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) { 185 context_->GetDiagnostics()->Error(DiagMessage(source_) 186 << "failed to serialize to proto XML: " << *file->path); 187 return false; 188 } 189 190 file->type = ResourceFile::Type::kProtoXml; 191 } 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); 204 }; 205 206 class 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 257 int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer, 258 ApkFormat output_format, TableFlattenerOptions table_flattener_options, 259 XmlFlattenerOptions xml_flattener_options) { 260 unique_ptr<IApkSerializer> serializer; 261 if (output_format == ApkFormat::kBinary) { 262 serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), table_flattener_options, 263 xml_flattener_options)); 264 } 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()) 275 ? ArchiveEntry::kCompress : 0u)) { 276 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 285 std::unordered_set<std::string> files_written; 286 287 // 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 300 // 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()) 304 << "failed to serialize file " << *file->path); 305 return 1; 306 } 307 } 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()) 338 << "failed to copy file " << path); 339 return 1; 340 } 341 } 342 343 return 0; 344 } 345 346 const char* ConvertCommand::kOutputFormatProto = "proto"; 347 const char* ConvertCommand::kOutputFormatBinary = "binary"; 348 349 int ConvertCommand::Action(const std::vector<std::string>& args) { 350 if (args.size() != 1) { 351 std::cerr << "must supply a single APK\n"; 352 Usage(&std::cerr); 353 return 1; 354 } 355 356 Context context; 357 const StringPiece& path = args[0]; 358 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 364 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), 365 context.GetDiagnostics()); 366 if (!app_info) { 367 return 1; 368 } 369 370 context.package_ = app_info.value().package; 371 unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), 372 output_path_); 373 if (writer == nullptr) { 374 return 1; 375 } 376 377 ApkFormat format; 378 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) { 379 format = ApkFormat::kBinary; 380 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) { 381 format = ApkFormat::kProto; 382 } else { 383 context.GetDiagnostics()->Error(DiagMessage(path) << "Invalid value for flag --output-format: " 384 << output_format_.value()); 385 return 1; 386 } 387 388 return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_, 389 xml_flattener_options_); 390 } 391 392 } // namespace aapt 393