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