diff options
Diffstat (limited to 'toolkit/crashreporter/client/crashreporter_gtk_common.cpp')
-rw-r--r-- | toolkit/crashreporter/client/crashreporter_gtk_common.cpp | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/toolkit/crashreporter/client/crashreporter_gtk_common.cpp b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp new file mode 100644 index 0000000000..b9a957cfd9 --- /dev/null +++ b/toolkit/crashreporter/client/crashreporter_gtk_common.cpp @@ -0,0 +1,361 @@ +/* -*- 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 <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 "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; +StringTable gFiles; +Json::Value gQueryParameters; +string gHttpProxy; +string gAuth; +string gCACertificateFile; +string gSendURL; +string gURLParameter; +vector<string> gRestartArgs; +GThread* gSendThreadID; + +// From crashreporter_linux.cpp +void SendReport(); +void DisableGUIAndSendReport(); +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) { + free(argv); + return false; + } + + if (pid == 0) { + (void)execv(argv[0], argv); + _exit(1); + } + + free(argv); + + return true; +} + +// Quit the app, used as a timeout callback +gboolean CloseApp(gpointer data) { + if (!gAutoSubmit) { + 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; +} + +#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(). +} + +gpointer SendThread(gpointer args) { + Json::StreamWriterBuilder builder; + builder["indentation"] = ""; + string parameters(writeString(builder, gQueryParameters)); + + string response, error; + long response_code; + + bool success = google_breakpad::HTTPUpload::SendRequest( + gSendURL, parameters, gFiles, gHttpProxy, gAuth, gCACertificateFile, + &response, &response_code, &error); + if (success) { + LogMessage("Crash report submitted successfully"); + } else { + LogMessage("Crash report submission failed: " + error); + } + + SendCompleted(success, response); + + if (!gAutoSubmit) { + // 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; + DisableGUIAndSendReport(); + } 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.removeMember("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 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] = {const_cast<char*>("mv"), strdup(file.c_str()), + strdup(newfile.c_str()), 0}; + if (args[1] && args[2]) execve("/bin/mv", args, 0); + free(args[1]); + free(args[2]); + exit(-1); + } + int status; + waitpid(pID, &status, 0); + return UIFileExists(newfile); +} |