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