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 else
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 } else {
532 return ""; // If we encountered an error, return an empty hash
533 }
534 }
535
536 } // namespace CrashReporter
537
538 using namespace CrashReporter;
539
540 Json::Value kEmptyJsonString("");
541
RewriteStrings(Json::Value & aExtraData)542 void RewriteStrings(Json::Value& aExtraData) {
543 // rewrite some UI strings with the values from the query parameters
544 string product = aExtraData.get("ProductName", kEmptyJsonString).asString();
545 Json::Value mozilla("Mozilla");
546 string vendor = aExtraData.get("Vendor", mozilla).asString();
547
548 char buf[4096];
549 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(),
550 vendor.c_str());
551 gStrings[ST_CRASHREPORTERTITLE] = buf;
552
553 string str = gStrings[ST_CRASHREPORTERPRODUCTERROR];
554 // Only do the replacement here if the string has two
555 // format specifiers to start. Otherwise
556 // we assume it has the product name hardcoded.
557 string::size_type pos = str.find("%s");
558 if (pos != string::npos) pos = str.find("%s", pos + 2);
559 if (pos != string::npos) {
560 // Leave a format specifier for UIError to fill in
561 UI_SNPRINTF(buf, sizeof(buf),
562 gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(), product.c_str(),
563 "%s");
564 gStrings[ST_CRASHREPORTERERROR] = buf;
565 } else {
566 // product name is hardcoded
567 gStrings[ST_CRASHREPORTERERROR] = str;
568 }
569
570 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(),
571 product.c_str());
572 gStrings[ST_CRASHREPORTERDESCRIPTION] = buf;
573
574 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CHECKSUBMIT].c_str(),
575 vendor.c_str());
576 gStrings[ST_CHECKSUBMIT] = buf;
577
578 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_CHECKEMAIL].c_str(),
579 vendor.c_str());
580 gStrings[ST_CHECKEMAIL] = buf;
581
582 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_RESTART].c_str(), product.c_str());
583 gStrings[ST_RESTART] = buf;
584
585 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_QUIT].c_str(), product.c_str());
586 gStrings[ST_QUIT] = buf;
587
588 UI_SNPRINTF(buf, sizeof(buf), gStrings[ST_ERROR_ENDOFLIFE].c_str(),
589 product.c_str());
590 gStrings[ST_ERROR_ENDOFLIFE] = buf;
591 }
592
CheckEndOfLifed(const Json::Value & aVersion)593 bool CheckEndOfLifed(const Json::Value& aVersion) {
594 if (!aVersion.isString()) {
595 return false;
596 }
597
598 string reportPath =
599 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + aVersion.asString();
600 return UIFileExists(reportPath);
601 }
602
GetProgramPath(const string & exename)603 static string GetProgramPath(const string& exename) {
604 string path = gArgv[0];
605 size_t pos = path.rfind(UI_CRASH_REPORTER_FILENAME BIN_SUFFIX);
606 path.erase(pos);
607 #ifdef XP_MACOSX
608 // On macOS the crash reporter client is shipped as an application bundle
609 // contained within Firefox' main application bundle. So when it's invoked
610 // its current working directory looks like:
611 // Firefox.app/Contents/MacOS/crashreporter.app/Contents/MacOS/
612 // The other applications we ship with Firefox are stored in the main bundle
613 // (Firefox.app/Contents/MacOS/) so we we need to go back three directories
614 // to reach them.
615 path.append("../../../");
616 #endif // XP_MACOSX
617 path.append(exename + BIN_SUFFIX);
618
619 return path;
620 }
621
main(int argc,char ** argv)622 int main(int argc, char** argv) {
623 gArgc = argc;
624 gArgv = argv;
625
626 string autoSubmitEnv = UIGetEnv("MOZ_CRASHREPORTER_AUTO_SUBMIT");
627 gAutoSubmit = !autoSubmitEnv.empty();
628
629 if (!ReadConfig()) {
630 UIError("Couldn't read configuration.");
631 return 0;
632 }
633
634 if (!UIInit()) {
635 return 0;
636 }
637
638 if (argc > 1) {
639 gReporterDumpFile = argv[1];
640 }
641
642 if (gReporterDumpFile.empty()) {
643 // no dump file specified, run the default UI
644 if (!gAutoSubmit) {
645 UIShowDefaultUI();
646 }
647 } else {
648 // Start by running minidump analyzer to gather stack traces.
649 string reporterDumpFile = gReporterDumpFile;
650 vector<string> args = {reporterDumpFile};
651 string dumpAllThreadsEnv = UIGetEnv("MOZ_CRASHREPORTER_DUMP_ALL_THREADS");
652 if (!dumpAllThreadsEnv.empty()) {
653 args.insert(args.begin(), "--full");
654 }
655 UIRunProgram(GetProgramPath(UI_MINIDUMP_ANALYZER_FILENAME), args,
656 /* wait */ true);
657
658 // go ahead with the crash reporter
659 gExtraFile = GetAdditionalFilename(gReporterDumpFile, kExtraDataExtension);
660 if (gExtraFile.empty()) {
661 UIError(gStrings[ST_ERROR_BADARGUMENTS]);
662 return 0;
663 }
664
665 if (!UIFileExists(gExtraFile)) {
666 UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]);
667 return 0;
668 }
669
670 gMemoryFile =
671 GetAdditionalFilename(gReporterDumpFile, kMemoryReportExtension);
672 if (!UIFileExists(gMemoryFile)) {
673 gMemoryFile.erase();
674 }
675
676 Json::Value extraData;
677 if (!ReadExtraFile(gExtraFile, extraData)) {
678 UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
679 return 0;
680 }
681
682 if (!extraData.isMember("ProductName")) {
683 UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
684 return 0;
685 }
686
687 // There is enough information in the extra file to rewrite strings
688 // to be product specific
689 RewriteStrings(extraData);
690
691 if (!extraData.isMember("ServerURL")) {
692 UIError(gStrings[ST_ERROR_NOSERVERURL]);
693 return 0;
694 }
695
696 // Hopefully the settings path exists in the environment. Try that before
697 // asking the platform-specific code to guess.
698 gSettingsPath = UIGetEnv("MOZ_CRASHREPORTER_DATA_DIRECTORY");
699 if (gSettingsPath.empty()) {
700 string product =
701 extraData.get("ProductName", kEmptyJsonString).asString();
702 string vendor = extraData.get("Vendor", kEmptyJsonString).asString();
703
704 if (!UIGetSettingsPath(vendor, product, gSettingsPath)) {
705 gSettingsPath.clear();
706 }
707 }
708
709 if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
710 UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
711 return 0;
712 }
713
714 OpenLogFile();
715
716 gEventsPath = UIGetEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY");
717 gPingPath = UIGetEnv("MOZ_CRASHREPORTER_PING_DIRECTORY");
718
719 // Assemble and send the crash ping
720 string hash = ComputeDumpHash();
721
722 string pingUuid;
723 SendCrashPing(extraData, hash, pingUuid, gPingPath);
724 UpdateEventFile(extraData, hash, pingUuid);
725
726 if (!UIFileExists(gReporterDumpFile)) {
727 UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
728 return 0;
729 }
730
731 string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
732 if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile,
733 gMemoryFile)) {
734 return 0;
735 }
736
737 string sendURL = extraData.get("ServerURL", kEmptyJsonString).asString();
738 // we don't need to actually send these
739 extraData.removeMember("ServerURL");
740 extraData.removeMember("StackTraces");
741
742 extraData["Throttleable"] = "1";
743
744 // re-set XUL_APP_FILE for xulrunner wrapped apps
745 const char* appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE");
746 if (appfile && *appfile) {
747 const char prefix[] = "XUL_APP_FILE=";
748 char* env = (char*)malloc(strlen(appfile) + strlen(prefix) + 1);
749 if (!env) {
750 UIError("Out of memory");
751 return 0;
752 }
753 strcpy(env, prefix);
754 strcat(env, appfile);
755 putenv(env);
756 free(env);
757 }
758
759 vector<string> restartArgs;
760
761 ostringstream paramName;
762 int i = 0;
763 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
764 const char* param = getenv(paramName.str().c_str());
765 while (param && *param) {
766 restartArgs.push_back(param);
767
768 paramName.str("");
769 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
770 param = getenv(paramName.str().c_str());
771 }
772
773 // allow override of the server url via environment variable
774 // XXX: remove this in the far future when our robot
775 // masters force everyone to use XULRunner
776 char* urlEnv = getenv("MOZ_CRASHREPORTER_URL");
777 if (urlEnv && *urlEnv) {
778 sendURL = urlEnv;
779 }
780
781 // see if this version has been end-of-lifed
782
783 if (extraData.isMember("Version") &&
784 CheckEndOfLifed(extraData["Version"])) {
785 UIError(gStrings[ST_ERROR_ENDOFLIFE]);
786 DeleteDump();
787 return 0;
788 }
789
790 StringTable files;
791 files["upload_file_minidump"] = gReporterDumpFile;
792 if (!gMemoryFile.empty()) {
793 files["memory_report"] = gMemoryFile;
794 }
795
796 if (!UIShowCrashUI(files, extraData, sendURL, restartArgs)) {
797 DeleteDump();
798 }
799 }
800
801 UIShutdown();
802
803 return 0;
804 }
805
806 #if defined(XP_WIN) && !defined(__GNUC__)
807 # include <windows.h>
808
809 // We need WinMain in order to not be a console app. This function is unused
810 // if we are a console application.
wWinMain(HINSTANCE,HINSTANCE,LPWSTR args,int)811 int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR args, int) {
812 // Remove everything except close window from the context menu
813 {
814 HKEY hkApp;
815 RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0,
816 nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr,
817 &hkApp, nullptr);
818 RegCloseKey(hkApp);
819 if (RegCreateKeyExW(HKEY_CURRENT_USER,
820 L"Software\\Classes\\Applications\\crashreporter.exe",
821 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, nullptr,
822 &hkApp, nullptr) == ERROR_SUCCESS) {
823 RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0);
824 RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0);
825 RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0);
826 RegCloseKey(hkApp);
827 }
828 }
829
830 char** argv = static_cast<char**>(malloc(__argc * sizeof(char*)));
831 for (int i = 0; i < __argc; i++) {
832 argv[i] = strdup(WideToUTF8(__wargv[i]).c_str());
833 }
834
835 // Do the real work.
836 return main(__argc, argv);
837 }
838 #endif
839