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 <vector> 18 19 #include "android-base/macros.h" 20 #include "androidfw/StringPiece.h" 21 22 #include "Flags.h" 23 #include "LoadedApk.h" 24 #include "ValueVisitor.h" 25 #include "cmd/Util.h" 26 #include "format/binary/TableFlattener.h" 27 #include "format/binary/XmlFlattener.h" 28 #include "format/proto/ProtoDeserialize.h" 29 #include "format/proto/ProtoSerialize.h" 30 #include "io/BigBufferStream.h" 31 #include "io/Util.h" 32 #include "process/IResourceTableConsumer.h" 33 #include "process/SymbolTable.h" 34 #include "util/Util.h" 35 36 using ::android::StringPiece; 37 using ::android::base::StringPrintf; 38 using ::std::unique_ptr; 39 using ::std::vector; 40 41 namespace aapt { 42 43 class IApkSerializer { 44 public: 45 IApkSerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {} 46 47 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, 48 IArchiveWriter* writer) = 0; 49 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0; 50 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0; 51 52 virtual ~IApkSerializer() = default; 53 54 protected: 55 IAaptContext* context_; 56 Source source_; 57 }; 58 59 bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer, 60 IArchiveWriter* writer) { 61 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) { 62 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) 63 << "failed to serialize AndroidManifest.xml"); 64 return false; 65 } 66 67 if (apk->GetResourceTable() != nullptr) { 68 // The table might be modified by below code. 69 auto converted_table = apk->GetResourceTable(); 70 71 // Resources 72 for (const auto& package : converted_table->packages) { 73 for (const auto& type : package->types) { 74 for (const auto& entry : type->entries) { 75 for (const auto& config_value : entry->values) { 76 FileReference* file = ValueCast<FileReference>(config_value->value.get()); 77 if (file != nullptr) { 78 if (file->file == nullptr) { 79 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) 80 << "no file associated with " << *file); 81 return false; 82 } 83 84 if (!serializer->SerializeFile(file, writer)) { 85 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) 86 << "failed to serialize file " << *file->path); 87 return false; 88 } 89 } // file 90 } // config_value 91 } // entry 92 } // type 93 } // package 94 95 // Converted resource table 96 if (!serializer->SerializeTable(converted_table, writer)) { 97 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) 98 << "failed to serialize the resource table"); 99 return false; 100 } 101 } 102 103 // Other files 104 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator(); 105 while (iterator->HasNext()) { 106 io::IFile* file = iterator->Next(); 107 108 std::string path = file->GetSource().path; 109 // The name of the path has the format "<zip-file-name>@<path-to-file>". 110 path = path.substr(path.find('@') + 1); 111 112 // Manifest, resource table and resources have already been taken care of. 113 if (path == kAndroidManifestPath || 114 path == kApkResourceTablePath || 115 path == kProtoResourceTablePath || 116 path.find("res/") == 0) { 117 continue; 118 } 119 120 if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) { 121 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) 122 << "failed to copy file " << path); 123 return false; 124 } 125 } 126 127 return true; 128 } 129 130 131 class BinaryApkSerializer : public IApkSerializer { 132 public: 133 BinaryApkSerializer(IAaptContext* context, const Source& source, 134 const TableFlattenerOptions& options) 135 : IApkSerializer(context, source), tableFlattenerOptions_(options) {} 136 137 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, 138 IArchiveWriter* writer) override { 139 BigBuffer buffer(4096); 140 XmlFlattenerOptions options = {}; 141 options.use_utf16 = utf16; 142 options.keep_raw_values = true; 143 XmlFlattener flattener(&buffer, options); 144 if (!flattener.Consume(context_, xml)) { 145 return false; 146 } 147 148 io::BigBufferInputStream input_stream(&buffer); 149 return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress, 150 writer); 151 } 152 153 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { 154 BigBuffer buffer(4096); 155 TableFlattener table_flattener(tableFlattenerOptions_, &buffer); 156 if (!table_flattener.Consume(context_, table)) { 157 return false; 158 } 159 160 io::BigBufferInputStream input_stream(&buffer); 161 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath, 162 ArchiveEntry::kAlign, writer); 163 } 164 165 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override { 166 if (file->type == ResourceFile::Type::kProtoXml) { 167 unique_ptr<io::InputStream> in = file->file->OpenInputStream(); 168 if (in == nullptr) { 169 context_->GetDiagnostics()->Error(DiagMessage(source_) 170 << "failed to open file " << *file->path); 171 return false; 172 } 173 174 pb::XmlNode pb_node; 175 io::ZeroCopyInputAdaptor adaptor(in.get()); 176 if (!pb_node.ParseFromZeroCopyStream(&adaptor)) { 177 context_->GetDiagnostics()->Error(DiagMessage(source_) 178 << "failed to parse proto XML " << *file->path); 179 return false; 180 } 181 182 std::string error; 183 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error); 184 if (xml == nullptr) { 185 context_->GetDiagnostics()->Error(DiagMessage(source_) 186 << "failed to deserialize proto XML " 187 << *file->path << ": " << error); 188 return false; 189 } 190 191 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) { 192 context_->GetDiagnostics()->Error(DiagMessage(source_) 193 << "failed to serialize to binary XML: " << *file->path); 194 return false; 195 } 196 197 file->type = ResourceFile::Type::kBinaryXml; 198 } else { 199 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { 200 context_->GetDiagnostics()->Error(DiagMessage(source_) 201 << "failed to copy file " << *file->path); 202 return false; 203 } 204 } 205 206 return true; 207 } 208 209 private: 210 TableFlattenerOptions tableFlattenerOptions_; 211 212 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer); 213 }; 214 215 class ProtoApkSerializer : public IApkSerializer { 216 public: 217 ProtoApkSerializer(IAaptContext* context, const Source& source) 218 : IApkSerializer(context, source) {} 219 220 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, 221 IArchiveWriter* writer) override { 222 pb::XmlNode pb_node; 223 SerializeXmlResourceToPb(*xml, &pb_node); 224 return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer); 225 } 226 227 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { 228 pb::ResourceTable pb_table; 229 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics()); 230 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, 231 ArchiveEntry::kCompress, writer); 232 } 233 234 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override { 235 if (file->type == ResourceFile::Type::kBinaryXml) { 236 std::unique_ptr<io::IData> data = file->file->OpenAsData(); 237 if (!data) { 238 context_->GetDiagnostics()->Error(DiagMessage(source_) 239 << "failed to open file " << *file->path); 240 return false; 241 } 242 243 std::string error; 244 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error); 245 if (xml == nullptr) { 246 context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: " 247 << error); 248 return false; 249 } 250 251 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) { 252 context_->GetDiagnostics()->Error(DiagMessage(source_) 253 << "failed to serialize to proto XML: " << *file->path); 254 return false; 255 } 256 257 file->type = ResourceFile::Type::kProtoXml; 258 } else { 259 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { 260 context_->GetDiagnostics()->Error(DiagMessage(source_) 261 << "failed to copy file " << *file->path); 262 return false; 263 } 264 } 265 266 return true; 267 } 268 269 private: 270 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer); 271 }; 272 273 class Context : public IAaptContext { 274 public: 275 Context() : mangler_({}), symbols_(&mangler_) { 276 } 277 278 PackageType GetPackageType() override { 279 return PackageType::kApp; 280 } 281 282 SymbolTable* GetExternalSymbols() override { 283 return &symbols_; 284 } 285 286 IDiagnostics* GetDiagnostics() override { 287 return &diag_; 288 } 289 290 const std::string& GetCompilationPackage() override { 291 return package_; 292 } 293 294 uint8_t GetPackageId() override { 295 // Nothing should call this. 296 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary"; 297 return 0; 298 } 299 300 NameMangler* GetNameMangler() override { 301 UNIMPLEMENTED(FATAL); 302 return nullptr; 303 } 304 305 bool IsVerbose() override { 306 return verbose_; 307 } 308 309 int GetMinSdkVersion() override { 310 return 0u; 311 } 312 313 bool verbose_ = false; 314 std::string package_; 315 316 private: 317 DISALLOW_COPY_AND_ASSIGN(Context); 318 319 NameMangler mangler_; 320 SymbolTable symbols_; 321 StdErrDiagnostics diag_; 322 }; 323 324 int Convert(const vector<StringPiece>& args) { 325 326 static const char* kOutputFormatProto = "proto"; 327 static const char* kOutputFormatBinary = "binary"; 328 329 Context context; 330 std::string output_path; 331 Maybe<std::string> output_format; 332 TableFlattenerOptions options; 333 Flags flags = 334 Flags() 335 .RequiredFlag("-o", "Output path", &output_path) 336 .OptionalFlag("--output-format", StringPrintf("Format of the output. Accepted values are " 337 "'%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, 338 kOutputFormatBinary, kOutputFormatBinary), &output_format) 339 .OptionalSwitch("--enable-sparse-encoding", 340 "Enables encoding sparse entries using a binary search tree.\n" 341 "This decreases APK size at the cost of resource retrieval performance.", 342 &options.use_sparse_entries) 343 .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_); 344 if (!flags.Parse("aapt2 convert", args, &std::cerr)) { 345 return 1; 346 } 347 348 if (flags.GetArgs().size() != 1) { 349 std::cerr << "must supply a single proto APK\n"; 350 flags.Usage("aapt2 convert", &std::cerr); 351 return 1; 352 } 353 354 const StringPiece& path = flags.GetArgs()[0]; 355 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics()); 356 if (apk == nullptr) { 357 context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK"); 358 return 1; 359 } 360 361 Maybe<AppInfo> app_info = 362 ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics()); 363 if (!app_info) { 364 return 1; 365 } 366 367 context.package_ = app_info.value().package; 368 369 unique_ptr<IArchiveWriter> writer = 370 CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path); 371 if (writer == nullptr) { 372 return 1; 373 } 374 375 unique_ptr<IApkSerializer> serializer; 376 if (!output_format || output_format.value() == kOutputFormatBinary) { 377 serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options)); 378 } else if (output_format.value() == kOutputFormatProto) { 379 serializer.reset(new ProtoApkSerializer(&context, apk->GetSource())); 380 } else { 381 context.GetDiagnostics()->Error(DiagMessage(path) 382 << "Invalid value for flag --output-format: " 383 << output_format.value()); 384 return 1; 385 } 386 387 388 return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1; 389 } 390 391 } // namespace aapt 392