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