|
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 <sstream> |
|
15 #include <memory> |
|
16 #include <time.h> |
|
17 #include <stdlib.h> |
|
18 #include <string.h> |
|
19 #include "mozilla/NullPtr.h" |
|
20 |
|
21 using std::string; |
|
22 using std::istream; |
|
23 using std::ifstream; |
|
24 using std::istringstream; |
|
25 using std::ostringstream; |
|
26 using std::ostream; |
|
27 using std::ofstream; |
|
28 using std::vector; |
|
29 using std::auto_ptr; |
|
30 |
|
31 namespace CrashReporter { |
|
32 |
|
33 StringTable gStrings; |
|
34 string gSettingsPath; |
|
35 int gArgc; |
|
36 char** gArgv; |
|
37 |
|
38 static auto_ptr<ofstream> gLogStream(nullptr); |
|
39 static string gReporterDumpFile; |
|
40 static string gExtraFile; |
|
41 |
|
42 static string kExtraDataExtension = ".extra"; |
|
43 |
|
44 void UIError(const string& message) |
|
45 { |
|
46 string errorMessage; |
|
47 if (!gStrings[ST_CRASHREPORTERERROR].empty()) { |
|
48 char buf[2048]; |
|
49 UI_SNPRINTF(buf, 2048, |
|
50 gStrings[ST_CRASHREPORTERERROR].c_str(), |
|
51 message.c_str()); |
|
52 errorMessage = buf; |
|
53 } else { |
|
54 errorMessage = message; |
|
55 } |
|
56 |
|
57 UIError_impl(errorMessage); |
|
58 } |
|
59 |
|
60 static string Unescape(const string& str) |
|
61 { |
|
62 string ret; |
|
63 for (string::const_iterator iter = str.begin(); |
|
64 iter != str.end(); |
|
65 iter++) { |
|
66 if (*iter == '\\') { |
|
67 iter++; |
|
68 if (*iter == '\\'){ |
|
69 ret.push_back('\\'); |
|
70 } else if (*iter == 'n') { |
|
71 ret.push_back('\n'); |
|
72 } else if (*iter == 't') { |
|
73 ret.push_back('\t'); |
|
74 } |
|
75 } else { |
|
76 ret.push_back(*iter); |
|
77 } |
|
78 } |
|
79 |
|
80 return ret; |
|
81 } |
|
82 |
|
83 static string Escape(const string& str) |
|
84 { |
|
85 string ret; |
|
86 for (string::const_iterator iter = str.begin(); |
|
87 iter != str.end(); |
|
88 iter++) { |
|
89 if (*iter == '\\') { |
|
90 ret += "\\\\"; |
|
91 } else if (*iter == '\n') { |
|
92 ret += "\\n"; |
|
93 } else if (*iter == '\t') { |
|
94 ret += "\\t"; |
|
95 } else { |
|
96 ret.push_back(*iter); |
|
97 } |
|
98 } |
|
99 |
|
100 return ret; |
|
101 } |
|
102 |
|
103 bool ReadStrings(istream& in, StringTable& strings, bool unescape) |
|
104 { |
|
105 string currentSection; |
|
106 while (!in.eof()) { |
|
107 string line; |
|
108 std::getline(in, line); |
|
109 int sep = line.find('='); |
|
110 if (sep >= 0) { |
|
111 string key, value; |
|
112 key = line.substr(0, sep); |
|
113 value = line.substr(sep + 1); |
|
114 if (unescape) |
|
115 value = Unescape(value); |
|
116 strings[key] = value; |
|
117 } |
|
118 } |
|
119 |
|
120 return true; |
|
121 } |
|
122 |
|
123 bool ReadStringsFromFile(const string& path, |
|
124 StringTable& strings, |
|
125 bool unescape) |
|
126 { |
|
127 ifstream* f = UIOpenRead(path); |
|
128 bool success = false; |
|
129 if (f->is_open()) { |
|
130 success = ReadStrings(*f, strings, unescape); |
|
131 f->close(); |
|
132 } |
|
133 |
|
134 delete f; |
|
135 return success; |
|
136 } |
|
137 |
|
138 bool WriteStrings(ostream& out, |
|
139 const string& header, |
|
140 StringTable& strings, |
|
141 bool escape) |
|
142 { |
|
143 out << "[" << header << "]" << std::endl; |
|
144 for (StringTable::iterator iter = strings.begin(); |
|
145 iter != strings.end(); |
|
146 iter++) { |
|
147 out << iter->first << "="; |
|
148 if (escape) |
|
149 out << Escape(iter->second); |
|
150 else |
|
151 out << iter->second; |
|
152 |
|
153 out << std::endl; |
|
154 } |
|
155 |
|
156 return true; |
|
157 } |
|
158 |
|
159 bool WriteStringsToFile(const string& path, |
|
160 const string& header, |
|
161 StringTable& strings, |
|
162 bool escape) |
|
163 { |
|
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 |
|
175 void LogMessage(const std::string& message) |
|
176 { |
|
177 if (gLogStream.get()) { |
|
178 char date[64]; |
|
179 time_t tm; |
|
180 time(&tm); |
|
181 if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0) |
|
182 date[0] = '\0'; |
|
183 (*gLogStream) << "[" << date << "] " << message << std::endl; |
|
184 } |
|
185 } |
|
186 |
|
187 static void OpenLogFile() |
|
188 { |
|
189 string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log"; |
|
190 gLogStream.reset(UIOpenWrite(logPath.c_str(), true)); |
|
191 } |
|
192 |
|
193 static bool ReadConfig() |
|
194 { |
|
195 string iniPath; |
|
196 if (!UIGetIniPath(iniPath)) |
|
197 return false; |
|
198 |
|
199 if (!ReadStringsFromFile(iniPath, gStrings, true)) |
|
200 return false; |
|
201 |
|
202 // See if we have a string override file, if so process it |
|
203 char* overrideEnv = getenv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE"); |
|
204 if (overrideEnv && *overrideEnv && UIFileExists(overrideEnv)) |
|
205 ReadStringsFromFile(overrideEnv, gStrings, true); |
|
206 |
|
207 return true; |
|
208 } |
|
209 |
|
210 static string GetExtraDataFilename(const string& dumpfile) |
|
211 { |
|
212 string filename(dumpfile); |
|
213 int dot = filename.rfind('.'); |
|
214 if (dot < 0) |
|
215 return ""; |
|
216 |
|
217 filename.replace(dot, filename.length() - dot, kExtraDataExtension); |
|
218 return filename; |
|
219 } |
|
220 |
|
221 static string Basename(const string& file) |
|
222 { |
|
223 int slashIndex = file.rfind(UI_DIR_SEPARATOR); |
|
224 if (slashIndex >= 0) |
|
225 return file.substr(slashIndex + 1); |
|
226 else |
|
227 return file; |
|
228 } |
|
229 |
|
230 static bool MoveCrashData(const string& toDir, |
|
231 string& dumpfile, |
|
232 string& extrafile) |
|
233 { |
|
234 if (!UIEnsurePathExists(toDir)) { |
|
235 UIError(gStrings[ST_ERROR_CREATEDUMPDIR]); |
|
236 return false; |
|
237 } |
|
238 |
|
239 string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile); |
|
240 string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile); |
|
241 |
|
242 if (!UIMoveFile(dumpfile, newDump)) { |
|
243 UIError(gStrings[ST_ERROR_DUMPFILEMOVE]); |
|
244 return false; |
|
245 } |
|
246 |
|
247 if (!UIMoveFile(extrafile, newExtra)) { |
|
248 UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]); |
|
249 return false; |
|
250 } |
|
251 |
|
252 dumpfile = newDump; |
|
253 extrafile = newExtra; |
|
254 |
|
255 return true; |
|
256 } |
|
257 |
|
258 static bool AddSubmittedReport(const string& serverResponse) |
|
259 { |
|
260 StringTable responseItems; |
|
261 istringstream in(serverResponse); |
|
262 ReadStrings(in, responseItems, false); |
|
263 |
|
264 if (responseItems.find("StopSendingReportsFor") != responseItems.end()) { |
|
265 // server wants to tell us to stop sending reports for a certain version |
|
266 string reportPath = |
|
267 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + |
|
268 responseItems["StopSendingReportsFor"]; |
|
269 |
|
270 ofstream* reportFile = UIOpenWrite(reportPath); |
|
271 if (reportFile->is_open()) { |
|
272 // don't really care about the contents |
|
273 *reportFile << 1 << "\n"; |
|
274 reportFile->close(); |
|
275 } |
|
276 delete reportFile; |
|
277 } |
|
278 |
|
279 if (responseItems.find("Discarded") != responseItems.end()) { |
|
280 // server discarded this report... save it so the user can resubmit it |
|
281 // manually |
|
282 return false; |
|
283 } |
|
284 |
|
285 if (responseItems.find("CrashID") == responseItems.end()) |
|
286 return false; |
|
287 |
|
288 string submittedDir = |
|
289 gSettingsPath + UI_DIR_SEPARATOR + "submitted"; |
|
290 if (!UIEnsurePathExists(submittedDir)) { |
|
291 return false; |
|
292 } |
|
293 |
|
294 string path = submittedDir + UI_DIR_SEPARATOR + |
|
295 responseItems["CrashID"] + ".txt"; |
|
296 |
|
297 ofstream* file = UIOpenWrite(path); |
|
298 if (!file->is_open()) { |
|
299 delete file; |
|
300 return false; |
|
301 } |
|
302 |
|
303 char buf[1024]; |
|
304 UI_SNPRINTF(buf, 1024, |
|
305 gStrings["CrashID"].c_str(), |
|
306 responseItems["CrashID"].c_str()); |
|
307 *file << buf << "\n"; |
|
308 |
|
309 if (responseItems.find("ViewURL") != responseItems.end()) { |
|
310 UI_SNPRINTF(buf, 1024, |
|
311 gStrings["CrashDetailsURL"].c_str(), |
|
312 responseItems["ViewURL"].c_str()); |
|
313 *file << buf << "\n"; |
|
314 } |
|
315 |
|
316 file->close(); |
|
317 delete file; |
|
318 |
|
319 return true; |
|
320 } |
|
321 |
|
322 void DeleteDump() |
|
323 { |
|
324 const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP"); |
|
325 if (!noDelete || *noDelete == '\0') { |
|
326 if (!gReporterDumpFile.empty()) |
|
327 UIDeleteFile(gReporterDumpFile); |
|
328 if (!gExtraFile.empty()) |
|
329 UIDeleteFile(gExtraFile); |
|
330 } |
|
331 } |
|
332 |
|
333 void SendCompleted(bool success, const string& serverResponse) |
|
334 { |
|
335 if (success) { |
|
336 if (AddSubmittedReport(serverResponse)) { |
|
337 DeleteDump(); |
|
338 } |
|
339 else { |
|
340 string directory = gReporterDumpFile; |
|
341 int slashpos = directory.find_last_of("/\\"); |
|
342 if (slashpos < 2) |
|
343 return; |
|
344 directory.resize(slashpos); |
|
345 UIPruneSavedDumps(directory); |
|
346 } |
|
347 } |
|
348 } |
|
349 |
|
350 bool ShouldEnableSending() |
|
351 { |
|
352 srand(time(0)); |
|
353 return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT); |
|
354 } |
|
355 |
|
356 } // namespace CrashReporter |
|
357 |
|
358 using namespace CrashReporter; |
|
359 |
|
360 void RewriteStrings(StringTable& queryParameters) |
|
361 { |
|
362 // rewrite some UI strings with the values from the query parameters |
|
363 string product = queryParameters["ProductName"]; |
|
364 string vendor = queryParameters["Vendor"]; |
|
365 if (vendor.empty()) { |
|
366 // Assume Mozilla if no vendor is specified |
|
367 vendor = "Mozilla"; |
|
368 } |
|
369 |
|
370 char buf[4096]; |
|
371 UI_SNPRINTF(buf, sizeof(buf), |
|
372 gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(), |
|
373 vendor.c_str()); |
|
374 gStrings[ST_CRASHREPORTERTITLE] = buf; |
|
375 |
|
376 |
|
377 string str = gStrings[ST_CRASHREPORTERPRODUCTERROR]; |
|
378 // Only do the replacement here if the string has two |
|
379 // format specifiers to start. Otherwise |
|
380 // we assume it has the product name hardcoded. |
|
381 string::size_type pos = str.find("%s"); |
|
382 if (pos != string::npos) |
|
383 pos = str.find("%s", pos+2); |
|
384 if (pos != string::npos) { |
|
385 // Leave a format specifier for UIError to fill in |
|
386 UI_SNPRINTF(buf, sizeof(buf), |
|
387 gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(), |
|
388 product.c_str(), |
|
389 "%s"); |
|
390 gStrings[ST_CRASHREPORTERERROR] = buf; |
|
391 } |
|
392 else { |
|
393 // product name is hardcoded |
|
394 gStrings[ST_CRASHREPORTERERROR] = str; |
|
395 } |
|
396 |
|
397 UI_SNPRINTF(buf, sizeof(buf), |
|
398 gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(), |
|
399 product.c_str()); |
|
400 gStrings[ST_CRASHREPORTERDESCRIPTION] = buf; |
|
401 |
|
402 UI_SNPRINTF(buf, sizeof(buf), |
|
403 gStrings[ST_CHECKSUBMIT].c_str(), |
|
404 vendor.c_str()); |
|
405 gStrings[ST_CHECKSUBMIT] = buf; |
|
406 |
|
407 UI_SNPRINTF(buf, sizeof(buf), |
|
408 gStrings[ST_CHECKEMAIL].c_str(), |
|
409 vendor.c_str()); |
|
410 gStrings[ST_CHECKEMAIL] = buf; |
|
411 |
|
412 UI_SNPRINTF(buf, sizeof(buf), |
|
413 gStrings[ST_RESTART].c_str(), |
|
414 product.c_str()); |
|
415 gStrings[ST_RESTART] = buf; |
|
416 |
|
417 UI_SNPRINTF(buf, sizeof(buf), |
|
418 gStrings[ST_QUIT].c_str(), |
|
419 product.c_str()); |
|
420 gStrings[ST_QUIT] = buf; |
|
421 |
|
422 UI_SNPRINTF(buf, sizeof(buf), |
|
423 gStrings[ST_ERROR_ENDOFLIFE].c_str(), |
|
424 product.c_str()); |
|
425 gStrings[ST_ERROR_ENDOFLIFE] = buf; |
|
426 } |
|
427 |
|
428 bool CheckEndOfLifed(string version) |
|
429 { |
|
430 string reportPath = |
|
431 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + version; |
|
432 return UIFileExists(reportPath); |
|
433 } |
|
434 |
|
435 int main(int argc, char** argv) |
|
436 { |
|
437 gArgc = argc; |
|
438 gArgv = argv; |
|
439 |
|
440 if (!ReadConfig()) { |
|
441 UIError("Couldn't read configuration."); |
|
442 return 0; |
|
443 } |
|
444 |
|
445 if (!UIInit()) |
|
446 return 0; |
|
447 |
|
448 if (argc > 1) { |
|
449 gReporterDumpFile = argv[1]; |
|
450 } |
|
451 |
|
452 if (gReporterDumpFile.empty()) { |
|
453 // no dump file specified, run the default UI |
|
454 UIShowDefaultUI(); |
|
455 } else { |
|
456 gExtraFile = GetExtraDataFilename(gReporterDumpFile); |
|
457 if (gExtraFile.empty()) { |
|
458 UIError(gStrings[ST_ERROR_BADARGUMENTS]); |
|
459 return 0; |
|
460 } |
|
461 |
|
462 if (!UIFileExists(gExtraFile)) { |
|
463 UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]); |
|
464 return 0; |
|
465 } |
|
466 |
|
467 StringTable queryParameters; |
|
468 if (!ReadStringsFromFile(gExtraFile, queryParameters, true)) { |
|
469 UIError(gStrings[ST_ERROR_EXTRAFILEREAD]); |
|
470 return 0; |
|
471 } |
|
472 |
|
473 if (queryParameters.find("ProductName") == queryParameters.end()) { |
|
474 UIError(gStrings[ST_ERROR_NOPRODUCTNAME]); |
|
475 return 0; |
|
476 } |
|
477 |
|
478 // There is enough information in the extra file to rewrite strings |
|
479 // to be product specific |
|
480 RewriteStrings(queryParameters); |
|
481 |
|
482 if (queryParameters.find("ServerURL") == queryParameters.end()) { |
|
483 UIError(gStrings[ST_ERROR_NOSERVERURL]); |
|
484 return 0; |
|
485 } |
|
486 |
|
487 // Hopefully the settings path exists in the environment. Try that before |
|
488 // asking the platform-specific code to guess. |
|
489 #ifdef XP_WIN32 |
|
490 static const wchar_t kDataDirKey[] = L"MOZ_CRASHREPORTER_DATA_DIRECTORY"; |
|
491 const wchar_t *settingsPath = _wgetenv(kDataDirKey); |
|
492 if (settingsPath && *settingsPath) { |
|
493 gSettingsPath = WideToUTF8(settingsPath); |
|
494 } |
|
495 #else |
|
496 static const char kDataDirKey[] = "MOZ_CRASHREPORTER_DATA_DIRECTORY"; |
|
497 const char *settingsPath = getenv(kDataDirKey); |
|
498 if (settingsPath && *settingsPath) { |
|
499 gSettingsPath = settingsPath; |
|
500 } |
|
501 #endif |
|
502 else { |
|
503 string product = queryParameters["ProductName"]; |
|
504 string vendor = queryParameters["Vendor"]; |
|
505 if (!UIGetSettingsPath(vendor, product, gSettingsPath)) { |
|
506 gSettingsPath.clear(); |
|
507 } |
|
508 } |
|
509 |
|
510 if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) { |
|
511 UIError(gStrings[ST_ERROR_NOSETTINGSPATH]); |
|
512 return 0; |
|
513 } |
|
514 |
|
515 OpenLogFile(); |
|
516 |
|
517 if (!UIFileExists(gReporterDumpFile)) { |
|
518 UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]); |
|
519 return 0; |
|
520 } |
|
521 |
|
522 string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending"; |
|
523 if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile)) { |
|
524 return 0; |
|
525 } |
|
526 |
|
527 string sendURL = queryParameters["ServerURL"]; |
|
528 // we don't need to actually send this |
|
529 queryParameters.erase("ServerURL"); |
|
530 |
|
531 queryParameters["Throttleable"] = "1"; |
|
532 |
|
533 // re-set XUL_APP_FILE for xulrunner wrapped apps |
|
534 const char *appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE"); |
|
535 if (appfile && *appfile) { |
|
536 const char prefix[] = "XUL_APP_FILE="; |
|
537 char *env = (char*) malloc(strlen(appfile) + strlen(prefix) + 1); |
|
538 if (!env) { |
|
539 UIError("Out of memory"); |
|
540 return 0; |
|
541 } |
|
542 strcpy(env, prefix); |
|
543 strcat(env, appfile); |
|
544 putenv(env); |
|
545 free(env); |
|
546 } |
|
547 |
|
548 vector<string> restartArgs; |
|
549 |
|
550 ostringstream paramName; |
|
551 int i = 0; |
|
552 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; |
|
553 const char *param = getenv(paramName.str().c_str()); |
|
554 while (param && *param) { |
|
555 restartArgs.push_back(param); |
|
556 |
|
557 paramName.str(""); |
|
558 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++; |
|
559 param = getenv(paramName.str().c_str()); |
|
560 }; |
|
561 |
|
562 // allow override of the server url via environment variable |
|
563 //XXX: remove this in the far future when our robot |
|
564 // masters force everyone to use XULRunner |
|
565 char* urlEnv = getenv("MOZ_CRASHREPORTER_URL"); |
|
566 if (urlEnv && *urlEnv) { |
|
567 sendURL = urlEnv; |
|
568 } |
|
569 |
|
570 // see if this version has been end-of-lifed |
|
571 if (queryParameters.find("Version") != queryParameters.end() && |
|
572 CheckEndOfLifed(queryParameters["Version"])) { |
|
573 UIError(gStrings[ST_ERROR_ENDOFLIFE]); |
|
574 DeleteDump(); |
|
575 return 0; |
|
576 } |
|
577 |
|
578 if (!UIShowCrashUI(gReporterDumpFile, queryParameters, sendURL, restartArgs)) |
|
579 DeleteDump(); |
|
580 } |
|
581 |
|
582 UIShutdown(); |
|
583 |
|
584 return 0; |
|
585 } |
|
586 |
|
587 #if defined(XP_WIN) && !defined(__GNUC__) |
|
588 #include <windows.h> |
|
589 |
|
590 // We need WinMain in order to not be a console app. This function is unused |
|
591 // if we are a console application. |
|
592 int WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR args, int ) |
|
593 { |
|
594 // Remove everything except close window from the context menu |
|
595 { |
|
596 HKEY hkApp; |
|
597 RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Classes\\Applications", 0, |
|
598 nullptr, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, nullptr, |
|
599 &hkApp, nullptr); |
|
600 RegCloseKey(hkApp); |
|
601 if (RegCreateKeyExW(HKEY_CURRENT_USER, |
|
602 L"Software\\Classes\\Applications\\crashreporter.exe", |
|
603 0, nullptr, REG_OPTION_VOLATILE, KEY_SET_VALUE, |
|
604 nullptr, &hkApp, nullptr) == ERROR_SUCCESS) { |
|
605 RegSetValueExW(hkApp, L"IsHostApp", 0, REG_NONE, 0, 0); |
|
606 RegSetValueExW(hkApp, L"NoOpenWith", 0, REG_NONE, 0, 0); |
|
607 RegSetValueExW(hkApp, L"NoStartPage", 0, REG_NONE, 0, 0); |
|
608 RegCloseKey(hkApp); |
|
609 } |
|
610 } |
|
611 |
|
612 char** argv = static_cast<char**>(malloc(__argc * sizeof(char*))); |
|
613 for (int i = 0; i < __argc; i++) { |
|
614 argv[i] = strdup(WideToUTF8(__wargv[i]).c_str()); |
|
615 } |
|
616 |
|
617 // Do the real work. |
|
618 return main(__argc, argv); |
|
619 } |
|
620 #endif |