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