1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "crashreporter.h"
7
8 #ifdef _MSC_VER
9 // Disable exception handler warnings.
10 # pragma warning(disable : 4530)
11 #endif
12
13 #include <fstream>
14 #include <iomanip>
15 #include <sstream>
16 #include <memory>
17 #include <ctime>
18 #include <cstdlib>
19 #include <cstring>
20 #include <string>
21 #include <utility>
22
23 #ifdef XP_LINUX
24 # include <dlfcn.h>
25 #endif
26
27 #include "json/json.h"
28 #include "nss.h"
29 #include "sechash.h"
30
31 using std::ifstream;
32 using std::ios;
33 using std::istream;
34 using std::istringstream;
35 using std::ofstream;
36 using std::ostream;
37 using std::ostringstream;
38 using std::string;
39 using std::unique_ptr;
40 using std::vector;
41
42 namespace CrashReporter {
43
44 StringTable gStrings;
45 Json::Value gData;
46 string gSettingsPath;
47 string gEventsPath;
48 string gPingPath;
49 int gArgc;
50 char** gArgv;
51 bool gAutoSubmit;
52
53 enum SubmissionResult { Succeeded, Failed };
54
55 static unique_ptr<ofstream> gLogStream(nullptr);
56 static string gReporterDumpFile;
57 static string gExtraFile;
58 static string gMemoryFile;
59
60 static const char kExtraDataExtension[] = ".extra";
61 static const char kMemoryReportExtension[] = ".memory.json.gz";
62
UIError(const string & message)63 void UIError(const string& message) {
64 if (gAutoSubmit) {
65 return;
66 }
67
68 string errorMessage;
69 if (!gStrings[ST_CRASHREPORTERERROR].empty()) {
70 char buf[2048];
71 UI_SNPRINTF(buf, 2048, gStrings[ST_CRASHREPORTERERROR].c_str(),
72 message.c_str());
73 errorMessage = buf;
74 } else {
75 errorMessage = message;
76 }
77
78 UIError_impl(errorMessage);
79 }
80
Unescape(const string & str)81 static string Unescape(const string& str) {
82 string ret;
83 for (string::const_iterator iter = str.begin(); iter != str.end(); iter++) {
84 if (*iter == '\\') {
85 iter++;
86 if (*iter == '\\') {
87 ret.push_back('\\');
88 } else if (*iter == 'n') {
89 ret.push_back('\n');
90 } else if (*iter == 't') {
91 ret.push_back('\t');
92 }
93 } else {
94 ret.push_back(*iter);
95 }
96 }
97
98 return ret;
99 }
100
ReadStrings(istream & in,StringTable & strings,bool unescape)101 bool ReadStrings(istream& in, StringTable& strings, bool unescape) {
102 string currentSection;
103 while (!in.eof()) {
104 string line;
105 std::getline(in, line);
106 int sep = line.find('=');
107 if (sep >= 0) {
108 string key, value;
109 key = line.substr(0, sep);
110 value = line.substr(sep + 1);
111 if (unescape) value = Unescape(value);
112 strings[key] = value;
113 }
114 }
115
116 return true;
117 }
118
ReadStringsFromFile(const string & path,StringTable & strings,bool unescape)119 bool ReadStringsFromFile(const string& path, StringTable& strings,
120 bool unescape) {
121 ifstream* f = UIOpenRead(path, ios::in);
122 bool success = false;
123 if (f->is_open()) {
124 success = ReadStrings(*f, strings, unescape);
125 f->close();
126 }
127
128 delete f;
129 return success;
130 }
131
ReadExtraFile(const string & aExtraDataPath,Json::Value & aExtra)132 static bool ReadExtraFile(const string& aExtraDataPath, Json::Value& aExtra) {
133 bool success = false;
134 ifstream* f = UIOpenRead(aExtraDataPath, ios::in);
135 if (f->is_open()) {
136 Json::CharReaderBuilder builder;
137 success = parseFromStream(builder, *f, &aExtra, nullptr);
138 }
139
140 delete f;
141 return success;
142 }
143
Basename(const string & file)144 static string Basename(const string& file) {
145 string::size_type slashIndex = file.rfind(UI_DIR_SEPARATOR);
146 if (slashIndex != string::npos) {
147 return file.substr(slashIndex + 1);
148 }
149 return file;
150 }
151
GetDumpLocalID()152 static string GetDumpLocalID() {
153 string localId = Basename(gReporterDumpFile);
154 string::size_type dot = localId.rfind('.');
155
156 if (dot == string::npos) return "";
157
158 return localId.substr(0, dot);
159 }
160
ReadEventFile(const string & aPath,string & aEventVersion,string & aTime,string & aUuid,Json::Value & aData)161 static bool ReadEventFile(const string& aPath, string& aEventVersion,
162 string& aTime, string& aUuid, Json::Value& aData) {
163 bool res = false;
164 ifstream* f = UIOpenRead(aPath, ios::binary);
165
166 if (f->is_open()) {
167 std::getline(*f, aEventVersion, '\n');
168 res = f->good();
169 std::getline(*f, aTime, '\n');
170 res &= f->good();
171 std::getline(*f, aUuid, '\n');
172 res &= f->good();
173
174 if (res) {
175 Json::CharReaderBuilder builder;
176 res = parseFromStream(builder, *f, &aData, nullptr);
177 }
178 }
179
180 delete f;
181 return res;
182 }
183
OverwriteEventFile(const string & aPath,const string & aEventVersion,const string & aTime,const string & aUuid,const Json::Value & aData)184 static void OverwriteEventFile(const string& aPath, const string& aEventVersion,
185 const string& aTime, const string& aUuid,
186 const Json::Value& aData) {
187 ofstream* f = UIOpenWrite(aPath, ios::binary | ios::trunc);
188 if (f->is_open()) {
189 f->write(aEventVersion.c_str(), aEventVersion.length()) << '\n';
190 f->write(aTime.c_str(), aTime.length()) << '\n';
191 f->write(aUuid.c_str(), aUuid.length()) << '\n';
192
193 Json::StreamWriterBuilder builder;
194 builder["indentation"] = "";
195 std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
196 writer->write(aData, f);
197 *f << "\n";
198 }
199
200 delete f;
201 }
202
UpdateEventFile(const Json::Value & aExtraData,const string & aHash,const string & aPingUuid)203 static void UpdateEventFile(const Json::Value& aExtraData, const string& aHash,
204 const string& aPingUuid) {
205 if (gEventsPath.empty()) {
206 // If there is no path for finding the crash event, skip this step.
207 return;
208 }
209
210 string localId = GetDumpLocalID();
211 string path = gEventsPath + UI_DIR_SEPARATOR + localId;
212 string eventVersion;
213 string crashTime;
214 string crashUuid;
215 Json::Value eventData;
216
217 if (!ReadEventFile(path, eventVersion, crashTime, crashUuid, eventData)) {
218 return;
219 }
220
221 if (!aHash.empty()) {
222 eventData["MinidumpSha256Hash"] = aHash;
223 }
224
225 if (!aPingUuid.empty()) {
226 eventData["CrashPingUUID"] = aPingUuid;
227 }
228
229 if (aExtraData.isMember("StackTraces")) {
230 eventData["StackTraces"] = aExtraData["StackTraces"];
231 }
232
233 OverwriteEventFile(path, eventVersion, crashTime, crashUuid, eventData);
234 }
235
WriteSubmissionEvent(SubmissionResult result,const string & remoteId)236 static void WriteSubmissionEvent(SubmissionResult result,
237 const string& remoteId) {
238 if (gEventsPath.empty()) {
239 // If there is no path for writing the submission event, skip it.
240 return;
241 }
242
243 string localId = GetDumpLocalID();
244 string fpath = gEventsPath + UI_DIR_SEPARATOR + localId + "-submission";
245 ofstream* f = UIOpenWrite(fpath, ios::binary);
246 time_t tm;
247 time(&tm);
248
249 if (f->is_open()) {
250 *f << "crash.submission.1\n";
251 *f << tm << "\n";
252 *f << localId << "\n";
253 *f << (result == Succeeded ? "true" : "false") << "\n";
254 *f << remoteId;
255
256 f->close();
257 }
258
259 delete f;
260 }
261
LogMessage(const std::string & message)262 void LogMessage(const std::string& message) {
263 if (gLogStream.get()) {
264 char date[64];
265 time_t tm;
266 time(&tm);
267 if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0)
268 date[0] = '\0';
269 (*gLogStream) << "[" << date << "] " << message << std::endl;
270 }
271 }
272
OpenLogFile()273 static void OpenLogFile() {
274 string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log";
275 gLogStream.reset(UIOpenWrite(logPath, ios::app));
276 }
277
ReadConfig()278 static bool ReadConfig() {
279 string iniPath;
280 if (!UIGetIniPath(iniPath)) {
281 return false;
282 }
283
284 if (!ReadStringsFromFile(iniPath, gStrings, true)) return false;
285
286 // See if we have a string override file, if so process it
287 char* overrideEnv = getenv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE");
288 if (overrideEnv && *overrideEnv && UIFileExists(overrideEnv))
289 ReadStringsFromFile(overrideEnv, gStrings, true);
290
291 return true;
292 }
293
GetAdditionalFilename(const string & dumpfile,const char * extension)294 static string GetAdditionalFilename(const string& dumpfile,
295 const char* extension) {
296 string filename(dumpfile);
297 int dot = filename.rfind('.');
298 if (dot < 0) return "";
299
300 filename.replace(dot, filename.length() - dot, extension);
301 return filename;
302 }
303
MoveCrashData(const string & toDir,string & dumpfile,string & extrafile,string & memoryfile)304 static bool MoveCrashData(const string& toDir, string& dumpfile,
305 string& extrafile, string& memoryfile) {
306 if (!UIEnsurePathExists(toDir)) {
307 UIError(gStrings[ST_ERROR_CREATEDUMPDIR]);
308 return false;
309 }
310
311 string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile);
312 string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile);
313 string newMemory = toDir + UI_DIR_SEPARATOR + Basename(memoryfile);
314
315 if (!UIMoveFile(dumpfile, newDump)) {
316 UIError(gStrings[ST_ERROR_DUMPFILEMOVE]);
317 return false;
318 }
319
320 if (!UIMoveFile(extrafile, newExtra)) {
321 UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]);
322 return false;
323 }
324
325 if (!memoryfile.empty()) {
326 // Ignore errors from moving the memory file
327 if (!UIMoveFile(memoryfile, newMemory)) {
328 UIDeleteFile(memoryfile);
329 newMemory.erase();
330 }
331 memoryfile = newMemory;
332 }
333
334 dumpfile = newDump;
335 extrafile = newExtra;
336
337 return true;
338 }
339
AddSubmittedReport(const string & serverResponse)340 static bool AddSubmittedReport(const string& serverResponse) {
341 StringTable responseItems;
342 istringstream in(serverResponse);
343 ReadStrings(in, responseItems, false);
344
345 if (responseItems.find("StopSendingReportsFor") != responseItems.end()) {
346 // server wants to tell us to stop sending reports for a certain version
347 string reportPath = gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" +
348 responseItems["StopSendingReportsFor"];
349
350 ofstream* reportFile = UIOpenWrite(reportPath, ios::trunc);
351 if (reportFile->is_open()) {
352 // don't really care about the contents
353 *reportFile << 1 << "\n";
354 reportFile->close();
355 }
356 delete reportFile;
357 }
358
359 if (responseItems.find("Discarded") != responseItems.end()) {
360 // server discarded this report... save it so the user can resubmit it
361 // manually
362 return false;
363 }
364
365 if (responseItems.find("CrashID") == responseItems.end()) return false;
366
367 string submittedDir = gSettingsPath + UI_DIR_SEPARATOR + "submitted";
368 if (!UIEnsurePathExists(submittedDir)) {
369 return false;
370 }
371
372 string path =
373 submittedDir + UI_DIR_SEPARATOR + responseItems["CrashID"] + ".txt";
374
375 ofstream* file = UIOpenWrite(path, ios::trunc);
376 if (!file->is_open()) {
377 delete file;
378 return false;
379 }
380
381 char buf[1024];
382 UI_SNPRINTF(buf, 1024, gStrings["CrashID"].c_str(),
383 responseItems["CrashID"].c_str());
384 *file << buf << "\n";
385
386 if (responseItems.find("ViewURL") != responseItems.end()) {
387 UI_SNPRINTF(buf, 1024, gStrings["CrashDetailsURL"].c_str(),
388 responseItems["ViewURL"].c_str());
389 *file << buf << "\n";
390 }
391
392 file->close();
393 delete file;
394
395 WriteSubmissionEvent(Succeeded, responseItems["CrashID"]);
396 return true;
397 }
398
DeleteDump()399 void DeleteDump() {
400 const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP");
401 if (!noDelete || *noDelete == '\0') {
402 if (!gReporterDumpFile.empty()) UIDeleteFile(gReporterDumpFile);
403 if (!gExtraFile.empty()) UIDeleteFile(gExtraFile);
404 if (!gMemoryFile.empty()) UIDeleteFile(gMemoryFile);
405 }
406 }
407
SendCompleted(bool success,const string & serverResponse)408 void SendCompleted(bool success, const string& serverResponse) {
409 if (success) {
410 if (AddSubmittedReport(serverResponse)) {
411 DeleteDump();
412 } else {
413 string directory = gReporterDumpFile;
414 int slashpos = directory.find_last_of("/\\");
415 if (slashpos < 2) return;
416 directory.resize(slashpos);
417 UIPruneSavedDumps(directory);
418 WriteSubmissionEvent(Failed, "");
419 }
420 } else {
421 WriteSubmissionEvent(Failed, "");
422 }
423 }
424
ComputeDumpHash()425 static string ComputeDumpHash() {
426 #ifdef XP_LINUX
427 // On Linux we rely on the system-provided libcurl which uses nss so we have
428 // to also use the system-provided nss instead of the ones we have bundled.
429 const char* libnssNames[] = {
430 "libnss3.so",
431 # ifndef HAVE_64BIT_BUILD
432 // 32-bit versions on 64-bit hosts
433 "/usr/lib32/libnss3.so",
434 # endif
435 };
436 void* lib = nullptr;
437
438 for (const char* libname : libnssNames) {
439 lib = dlopen(libname, RTLD_NOW);
440
441 if (lib) {
442 break;
443 }
444 }
445
446 if (!lib) {
447 return "";
448 }
449
450 SECStatus (*NSS_Initialize)(const char*, const char*, const char*,
451 const char*, PRUint32);
452 HASHContext* (*HASH_Create)(HASH_HashType);
453 void (*HASH_Destroy)(HASHContext*);
454 void (*HASH_Begin)(HASHContext*);
455 void (*HASH_Update)(HASHContext*, const unsigned char*, unsigned int);
456 void (*HASH_End)(HASHContext*, unsigned char*, unsigned int*, unsigned int);
457
458 *(void**)(&NSS_Initialize) = dlsym(lib, "NSS_Initialize");
459 *(void**)(&HASH_Create) = dlsym(lib, "HASH_Create");
460 *(void**)(&HASH_Destroy) = dlsym(lib, "HASH_Destroy");
461 *(void**)(&HASH_Begin) = dlsym(lib, "HASH_Begin");
462 *(void**)(&HASH_Update) = dlsym(lib, "HASH_Update");
463 *(void**)(&HASH_End) = dlsym(lib, "HASH_End");
464
465 if (!HASH_Create || !HASH_Destroy || !HASH_Begin || !HASH_Update ||
466 !HASH_End) {
467 return "";
468 }
469 #endif
470 // Minimal NSS initialization so we can use the hash functions
471 const PRUint32 kNssFlags = NSS_INIT_READONLY | NSS_INIT_NOROOTINIT |
472 NSS_INIT_NOMODDB | NSS_INIT_NOCERTDB;
473 if (NSS_Initialize(nullptr, "", "", "", kNssFlags) != SECSuccess) {
474 return "";
475 }
476
477 HASHContext* hashContext = HASH_Create(HASH_AlgSHA256);
478
479 if (!hashContext) {
480 return "";
481 }
482
483 HASH_Begin(hashContext);
484
485 ifstream* f = UIOpenRead(gReporterDumpFile, ios::binary);
486 bool error = false;
487
488 // Read the minidump contents
489 if (f->is_open()) {
490 uint8_t buff[4096];
491
492 do {
493 f->read((char*)buff, sizeof(buff));
494
495 if (f->bad()) {
496 error = true;
497 break;
498 }
499
500 HASH_Update(hashContext, buff, f->gcount());
501 } while (!f->eof());
502
503 f->close();
504 } else {
505 error = true;
506 }
507
508 delete f;
509
510 // Finalize the hash computation
511 uint8_t result[SHA256_LENGTH];
512 uint32_t resultLen = 0;
513
514 HASH_End(hashContext, result, &resultLen, SHA256_LENGTH);
515
516 if (resultLen != SHA256_LENGTH) {
517 error = true;
518 }
519
520 HASH_Destroy(hashContext);
521
522 if (!error) {
523 ostringstream hash;
524
525 for (size_t i = 0; i < SHA256_LENGTH; i++) {
526 hash << std::setw(2) << std::setfill('0') << std::hex
527 << static_cast<unsigned int>(result[i]);
528 }
529
530 return hash.str();
531 }
532 return ""; // If we encountered an error, return an empty hash
533 }
534
535 } // namespace CrashReporter
536
537 using namespace CrashReporter;
538
539 Json::Value kEmptyJsonString("");
540
RewriteStrings(Json::Value & aExtraData)541 void RewriteStrings(Json::Value& aExtraData) {
542 // rewrite some UI strings with the values from the query parameters
543 string product = aExtraData.get("ProductName", kEmptyJsonString).asString();
544 Json::Value mozilla("Mozilla");
545 string vendor = aExtraData.get("Vendor", mozilla).asString();
546
547 char buf[4096];
548 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(),
549 vendor.c_str());
550 gStrings[ST_CRASHREPORTERTITLE] = buf;
551
552 string str = gStrings[ST_CRASHREPORTERPRODUCTERROR];
553 // Only do the replacement here if the string has two
554 // format specifiers to start. Otherwise
555 // we assume it has the product name hardcoded.
556 string::size_type pos = str.find("%s");
557 if (pos != string::npos) pos = str.find("%s", pos + 2);
558 if (pos != string::npos) {
559 // Leave a format specifier for UIError to fill in
560 UI_SNPRINTF(buf, sizeof(buf),
561 gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(), product.c_str(),
562 "%s");
563 gStrings[ST_CRASHREPORTERERROR] = buf;
564 } else {
565 // product name is hardcoded
566 gStrings[ST_CRASHREPORTERERROR] = str;
567 }
568
569 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(),
570 product.c_str());
571 gStrings[ST_CRASHREPORTERDESCRIPTION] = buf;
572
573 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CHECKSUBMIT].c_str(),
574 vendor.c_str());
575 gStrings[ST_CHECKSUBMIT] = buf;
576
577 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_RESTART].c_str(), product.c_str());
578 gStrings[ST_RESTART] = buf;
579
580 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_QUIT].c_str(), product.c_str());
581 gStrings[ST_QUIT] = buf;
582
583 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_ERROR_ENDOFLIFE].c_str(),
584 product.c_str());
585 gStrings[ST_ERROR_ENDOFLIFE] = buf;
586 }
587
CheckEndOfLifed(const Json::Value & aVersion)588 bool CheckEndOfLifed(const Json::Value& aVersion) {
589 if (!aVersion.isString()) {
590 return false;
591 }
592
593 string reportPath =
594 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + aVersion.asString();
595 return UIFileExists(reportPath);
596 }
597
GetProgramPath(const string & exename)598 static string GetProgramPath(const string& exename) {
599 string path = gArgv[0];
600 size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
601 path.erase(pos);
602 #ifdef XP_MACOSX
603 // On macOS the crash reporter client is shipped as an application bundle
604 // contained within Firefox' main application bundle. So when it's invoked
605 // its current working directory looks like:
606 // Firefox.app/Contents/MacOS/crashreporter.app/Contents/MacOS/
607 // The other applications we ship with Firefox are stored in the main bundle
608 // (Firefox.app/Contents/MacOS/) so we we need to go back three directories
609 // to reach them.
610 path.append("../../../");
611 #endif // XP_MACOSX
612 path.append(exename + BIN_SUFFIX);
613
614 return path;
615 }
616
main(int argc,char ** argv)617 int main(int argc, char** argv) {
618 gArgc = argc;
619 gArgv = argv;
620
621 string autoSubmitEnv = UIGetEnv("MOZ_CRASHREPORTER_AUTO_SUBMIT");
622 gAutoSubmit = !autoSubmitEnv.empty();
623
624 if (!ReadConfig()) {
625 UIError("Couldn't read configuration.");
626 return 0;
627 }
628
629 if (!UIInit()) {
630 return 0;
631 }
632
633 if (argc > 1) {
634 gReporterDumpFile = argv[1];
635 }
636
637 if (gReporterDumpFile.empty()) {
638 // no dump file specified, run the default UI
639 if (!gAutoSubmit) {
640 UIShowDefaultUI();
641 }
642 } else {
643 // Start by running minidump analyzer to gather stack traces.
644 string reporterDumpFile = gReporterDumpFile;
645 vector<string> args = {reporterDumpFile};
646 string dumpAllThreadsEnv = UIGetEnv("MOZ_CRASHREPORTER_DUMP_ALL_THREADS");
647 if (!dumpAllThreadsEnv.empty()) {
648 args.insert(args.begin(), "--full");
649 }
650 UIRunProgram(GetProgramPath(UI_MINIDUMP_ANALYZER_FILENAME), args,
651 /* wait */ true);
652
653 // go ahead with the crash reporter
654 gExtraFile = GetAdditionalFilename(gReporterDumpFile, kExtraDataExtension);
655 if (gExtraFile.empty()) {
656 UIError(gStrings[ST_ERROR_BADARGUMENTS]);
657 return 0;
658 }
659
660 if (!UIFileExists(gExtraFile)) {
661 UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]);
662 return 0;
663 }
664
665 gMemoryFile =
666 GetAdditionalFilename(gReporterDumpFile, kMemoryReportExtension);
667 if (!UIFileExists(gMemoryFile)) {
668 gMemoryFile.erase();
669 }
670
671 Json::Value extraData;
672 if (!ReadExtraFile(gExtraFile, extraData)) {
673 UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
674 return 0;
675 }
676
677 if (!extraData.isMember("ProductName")) {
678 UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
679 return 0;
680 }
681
682 // There is enough information in the extra file to rewrite strings
683 // to be product specific
684 RewriteStrings(extraData);
685
686 if (!extraData.isMember("ServerURL")) {
687 UIError(gStrings[ST_ERROR_NOSERVERURL]);
688 return 0;
689 }
690
691 // Hopefully the settings path exists in the environment. Try that before
692 // asking the platform-specific code to guess.
693 gSettingsPath = UIGetEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY");
694 if (gSettingsPath.empty()) {
695 string product =
696 extraData.get("ProductName", kEmptyJsonString).asString();
697 string vendor = extraData.get("Vendor", kEmptyJsonString).asString();
698
699 if (!UIGetSettingsPath(vendor, product, gSettingsPath)) {
700 gSettingsPath.clear();
701 }
702 }
703
704 if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
705 UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
706 return 0;
707 }
708
709 OpenLogFile();
710
711 gEventsPath = UIGetEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY");
712 gPingPath = UIGetEnv("MOZ_CRASHREPORTER_PING_DIRECTORY");
713
714 // Assemble and send the crash ping
715 string hash = ComputeDumpHash();
716
717 string pingUuid;
718 SendCrashPing(extraData, hash, pingUuid, gPingPath);
719 UpdateEventFile(extraData, hash, pingUuid);
720
721 if (!UIFileExists(gReporterDumpFile)) {
722 UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
723 return 0;
724 }
725
726 string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
727 if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile,
728 gMemoryFile)) {
729 return 0;
730 }
731
732 string sendURL = extraData.get("ServerURL", kEmptyJsonString).asString();
733 // we don't need to actually send these
734 extraData.removeMember("ServerURL");
735 extraData.removeMember("StackTraces");
736
737 extraData["Throttleable"] = "1";
738
739 // re-set XUL_APP_FILE for xulrunner wrapped apps
740 const char* appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE");
741 if (appfile && *appfile) {
742 const char prefix[] = "XUL_APP_FILE=";
743 char* env = (char*)malloc(strlen(appfile) + strlen(prefix) + 1);
744 if (!env) {
745 UIError("Out of memory");
746 return 0;
747 }
748 strcpy(env, prefix);
749 strcat(env, appfile);
750 putenv(env);
751 free(env);
752 }
753
754 vector<string> restartArgs;
755
756 ostringstream paramName;
757 int i = 0;
758 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
759 const char* param = getenv(paramName.str().c_str());
760 while (param && *param) {
761 restartArgs.push_back(param);
762
763 paramName.str("");
764 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
765 param = getenv(paramName.str().c_str());
766 }
767
768 // allow override of the server url via environment variable
769 // XXX: remove this in the far future when our robot
770 // masters force everyone to use XULRunner
771 char* urlEnv = getenv("MOZ_CRASHREPORTER_URL");
772 if (urlEnv && *urlEnv) {
773 sendURL = urlEnv;
774 }
775
776 // see if this version has been end-of-lifed
777
778 if (extraData.isMember("Version") &&
779 CheckEndOfLifed(extraData["Version"])) {
780 UIError(gStrings[ST_ERROR_ENDOFLIFE]);
781 DeleteDump();
782 return 0;
783 }
784
785 StringTable files;
786 files["upload_file_minidump"] = gReporterDumpFile;
787 if (!gMemoryFile.empty()) {
788 files["memory_report"] = gMemoryFile;
789 }
790
791 if (!UIShowCrashUI(files, extraData, sendURL, restartArgs)) {
792 DeleteDump();
793 }
794 }
795
796 UIShutdown();
797
798 return 0;
799 }
800
801 #if defined(XP_WIN) && !defined(__GNUC__)
802 # include <windows.h>
803
804 // We need WinMain in order to not be a console app. This function is unused
805 // if we are a console application.
wWinMain(HINSTANCE,HINSTANCE,LPWSTR args,int)806 int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR args, int) {
807 // Remove everything except close window from the context menu
808 {
809 HKEY hkApp;
810 RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0,
811 nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
812 &hkApp, nullptr);
813 RegCloseKey(hkApp);
814 if (RegCreateKeyExW(HKEY_CURRENT_USER,
815 L"Software\\Classes\\Applications\\crashreporter.exe",
816 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
817 &hkApp, nullptr) == ERROR_SUCCESS) {
818 RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
819 RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
820 RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
821 RegCloseKey(hkApp);
822 }
823 }
824
825 char** argv = static_cast<char**>(malloc(__argc * sizeof(char*)));
826 for (int i = 0; i < __argc; i++) {
827 argv[i] = strdup(WideToUTF8(__wargv[i]).c_str());
828 }
829
830 // Do the real work.
831 return main(__argc, argv);
832 }
833 #endif
834