Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
6 #include "crashreporter.h"
8 #ifdef _MSC_VER
9 // Disable exception handler warnings.
10 # pragma warning( disable : 4530 )
11 #endif
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"
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;
31 namespace CrashReporter {
33 StringTable gStrings;
34 string gSettingsPath;
35 int gArgc;
36 char** gArgv;
38 static auto_ptr<ofstream> gLogStream(nullptr);
39 static string gReporterDumpFile;
40 static string gExtraFile;
42 static string kExtraDataExtension = ".extra";
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 }
57 UIError_impl(errorMessage);
58 }
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 }
80 return ret;
81 }
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 }
100 return ret;
101 }
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 }
120 return true;
121 }
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 }
134 delete f;
135 return success;
136 }
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;
153 out << std::endl;
154 }
156 return true;
157 }
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 }
171 delete f;
172 return success;
173 }
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 }
187 static void OpenLogFile()
188 {
189 string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log";
190 gLogStream.reset(UIOpenWrite(logPath.c_str(), true));
191 }
193 static bool ReadConfig()
194 {
195 string iniPath;
196 if (!UIGetIniPath(iniPath))
197 return false;
199 if (!ReadStringsFromFile(iniPath, gStrings, true))
200 return false;
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);
207 return true;
208 }
210 static string GetExtraDataFilename(const string& dumpfile)
211 {
212 string filename(dumpfile);
213 int dot = filename.rfind('.');
214 if (dot < 0)
215 return "";
217 filename.replace(dot, filename.length() - dot, kExtraDataExtension);
218 return filename;
219 }
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 }
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 }
239 string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile);
240 string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile);
242 if (!UIMoveFile(dumpfile, newDump)) {
243 UIError(gStrings[ST_ERROR_DUMPFILEMOVE]);
244 return false;
245 }
247 if (!UIMoveFile(extrafile, newExtra)) {
248 UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]);
249 return false;
250 }
252 dumpfile = newDump;
253 extrafile = newExtra;
255 return true;
256 }
258 static bool AddSubmittedReport(const string& serverResponse)
259 {
260 StringTable responseItems;
261 istringstream in(serverResponse);
262 ReadStrings(in, responseItems, false);
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"];
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 }
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 }
285 if (responseItems.find("CrashID") == responseItems.end())
286 return false;
288 string submittedDir =
289 gSettingsPath + UI_DIR_SEPARATOR + "submitted";
290 if (!UIEnsurePathExists(submittedDir)) {
291 return false;
292 }
294 string path = submittedDir + UI_DIR_SEPARATOR +
295 responseItems["CrashID"] + ".txt";
297 ofstream* file = UIOpenWrite(path);
298 if (!file->is_open()) {
299 delete file;
300 return false;
301 }
303 char buf[1024];
304 UI_SNPRINTF(buf, 1024,
305 gStrings["CrashID"].c_str(),
306 responseItems["CrashID"].c_str());
307 *file << buf << "\n";
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 }
316 file->close();
317 delete file;
319 return true;
320 }
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 }
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 }
350 bool ShouldEnableSending()
351 {
352 srand(time(0));
353 return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT);
354 }
356 } // namespace CrashReporter
358 using namespace CrashReporter;
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 }
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;
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 }
397 UI_SNPRINTF(buf, sizeof(buf),
398 gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(),
399 product.c_str());
400 gStrings[ST_CRASHREPORTERDESCRIPTION] = buf;
402 UI_SNPRINTF(buf, sizeof(buf),
403 gStrings[ST_CHECKSUBMIT].c_str(),
404 vendor.c_str());
405 gStrings[ST_CHECKSUBMIT] = buf;
407 UI_SNPRINTF(buf, sizeof(buf),
408 gStrings[ST_CHECKEMAIL].c_str(),
409 vendor.c_str());
410 gStrings[ST_CHECKEMAIL] = buf;
412 UI_SNPRINTF(buf, sizeof(buf),
413 gStrings[ST_RESTART].c_str(),
414 product.c_str());
415 gStrings[ST_RESTART] = buf;
417 UI_SNPRINTF(buf, sizeof(buf),
418 gStrings[ST_QUIT].c_str(),
419 product.c_str());
420 gStrings[ST_QUIT] = buf;
422 UI_SNPRINTF(buf, sizeof(buf),
423 gStrings[ST_ERROR_ENDOFLIFE].c_str(),
424 product.c_str());
425 gStrings[ST_ERROR_ENDOFLIFE] = buf;
426 }
428 bool CheckEndOfLifed(string version)
429 {
430 string reportPath =
431 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + version;
432 return UIFileExists(reportPath);
433 }
435 int main(int argc, char** argv)
436 {
437 gArgc = argc;
438 gArgv = argv;
440 if (!ReadConfig()) {
441 UIError("Couldn't read configuration.");
442 return 0;
443 }
445 if (!UIInit())
446 return 0;
448 if (argc > 1) {
449 gReporterDumpFile = argv[1];
450 }
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 }
462 if (!UIFileExists(gExtraFile)) {
463 UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]);
464 return 0;
465 }
467 StringTable queryParameters;
468 if (!ReadStringsFromFile(gExtraFile, queryParameters, true)) {
469 UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
470 return 0;
471 }
473 if (queryParameters.find("ProductName") == queryParameters.end()) {
474 UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
475 return 0;
476 }
478 // There is enough information in the extra file to rewrite strings
479 // to be product specific
480 RewriteStrings(queryParameters);
482 if (queryParameters.find("ServerURL") == queryParameters.end()) {
483 UIError(gStrings[ST_ERROR_NOSERVERURL]);
484 return 0;
485 }
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 }
510 if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
511 UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
512 return 0;
513 }
515 OpenLogFile();
517 if (!UIFileExists(gReporterDumpFile)) {
518 UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
519 return 0;
520 }
522 string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
523 if (!MoveCrashData(pendingDir, gReporterDumpFile, gExtraFile)) {
524 return 0;
525 }
527 string sendURL = queryParameters["ServerURL"];
528 // we don't need to actually send this
529 queryParameters.erase("ServerURL");
531 queryParameters["Throttleable"] = "1";
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 }
548 vector<string> restartArgs;
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);
557 paramName.str("");
558 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
559 param = getenv(paramName.str().c_str());
560 };
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 }
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 }
578 if (!UIShowCrashUI(gReporterDumpFile, queryParameters, sendURL, restartArgs))
579 DeleteDump();
580 }
582 UIShutdown();
584 return 0;
585 }
587 #if defined(XP_WIN) && !defined(__GNUC__)
588 #include <windows.h>
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 }
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 }
617 // Do the real work.
618 return main(__argc, argv);
619 }
620 #endif