gecko/toolkit/crashreporter/client/crashreporter_gtk_common.cpp
Steven MacLeod 3c24431e2e Bug 994707 - Fix crash reporter writing submission events with CRLF line endings on windows. r=bsmedberg
This introduces a new argument to UIOpenWrite to open the file in binary
mode. We now write the submission events in binary mode so that LF
characters will not be translated to CRLF on Windows.

--HG--
extra : rebase_source : 828d593503960c77bcb6baee69d23161c838d3f6
2014-07-16 15:47:57 -04:00

458 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "crashreporter.h"
#include <unistd.h>
#include <dlfcn.h>
#include <errno.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <gdk/gdkkeysyms.h>
#include <algorithm>
#include <string>
#include <vector>
#include "mozilla/NullPtr.h"
#include "common/linux/http_upload.h"
#include "crashreporter.h"
#include "crashreporter_gtk_common.h"
#ifndef GDK_KEY_Escape
#define GDK_KEY_Escape GDK_Escape
#endif
using std::string;
using std::vector;
using namespace CrashReporter;
GtkWidget* gWindow = 0;
GtkWidget* gSubmitReportCheck = 0;
GtkWidget* gIncludeURLCheck = 0;
GtkWidget* gThrobber = 0;
GtkWidget* gProgressLabel = 0;
GtkWidget* gCloseButton = 0;
GtkWidget* gRestartButton = 0;
bool gInitialized = false;
bool gDidTrySend = false;
string gDumpFile;
StringTable gQueryParameters;
string gHttpProxy;
string gAuth;
string gCACertificateFile;
string gSendURL;
string gURLParameter;
vector<string> gRestartArgs;
GThread* gSendThreadID;
// From crashreporter_linux.cpp
void SaveSettings();
void SendReport();
void TryInitGnome();
void UpdateSubmit();
static bool RestartApplication()
{
char** argv = reinterpret_cast<char**>(
malloc(sizeof(char*) * (gRestartArgs.size() + 1)));
if (!argv) return false;
unsigned int i;
for (i = 0; i < gRestartArgs.size(); i++) {
argv[i] = (char*)gRestartArgs[i].c_str();
}
argv[i] = 0;
pid_t pid = fork();
if (pid == -1)
return false;
else if (pid == 0) {
(void)execv(argv[0], argv);
_exit(1);
}
free(argv);
return true;
}
// Quit the app, used as a timeout callback
static gboolean CloseApp(gpointer data)
{
gtk_main_quit();
g_thread_join(gSendThreadID);
return FALSE;
}
static gboolean ReportCompleted(gpointer success)
{
gtk_widget_hide(gThrobber);
string str = success ? gStrings[ST_REPORTSUBMITSUCCESS]
: gStrings[ST_SUBMITFAILED];
gtk_label_set_text(GTK_LABEL(gProgressLabel), str.c_str());
g_timeout_add(5000, CloseApp, 0);
return FALSE;
}
#ifdef MOZ_ENABLE_GCONF
#define HTTP_PROXY_DIR "/system/http_proxy"
void LoadProxyinfo()
{
class GConfClient;
typedef GConfClient * (*_gconf_default_fn)();
typedef gboolean (*_gconf_bool_fn)(GConfClient *, const gchar *, GError **);
typedef gint (*_gconf_int_fn)(GConfClient *, const gchar *, GError **);
typedef gchar * (*_gconf_string_fn)(GConfClient *, const gchar *, GError **);
if (getenv ("http_proxy"))
return; // libcurl can use the value from the environment
static void* gconfLib = dlopen("libgconf-2.so.4", RTLD_LAZY);
if (!gconfLib)
return;
_gconf_default_fn gconf_client_get_default =
(_gconf_default_fn)dlsym(gconfLib, "gconf_client_get_default");
_gconf_bool_fn gconf_client_get_bool =
(_gconf_bool_fn)dlsym(gconfLib, "gconf_client_get_bool");
_gconf_int_fn gconf_client_get_int =
(_gconf_int_fn)dlsym(gconfLib, "gconf_client_get_int");
_gconf_string_fn gconf_client_get_string =
(_gconf_string_fn)dlsym(gconfLib, "gconf_client_get_string");
if(!(gconf_client_get_default &&
gconf_client_get_bool &&
gconf_client_get_int &&
gconf_client_get_string)) {
dlclose(gconfLib);
return;
}
GConfClient *conf = gconf_client_get_default();
if (gconf_client_get_bool(conf, HTTP_PROXY_DIR "/use_http_proxy", nullptr)) {
gint port;
gchar *host = nullptr, *httpproxy = nullptr;
host = gconf_client_get_string(conf, HTTP_PROXY_DIR "/host", nullptr);
port = gconf_client_get_int(conf, HTTP_PROXY_DIR "/port", nullptr);
if (port && host && *host != '\0') {
httpproxy = g_strdup_printf("http://%s:%d/", host, port);
gHttpProxy = httpproxy;
}
g_free(host);
g_free(httpproxy);
if (gconf_client_get_bool(conf, HTTP_PROXY_DIR "/use_authentication",
nullptr)) {
gchar *user, *password, *auth = nullptr;
user = gconf_client_get_string(conf,
HTTP_PROXY_DIR "/authentication_user",
nullptr);
password = gconf_client_get_string(conf,
HTTP_PROXY_DIR
"/authentication_password",
nullptr);
if (user && password) {
auth = g_strdup_printf("%s:%s", user, password);
gAuth = auth;
}
g_free(user);
g_free(password);
g_free(auth);
}
}
g_object_unref(conf);
// Don't dlclose gconfLib as libORBit-2 uses atexit().
}
#endif
gpointer SendThread(gpointer args)
{
string response, error;
long response_code;
bool success = google_breakpad::HTTPUpload::SendRequest
(gSendURL,
gQueryParameters,
gDumpFile,
"upload_file_minidump",
gHttpProxy, gAuth,
gCACertificateFile,
&response,
&response_code,
&error);
if (success) {
LogMessage("Crash report submitted successfully");
}
else {
LogMessage("Crash report submission failed: " + error);
}
SendCompleted(success, response);
// Apparently glib is threadsafe, and will schedule this
// on the main thread, see:
// http://library.gnome.org/devel/gtk-faq/stable/x499.html
g_idle_add(ReportCompleted, (gpointer)success);
return nullptr;
}
gboolean WindowDeleted(GtkWidget* window,
GdkEvent* event,
gpointer userData)
{
SaveSettings();
gtk_main_quit();
return TRUE;
}
gboolean check_escape(GtkWidget* window,
GdkEventKey* event,
gpointer userData)
{
if (event->keyval == GDK_KEY_Escape) {
gtk_main_quit();
return TRUE;
}
return FALSE;
}
static void MaybeSubmitReport()
{
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))) {
gDidTrySend = true;
SendReport();
} else {
gtk_main_quit();
}
}
void CloseClicked(GtkButton* button,
gpointer userData)
{
SaveSettings();
MaybeSubmitReport();
}
void RestartClicked(GtkButton* button,
gpointer userData)
{
SaveSettings();
RestartApplication();
MaybeSubmitReport();
}
static void UpdateURL()
{
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck))) {
gQueryParameters["URL"] = gURLParameter;
} else {
gQueryParameters.erase("URL");
}
}
void SubmitReportChecked(GtkButton* sender, gpointer userData)
{
UpdateSubmit();
}
void IncludeURLClicked(GtkButton* sender, gpointer userData)
{
UpdateURL();
}
/* === Crashreporter UI Functions === */
bool UIInit()
{
// breakpad probably left us with blocked signals, unblock them here
sigset_t signals, old;
sigfillset(&signals);
sigprocmask(SIG_UNBLOCK, &signals, &old);
// tell glib we're going to use threads
g_thread_init(nullptr);
if (gtk_init_check(&gArgc, &gArgv)) {
gInitialized = true;
if (gStrings.find("isRTL") != gStrings.end() &&
gStrings["isRTL"] == "yes")
gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL);
return true;
}
return false;
}
void UIShowDefaultUI()
{
GtkWidget* errorDialog =
gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"%s", gStrings[ST_CRASHREPORTERDEFAULT].c_str());
gtk_window_set_title(GTK_WINDOW(errorDialog),
gStrings[ST_CRASHREPORTERTITLE].c_str());
gtk_dialog_run(GTK_DIALOG(errorDialog));
}
void UIError_impl(const string& message)
{
if (!gInitialized) {
// Didn't initialize, this is the best we can do
printf("Error: %s\n", message.c_str());
return;
}
GtkWidget* errorDialog =
gtk_message_dialog_new(nullptr, GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_CLOSE,
"%s", message.c_str());
gtk_window_set_title(GTK_WINDOW(errorDialog),
gStrings[ST_CRASHREPORTERTITLE].c_str());
gtk_dialog_run(GTK_DIALOG(errorDialog));
}
bool UIGetIniPath(string& path)
{
path = gArgv[0];
path.append(".ini");
return true;
}
/*
* Settings are stored in ~/.vendor/product, or
* ~/.product if vendor is empty.
*/
bool UIGetSettingsPath(const string& vendor,
const string& product,
string& settingsPath)
{
char* home = getenv("HOME");
if (!home)
return false;
settingsPath = home;
settingsPath += "/.";
if (!vendor.empty()) {
string lc_vendor;
std::transform(vendor.begin(), vendor.end(), back_inserter(lc_vendor),
(int(*)(int)) std::tolower);
settingsPath += lc_vendor + "/";
}
string lc_product;
std::transform(product.begin(), product.end(), back_inserter(lc_product),
(int(*)(int)) std::tolower);
settingsPath += lc_product + "/Crash Reports";
return true;
}
bool UIEnsurePathExists(const string& path)
{
int ret = mkdir(path.c_str(), S_IRWXU);
int e = errno;
if (ret == -1 && e != EEXIST)
return false;
return true;
}
bool UIFileExists(const string& path)
{
struct stat sb;
int ret = stat(path.c_str(), &sb);
if (ret == -1 || !(sb.st_mode & S_IFREG))
return false;
return true;
}
bool UIMoveFile(const string& file, const string& newfile)
{
if (!rename(file.c_str(), newfile.c_str()))
return true;
if (errno != EXDEV)
return false;
// use system /bin/mv instead, time to fork
pid_t pID = vfork();
if (pID < 0) {
// Failed to fork
return false;
}
if (pID == 0) {
char* const args[4] = {
"mv",
strdup(file.c_str()),
strdup(newfile.c_str()),
0
};
if (args[1] && args[2])
execve("/bin/mv", args, 0);
if (args[1])
free(args[1]);
if (args[2])
free(args[2]);
exit(-1);
}
int status;
waitpid(pID, &status, 0);
return UIFileExists(newfile);
}
bool UIDeleteFile(const string& file)
{
return (unlink(file.c_str()) != -1);
}
std::ifstream* UIOpenRead(const string& filename)
{
return new std::ifstream(filename.c_str(), std::ios::in);
}
std::ofstream* UIOpenWrite(const string& filename,
bool append, // append=false
bool binary) // binary=false
{
std::ios_base::openmode mode = std::ios::out;
if (append) {
mode = mode | std::ios::app;
}
if (binary) {
mode = mode | std::ios::binary;
}
return new std::ofstream(filename.c_str(), mode);
}