toolkit/crashreporter/client/crashreporter.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

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

mercurial