Bug 452317 - FeedConverter.js: QueryInterface should throw NS_ERROR_NO_INTERFACE...
[wine-gecko.git] / toolkit / crashreporter / client / crashreporter.cpp
blobe695f8bfcbdb774db6ca8f622459342faff6f334
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is Mozilla Toolkit Crash Reporter
17 * The Initial Developer of the Original Code is
18 * Mozilla Corporation
19 * Portions created by the Initial Developer are Copyright (C) 2006-2007
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Ted Mielczarek <ted.mielczarek@gmail.com>
24 * Dave Camp <dcamp@mozilla.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "crashreporter.h"
42 #ifdef _MSC_VER
43 // Disable exception handler warnings.
44 # pragma warning( disable : 4530 )
45 #endif
47 #include <fstream>
48 #include <sstream>
49 #include <memory>
50 #include <time.h>
51 #include <stdlib.h>
52 #include <string.h>
54 using std::string;
55 using std::istream;
56 using std::ifstream;
57 using std::istringstream;
58 using std::ostringstream;
59 using std::ostream;
60 using std::ofstream;
61 using std::vector;
62 using std::auto_ptr;
64 namespace CrashReporter {
66 StringTable gStrings;
67 string gSettingsPath;
68 int gArgc;
69 char** gArgv;
71 static auto_ptr<ofstream> gLogStream(NULL);
72 static string gDumpFile;
73 static string gExtraFile;
75 static string kExtraDataExtension = ".extra";
77 void UIError(const string& message)
79 string errorMessage;
80 if (!gStrings[ST_CRASHREPORTERERROR].empty()) {
81 char buf[2048];
82 UI_SNPRINTF(buf, 2048,
83 gStrings[ST_CRASHREPORTERERROR].c_str(),
84 message.c_str());
85 errorMessage = buf;
86 } else {
87 errorMessage = message;
90 UIError_impl(errorMessage);
93 static string Unescape(const string& str)
95 string ret;
96 for (string::const_iterator iter = str.begin();
97 iter != str.end();
98 iter++) {
99 if (*iter == '\\') {
100 iter++;
101 if (*iter == '\\'){
102 ret.push_back('\\');
103 } else if (*iter == 'n') {
104 ret.push_back('\n');
105 } else if (*iter == 't') {
106 ret.push_back('\t');
108 } else {
109 ret.push_back(*iter);
113 return ret;
116 static string Escape(const string& str)
118 string ret;
119 for (string::const_iterator iter = str.begin();
120 iter != str.end();
121 iter++) {
122 if (*iter == '\\') {
123 ret += "\\\\";
124 } else if (*iter == '\n') {
125 ret += "\\n";
126 } else if (*iter == '\t') {
127 ret += "\\t";
128 } else {
129 ret.push_back(*iter);
133 return ret;
136 bool ReadStrings(istream& in, StringTable& strings, bool unescape)
138 string currentSection;
139 while (!in.eof()) {
140 string line;
141 std::getline(in, line);
142 int sep = line.find('=');
143 if (sep >= 0) {
144 string key, value;
145 key = line.substr(0, sep);
146 value = line.substr(sep + 1);
147 if (unescape)
148 value = Unescape(value);
149 strings[key] = value;
153 return true;
156 bool ReadStringsFromFile(const string& path,
157 StringTable& strings,
158 bool unescape)
160 ifstream* f = UIOpenRead(path);
161 bool success = false;
162 if (f->is_open()) {
163 success = ReadStrings(*f, strings, unescape);
164 f->close();
167 delete f;
168 return success;
171 bool WriteStrings(ostream& out,
172 const string& header,
173 StringTable& strings,
174 bool escape)
176 out << "[" << header << "]" << std::endl;
177 for (StringTable::iterator iter = strings.begin();
178 iter != strings.end();
179 iter++) {
180 out << iter->first << "=";
181 if (escape)
182 out << Escape(iter->second);
183 else
184 out << iter->second;
186 out << std::endl;
189 return true;
192 bool WriteStringsToFile(const string& path,
193 const string& header,
194 StringTable& strings,
195 bool escape)
197 ofstream* f = UIOpenWrite(path.c_str());
198 bool success = false;
199 if (f->is_open()) {
200 success = WriteStrings(*f, header, strings, escape);
201 f->close();
204 delete f;
205 return success;
208 void LogMessage(const std::string& message)
210 if (gLogStream.get()) {
211 char date[64];
212 time_t tm;
213 time(&tm);
214 if (strftime(date, sizeof(date) - 1, "%c", localtime(&tm)) == 0)
215 date[0] = '\0';
216 (*gLogStream) << "[" << date << "] " << message << std::endl;
220 static void OpenLogFile()
222 string logPath = gSettingsPath + UI_DIR_SEPARATOR + "submit.log";
223 gLogStream.reset(UIOpenWrite(logPath.c_str(), true));
226 static bool ReadConfig()
228 string iniPath;
229 if (!UIGetIniPath(iniPath))
230 return false;
232 if (!ReadStringsFromFile(iniPath, gStrings, true))
233 return false;
235 // See if we have a string override file, if so process it
236 char* overrideEnv = getenv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE");
237 if (overrideEnv && *overrideEnv && UIFileExists(overrideEnv))
238 ReadStringsFromFile(overrideEnv, gStrings, true);
240 return true;
243 static string GetExtraDataFilename(const string& dumpfile)
245 string filename(dumpfile);
246 int dot = filename.rfind('.');
247 if (dot < 0)
248 return "";
250 filename.replace(dot, filename.length() - dot, kExtraDataExtension);
251 return filename;
254 static string Basename(const string& file)
256 int slashIndex = file.rfind(UI_DIR_SEPARATOR);
257 if (slashIndex >= 0)
258 return file.substr(slashIndex + 1);
259 else
260 return file;
263 static bool MoveCrashData(const string& toDir,
264 string& dumpfile,
265 string& extrafile)
267 if (!UIEnsurePathExists(toDir)) {
268 UIError(gStrings[ST_ERROR_CREATEDUMPDIR]);
269 return false;
272 string newDump = toDir + UI_DIR_SEPARATOR + Basename(dumpfile);
273 string newExtra = toDir + UI_DIR_SEPARATOR + Basename(extrafile);
275 if (!UIMoveFile(dumpfile, newDump)) {
276 UIError(gStrings[ST_ERROR_DUMPFILEMOVE]);
277 return false;
280 if (!UIMoveFile(extrafile, newExtra)) {
281 UIError(gStrings[ST_ERROR_EXTRAFILEMOVE]);
282 return false;
285 dumpfile = newDump;
286 extrafile = newExtra;
288 return true;
291 static bool AddSubmittedReport(const string& serverResponse)
293 StringTable responseItems;
294 istringstream in(serverResponse);
295 ReadStrings(in, responseItems, false);
297 if (responseItems.find("StopSendingReportsFor") != responseItems.end()) {
298 // server wants to tell us to stop sending reports for a certain version
299 string reportPath =
300 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" +
301 responseItems["StopSendingReportsFor"];
303 ofstream* reportFile = UIOpenWrite(reportPath);
304 if (reportFile->is_open()) {
305 // don't really care about the contents
306 *reportFile << 1 << "\n";
307 reportFile->close();
309 delete reportFile;
312 if (responseItems.find("CrashID") == responseItems.end())
313 return false;
315 string submittedDir =
316 gSettingsPath + UI_DIR_SEPARATOR + "submitted";
317 if (!UIEnsurePathExists(submittedDir)) {
318 return false;
321 string path = submittedDir + UI_DIR_SEPARATOR +
322 responseItems["CrashID"] + ".txt";
324 ofstream* file = UIOpenWrite(path);
325 if (!file->is_open()) {
326 delete file;
327 return false;
330 char buf[1024];
331 UI_SNPRINTF(buf, 1024,
332 gStrings["CrashID"].c_str(),
333 responseItems["CrashID"].c_str());
334 *file << buf << "\n";
336 if (responseItems.find("ViewURL") != responseItems.end()) {
337 UI_SNPRINTF(buf, 1024,
338 gStrings["CrashDetailsURL"].c_str(),
339 responseItems["ViewURL"].c_str());
340 *file << buf << "\n";
343 file->close();
344 delete file;
346 return true;
349 void DeleteDump()
351 const char* noDelete = getenv("MOZ_CRASHREPORTER_NO_DELETE_DUMP");
352 if (!noDelete || *noDelete == '\0') {
353 if (!gDumpFile.empty())
354 UIDeleteFile(gDumpFile);
355 if (!gExtraFile.empty())
356 UIDeleteFile(gExtraFile);
360 bool SendCompleted(bool success, const string& serverResponse)
362 if (success) {
363 DeleteDump();
364 return AddSubmittedReport(serverResponse);
366 return true;
369 bool ShouldEnableSending()
371 srand(time(0));
372 return ((rand() % 100) < MOZ_CRASHREPORTER_ENABLE_PERCENT);
375 } // namespace CrashReporter
377 using namespace CrashReporter;
379 void RewriteStrings(StringTable& queryParameters)
381 // rewrite some UI strings with the values from the query parameters
382 string product = queryParameters["ProductName"];
383 string vendor = queryParameters["Vendor"];
384 if (vendor.empty()) {
385 // Assume Mozilla if no vendor is specified
386 vendor = "Mozilla";
389 char buf[4096];
390 UI_SNPRINTF(buf, sizeof(buf),
391 gStrings[ST_CRASHREPORTERVENDORTITLE].c_str(),
392 vendor.c_str());
393 gStrings[ST_CRASHREPORTERTITLE] = buf;
396 string str = gStrings[ST_CRASHREPORTERPRODUCTERROR];
397 // Only do the replacement here if the string has two
398 // format specifiers to start. Otherwise
399 // we assume it has the product name hardcoded.
400 string::size_type pos = str.find("%s");
401 if (pos != string::npos)
402 pos = str.find("%s", pos+2);
403 if (pos != string::npos) {
404 // Leave a format specifier for UIError to fill in
405 UI_SNPRINTF(buf, sizeof(buf),
406 gStrings[ST_CRASHREPORTERPRODUCTERROR].c_str(),
407 product.c_str(),
408 "%s");
409 gStrings[ST_CRASHREPORTERERROR] = buf;
411 else {
412 // product name is hardcoded
413 gStrings[ST_CRASHREPORTERERROR] = str;
416 UI_SNPRINTF(buf, sizeof(buf),
417 gStrings[ST_CRASHREPORTERDESCRIPTION].c_str(),
418 product.c_str());
419 gStrings[ST_CRASHREPORTERDESCRIPTION] = buf;
421 UI_SNPRINTF(buf, sizeof(buf),
422 gStrings[ST_CHECKSUBMIT].c_str(),
423 vendor.c_str());
424 gStrings[ST_CHECKSUBMIT] = buf;
426 UI_SNPRINTF(buf, sizeof(buf),
427 gStrings[ST_RESTART].c_str(),
428 product.c_str());
429 gStrings[ST_RESTART] = buf;
431 UI_SNPRINTF(buf, sizeof(buf),
432 gStrings[ST_QUIT].c_str(),
433 product.c_str());
434 gStrings[ST_QUIT] = buf;
436 UI_SNPRINTF(buf, sizeof(buf),
437 gStrings[ST_ERROR_ENDOFLIFE].c_str(),
438 product.c_str());
439 gStrings[ST_ERROR_ENDOFLIFE] = buf;
442 bool CheckEndOfLifed(string version)
444 string reportPath =
445 gSettingsPath + UI_DIR_SEPARATOR + "EndOfLife" + version;
446 return UIFileExists(reportPath);
449 int main(int argc, char** argv)
451 gArgc = argc;
452 gArgv = argv;
454 if (!ReadConfig()) {
455 UIError("Couldn't read configuration.");
456 return 0;
459 if (!UIInit())
460 return 0;
462 if (argc > 1) {
463 gDumpFile = argv[1];
466 if (gDumpFile.empty()) {
467 // no dump file specified, run the default UI
468 UIShowDefaultUI();
469 } else {
470 gExtraFile = GetExtraDataFilename(gDumpFile);
471 if (gExtraFile.empty()) {
472 UIError(gStrings[ST_ERROR_BADARGUMENTS]);
473 return 0;
476 if (!UIFileExists(gExtraFile)) {
477 UIError(gStrings[ST_ERROR_EXTRAFILEEXISTS]);
478 return 0;
481 StringTable queryParameters;
482 if (!ReadStringsFromFile(gExtraFile, queryParameters, true)) {
483 UIError(gStrings[ST_ERROR_EXTRAFILEREAD]);
484 return 0;
487 if (queryParameters.find("ProductName") == queryParameters.end()) {
488 UIError(gStrings[ST_ERROR_NOPRODUCTNAME]);
489 return 0;
492 // There is enough information in the extra file to rewrite strings
493 // to be product specific
494 RewriteStrings(queryParameters);
496 if (queryParameters.find("ServerURL") == queryParameters.end()) {
497 UIError(gStrings[ST_ERROR_NOSERVERURL]);
498 return 0;
501 // Hopefully the settings path exists in the environment. Try that before
502 // asking the platform-specific code to guess.
503 #ifdef XP_WIN32
504 static const wchar_t kDataDirKey[] = L"MOZ_CRASHREPORTER_DATA_DIRECTORY";
505 const wchar_t *settingsPath = _wgetenv(kDataDirKey);
506 if (settingsPath && *settingsPath) {
507 gSettingsPath = WideToUTF8(settingsPath);
509 #else
510 static const char kDataDirKey[] = "MOZ_CRASHREPORTER_DATA_DIRECTORY";
511 const char *settingsPath = getenv(kDataDirKey);
512 if (settingsPath && *settingsPath) {
513 gSettingsPath = settingsPath;
515 #endif
516 else {
517 string product = queryParameters["ProductName"];
518 string vendor = queryParameters["Vendor"];
519 if (!UIGetSettingsPath(vendor, product, gSettingsPath)) {
520 gSettingsPath.clear();
524 if (gSettingsPath.empty() || !UIEnsurePathExists(gSettingsPath)) {
525 UIError(gStrings[ST_ERROR_NOSETTINGSPATH]);
526 return 0;
529 OpenLogFile();
531 if (!UIFileExists(gDumpFile)) {
532 UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
533 return 0;
536 string pendingDir = gSettingsPath + UI_DIR_SEPARATOR + "pending";
537 if (!MoveCrashData(pendingDir, gDumpFile, gExtraFile)) {
538 return 0;
541 string sendURL = queryParameters["ServerURL"];
542 // we don't need to actually send this
543 queryParameters.erase("ServerURL");
545 // re-set XUL_APP_FILE for xulrunner wrapped apps
546 const char *appfile = getenv("MOZ_CRASHREPORTER_RESTART_XUL_APP_FILE");
547 if (appfile && *appfile) {
548 const char prefix[] = "XUL_APP_FILE=";
549 char *env = (char*) malloc(strlen(appfile)+strlen(prefix));
550 if (!env) {
551 UIError("Out of memory");
552 return 0;
554 strcpy(env, prefix);
555 strcat(env, appfile);
556 putenv(env);
557 free(env);
560 vector<string> restartArgs;
562 ostringstream paramName;
563 int i = 0;
564 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
565 const char *param = getenv(paramName.str().c_str());
566 while (param && *param) {
567 restartArgs.push_back(param);
569 paramName.str("");
570 paramName << "MOZ_CRASHREPORTER_RESTART_ARG_" << i++;
571 param = getenv(paramName.str().c_str());
574 // allow override of the server url via environment variable
575 //XXX: remove this in the far future when our robot
576 // masters force everyone to use XULRunner
577 char* urlEnv = getenv("MOZ_CRASHREPORTER_URL");
578 if (urlEnv && *urlEnv) {
579 sendURL = urlEnv;
582 // see if this version has been end-of-lifed
583 if (queryParameters.find("Version") != queryParameters.end() &&
584 CheckEndOfLifed(queryParameters["Version"])) {
585 UIError(gStrings[ST_ERROR_ENDOFLIFE]);
586 DeleteDump();
587 return 0;
590 if (!UIShowCrashUI(gDumpFile, queryParameters, sendURL, restartArgs))
591 DeleteDump();
594 UIShutdown();
596 return 0;
599 #if defined(XP_WIN) && !defined(__GNUC__)
600 // We need WinMain in order to not be a console app. This function is unused
601 // if we are a console application.
602 int WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR args, int )
604 char** argv = static_cast<char**>(malloc(__argc * sizeof(char*)));
605 for (int i = 0; i < __argc; i++) {
606 argv[i] = strdup(WideToUTF8(__wargv[i]).c_str());
609 // Do the real work.
610 return main(__argc, argv);
612 #endif