1/* 2 * Copyright (C) 2016 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 <cinttypes> 18#include <vector> 19 20#include "android-base/stringprintf.h" 21#include "androidfw/StringPiece.h" 22 23#include "Debug.h" 24#include "Diagnostics.h" 25#include "Flags.h" 26#include "format/Container.h" 27#include "format/binary/BinaryResourceParser.h" 28#include "format/proto/ProtoDeserialize.h" 29#include "io/FileStream.h" 30#include "io/ZipArchive.h" 31#include "process/IResourceTableConsumer.h" 32#include "text/Printer.h" 33#include "util/Files.h" 34 35using ::aapt::text::Printer; 36using ::android::StringPiece; 37using ::android::base::StringPrintf; 38 39namespace aapt { 40 41struct DumpOptions { 42 DebugPrintTableOptions print_options; 43 44 // The path to a file within an APK to dump. 45 Maybe<std::string> file_to_dump_path; 46}; 47 48static const char* ResourceFileTypeToString(const ResourceFile::Type& type) { 49 switch (type) { 50 case ResourceFile::Type::kPng: 51 return "PNG"; 52 case ResourceFile::Type::kBinaryXml: 53 return "BINARY_XML"; 54 case ResourceFile::Type::kProtoXml: 55 return "PROTO_XML"; 56 default: 57 break; 58 } 59 return "UNKNOWN"; 60} 61 62static void DumpCompiledFile(const ResourceFile& file, const Source& source, off64_t offset, 63 size_t len, Printer* printer) { 64 printer->Print("Resource: "); 65 printer->Println(file.name.to_string()); 66 67 printer->Print("Config: "); 68 printer->Println(file.config.to_string()); 69 70 printer->Print("Source: "); 71 printer->Println(file.source.to_string()); 72 73 printer->Print("Type: "); 74 printer->Println(ResourceFileTypeToString(file.type)); 75 76 printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len)); 77} 78 79static bool DumpXmlFile(IAaptContext* context, io::IFile* file, bool proto, 80 text::Printer* printer) { 81 std::unique_ptr<xml::XmlResource> doc; 82 if (proto) { 83 std::unique_ptr<io::InputStream> in = file->OpenInputStream(); 84 if (in == nullptr) { 85 context->GetDiagnostics()->Error(DiagMessage() << "failed to open file"); 86 return false; 87 } 88 89 io::ZeroCopyInputAdaptor adaptor(in.get()); 90 pb::XmlNode pb_node; 91 if (!pb_node.ParseFromZeroCopyStream(&adaptor)) { 92 context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as proto XML"); 93 return false; 94 } 95 96 std::string err; 97 doc = DeserializeXmlResourceFromPb(pb_node, &err); 98 if (doc == nullptr) { 99 context->GetDiagnostics()->Error(DiagMessage() << "failed to deserialize proto XML"); 100 return false; 101 } 102 printer->Println("Proto XML"); 103 } else { 104 std::unique_ptr<io::IData> data = file->OpenAsData(); 105 if (data == nullptr) { 106 context->GetDiagnostics()->Error(DiagMessage() << "failed to open file"); 107 return false; 108 } 109 110 std::string err; 111 doc = xml::Inflate(data->data(), data->size(), &err); 112 if (doc == nullptr) { 113 context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as binary XML"); 114 return false; 115 } 116 printer->Println("Binary XML"); 117 } 118 119 Debug::DumpXml(*doc, printer); 120 return true; 121} 122 123static bool TryDumpFile(IAaptContext* context, const std::string& file_path, 124 const DumpOptions& options) { 125 // Use a smaller buffer so that there is less latency for dumping to stdout. 126 constexpr size_t kStdOutBufferSize = 1024u; 127 io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize); 128 Printer printer(&fout); 129 130 std::string err; 131 std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err); 132 if (zip) { 133 ResourceTable table; 134 bool proto = false; 135 if (io::IFile* file = zip->FindFile("resources.pb")) { 136 proto = true; 137 138 std::unique_ptr<io::IData> data = file->OpenAsData(); 139 if (data == nullptr) { 140 context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb"); 141 return false; 142 } 143 144 pb::ResourceTable pb_table; 145 if (!pb_table.ParseFromArray(data->data(), data->size())) { 146 context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb"); 147 return false; 148 } 149 150 if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) { 151 context->GetDiagnostics()->Error(DiagMessage(file_path) 152 << "failed to parse table: " << err); 153 return false; 154 } 155 } else if (io::IFile* file = zip->FindFile("resources.arsc")) { 156 std::unique_ptr<io::IData> data = file->OpenAsData(); 157 if (!data) { 158 context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.arsc"); 159 return false; 160 } 161 162 BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path), 163 data->data(), data->size()); 164 if (!parser.Parse()) { 165 return false; 166 } 167 } 168 169 if (!options.file_to_dump_path) { 170 if (proto) { 171 printer.Println("Proto APK"); 172 } else { 173 printer.Println("Binary APK"); 174 } 175 Debug::PrintTable(table, options.print_options, &printer); 176 return true; 177 } 178 179 io::IFile* file = zip->FindFile(options.file_to_dump_path.value()); 180 if (file == nullptr) { 181 context->GetDiagnostics()->Error(DiagMessage(file_path) 182 << "file '" << options.file_to_dump_path.value() 183 << "' not found in APK"); 184 return false; 185 } 186 return DumpXmlFile(context, file, proto, &printer); 187 } 188 189 err.clear(); 190 191 io::FileInputStream input(file_path); 192 if (input.HadError()) { 193 context->GetDiagnostics()->Error(DiagMessage(file_path) 194 << "failed to open file: " << input.GetError()); 195 return false; 196 } 197 198 // Try as a compiled file. 199 ContainerReader reader(&input); 200 if (reader.HadError()) { 201 context->GetDiagnostics()->Error(DiagMessage(file_path) 202 << "failed to read container: " << reader.GetError()); 203 return false; 204 } 205 206 printer.Println("AAPT2 Container (APC)"); 207 ContainerReaderEntry* entry; 208 while ((entry = reader.Next()) != nullptr) { 209 if (entry->Type() == ContainerEntryType::kResTable) { 210 printer.Println("kResTable"); 211 212 pb::ResourceTable pb_table; 213 if (!entry->GetResTable(&pb_table)) { 214 context->GetDiagnostics()->Error(DiagMessage(file_path) 215 << "failed to parse proto table: " << entry->GetError()); 216 continue; 217 } 218 219 ResourceTable table; 220 err.clear(); 221 if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) { 222 context->GetDiagnostics()->Error(DiagMessage(file_path) 223 << "failed to parse table: " << err); 224 continue; 225 } 226 227 printer.Indent(); 228 Debug::PrintTable(table, options.print_options, &printer); 229 printer.Undent(); 230 } else if (entry->Type() == ContainerEntryType::kResFile) { 231 printer.Println("kResFile"); 232 pb::internal::CompiledFile pb_compiled_file; 233 off64_t offset; 234 size_t length; 235 if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) { 236 context->GetDiagnostics()->Error( 237 DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError()); 238 continue; 239 } 240 241 ResourceFile file; 242 std::string error; 243 if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) { 244 context->GetDiagnostics()->Warn(DiagMessage(file_path) 245 << "failed to parse compiled file: " << error); 246 continue; 247 } 248 249 printer.Indent(); 250 DumpCompiledFile(file, Source(file_path), offset, length, &printer); 251 printer.Undent(); 252 } 253 } 254 return true; 255} 256 257namespace { 258 259class DumpContext : public IAaptContext { 260 public: 261 PackageType GetPackageType() override { 262 // Doesn't matter. 263 return PackageType::kApp; 264 } 265 266 IDiagnostics* GetDiagnostics() override { 267 return &diagnostics_; 268 } 269 270 NameMangler* GetNameMangler() override { 271 UNIMPLEMENTED(FATAL); 272 return nullptr; 273 } 274 275 const std::string& GetCompilationPackage() override { 276 static std::string empty; 277 return empty; 278 } 279 280 uint8_t GetPackageId() override { 281 return 0; 282 } 283 284 SymbolTable* GetExternalSymbols() override { 285 UNIMPLEMENTED(FATAL); 286 return nullptr; 287 } 288 289 bool IsVerbose() override { 290 return verbose_; 291 } 292 293 void SetVerbose(bool val) { 294 verbose_ = val; 295 } 296 297 int GetMinSdkVersion() override { 298 return 0; 299 } 300 301 private: 302 StdErrDiagnostics diagnostics_; 303 bool verbose_ = false; 304}; 305 306} // namespace 307 308// Entry point for dump command. 309int Dump(const std::vector<StringPiece>& args) { 310 bool verbose = false; 311 bool no_values = false; 312 DumpOptions options; 313 Flags flags = Flags() 314 .OptionalSwitch("--no-values", 315 "Suppresses output of values when displaying resource tables.", 316 &no_values) 317 .OptionalFlag("--file", "Dumps the specified file from the APK passed as arg.", 318 &options.file_to_dump_path) 319 .OptionalSwitch("-v", "increase verbosity of output", &verbose); 320 if (!flags.Parse("aapt2 dump", args, &std::cerr)) { 321 return 1; 322 } 323 324 DumpContext context; 325 context.SetVerbose(verbose); 326 327 options.print_options.show_sources = true; 328 options.print_options.show_values = !no_values; 329 for (const std::string& arg : flags.GetArgs()) { 330 if (!TryDumpFile(&context, arg, options)) { 331 return 1; 332 } 333 } 334 return 0; 335} 336 337} // namespace aapt 338