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
35 using ::aapt::text::Printer;
36 using ::android::StringPiece;
37 using ::android::base::StringPrintf;
38
39 namespace aapt {
40
41 struct 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
ResourceFileTypeToString(const ResourceFile::Type & type)48 static 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
DumpCompiledFile(const ResourceFile & file,const Source & source,off64_t offset,size_t len,Printer * printer)62 static 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
DumpXmlFile(IAaptContext * context,io::IFile * file,bool proto,text::Printer * printer)79 static 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
TryDumpFile(IAaptContext * context,const std::string & file_path,const DumpOptions & options)123 static 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
257 namespace {
258
259 class DumpContext : public IAaptContext {
260 public:
GetPackageType()261 PackageType GetPackageType() override {
262 // Doesn't matter.
263 return PackageType::kApp;
264 }
265
GetDiagnostics()266 IDiagnostics* GetDiagnostics() override {
267 return &diagnostics_;
268 }
269
GetNameMangler()270 NameMangler* GetNameMangler() override {
271 UNIMPLEMENTED(FATAL);
272 return nullptr;
273 }
274
GetCompilationPackage()275 const std::string& GetCompilationPackage() override {
276 static std::string empty;
277 return empty;
278 }
279
GetPackageId()280 uint8_t GetPackageId() override {
281 return 0;
282 }
283
GetExternalSymbols()284 SymbolTable* GetExternalSymbols() override {
285 UNIMPLEMENTED(FATAL);
286 return nullptr;
287 }
288
IsVerbose()289 bool IsVerbose() override {
290 return verbose_;
291 }
292
SetVerbose(bool val)293 void SetVerbose(bool val) {
294 verbose_ = val;
295 }
296
GetMinSdkVersion()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.
Dump(const std::vector<StringPiece> & args)309 int 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