summaryrefslogtreecommitdiffstats
path: root/src/remmina_bug_report.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/remmina_bug_report.c')
-rw-r--r--src/remmina_bug_report.c411
1 files changed, 411 insertions, 0 deletions
diff --git a/src/remmina_bug_report.c b/src/remmina_bug_report.c
new file mode 100644
index 0000000..7512223
--- /dev/null
+++ b/src/remmina_bug_report.c
@@ -0,0 +1,411 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2009-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2023-2024 Hiroyuki Tanaka, Sunil Bhat
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL.
+ * If you modify file(s) with this exception, you may extend this exception
+ * to your version of the file(s), but you are not obligated to do so.
+ * If you do not wish to do so, delete this exception statement from your
+ * version.
+ * If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ *
+ */
+
+#include <glib/gi18n.h>
+#include <string.h>
+#include "remmina_log.h"
+#include "remmina_bug_report.h"
+#include "remmina_public.h"
+#include "remmina/remmina_trace_calls.h"
+#include "remmina_info.h"
+#include "remmina_curl_connector.h"
+#include "remmina_utils.h"
+
+#if !JSON_CHECK_VERSION(1, 2, 0)
+ #define json_node_unref(x) json_node_free(x)
+#endif
+
+#define BUG_REPORT_UPLOAD_URL "https://info.remmina.org/bug_report/bug_report"
+static RemminaBugReportDialog *remmina_bug_report_dialog;
+
+#define GET_OBJECT(object_name) gtk_builder_get_object(remmina_bug_report_dialog->builder, object_name)
+
+#define TEMP_LOG_TEXT_FILE_NAME "remmina_shorter_logs.log"
+#define COMPRESSED_LOG_FILE_NAME "remmina_log_file.gz"
+#define LOG_RECENT_WINDOW 4000
+#define MAX_DESCRIPTION_LENGTH 8000
+const char *bug_report_preview_text_md = \
+"## Problem Description\n"
+"\n"
+"Write a detailed description of the problem.\n"
+"\n"
+"## What is the expected correct behavior?\n"
+"\n"
+"(What you want to see instead.)\n"
+"\n"
+"## Remote System Description\n"
+"\n"
+"* Server (OS name and version):\n"
+"* Special notes regarding the remote system (i.e. gateways, tunnel, etc.):\n"
+"\n"
+"## Debugging\n"
+"\n"
+"Please check the Include Debug Data box to allow for debug log to be sent with this bug report\n"
+"\n"
+"## Local System Description\n"
+"\n"
+"* Client (OS name and version):\n"
+"* Remmina version ( ```remmina --version``` ):\n"
+"* Installation(s):\n"
+" - [ ] Distribution package.\n"
+" - [ ] PPA.\n"
+" - [ ] Snap.\n"
+" - [ ] Flatpak.\n"
+" - [ ] Compiled from sources.\n"
+" - [ ] Other - detail:\n"
+"* Desktop environment (GNOME, Unity, KDE, ..):\n"
+"* Plugin(s):\n"
+" - [ ] RDP - freerdp version ( ```xfreerdp --version``` ):\n"
+" - [ ] VNC\n"
+" - [ ] SSH\n"
+" - [ ] SFTP\n"
+" - [ ] SPICE\n"
+" - [ ] WWW\n"
+" - [ ] EXEC\n"
+" - [ ] Other (please specify):\n"
+"* GTK backend (Wayland, Xorg):\n"
+"\n";
+
+
+/* Show the bug report dialog from the file ui/remmina_bug_report.glade */
+void remmina_bug_report_open(GtkWindow *parent)
+{
+ TRACE_CALL(__func__);
+ GtkTextBuffer* bug_report_preview_text_buffer;
+ remmina_bug_report_dialog = g_new0(RemminaBugReportDialog, 1);
+
+ remmina_bug_report_dialog->builder = remmina_public_gtk_builder_new_from_resource ("/org/remmina/Remmina/src/../data/ui/remmina_bug_report.glade");
+ remmina_bug_report_dialog->dialog = GTK_WIDGET(gtk_builder_get_object(remmina_bug_report_dialog->builder, "RemminaBugReportDialog"));
+ remmina_bug_report_dialog->bug_report_submit_button = GTK_BUTTON(GET_OBJECT("bug_report_submit_button"));
+ remmina_bug_report_dialog->bug_report_name_entry = GTK_ENTRY(GET_OBJECT("bug_report_name_entry"));
+ remmina_bug_report_dialog->bug_report_email_entry = GTK_ENTRY(GET_OBJECT("bug_report_email_entry"));
+ remmina_bug_report_dialog->bug_report_title_entry = GTK_ENTRY(GET_OBJECT("bug_report_title_entry"));
+ remmina_bug_report_dialog->bug_report_description_textview = GTK_TEXT_VIEW(GET_OBJECT("bug_report_description_textview"));
+ remmina_bug_report_dialog->bug_report_submit_status_label = GTK_LABEL(GET_OBJECT("bug_report_submit_status_label"));
+ remmina_bug_report_dialog->bug_report_debug_data_check_button = GTK_CHECK_BUTTON(GET_OBJECT("bug_report_debug_data_check_button"));
+ remmina_bug_report_dialog->bug_report_include_system_info_check_button = GTK_CHECK_BUTTON(GET_OBJECT("bug_report_include_system_info_check_button"));
+
+ // Set bug report markdown text in bug description window
+ bug_report_preview_text_buffer = gtk_text_buffer_new(NULL);
+ gtk_text_buffer_set_text(bug_report_preview_text_buffer, bug_report_preview_text_md, strlen(bug_report_preview_text_md));
+ gtk_text_view_set_buffer(remmina_bug_report_dialog->bug_report_description_textview, bug_report_preview_text_buffer);
+
+ if (parent) {
+ gtk_window_set_transient_for(GTK_WINDOW(remmina_bug_report_dialog->dialog), parent);
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(remmina_bug_report_dialog->dialog), TRUE);
+ }
+
+ g_signal_connect(remmina_bug_report_dialog->bug_report_submit_button, "clicked",
+ G_CALLBACK(remmina_bug_report_dialog_on_action_submit), (gpointer)remmina_bug_report_dialog);
+
+ gtk_window_present(GTK_WINDOW(remmina_bug_report_dialog->dialog));
+ g_object_unref(bug_report_preview_text_buffer);
+}
+
+
+void remmina_bug_report_dialog_on_action_submit(GSimpleAction *action, GVariant *param, gpointer data)
+{
+ TRACE_CALL(__func__);
+ JsonGenerator *g;
+ JsonObject *o;
+ gchar *b;
+
+ REMMINA_DEBUG("Submit Button Clicked. Uploading Bug Report data.");
+
+ gchar *markup = GRAY_TEXT("Sending Bug Report...");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+
+ // Store bug report info in JSON blob and encode before submitting
+ JsonNode *bug_report_data = remmina_bug_report_get_all();
+
+ if (bug_report_data == NULL || (o = json_node_get_object(bug_report_data)) == NULL) {
+ REMMINA_DEBUG("Failed to grab bug report data, no request is sent");
+ gchar *markup = RED_TEXT("Failure: Unable to generate bug report message. Please try again.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ json_node_unref(bug_report_data);
+ return;
+ } else if (strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Name"), "") == 0) {
+ REMMINA_DEBUG("No text in name entry of bug report data, no request is sent");
+ gchar *markup = RED_TEXT("Failure: Name/Username is required. Please enter a Name/Username.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ json_node_unref(bug_report_data);
+ return;
+ } else {
+ gchar *email_text = g_strdup(json_object_get_string_member(json_node_get_object(bug_report_data), "Email"));
+ if (strcmp(email_text, "") == 0) {
+ REMMINA_DEBUG("No text in email entry of bug report data, no request is sent");
+ gchar *markup = RED_TEXT("Failure: Email is required. Please enter an email.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ g_free(email_text);
+ json_node_unref(bug_report_data);
+ return;
+ } else {
+ gchar *save_ptr;
+ gchar *check_for_at_symbol = strtok_r(email_text, "@", &save_ptr);
+ gchar *check_for_dot = strtok_r(NULL, ".", &save_ptr);
+ gchar *check_for_domain = strtok_r(NULL, "", &save_ptr);
+ if (check_for_at_symbol == NULL || check_for_dot == NULL || check_for_domain == NULL) {
+ REMMINA_DEBUG("Text in email entry of bug report data is not a valid email, no request is sent");
+ gchar *markup = RED_TEXT("Failure: A valid email is required. Email is missing a prefix or domain.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ g_free(email_text);
+ json_node_unref(bug_report_data);
+ return;
+ } else if (strpbrk(check_for_at_symbol, "@.") != NULL || strpbrk(check_for_dot, "@.") != NULL || strpbrk(check_for_domain, "@.") != NULL) {
+ REMMINA_DEBUG("Text in email entry of bug report data is not a valid email, no request is sent");
+ gchar *markup = RED_TEXT("Failure: A valid email is required. Email contains extra @ and . characters.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ g_free(email_text);
+ json_node_unref(bug_report_data);
+ return;
+ }
+ }
+ g_free(email_text);
+ if (strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Bug_Title"), "") == 0) {
+ REMMINA_DEBUG("No text in bug title entry of bug report data, no request is sent");
+ gchar *markup = RED_TEXT("Failure: Bug Title is required. Please enter a Bug Title.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ json_node_unref(bug_report_data);
+ return;
+ } else if (strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Bug_Description"), "") == 0 ||
+ strcmp(json_object_get_string_member(json_node_get_object(bug_report_data), "Bug_Description"), bug_report_preview_text_md) == 0) {
+ REMMINA_DEBUG("No text in bug description of bug report data, no request is sent");
+ gchar *markup = RED_TEXT("Failure: Bug Description is required. Please fill out the template.");
+ gtk_label_set_markup(remmina_bug_report_dialog->bug_report_submit_status_label, markup);
+ g_free(markup);
+ json_node_unref(bug_report_data);
+ return;
+ }
+ }
+
+ g = json_generator_new();
+ json_generator_set_root(g, bug_report_data);
+ b = json_generator_to_data(g, NULL);
+ g_object_unref(g);
+
+ remmina_curl_compose_message(b, "POST", BUG_REPORT_UPLOAD_URL, remmina_bug_report_dialog->bug_report_submit_status_label);
+ json_node_unref(bug_report_data);
+}
+
+JsonNode *remmina_bug_report_get_all()
+{
+ TRACE_CALL(__func__);
+ JsonBuilder *b_inner, *b_outer; // holds entire JSON blob
+ JsonGenerator *g;
+ JsonNode *n; // temp JSON node
+ gchar *unenc_b, *enc_b;
+ gchar *uid;
+
+ // Initialize JSON blob
+ b_outer = json_builder_new();
+ if (b_outer == NULL) {
+ g_object_unref(b_outer);
+ return NULL;
+ }
+ json_builder_begin_object(b_outer);
+
+ gboolean include_debug_data = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_bug_report_dialog->bug_report_debug_data_check_button));
+ gboolean include_system_info = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(remmina_bug_report_dialog->bug_report_include_system_info_check_button));
+
+ if (include_debug_data || include_system_info) {
+ b_inner = json_builder_new();
+ if (b_inner == NULL) {
+ g_object_unref(b_inner);
+ g_object_unref(b_outer);
+ return NULL;
+ }
+ json_builder_begin_object(b_inner);
+
+ if (include_system_info){
+ n = remmina_info_stats_get_all();
+
+ json_builder_set_member_name(b_outer, "System_Info_Enabled");
+ json_builder_add_string_value(b_outer, "ON");
+
+ json_builder_set_member_name(b_inner, "System_Info");
+ json_builder_add_value(b_inner, n);
+ }
+ else{
+ json_builder_set_member_name(b_outer, "System_Info_Enabled");
+ json_builder_add_string_value(b_outer, "OFF");
+ }
+
+ if (include_debug_data){
+ gboolean log_file_result;
+ gchar *log_text;
+ gsize log_length;
+ GError *log_read_error;
+ gchar *log_filename = g_build_filename(g_get_tmp_dir(), LOG_FILE_NAME, NULL);
+ log_file_result = g_file_get_contents(log_filename, &log_text, &log_length, &log_read_error);
+ g_free(log_filename);
+ if (!log_file_result && log_read_error == NULL) {
+ REMMINA_DEBUG("Failed to grab entire log text, read error: %s", log_read_error->message);
+ }
+ gboolean shorter_logs_result;
+ gchar *temp_log_filename = g_build_filename(g_get_tmp_dir(), TEMP_LOG_TEXT_FILE_NAME, NULL);
+ gchar *recent_log_text;
+ if (log_length <= LOG_RECENT_WINDOW) {
+ recent_log_text = log_text;
+ shorter_logs_result = g_file_set_contents(temp_log_filename, recent_log_text, log_length, NULL);
+ }
+ else {
+ recent_log_text = log_text + (log_length - LOG_RECENT_WINDOW);
+ shorter_logs_result = g_file_set_contents(temp_log_filename, recent_log_text, LOG_RECENT_WINDOW, NULL);
+ }
+ g_free(log_text);
+ if (!shorter_logs_result) {
+ REMMINA_DEBUG("Failed to write recent window of logs to temp file.");
+ }
+
+ gchar *comp_log_filename = g_build_filename(g_get_tmp_dir(), COMPRESSED_LOG_FILE_NAME, NULL);
+ GFile *in = g_file_new_for_path(temp_log_filename);
+ GFile *out = g_file_new_for_path(comp_log_filename);
+ // Clear out old compressed log file to prevent appending
+ g_file_delete(out, NULL, NULL);
+ remmina_compress_from_file_to_file(in, out);
+ g_free(temp_log_filename);
+
+ gboolean com_log_file_result;
+ gchar *compressed_logs;
+ gsize com_log_length;
+ GError *com_log_read_error;
+ com_log_file_result = g_file_get_contents(comp_log_filename, &compressed_logs, &com_log_length, &com_log_read_error);
+ n = json_node_alloc();
+ if (!com_log_file_result && com_log_read_error != NULL) {
+ REMMINA_DEBUG("Failed to grab entire compressed log text, read error: %s", com_log_read_error->message);
+ json_node_init_string(n, "");
+ }
+ else {
+ gchar *logs_base64 = g_base64_encode((guchar *)compressed_logs, com_log_length);
+ json_node_init_string(n, logs_base64);
+ g_free(logs_base64);
+ }
+
+ json_builder_set_member_name(b_outer, "Debug_Log_Enabled");
+ json_builder_add_string_value(b_outer, "ON");
+ json_builder_set_member_name(b_inner, "Compressed_Logs");
+ json_builder_add_value(b_inner, n);
+ g_free(compressed_logs);
+ g_free(comp_log_filename);
+
+ }
+ else{
+ json_builder_set_member_name(b_outer, "Debug_Log_Enabled");
+ json_builder_add_string_value(b_outer, "OFF");
+ }
+
+ // Wrap up by setting n to the final JSON blob
+ json_builder_end_object(b_inner);
+ n = json_builder_get_root(b_inner);
+ g_object_unref(b_inner);
+
+ g = json_generator_new();
+ json_generator_set_root(g, n);
+ json_node_unref(n);
+ unenc_b = json_generator_to_data(g, NULL); // unenc_b=bug report data
+ g_object_unref(g);
+
+ // Now base64 encode report data
+ enc_b = g_base64_encode((guchar *)unenc_b, strlen(unenc_b));
+ if (enc_b == NULL) {
+ g_object_unref(b_outer);
+ g_free(unenc_b);
+ REMMINA_DEBUG("Failed to encode inner JSON");
+ return NULL;
+ }
+ g_free(unenc_b);
+
+ json_builder_set_member_name(b_outer, "encdata");
+ json_builder_add_string_value(b_outer, enc_b);
+ g_free(enc_b);
+ }
+ else {
+ json_builder_set_member_name(b_outer, "System_Info_Enabled");
+ json_builder_add_string_value(b_outer, "OFF");
+ json_builder_set_member_name(b_outer, "Debug_Log_Enabled");
+ json_builder_add_string_value(b_outer, "OFF");
+ }
+
+ n = remmina_info_stats_get_uid();
+ uid = g_strdup(json_node_get_string(n));
+ json_builder_set_member_name(b_outer, "UID");
+ json_builder_add_string_value(b_outer, uid);
+
+ // Allocate new json node for each text entry and add to total blob
+ n = json_node_alloc();
+ json_node_init_string(n, gtk_entry_get_text(remmina_bug_report_dialog->bug_report_name_entry));
+ json_builder_set_member_name(b_outer, "Name");
+ json_builder_add_value(b_outer, n);
+
+ n = json_node_alloc();
+ json_node_init_string(n, gtk_entry_get_text(remmina_bug_report_dialog->bug_report_email_entry));
+ json_builder_set_member_name(b_outer, "Email");
+ json_builder_add_value(b_outer, n);
+
+ n = json_node_alloc();
+ json_node_init_string(n, gtk_entry_get_text(remmina_bug_report_dialog->bug_report_title_entry));
+ json_builder_set_member_name(b_outer, "Bug_Title");
+ json_builder_add_value(b_outer, n);
+
+ GtkTextBuffer *buffer;
+ gchar *description_text;
+ GtkTextIter start, end;
+ buffer = gtk_text_view_get_buffer(remmina_bug_report_dialog->bug_report_description_textview);
+ gtk_text_buffer_get_start_iter(buffer, &start);
+ gtk_text_buffer_get_iter_at_offset(buffer, &end, MAX_DESCRIPTION_LENGTH);
+ description_text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
+
+ n = json_node_alloc();
+ json_node_init_string(n, description_text);
+ json_builder_set_member_name(b_outer, "Bug_Description");
+ json_builder_add_value(b_outer, n);
+
+ json_builder_end_object(b_outer);
+ n = json_builder_get_root(b_outer);
+ g_object_unref(b_outer);
+
+ g_free(uid);
+
+ return n;
+}