From 6c8113dfeab05d0ea35f15bf68fb3554767b363c Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Wed, 22 Jul 2026 11:54:01 +0200 Subject: [PATCH] feat(osint): execute DNS lookup action --- Makefile | 13 +- README.md | 4 +- include/models/osint_dns_query.h | 30 +++ include/views/application_message_dialog.h | 20 ++ include/views/main_window.h | 28 ++ include/widgets/workspace.h | 31 +++ src/core/application.c | 290 +++++++++++++++++++++ src/models/osint_action_catalog.c | 2 +- src/models/osint_dns_query.c | 70 +++++ src/views/application_message_dialog.c | 87 +++++++ src/views/main_window.c | 50 ++++ src/widgets/workspace.c | 78 +++++- tests/test_osint_dns_query.c | 35 +++ 13 files changed, 720 insertions(+), 18 deletions(-) create mode 100644 include/models/osint_dns_query.h create mode 100644 src/models/osint_dns_query.c create mode 100644 tests/test_osint_dns_query.c diff --git a/Makefile b/Makefile index b364a03..630ad8f 100644 --- a/Makefile +++ b/Makefile @@ -115,6 +115,7 @@ TEST_INVESTIGATION_GRAPH_LOAD_TASK := tests/test_investigation_graph_load_task TEST_GRAPH_NODE_POSITION_DAO := tests/test_graph_node_position_dao TEST_OSINT_SELECTION_CONTEXT := tests/test_osint_selection_context TEST_OSINT_ACTION_CATALOG := tests/test_osint_action_catalog +TEST_OSINT_DNS_QUERY := tests/test_osint_dns_query all: $(TARGET) @@ -545,6 +546,11 @@ $(TEST_OSINT_ACTION_CATALOG): \ src/models/relation_record.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_OSINT_DNS_QUERY): \ + tests/test_osint_dns_query.c \ + src/models/osint_dns_query.c + $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) + $(TEST_INVESTIGATION_GRAPH_LOAD_TASK): \ tests/test_investigation_graph_load_task.c \ src/core/investigation_graph_load_task.c \ @@ -614,7 +620,8 @@ test: \ $(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \ $(TEST_GRAPH_NODE_POSITION_DAO) \ $(TEST_OSINT_SELECTION_CONTEXT) \ - $(TEST_OSINT_ACTION_CATALOG) + $(TEST_OSINT_ACTION_CATALOG) \ + $(TEST_OSINT_DNS_QUERY) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -664,6 +671,7 @@ test: \ @$(TEST_GRAPH_NODE_POSITION_DAO) @$(TEST_OSINT_SELECTION_CONTEXT) @$(TEST_OSINT_ACTION_CATALOG) + @$(TEST_OSINT_DNS_QUERY) @echo "Tous les tests sont valides." %.o: %.c @@ -719,7 +727,8 @@ clean: $(TEST_INVESTIGATION_GRAPH_LOAD_TASK) \ $(TEST_GRAPH_NODE_POSITION_DAO) \ $(TEST_OSINT_SELECTION_CONTEXT) \ - $(TEST_OSINT_ACTION_CATALOG) + $(TEST_OSINT_ACTION_CATALOG) \ + $(TEST_OSINT_DNS_QUERY) -include $(DEP) diff --git a/README.md b/README.md index 69e5116..424d640 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,9 @@ Le socle actuel comprend notamment : - menu OSINT contextuel préparé pour les entités et relations du graphe ; - contexte de sélection OSINT indépendant de GTK et validé par des tests ; - catalogue déterministe des actions compatibles avec la sélection OSINT ; -- disponibilité des actions OSINT synchronisée avec le registre d’outils. +- disponibilité des actions OSINT synchronisée avec le registre d’outils ; +- résolution DNS asynchrone avec `dig` depuis une entité domaine ; +- affichage sélectionnable des sorties standard et d’erreur, sans persistance. Les outils actuellement présents dans le catalogue initial sont : diff --git a/include/models/osint_dns_query.h b/include/models/osint_dns_query.h new file mode 100644 index 0000000..1fcba7c --- /dev/null +++ b/include/models/osint_dns_query.h @@ -0,0 +1,30 @@ +/****************************************************************************** + * @file osint_dns_query.h + * @brief Validation des cibles utilisées pour une résolution DNS OSINT. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_OSINT_DNS_QUERY_H +#define LABFY_INVESTIGATION_OSINT_DNS_QUERY_H + +#include + +G_BEGIN_DECLS + +/** + * @brief Vérifie qu'une cible est un nom de domaine DNS exploitable. + * + * La fonction accepte les lettres ASCII sans distinction de casse, les + * chiffres, les tirets internes et les points séparant les labels. Un point + * final représentant la racine DNS est également accepté. + * + * @param target_value Valeur à vérifier. + * + * @return TRUE lorsque la cible peut être transmise à dig. + */ +gboolean osint_dns_query_is_valid_target( + const char *target_value +); + +G_END_DECLS + +#endif diff --git a/include/views/application_message_dialog.h b/include/views/application_message_dialog.h index 6428ff9..fe0d95c 100644 --- a/include/views/application_message_dialog.h +++ b/include/views/application_message_dialog.h @@ -49,6 +49,26 @@ void application_message_dialog_present( const char *message ); +/** + * @brief Affiche un message accompagné d'un contenu textuel défilable. + * + * Le contenu détaillé est affiché dans une zone monospace sélectionnable. + * Les chaînes sont copiées par les widgets GTK. + * + * @param parent_window Fenêtre parente, ou NULL. + * @param message_type Type de message. + * @param title Titre de la fenêtre, ou NULL. + * @param message Message principal, ou NULL. + * @param details Contenu détaillé, ou NULL. + */ +void application_message_dialog_present_details( + GtkWindow *parent_window, + ApplicationMessageDialogType message_type, + const char *title, + const char *message, + const char *details +); + /** * @brief Affiche un dialogue modal demandant une confirmation. * diff --git a/include/views/main_window.h b/include/views/main_window.h index a6a991a..a1a9b20 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -121,6 +121,19 @@ typedef void (*MainWindowAddRelationCallback)( gpointer user_data ); +/** + * @brief Callback appelé lors du déclenchement d'une action OSINT. + * + * @param action_identifier Identifiant stable de l'action. + * @param target_value Valeur métier ciblée. + * @param user_data Données privées fournies par l'appelant. + */ +typedef void (*MainWindowOsintActionCallback)( + const char *action_identifier, + const char *target_value, + gpointer user_data +); + /** * @brief Définit le callback de vérification d'une preuve. * @@ -181,6 +194,21 @@ void main_window_set_add_relation_callback( gpointer user_data ); +/** + * @brief Définit le callback de déclenchement des actions OSINT. + * + * MainWindow relaie uniquement la demande provenant du Workspace. + * + * @param main_window Fenêtre principale. + * @param callback Callback facultatif. + * @param user_data Données privées transmises au callback. + */ +void main_window_set_osint_action_callback( + MainWindow *main_window, + MainWindowOsintActionCallback callback, + gpointer user_data +); + /** * @brief Définit le callback du bouton « Nouvelle enquête ». * diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index b13375d..8283101 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -78,6 +78,21 @@ typedef void (*WorkspaceAddRelationCallback)( gpointer user_data ); +/** + * @brief Callback appelé lors du déclenchement d'une action OSINT. + * + * Les chaînes sont empruntées et uniquement valides pendant l'appel. + * + * @param action_identifier Identifiant stable de l'action. + * @param target_value Valeur métier ciblée par l'action. + * @param user_data Données empruntées fournies par l'appelant. + */ +typedef void (*WorkspaceOsintActionCallback)( + const char *action_identifier, + const char *target_value, + gpointer user_data +); + /** * @brief Crée une nouvelle zone de travail. * @@ -171,6 +186,22 @@ void workspace_set_osint_tool_state( const char *version ); +/** + * @brief Définit le callback de déclenchement des actions OSINT. + * + * Le Workspace ne lance aucun outil externe. Il relaie uniquement l'action + * et la valeur du contexte courant vers la couche applicative. + * + * @param workspace Zone de travail. + * @param callback Callback facultatif. + * @param user_data Données empruntées transmises au callback. + */ +void workspace_set_osint_action_callback( + Workspace *workspace, + WorkspaceOsintActionCallback callback, + gpointer user_data +); + /** * @brief Définit le callback de vérification de la preuve affichée. * diff --git a/src/core/application.c b/src/core/application.c index 10ef095..d6b16d2 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -13,6 +13,7 @@ #include "models/investigation_graph_layout.h" #include "models/investigation_graph_model.h" #include "models/investigation_record.h" +#include "models/osint_dns_query.h" #include "core/investigation_tree_builder.h" #include "views/create_investigation_dialog.h" #include "views/folder_dialog.h" @@ -21,6 +22,7 @@ #include "core/task_manager.h" #include "core/background_task.h" #include "core/tool_initializer.h" +#include "core/tool_task.h" #include "views/file_dialog.h" #include "core/evidence_import_task.h" #include "models/evidence_record.h" @@ -151,6 +153,33 @@ typedef struct char *original_name; } ApplicationEvidenceIntegrityContext; +/** + * @brief Contexte conservé jusqu'à la fin d'une action OSINT. + */ +typedef struct +{ + Application *application; + char *target_value; +} ApplicationOsintActionContext; + +/** + * @brief Libère le contexte d'une action OSINT. + */ +static void application_osint_action_context_free( + gpointer user_data +) +{ + ApplicationOsintActionContext *context = user_data; + + if (context == NULL) + { + return; + } + + g_free(context->target_value); + g_free(context); +} + /** * @brief Libère le contexte du dialogue de métadonnées. */ @@ -914,6 +943,261 @@ static void application_start_tool_initialization( } } +/** + * @brief Convertit une sortie binaire d'outil en texte UTF-8 affichable. + * + * @param bytes Octets empruntés, ou NULL. + * + * @return Nouvelle chaîne UTF-8, jamais NULL. + */ +static char *application_osint_output_to_utf8( + GBytes *bytes +) +{ + gconstpointer data = NULL; + gsize data_size = 0; + + if (bytes == NULL) + { + return g_strdup(""); + } + + data = g_bytes_get_data(bytes, &data_size); + if (data == NULL || data_size == 0U) + { + return g_strdup(""); + } + + return g_utf8_make_valid(data, (gssize) data_size); +} + +/** + * @brief Présente le résultat final d'une résolution DNS. + */ +static void application_on_dns_lookup_completed( + BackgroundTask *task, + gpointer user_data +) +{ + ApplicationOsintActionContext *context = user_data; + Application *application = NULL; + const ToolTaskResult *task_result = NULL; + const ToolProcessResult *process_result = NULL; + GBytes *stdout_bytes = NULL; + GBytes *stderr_bytes = NULL; + char *stdout_text = NULL; + char *stderr_text = NULL; + char *details = NULL; + char *message = NULL; + GError *error = NULL; + + if (task == NULL || context == NULL || context->application == NULL) + { + return; + } + + application = context->application; + if (application->main_window == NULL) + { + return; + } + + if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED) + { + error = background_task_dup_error(task); + application_present_error( + application, + "Résolution DNS impossible", + error != NULL + ? error->message + : "L'exécution de dig a été annulée ou a échoué." + ); + g_clear_error(&error); + return; + } + + task_result = tool_task_result_from_background_task(task); + process_result = tool_task_result_get_process_result(task_result); + if (process_result == NULL) + { + application_present_error( + application, + "Résolution DNS incomplète", + "La tâche s'est terminée sans résultat exploitable." + ); + return; + } + + stdout_bytes = tool_process_result_ref_stdout(process_result); + stderr_bytes = tool_process_result_ref_stderr(process_result); + stdout_text = application_osint_output_to_utf8(stdout_bytes); + stderr_text = application_osint_output_to_utf8(stderr_bytes); + + if (stdout_text != NULL && stdout_text[0] != '\0' && + stderr_text != NULL && stderr_text[0] != '\0') + { + details = g_strdup_printf( + "Sortie standard :\n%s\n\nSortie d'erreur :\n%s", + stdout_text, + stderr_text + ); + } + else if (stdout_text != NULL && stdout_text[0] != '\0') + { + details = g_strdup(stdout_text); + } + else if (stderr_text != NULL && stderr_text[0] != '\0') + { + details = g_strdup(stderr_text); + } + else + { + details = g_strdup("Aucun enregistrement DNS retourné."); + } + + message = g_strdup_printf( + "Cible : %s — code de sortie : %d", + context->target_value, + tool_process_result_get_exit_status(process_result) + ); + + application_message_dialog_present_details( + main_window_get_window(application->main_window), + tool_process_result_is_success(process_result) + ? APPLICATION_MESSAGE_DIALOG_INFORMATION + : APPLICATION_MESSAGE_DIALOG_WARNING, + "Résultat de la résolution DNS", + message, + details + ); + + g_clear_pointer(&stdout_bytes, g_bytes_unref); + g_clear_pointer(&stderr_bytes, g_bytes_unref); + g_free(stdout_text); + g_free(stderr_text); + g_free(details); + g_free(message); +} + +/** + * @brief Prépare et démarre une résolution DNS avec dig. + */ +static void application_start_dns_lookup( + Application *application, + const char *target_value +) +{ + ToolRegistry *tool_registry = NULL; + ToolTask *tool_task = NULL; + BackgroundTask *background_task = NULL; + ApplicationOsintActionContext *context = NULL; + const char *arguments[] = {"+noall", "+answer", target_value, NULL}; + GError *error = NULL; + + if (application == NULL || application->task_manager == NULL || + application->tool_initializer == NULL || target_value == NULL || + !osint_dns_query_is_valid_target(target_value)) + { + if (application != NULL) + { + application_present_error( + application, + "Résolution DNS impossible", + "Le nom de domaine sélectionné est invalide." + ); + } + return; + } + + tool_registry = tool_initializer_get_registry(application->tool_initializer); + tool_task = tool_task_new( + tool_registry, + "dns.dig", + "Résolution DNS", + arguments, + NULL, + &error + ); + + if (tool_task == NULL) + { + application_present_error( + application, + "Résolution DNS impossible", + error != NULL ? error->message : "Impossible de préparer dig." + ); + g_clear_error(&error); + return; + } + + context = g_try_new0(ApplicationOsintActionContext, 1); + if (context != NULL) + { + context->application = application; + context->target_value = g_strdup(target_value); + } + + if (context == NULL || context->target_value == NULL) + { + application_osint_action_context_free(context); + tool_task_free(tool_task); + application_present_error( + application, + "Résolution DNS impossible", + "Impossible de conserver le contexte de l'action." + ); + return; + } + + background_task = tool_task_get_background_task(tool_task); + if (!task_manager_add(application->task_manager, background_task, &error) || + !tool_task_start( + tool_task, + application_on_dns_lookup_completed, + context, + application_osint_action_context_free, + &error + )) + { + task_manager_remove(application->task_manager, background_task); + application_osint_action_context_free(context); + application_present_error( + application, + "Résolution DNS impossible", + error != NULL ? error->message : "Impossible de démarrer dig." + ); + g_clear_error(&error); + tool_task_free(tool_task); + return; + } + + tool_task_free(tool_task); +} + +/** + * @brief Traite une action OSINT relayée par la fenêtre principale. + */ +static void application_on_osint_action_requested( + const char *action_identifier, + const char *target_value, + gpointer user_data +) +{ + Application *application = user_data; + + if (g_strcmp0(action_identifier, "dns-preview") == 0) + { + application_start_dns_lookup(application, target_value); + return; + } + + application_present_error( + application, + "Action OSINT inconnue", + "Cette action n'est pas encore prise en charge." + ); +} + /** * @brief Affiche une erreur dans une fenêtre GTK. * @@ -5102,6 +5386,12 @@ static void application_on_activate( application ); + main_window_set_osint_action_callback( + application->main_window, + application_on_osint_action_requested, + application + ); + main_window_set_new_investigation_callback( application->main_window, application_on_new_investigation_requested, diff --git a/src/models/osint_action_catalog.c b/src/models/osint_action_catalog.c index ef5d3f5..1a75857 100644 --- a/src/models/osint_action_catalog.c +++ b/src/models/osint_action_catalog.c @@ -84,7 +84,7 @@ OsintActionCatalog *osint_action_catalog_new_defaults(void) ); dns_action = osint_action_new( "dns-preview", "Résolution DNS", - "Préfiguration de l'adaptateur DNS.", + "Exécute dig sur le nom de domaine sélectionné.", OSINT_SELECTION_CONTEXT_KIND_ENTITY, "domain_name", "dns.dig", FALSE, "L'outil requis n'a pas encore été vérifié." ); diff --git a/src/models/osint_dns_query.c b/src/models/osint_dns_query.c new file mode 100644 index 0000000..f119556 --- /dev/null +++ b/src/models/osint_dns_query.c @@ -0,0 +1,70 @@ +/****************************************************************************** + * @file osint_dns_query.c + * @brief Validation des cibles utilisées pour une résolution DNS OSINT. + ******************************************************************************/ + +#include "models/osint_dns_query.h" + +#include + +gboolean osint_dns_query_is_valid_target( + const char *target_value +) +{ + gsize target_length = 0; + gsize label_length = 0; + + if (target_value == NULL || target_value[0] == '\0') + { + return FALSE; + } + + target_length = strlen(target_value); + if (target_length > 253U) + { + return FALSE; + } + + for (gsize character_index = 0; + character_index < target_length; + character_index++) + { + const char character = target_value[character_index]; + + if (character == '.') + { + if (label_length == 0U) + { + return character_index == target_length - 1U && + character_index > 0U; + } + + if (target_value[character_index - 1U] == '-') + { + return FALSE; + } + + if (character_index == target_length - 1U) + { + return TRUE; + } + + label_length = 0U; + continue; + } + + if (!(g_ascii_isalnum(character) || character == '-')) + { + return FALSE; + } + + if ((label_length == 0U && character == '-') || label_length >= 63U) + { + return FALSE; + } + + label_length++; + } + + return label_length > 0U && target_value[target_length - 1U] != '-'; +} diff --git a/src/views/application_message_dialog.c b/src/views/application_message_dialog.c index dc0a2c7..1705eb9 100644 --- a/src/views/application_message_dialog.c +++ b/src/views/application_message_dialog.c @@ -355,6 +355,93 @@ void application_message_dialog_present( ); } +void application_message_dialog_present_details( + GtkWindow *parent_window, + ApplicationMessageDialogType message_type, + const char *title, + const char *message, + const char *details +) +{ + GtkWindow *dialog_window = GTK_WINDOW(gtk_window_new()); + GtkWidget *main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + GtkWidget *title_label = gtk_label_new( + application_message_dialog_get_safe_title(message_type, title) + ); + GtkWidget *message_label = gtk_label_new( + application_message_dialog_get_safe_message(message) + ); + GtkWidget *scrolled_window = gtk_scrolled_window_new(); + GtkWidget *details_view = gtk_text_view_new(); + GtkWidget *close_button = gtk_button_new_with_label("Fermer"); + GtkTextBuffer *details_buffer = gtk_text_view_get_buffer( + GTK_TEXT_VIEW(details_view) + ); + + gtk_window_set_title( + dialog_window, + application_message_dialog_get_safe_title(message_type, title) + ); + gtk_window_set_default_size(dialog_window, 760, 520); + gtk_window_set_modal(dialog_window, TRUE); + gtk_window_set_destroy_with_parent(dialog_window, TRUE); + if (parent_window != NULL) + { + gtk_window_set_transient_for(dialog_window, parent_window); + } + + gtk_widget_set_margin_start(main_box, 20); + gtk_widget_set_margin_end(main_box, 20); + gtk_widget_set_margin_top(main_box, 20); + gtk_widget_set_margin_bottom(main_box, 20); + + gtk_widget_set_halign(title_label, GTK_ALIGN_START); + gtk_label_set_xalign(GTK_LABEL(title_label), 0.0F); + gtk_widget_add_css_class(title_label, "title-2"); + + gtk_widget_set_halign(message_label, GTK_ALIGN_FILL); + gtk_label_set_xalign(GTK_LABEL(message_label), 0.0F); + gtk_label_set_wrap(GTK_LABEL(message_label), TRUE); + + gtk_text_view_set_editable(GTK_TEXT_VIEW(details_view), FALSE); + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(details_view), TRUE); + gtk_text_view_set_monospace(GTK_TEXT_VIEW(details_view), TRUE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(details_view), GTK_WRAP_NONE); + gtk_text_buffer_set_text( + details_buffer, + details != NULL && details[0] != '\0' + ? details + : APPLICATION_MESSAGE_DIALOG_DEFAULT_MESSAGE, + -1 + ); + + gtk_scrolled_window_set_policy( + GTK_SCROLLED_WINDOW(scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC + ); + gtk_widget_set_vexpand(scrolled_window, TRUE); + gtk_scrolled_window_set_child( + GTK_SCROLLED_WINDOW(scrolled_window), + details_view + ); + + gtk_widget_set_halign(close_button, GTK_ALIGN_END); + g_signal_connect( + close_button, + "clicked", + G_CALLBACK(application_message_dialog_on_close_clicked), + dialog_window + ); + + gtk_box_append(GTK_BOX(main_box), title_label); + gtk_box_append(GTK_BOX(main_box), message_label); + gtk_box_append(GTK_BOX(main_box), scrolled_window); + gtk_box_append(GTK_BOX(main_box), close_button); + gtk_window_set_child(dialog_window, main_box); + gtk_window_present(dialog_window); +} + /** * @brief Libère le contexte associé à une fenêtre de confirmation. */ diff --git a/src/views/main_window.c b/src/views/main_window.c index 01acb82..4b279b4 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -117,6 +117,12 @@ struct MainWindow gpointer add_relation_user_data; + MainWindowOsintActionCallback + osint_action_callback; + + gpointer + osint_action_user_data; + MainWindowQuitCallback quit_callback; @@ -125,6 +131,29 @@ struct MainWindow }; +/** + * @brief Relaie une action OSINT vers le contrôleur applicatif. + */ +static void main_window_on_osint_action_requested( + const char *action_identifier, + const char *target_value, + gpointer user_data +) +{ + MainWindow *main_window = user_data; + + if (main_window == NULL || main_window->osint_action_callback == NULL) + { + return; + } + + main_window->osint_action_callback( + action_identifier, + target_value, + main_window->osint_action_user_data + ); +} + /** * @brief Ouvre dans le workspace l'entité choisie dans la sidebar. */ @@ -745,6 +774,12 @@ MainWindow *main_window_new( main_window ); + workspace_set_osint_action_callback( + main_window->workspace, + main_window_on_osint_action_requested, + main_window + ); + workspace_widget = workspace_get_widget( main_window->workspace ); @@ -1245,6 +1280,21 @@ void main_window_set_osint_tool_state( ); } +void main_window_set_osint_action_callback( + MainWindow *main_window, + MainWindowOsintActionCallback callback, + gpointer user_data +) +{ + if (main_window == NULL) + { + return; + } + + main_window->osint_action_callback = callback; + main_window->osint_action_user_data = user_data; +} + void main_window_set_tree_selection_callback( MainWindow *main_window, InvestigationTreeViewSelectionCallback callback, diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index b8b6d1b..63c112a 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -112,6 +112,12 @@ struct Workspace gpointer add_relation_user_data; + WorkspaceOsintActionCallback + osint_action_callback; + + gpointer + osint_action_user_data; + GtkWidget *node_name_label; GtkWidget *node_path_label; GtkWidget *node_type_label; @@ -128,34 +134,62 @@ struct Workspace }; /** - * @brief Confirme localement le déclenchement de l'action de démonstration. + * @brief Traite localement l'aperçu ou relaie une action OSINT exécutable. */ -static void workspace_on_osint_demo_action_clicked( +static void workspace_on_osint_action_clicked( GtkButton *button, gpointer user_data ) { Workspace *workspace = user_data; - const char *action_label = g_object_get_data( + const char *action_identifier = g_object_get_data( G_OBJECT(button), - "osint-action-label" + "osint-action-identifier" ); - char *status_text = NULL; + const char *target_value = NULL; if (workspace == NULL || workspace->osint_tools_status_label == NULL) { return; } - status_text = g_strdup_printf( - "%s : démonstration locale, aucun outil exécuté.", - action_label != NULL ? action_label : "Action OSINT" + if (g_strcmp0(action_identifier, "selection-preview") == 0) + { + gtk_label_set_text( + GTK_LABEL(workspace->osint_tools_status_label), + "Aperçu local : aucun outil externe exécuté." + ); + return; + } + + target_value = osint_selection_context_get_value( + workspace->osint_selection_context ); + + if (workspace->osint_action_callback == NULL || + action_identifier == NULL || target_value == NULL) + { + gtk_label_set_text( + GTK_LABEL(workspace->osint_tools_status_label), + "Impossible de transmettre cette action OSINT." + ); + return; + } + gtk_label_set_text( GTK_LABEL(workspace->osint_tools_status_label), - status_text != NULL ? status_text : "Aucun outil exécuté." + "Exécution lancée ; suivez sa progression dans les tâches." + ); + + gtk_popover_popdown( + GTK_POPOVER(workspace->osint_tools_popover) + ); + + workspace->osint_action_callback( + action_identifier, + target_value, + workspace->osint_action_user_data ); - g_free(status_text); } /** @@ -282,15 +316,16 @@ static void workspace_update_osint_tools_menu( ? osint_action_get_description(action) : osint_action_get_unavailable_reason(action) ); - g_object_set_data( + g_object_set_data_full( G_OBJECT(action_button), - "osint-action-label", - (gpointer) osint_action_get_label(action) + "osint-action-identifier", + g_strdup(osint_action_get_identifier(action)), + g_free ); g_signal_connect( action_button, "clicked", - G_CALLBACK(workspace_on_osint_demo_action_clicked), + G_CALLBACK(workspace_on_osint_action_clicked), workspace ); gtk_box_append( @@ -2352,6 +2387,21 @@ void workspace_set_osint_tool_state( ); } +void workspace_set_osint_action_callback( + Workspace *workspace, + WorkspaceOsintActionCallback callback, + gpointer user_data +) +{ + if (workspace == NULL) + { + return; + } + + workspace->osint_action_callback = callback; + workspace->osint_action_user_data = user_data; +} + void workspace_set_graph_loading( Workspace *workspace ) diff --git a/tests/test_osint_dns_query.c b/tests/test_osint_dns_query.c new file mode 100644 index 0000000..59e9efa --- /dev/null +++ b/tests/test_osint_dns_query.c @@ -0,0 +1,35 @@ +/****************************************************************************** + * @file test_osint_dns_query.c + * @brief Tests de validation des cibles de résolution DNS OSINT. + ******************************************************************************/ + +#include "models/osint_dns_query.h" + +#include + +static void test_valid_targets(void) +{ + g_assert_true(osint_dns_query_is_valid_target("example.org")); + g_assert_true(osint_dns_query_is_valid_target("WWW.Example.ORG")); + g_assert_true(osint_dns_query_is_valid_target("sub-domain.example.org")); + g_assert_true(osint_dns_query_is_valid_target("example.org.")); +} + +static void test_invalid_targets(void) +{ + g_assert_false(osint_dns_query_is_valid_target(NULL)); + g_assert_false(osint_dns_query_is_valid_target("")); + g_assert_false(osint_dns_query_is_valid_target("-x.example.org")); + g_assert_false(osint_dns_query_is_valid_target("example..org")); + g_assert_false(osint_dns_query_is_valid_target("example.org +trace")); + g_assert_false(osint_dns_query_is_valid_target("example_.org")); + g_assert_false(osint_dns_query_is_valid_target("example-.org")); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func("/osint-dns-query/valid", test_valid_targets); + g_test_add_func("/osint-dns-query/invalid", test_invalid_targets); + return g_test_run(); +}