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