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
15 * The Original Code is Mozilla Toolkit Crash Reporter
17 * The Initial Developer of the Original Code is
19 * Portions created by the Initial Developer are Copyright (C) 2006-2007
20 * the Initial Developer. All Rights Reserved.
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"
43 // Disable exception handler warnings.
44 # pragma warning( disable : 4530 )
57 using std::istringstream
;
58 using std::ostringstream
;
64 namespace CrashReporter
{
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
)
80 if (!gStrings
[ST_CRASHREPORTERERROR
].empty()) {
82 UI_SNPRINTF(buf
, 2048,
83 gStrings
[ST_CRASHREPORTERERROR
].c_str(),
87 errorMessage
= message
;
90 UIError_impl(errorMessage
);
93 static string
Unescape(const string
& str
)
96 for (string::const_iterator iter
= str
.begin();
103 } else if (*iter
== 'n') {
105 } else if (*iter
== 't') {
109 ret
.push_back(*iter
);
116 static string
Escape(const string
& str
)
119 for (string::const_iterator iter
= str
.begin();
124 } else if (*iter
== '\n') {
126 } else if (*iter
== '\t') {
129 ret
.push_back(*iter
);
136 bool ReadStrings(istream
& in
, StringTable
& strings
, bool unescape
)
138 string currentSection
;
141 std::getline(in
, line
);
142 int sep
= line
.find('=');
145 key
= line
.substr(0, sep
);
146 value
= line
.substr(sep
+ 1);
148 value
= Unescape(value
);
149 strings
[key
] = value
;
156 bool ReadStringsFromFile(const string
& path
,
157 StringTable
& strings
,
160 ifstream
* f
= UIOpenRead(path
);
161 bool success
= false;
163 success
= ReadStrings(*f
, strings
, unescape
);
171 bool WriteStrings(ostream
& out
,
172 const string
& header
,
173 StringTable
& strings
,
176 out
<< "[" << header
<< "]" << std::endl
;
177 for (StringTable::iterator iter
= strings
.begin();
178 iter
!= strings
.end();
180 out
<< iter
->first
<< "=";
182 out
<< Escape(iter
->second
);
192 bool WriteStringsToFile(const string
& path
,
193 const string
& header
,
194 StringTable
& strings
,
197 ofstream
* f
= UIOpenWrite(path
.c_str());
198 bool success
= false;
200 success
= WriteStrings(*f
, header
, strings
, escape
);
208 void LogMessage(const std::string
& message
)
210 if (gLogStream
.get()) {
214 if (strftime(date
, sizeof(date
) - 1, "%c", localtime(&tm
)) == 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()
229 if (!UIGetIniPath(iniPath
))
232 if (!ReadStringsFromFile(iniPath
, gStrings
, true))
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);
243 static string
GetExtraDataFilename(const string
& dumpfile
)
245 string
filename(dumpfile
);
246 int dot
= filename
.rfind('.');
250 filename
.replace(dot
, filename
.length() - dot
, kExtraDataExtension
);
254 static string
Basename(const string
& file
)
256 int slashIndex
= file
.rfind(UI_DIR_SEPARATOR
);
258 return file
.substr(slashIndex
+ 1);
263 static bool MoveCrashData(const string
& toDir
,
267 if (!UIEnsurePathExists(toDir
)) {
268 UIError(gStrings
[ST_ERROR_CREATEDUMPDIR
]);
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
]);
280 if (!UIMoveFile(extrafile
, newExtra
)) {
281 UIError(gStrings
[ST_ERROR_EXTRAFILEMOVE
]);
286 extrafile
= newExtra
;
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
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";
312 if (responseItems
.find("CrashID") == responseItems
.end())
315 string submittedDir
=
316 gSettingsPath
+ UI_DIR_SEPARATOR
+ "submitted";
317 if (!UIEnsurePathExists(submittedDir
)) {
321 string path
= submittedDir
+ UI_DIR_SEPARATOR
+
322 responseItems
["CrashID"] + ".txt";
324 ofstream
* file
= UIOpenWrite(path
);
325 if (!file
->is_open()) {
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";
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
)
364 return AddSubmittedReport(serverResponse
);
369 bool ShouldEnableSending()
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
390 UI_SNPRINTF(buf
, sizeof(buf
),
391 gStrings
[ST_CRASHREPORTERVENDORTITLE
].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(),
409 gStrings
[ST_CRASHREPORTERERROR
] = buf
;
412 // product name is hardcoded
413 gStrings
[ST_CRASHREPORTERERROR
] = str
;
416 UI_SNPRINTF(buf
, sizeof(buf
),
417 gStrings
[ST_CRASHREPORTERDESCRIPTION
].c_str(),
419 gStrings
[ST_CRASHREPORTERDESCRIPTION
] = buf
;
421 UI_SNPRINTF(buf
, sizeof(buf
),
422 gStrings
[ST_CHECKSUBMIT
].c_str(),
424 gStrings
[ST_CHECKSUBMIT
] = buf
;
426 UI_SNPRINTF(buf
, sizeof(buf
),
427 gStrings
[ST_RESTART
].c_str(),
429 gStrings
[ST_RESTART
] = buf
;
431 UI_SNPRINTF(buf
, sizeof(buf
),
432 gStrings
[ST_QUIT
].c_str(),
434 gStrings
[ST_QUIT
] = buf
;
436 UI_SNPRINTF(buf
, sizeof(buf
),
437 gStrings
[ST_ERROR_ENDOFLIFE
].c_str(),
439 gStrings
[ST_ERROR_ENDOFLIFE
] = buf
;
442 bool CheckEndOfLifed(string version
)
445 gSettingsPath
+ UI_DIR_SEPARATOR
+ "EndOfLife" + version
;
446 return UIFileExists(reportPath
);
449 int main(int argc
, char** argv
)
455 UIError("Couldn't read configuration.");
466 if (gDumpFile
.empty()) {
467 // no dump file specified, run the default UI
470 gExtraFile
= GetExtraDataFilename(gDumpFile
);
471 if (gExtraFile
.empty()) {
472 UIError(gStrings
[ST_ERROR_BADARGUMENTS
]);
476 if (!UIFileExists(gExtraFile
)) {
477 UIError(gStrings
[ST_ERROR_EXTRAFILEEXISTS
]);
481 StringTable queryParameters
;
482 if (!ReadStringsFromFile(gExtraFile
, queryParameters
, true)) {
483 UIError(gStrings
[ST_ERROR_EXTRAFILEREAD
]);
487 if (queryParameters
.find("ProductName") == queryParameters
.end()) {
488 UIError(gStrings
[ST_ERROR_NOPRODUCTNAME
]);
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
]);
501 // Hopefully the settings path exists in the environment. Try that before
502 // asking the platform-specific code to guess.
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
);
510 static const char kDataDirKey
[] = "MOZ_CRASHREPORTER_DATA_DIRECTORY";
511 const char *settingsPath
= getenv(kDataDirKey
);
512 if (settingsPath
&& *settingsPath
) {
513 gSettingsPath
= settingsPath
;
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
]);
531 if (!UIFileExists(gDumpFile
)) {
532 UIError(gStrings
[ST_ERROR_DUMPFILEEXISTS
]);
536 string pendingDir
= gSettingsPath
+ UI_DIR_SEPARATOR
+ "pending";
537 if (!MoveCrashData(pendingDir
, gDumpFile
, gExtraFile
)) {
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
));
551 UIError("Out of memory");
555 strcat(env
, appfile
);
560 vector
<string
> restartArgs
;
562 ostringstream paramName
;
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
);
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
) {
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
]);
590 if (!UIShowCrashUI(gDumpFile
, queryParameters
, sendURL
, restartArgs
))
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());
610 return main(__argc
, argv
);