|
17 | 17 | #include <sys/stat.h> |
18 | 18 |
|
19 | 19 | #include "base/logging.h" |
| 20 | +#include "base/strings/stringprintf.h" |
20 | 21 | #include "base/strings/utf_string_conversions.h" |
21 | 22 | #include "build/build_config.h" |
22 | 23 | #include "util/file/directory_reader.h" |
| 24 | +#include "util/file/file_helper.h" |
23 | 25 | #include "util/file/filesystem.h" |
24 | 26 |
|
| 27 | +#include <mpack.h> |
| 28 | + |
25 | 29 | namespace crashpad { |
26 | 30 |
|
27 | 31 | namespace { |
@@ -75,6 +79,100 @@ base::FilePath EnsureUniqueFile(const base::FilePath& dir, |
75 | 79 |
|
76 | 80 | return unique; |
77 | 81 | } |
| 82 | + |
| 83 | +// Escapes a string for JSON (double quotes, backslash, control chars) |
| 84 | +std::string EscapeJsonString(const std::string& input) { |
| 85 | + std::string output; |
| 86 | + output.reserve(input.size() + 8); |
| 87 | + for (char c : input) { |
| 88 | + switch (c) { |
| 89 | + case '\"': |
| 90 | + output += "\\\""; |
| 91 | + break; |
| 92 | + case '\\': |
| 93 | + output += "\\\\"; |
| 94 | + break; |
| 95 | + case '\b': |
| 96 | + output += "\\b"; |
| 97 | + break; |
| 98 | + case '\f': |
| 99 | + output += "\\f"; |
| 100 | + break; |
| 101 | + case '\n': |
| 102 | + output += "\\n"; |
| 103 | + break; |
| 104 | + case '\r': |
| 105 | + output += "\\r"; |
| 106 | + break; |
| 107 | + case '\t': |
| 108 | + output += "\\t"; |
| 109 | + break; |
| 110 | + default: |
| 111 | + if (static_cast<unsigned char>(c) < 0x20) { |
| 112 | + char buf[7]; |
| 113 | + snprintf(buf, sizeof(buf), "\\u%04x", c); |
| 114 | + output += buf; |
| 115 | + } else { |
| 116 | + output += c; |
| 117 | + } |
| 118 | + } |
| 119 | + } |
| 120 | + return output; |
| 121 | +} |
| 122 | + |
| 123 | +std::string MpackToJsonString(mpack_node_t node) { |
| 124 | + switch (mpack_node_type(node)) { |
| 125 | + case mpack_type_nil: |
| 126 | + return "null"; |
| 127 | + case mpack_type_bool: |
| 128 | + return mpack_node_bool(node) ? "true" : "false"; |
| 129 | + case mpack_type_int: |
| 130 | + return std::to_string(mpack_node_i64(node)); |
| 131 | + case mpack_type_uint: |
| 132 | + return std::to_string(mpack_node_u64(node)); |
| 133 | + case mpack_type_float: |
| 134 | + return std::to_string(mpack_node_float(node)); |
| 135 | + case mpack_type_double: |
| 136 | + return std::to_string(mpack_node_double(node)); |
| 137 | + case mpack_type_str: { |
| 138 | + return "\"" + |
| 139 | + EscapeJsonString( |
| 140 | + std::string(mpack_node_str(node), mpack_node_strlen(node))) + |
| 141 | + "\""; |
| 142 | + } |
| 143 | + case mpack_type_array: { |
| 144 | + std::string result = "["; |
| 145 | + size_t n = mpack_node_array_length(node); |
| 146 | + for (size_t i = 0; i < n; ++i) { |
| 147 | + if (i > 0) { |
| 148 | + result += ","; |
| 149 | + } |
| 150 | + result += MpackToJsonString(mpack_node_array_at(node, i)); |
| 151 | + } |
| 152 | + result += "]"; |
| 153 | + return result; |
| 154 | + } |
| 155 | + case mpack_type_map: { |
| 156 | + std::string result = "{"; |
| 157 | + size_t n = mpack_node_map_count(node); |
| 158 | + for (size_t i = 0; i < n; ++i) { |
| 159 | + if (i > 0) { |
| 160 | + result += ","; |
| 161 | + } |
| 162 | + mpack_node_t key = mpack_node_map_key_at(node, i); |
| 163 | + mpack_node_t val = mpack_node_map_value_at(node, i); |
| 164 | + std::string key_str(mpack_node_str(key), mpack_node_strlen(key)); |
| 165 | + result += "\"" + key_str + "\":" + MpackToJsonString(val); |
| 166 | + } |
| 167 | + result += "}"; |
| 168 | + return result; |
| 169 | + } |
| 170 | + default: |
| 171 | + LOG(ERROR) << "Unsupported mpack node type: " << mpack_node_type(node); |
| 172 | + return ""; |
| 173 | + } |
| 174 | +} |
| 175 | + |
78 | 176 | } // namespace |
79 | 177 |
|
80 | 178 | CrashReportDatabase::Report::Report() |
@@ -204,6 +302,179 @@ bool CrashReportDatabase::UploadReport::Initialize(const base::FilePath& path, |
204 | 302 | return reader_->Open(path); |
205 | 303 | } |
206 | 304 |
|
| 305 | +bool CrashReportDatabase::Envelope::Initialize(const base::FilePath& path) { |
| 306 | + path_ = path; |
| 307 | + if (path.empty()) { |
| 308 | + return false; |
| 309 | + } |
| 310 | + writer_ = std::make_unique<FileWriter>(); |
| 311 | + if (!writer_->Open( |
| 312 | + path, FileWriteMode::kReuseOrCreate, FilePermissions::kOwnerOnly)) { |
| 313 | + return false; |
| 314 | + } |
| 315 | + writer_->Seek(0, SEEK_END); |
| 316 | + return true; |
| 317 | +} |
| 318 | + |
| 319 | +CrashReportDatabase::Envelope::Envelope(const UUID& uuid) : uuid_(uuid) {} |
| 320 | + |
| 321 | +void CrashReportDatabase::Envelope::AddAttachments( |
| 322 | + const std::vector<base::FilePath>& attachments) { |
| 323 | + base::FilePath event; |
| 324 | + std::vector<base::FilePath> breadcrumbs; |
| 325 | + std::vector<base::FilePath> others; |
| 326 | + |
| 327 | + for (const auto& attachment : attachments) { |
| 328 | +#if BUILDFLAG(IS_WIN) |
| 329 | + std::string basename = base::WideToUTF8(attachment.BaseName().value()); |
| 330 | +#else |
| 331 | + std::string basename = attachment.BaseName().value(); |
| 332 | +#endif |
| 333 | + if (basename == "__sentry-event") { |
| 334 | + event = attachment; |
| 335 | + } else if (basename.rfind("__sentry-breadcrumb", 0) == 0) { |
| 336 | + breadcrumbs.push_back(attachment); |
| 337 | + } else { |
| 338 | + others.push_back(attachment); |
| 339 | + } |
| 340 | + } |
| 341 | + |
| 342 | + AddEvent(event, breadcrumbs); |
| 343 | + for (const auto& attachment : others) { |
| 344 | + AddAttachment(attachment); |
| 345 | + } |
| 346 | +} |
| 347 | + |
| 348 | +void CrashReportDatabase::Envelope::AddMinidump(FileReaderInterface* reader) { |
| 349 | + FileOffset size = reader->Seek(0, SEEK_END); |
| 350 | + std::string header = base::StringPrintf( |
| 351 | + "{\"type\":\"attachment\"," |
| 352 | + "\"length\":%zu," |
| 353 | + "\"attachment_type\":\"event.minidump\"," |
| 354 | + "\"filename\":\"%s.dmp\"}", |
| 355 | + static_cast<size_t>(size), |
| 356 | + uuid_.ToString().c_str()); |
| 357 | + writer_->Write("\n", 1); |
| 358 | + writer_->Write(header.data(), header.size()); |
| 359 | + writer_->Write("\n", 1); |
| 360 | + reader->Seek(0, SEEK_SET); |
| 361 | + CopyFileContent(reader, writer_.get()); |
| 362 | +} |
| 363 | + |
| 364 | +void CrashReportDatabase::Envelope::AddEvent( |
| 365 | + const base::FilePath& event, |
| 366 | + const std::vector<base::FilePath>& breadcrumbs) { |
| 367 | + std::string event_data; |
| 368 | + if (!LoggingReadEntireFile(event, &event_data)) { |
| 369 | + return; |
| 370 | + } |
| 371 | + |
| 372 | + mpack_tree_t event_obj; |
| 373 | + mpack_tree_init_data(&event_obj, event_data.data(), event_data.size()); |
| 374 | + mpack_tree_parse(&event_obj); |
| 375 | + std::string event_json = MpackToJsonString(mpack_tree_root(&event_obj)); |
| 376 | + mpack_tree_destroy(&event_obj); |
| 377 | + |
| 378 | + // read all breadcrumb files |
| 379 | + size_t max_breadcrumbs = 0; |
| 380 | + std::vector<std::unique_ptr<mpack_tree_t, mpack_error_t (*)(mpack_tree_t*)>> |
| 381 | + all_breadcrumbs; |
| 382 | + std::vector<std::string> breadcrumb_datas(breadcrumbs.size()); |
| 383 | + for (size_t i = 0; i < breadcrumbs.size(); ++i) { |
| 384 | + auto& breadcrumb_data = breadcrumb_datas[i]; |
| 385 | + if (!LoggingReadEntireFile(breadcrumbs[i], &breadcrumb_data)) { |
| 386 | + continue; |
| 387 | + } |
| 388 | + |
| 389 | + size_t count = 0; |
| 390 | + size_t offset = 0; |
| 391 | + while (offset < breadcrumb_data.size()) { |
| 392 | + auto breadcrumb_obj = |
| 393 | + std::unique_ptr<mpack_tree_t, mpack_error_t (*)(mpack_tree_t*)>( |
| 394 | + new mpack_tree_t, mpack_tree_destroy); |
| 395 | + mpack_tree_init_data(breadcrumb_obj.get(), |
| 396 | + breadcrumb_data.data() + offset, |
| 397 | + breadcrumb_data.size() - offset); |
| 398 | + mpack_tree_parse(breadcrumb_obj.get()); |
| 399 | + |
| 400 | + size_t size = mpack_tree_size(breadcrumb_obj.get()); |
| 401 | + all_breadcrumbs.push_back(std::move(breadcrumb_obj)); |
| 402 | + |
| 403 | + offset += size; |
| 404 | + count++; |
| 405 | + } |
| 406 | + max_breadcrumbs = std::max(max_breadcrumbs, count); |
| 407 | + } |
| 408 | + |
| 409 | + // sort breadcrumbs by timestamp and limit to max_breadcrumbs |
| 410 | + std::sort(all_breadcrumbs.begin(), |
| 411 | + all_breadcrumbs.end(), |
| 412 | + [](const auto& a, const auto& b) { |
| 413 | + mpack_node_t ts_a = |
| 414 | + mpack_node_map_cstr(mpack_tree_root(a.get()), "timestamp"); |
| 415 | + mpack_node_t ts_b = |
| 416 | + mpack_node_map_cstr(mpack_tree_root(b.get()), "timestamp"); |
| 417 | + return strcmp(mpack_node_str(ts_a), mpack_node_str(ts_b)) < 0; |
| 418 | + }); |
| 419 | + std::string breadcrumbs_json; |
| 420 | + size_t start = std::max<size_t>(0, all_breadcrumbs.size() - max_breadcrumbs); |
| 421 | + for (size_t i = start; i < all_breadcrumbs.size(); ++i) { |
| 422 | + if (!breadcrumbs_json.empty()) { |
| 423 | + breadcrumbs_json += ","; |
| 424 | + } |
| 425 | + const auto& breadcrumb_tree = all_breadcrumbs[i]; |
| 426 | + breadcrumbs_json += |
| 427 | + MpackToJsonString(mpack_tree_root(breadcrumb_tree.get())); |
| 428 | + } |
| 429 | + |
| 430 | + // write event with breadcrumbs |
| 431 | + event_json.erase(0, 1); // leading '{' |
| 432 | + event_json.pop_back(); // trailing '}' |
| 433 | + std::string payload = base::StringPrintf("{%s,\"breadcrumbs\":[%s]}", |
| 434 | + event_json.c_str(), |
| 435 | + breadcrumbs_json.c_str()); |
| 436 | + std::string header = base::StringPrintf( |
| 437 | + "{\"type\":\"event\"," |
| 438 | + "\"length\":%zu}", |
| 439 | + payload.size()); |
| 440 | + writer_->Write("\n", 1); |
| 441 | + writer_->Write(header.data(), header.size()); |
| 442 | + writer_->Write("\n", 1); |
| 443 | + writer_->Write(payload.data(), payload.size()); |
| 444 | + writer_->Write("\n", 1); |
| 445 | +} |
| 446 | + |
| 447 | +void CrashReportDatabase::Envelope::AddAttachment( |
| 448 | + const base::FilePath& attachment) { |
| 449 | + std::string payload; |
| 450 | + if (!LoggingReadEntireFile(attachment, &payload)) { |
| 451 | + return; |
| 452 | + } |
| 453 | + |
| 454 | +#if BUILDFLAG(IS_WIN) |
| 455 | + std::string basename = base::WideToUTF8(attachment.BaseName().value()); |
| 456 | +#else |
| 457 | + std::string basename = attachment.BaseName().value(); |
| 458 | +#endif |
| 459 | + |
| 460 | + std::string header = base::StringPrintf( |
| 461 | + "{\"type\":\"attachment\"," |
| 462 | + "\"length\":%zu," |
| 463 | + "\"attachment_type\":\"event.attachment\"," |
| 464 | + "\"filename\": \"%s\"}", |
| 465 | + payload.size(), |
| 466 | + EscapeJsonString(basename).c_str()); |
| 467 | + writer_->Write("\n", 1); |
| 468 | + writer_->Write(header.data(), header.size()); |
| 469 | + writer_->Write("\n", 1); |
| 470 | + writer_->Write(payload.data(), payload.size()); |
| 471 | + writer_->Write("\n", 1); |
| 472 | +} |
| 473 | + |
| 474 | +void CrashReportDatabase::Envelope::Finish() { |
| 475 | + writer_->Close(); |
| 476 | +} |
| 477 | + |
207 | 478 | CrashReportDatabase::OperationStatus CrashReportDatabase::RecordUploadComplete( |
208 | 479 | std::unique_ptr<const UploadReport> report_in, |
209 | 480 | const std::string& id) { |
|
0 commit comments