feat(osint): add DNS result review
This commit is contained in:
parent
6c8113dfea
commit
5a196f6ac6
8 changed files with 551 additions and 10 deletions
13
Makefile
13
Makefile
|
|
@ -116,6 +116,7 @@ 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
|
||||
TEST_OSINT_DNS_PROPOSAL := tests/test_osint_dns_proposal
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
|
|
@ -551,6 +552,11 @@ $(TEST_OSINT_DNS_QUERY): \
|
|||
src/models/osint_dns_query.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_OSINT_DNS_PROPOSAL): \
|
||||
tests/test_osint_dns_proposal.c \
|
||||
src/models/osint_dns_proposal.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 \
|
||||
|
|
@ -621,7 +627,8 @@ test: \
|
|||
$(TEST_GRAPH_NODE_POSITION_DAO) \
|
||||
$(TEST_OSINT_SELECTION_CONTEXT) \
|
||||
$(TEST_OSINT_ACTION_CATALOG) \
|
||||
$(TEST_OSINT_DNS_QUERY)
|
||||
$(TEST_OSINT_DNS_QUERY) \
|
||||
$(TEST_OSINT_DNS_PROPOSAL)
|
||||
@echo "Exécution des tests..."
|
||||
@./$(TEST_NODE)
|
||||
@./$(TEST_TREE_MODEL)
|
||||
|
|
@ -672,6 +679,7 @@ test: \
|
|||
@$(TEST_OSINT_SELECTION_CONTEXT)
|
||||
@$(TEST_OSINT_ACTION_CATALOG)
|
||||
@$(TEST_OSINT_DNS_QUERY)
|
||||
@$(TEST_OSINT_DNS_PROPOSAL)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
||||
%.o: %.c
|
||||
|
|
@ -728,7 +736,8 @@ clean:
|
|||
$(TEST_GRAPH_NODE_POSITION_DAO) \
|
||||
$(TEST_OSINT_SELECTION_CONTEXT) \
|
||||
$(TEST_OSINT_ACTION_CATALOG) \
|
||||
$(TEST_OSINT_DNS_QUERY)
|
||||
$(TEST_OSINT_DNS_QUERY) \
|
||||
$(TEST_OSINT_DNS_PROPOSAL)
|
||||
|
||||
-include $(DEP)
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,8 @@ Le socle actuel comprend notamment :
|
|||
- catalogue déterministe des actions compatibles avec la sélection OSINT ;
|
||||
- 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.
|
||||
- affichage sélectionnable des sorties standard et d’erreur, sans persistance ;
|
||||
- révision des réponses DNS sous forme de propositions avant intégration.
|
||||
|
||||
Les outils actuellement présents dans le catalogue initial sont :
|
||||
|
||||
|
|
|
|||
80
include/models/osint_dns_proposal.h
Normal file
80
include/models/osint_dns_proposal.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/******************************************************************************
|
||||
* @file osint_dns_proposal.h
|
||||
* @brief Propositions structurées extraites d'une sortie DNS brute.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_OSINT_DNS_PROPOSAL_H
|
||||
#define LABFY_INVESTIGATION_OSINT_DNS_PROPOSAL_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/** @brief Proposition DNS opaque produite avant toute intégration. */
|
||||
typedef struct OsintDnsProposal OsintDnsProposal;
|
||||
|
||||
/**
|
||||
* @brief Extrait les enregistrements reconnus d'une sortie `dig +answer`.
|
||||
*
|
||||
* Le tableau retourné possède ses propositions et doit être libéré avec
|
||||
* g_ptr_array_unref(). Les lignes vides, les commentaires et les lignes
|
||||
* invalides sont ignorés sans modifier la sortie brute d'origine.
|
||||
*
|
||||
* @param target_value Cible DNS ayant produit la sortie.
|
||||
* @param raw_output Sortie standard UTF-8 de dig.
|
||||
*
|
||||
* @return Nouveau tableau, éventuellement vide, ou NULL si les arguments
|
||||
* sont invalides.
|
||||
*/
|
||||
GPtrArray *osint_dns_proposal_parse(
|
||||
const char *target_value,
|
||||
const char *raw_output
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le propriétaire de l'enregistrement.
|
||||
*
|
||||
* @param proposal Proposition consultée.
|
||||
*
|
||||
* @return Chaîne empruntée, ou NULL.
|
||||
*/
|
||||
const char *osint_dns_proposal_get_owner(
|
||||
const OsintDnsProposal *proposal
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le type d'enregistrement DNS.
|
||||
*
|
||||
* @param proposal Proposition consultée.
|
||||
*
|
||||
* @return Chaîne empruntée, ou NULL.
|
||||
*/
|
||||
const char *osint_dns_proposal_get_record_type(
|
||||
const OsintDnsProposal *proposal
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne la valeur brute de l'enregistrement.
|
||||
*
|
||||
* @param proposal Proposition consultée.
|
||||
*
|
||||
* @return Chaîne empruntée, ou NULL.
|
||||
*/
|
||||
const char *osint_dns_proposal_get_value(
|
||||
const OsintDnsProposal *proposal
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne la cible initialement interrogée.
|
||||
*
|
||||
* @param proposal Proposition consultée.
|
||||
*
|
||||
* @return Chaîne empruntée, ou NULL.
|
||||
*/
|
||||
const char *osint_dns_proposal_get_target(
|
||||
const OsintDnsProposal *proposal
|
||||
);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
|
@ -32,6 +32,15 @@ typedef void (*ApplicationMessageDialogConfirmationCallback)(
|
|||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Callback appelé par l'action facultative d'un dialogue détaillé.
|
||||
*
|
||||
* @param user_data Données associées à l'action.
|
||||
*/
|
||||
typedef void (*ApplicationMessageDialogActionCallback)(
|
||||
gpointer user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Affiche une fenêtre modale contenant un message.
|
||||
*
|
||||
|
|
@ -69,6 +78,34 @@ void application_message_dialog_present_details(
|
|||
const char *details
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Affiche un message détaillé avec une action facultative.
|
||||
*
|
||||
* Le dialogue devient propriétaire de action_data lorsque l'affichage est
|
||||
* créé. Les données sont libérées avec action_data_destroy à sa fermeture.
|
||||
*
|
||||
* @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.
|
||||
* @param action_label Libellé de l'action, ou NULL.
|
||||
* @param action_callback Callback de l'action, ou NULL.
|
||||
* @param action_data Données transmises au callback.
|
||||
* @param action_data_destroy Destructeur des données, ou NULL.
|
||||
*/
|
||||
void application_message_dialog_present_details_action(
|
||||
GtkWindow *parent_window,
|
||||
ApplicationMessageDialogType message_type,
|
||||
const char *title,
|
||||
const char *message,
|
||||
const char *details,
|
||||
const char *action_label,
|
||||
ApplicationMessageDialogActionCallback action_callback,
|
||||
gpointer action_data,
|
||||
GDestroyNotify action_data_destroy
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Affiche un dialogue modal demandant une confirmation.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "models/investigation_graph_model.h"
|
||||
#include "models/investigation_record.h"
|
||||
#include "models/osint_dns_query.h"
|
||||
#include "models/osint_dns_proposal.h"
|
||||
#include "core/investigation_tree_builder.h"
|
||||
#include "views/create_investigation_dialog.h"
|
||||
#include "views/folder_dialog.h"
|
||||
|
|
@ -162,6 +163,76 @@ typedef struct
|
|||
char *target_value;
|
||||
} ApplicationOsintActionContext;
|
||||
|
||||
/** @brief Données possédées par l'étape de révision OSINT. */
|
||||
typedef struct
|
||||
{
|
||||
Application *application;
|
||||
char *proposal_summary;
|
||||
} ApplicationOsintReviewContext;
|
||||
|
||||
/** @brief Libère les données de révision possédées par le dialogue. */
|
||||
static void application_osint_review_context_free(gpointer user_data)
|
||||
{
|
||||
ApplicationOsintReviewContext *context = user_data;
|
||||
if (context == NULL) return;
|
||||
g_free(context->proposal_summary);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Affiche les propositions sans les intégrer à l'enquête. */
|
||||
static void application_on_osint_prepare_integration(gpointer user_data)
|
||||
{
|
||||
ApplicationOsintReviewContext *context = user_data;
|
||||
if (context == NULL || context->application == NULL ||
|
||||
context->application->main_window == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
application_message_dialog_present_details(
|
||||
main_window_get_window(context->application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||
"Propositions DNS à réviser",
|
||||
"Aucune donnée n'a été enregistrée dans l'enquête.",
|
||||
context->proposal_summary
|
||||
);
|
||||
}
|
||||
|
||||
/** @brief Construit le résumé lisible des propositions DNS. */
|
||||
static char *application_build_dns_proposal_summary(GPtrArray *proposals)
|
||||
{
|
||||
GString *summary = g_string_new(NULL);
|
||||
|
||||
if (summary == NULL) return NULL;
|
||||
g_string_append_printf(
|
||||
summary,
|
||||
"Propositions détectées : %u\n\n",
|
||||
proposals != NULL ? proposals->len : 0U
|
||||
);
|
||||
|
||||
for (guint index = 0; proposals != NULL && index < proposals->len; index++)
|
||||
{
|
||||
const OsintDnsProposal *proposal = g_ptr_array_index(proposals, index);
|
||||
g_string_append_printf(
|
||||
summary,
|
||||
"%u. Cible : %s\n Propriétaire : %s\n Type : %s\n"
|
||||
" Valeur : %s\n\n",
|
||||
index + 1U,
|
||||
osint_dns_proposal_get_target(proposal),
|
||||
osint_dns_proposal_get_owner(proposal),
|
||||
osint_dns_proposal_get_record_type(proposal),
|
||||
osint_dns_proposal_get_value(proposal)
|
||||
);
|
||||
}
|
||||
|
||||
g_string_append(
|
||||
summary,
|
||||
"Ces propositions sont uniquement préparées pour révision. "
|
||||
"Elles ne sont pas enregistrées dans SQLite."
|
||||
);
|
||||
return g_string_free(summary, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Libère le contexte d'une action OSINT.
|
||||
*/
|
||||
|
|
@ -989,6 +1060,8 @@ static void application_on_dns_lookup_completed(
|
|||
char *stderr_text = NULL;
|
||||
char *details = NULL;
|
||||
char *message = NULL;
|
||||
GPtrArray *proposals = NULL;
|
||||
ApplicationOsintReviewContext *review_context = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
if (task == NULL || context == NULL || context->application == NULL)
|
||||
|
|
@ -1002,6 +1075,17 @@ static void application_on_dns_lookup_completed(
|
|||
return;
|
||||
}
|
||||
|
||||
if (background_task_get_state(task) == BACKGROUND_TASK_STATE_CANCELLED)
|
||||
{
|
||||
application_message_dialog_present(
|
||||
main_window_get_window(application->main_window),
|
||||
APPLICATION_MESSAGE_DIALOG_INFORMATION,
|
||||
"Résolution DNS annulée",
|
||||
"La tâche a été annulée. Aucun résultat n'a été enregistré."
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (background_task_get_state(task) != BACKGROUND_TASK_STATE_COMPLETED)
|
||||
{
|
||||
error = background_task_dup_error(task);
|
||||
|
|
@ -1055,20 +1139,45 @@ static void application_on_dns_lookup_completed(
|
|||
details = g_strdup("Aucun enregistrement DNS retourné.");
|
||||
}
|
||||
|
||||
proposals = osint_dns_proposal_parse(context->target_value, stdout_text);
|
||||
if (tool_process_result_is_success(process_result) &&
|
||||
proposals != NULL && proposals->len > 0U)
|
||||
{
|
||||
review_context = g_try_new0(ApplicationOsintReviewContext, 1);
|
||||
if (review_context != NULL)
|
||||
{
|
||||
review_context->application = application;
|
||||
review_context->proposal_summary =
|
||||
application_build_dns_proposal_summary(proposals);
|
||||
}
|
||||
if (review_context == NULL || review_context->proposal_summary == NULL)
|
||||
{
|
||||
application_osint_review_context_free(review_context);
|
||||
review_context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
message = g_strdup_printf(
|
||||
"Cible : %s — code de sortie : %d",
|
||||
"Cible : %s\nOutil : dns.dig\nCode de sortie : %d\n"
|
||||
"Résultat brut non enregistré dans l'enquête.",
|
||||
context->target_value,
|
||||
tool_process_result_get_exit_status(process_result)
|
||||
);
|
||||
|
||||
application_message_dialog_present_details(
|
||||
application_message_dialog_present_details_action(
|
||||
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
|
||||
details,
|
||||
review_context != NULL ? "Préparer l'intégration" : NULL,
|
||||
review_context != NULL
|
||||
? application_on_osint_prepare_integration
|
||||
: NULL,
|
||||
review_context,
|
||||
application_osint_review_context_free
|
||||
);
|
||||
|
||||
g_clear_pointer(&stdout_bytes, g_bytes_unref);
|
||||
|
|
@ -1077,6 +1186,7 @@ static void application_on_dns_lookup_completed(
|
|||
g_free(stderr_text);
|
||||
g_free(details);
|
||||
g_free(message);
|
||||
g_clear_pointer(&proposals, g_ptr_array_unref);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
157
src/models/osint_dns_proposal.c
Normal file
157
src/models/osint_dns_proposal.c
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/******************************************************************************
|
||||
* @file osint_dns_proposal.c
|
||||
* @brief Propositions structurées extraites d'une sortie DNS brute.
|
||||
******************************************************************************/
|
||||
|
||||
#include "models/osint_dns_proposal.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
struct OsintDnsProposal
|
||||
{
|
||||
char *owner;
|
||||
char *record_type;
|
||||
char *value;
|
||||
char *target;
|
||||
};
|
||||
|
||||
/** @brief Libère une proposition possédée par un tableau. */
|
||||
static void osint_dns_proposal_free(gpointer data)
|
||||
{
|
||||
OsintDnsProposal *proposal = data;
|
||||
if (proposal == NULL) return;
|
||||
g_free(proposal->target);
|
||||
g_free(proposal->value);
|
||||
g_free(proposal->record_type);
|
||||
g_free(proposal->owner);
|
||||
g_free(proposal);
|
||||
}
|
||||
|
||||
/** @brief Indique si un type DNS produit une proposition structurée. */
|
||||
static gboolean osint_dns_proposal_type_is_recognized(const char *record_type)
|
||||
{
|
||||
static const char *recognized_types[] = {
|
||||
"A", "AAAA", "CAA", "CNAME", "MX", "NS", "PTR", "SOA", "SRV",
|
||||
"TXT", NULL
|
||||
};
|
||||
|
||||
for (gsize index = 0; recognized_types[index] != NULL; index++)
|
||||
{
|
||||
if (g_strcmp0(record_type, recognized_types[index]) == 0) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/** @brief Extrait le prochain champ séparé par des espaces ASCII. */
|
||||
static char *osint_dns_proposal_take_field(char **cursor)
|
||||
{
|
||||
char *field_start = NULL;
|
||||
char *field_end = NULL;
|
||||
|
||||
if (cursor == NULL || *cursor == NULL) return NULL;
|
||||
field_start = *cursor;
|
||||
while (*field_start != '\0' && g_ascii_isspace(*field_start)) field_start++;
|
||||
if (*field_start == '\0')
|
||||
{
|
||||
*cursor = field_start;
|
||||
return NULL;
|
||||
}
|
||||
field_end = field_start;
|
||||
while (*field_end != '\0' && !g_ascii_isspace(*field_end)) field_end++;
|
||||
*cursor = field_end;
|
||||
return g_strndup(field_start, (gsize) (field_end - field_start));
|
||||
}
|
||||
|
||||
/** @brief Parse une ligne de réponse DNS, ou retourne NULL. */
|
||||
static OsintDnsProposal *osint_dns_proposal_parse_line(
|
||||
const char *target_value,
|
||||
const char *line
|
||||
)
|
||||
{
|
||||
char *line_copy = g_strdup(line);
|
||||
char *cursor = line_copy;
|
||||
char *owner = NULL;
|
||||
char *time_to_live = NULL;
|
||||
char *record_class = NULL;
|
||||
char *record_type = NULL;
|
||||
char *value = NULL;
|
||||
OsintDnsProposal *proposal = NULL;
|
||||
|
||||
if (line_copy == NULL) return NULL;
|
||||
g_strstrip(line_copy);
|
||||
if (line_copy[0] == '\0' || line_copy[0] == ';') goto cleanup;
|
||||
|
||||
owner = osint_dns_proposal_take_field(&cursor);
|
||||
time_to_live = osint_dns_proposal_take_field(&cursor);
|
||||
record_class = osint_dns_proposal_take_field(&cursor);
|
||||
record_type = osint_dns_proposal_take_field(&cursor);
|
||||
while (cursor != NULL && *cursor != '\0' && g_ascii_isspace(*cursor)) cursor++;
|
||||
value = cursor != NULL ? g_strdup(cursor) : NULL;
|
||||
if (value != NULL) g_strstrip(value);
|
||||
|
||||
if (owner == NULL || time_to_live == NULL || record_class == NULL ||
|
||||
record_type == NULL || value == NULL || value[0] == '\0' ||
|
||||
!g_ascii_string_to_unsigned(time_to_live, 10, 0, G_MAXUINT32, NULL, NULL) ||
|
||||
g_strcmp0(record_class, "IN") != 0 ||
|
||||
!osint_dns_proposal_type_is_recognized(record_type))
|
||||
{
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
proposal = g_try_new0(OsintDnsProposal, 1);
|
||||
if (proposal != NULL)
|
||||
{
|
||||
proposal->owner = g_strdup(owner);
|
||||
proposal->record_type = g_strdup(record_type);
|
||||
proposal->value = g_strdup(value);
|
||||
proposal->target = g_strdup(target_value);
|
||||
}
|
||||
if (proposal == NULL || proposal->owner == NULL ||
|
||||
proposal->record_type == NULL || proposal->value == NULL ||
|
||||
proposal->target == NULL)
|
||||
{
|
||||
osint_dns_proposal_free(proposal);
|
||||
proposal = NULL;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
g_free(value);
|
||||
g_free(record_type);
|
||||
g_free(record_class);
|
||||
g_free(time_to_live);
|
||||
g_free(owner);
|
||||
g_free(line_copy);
|
||||
return proposal;
|
||||
}
|
||||
|
||||
GPtrArray *osint_dns_proposal_parse(
|
||||
const char *target_value,
|
||||
const char *raw_output
|
||||
)
|
||||
{
|
||||
GPtrArray *proposals = NULL;
|
||||
char **lines = NULL;
|
||||
|
||||
if (target_value == NULL || target_value[0] == '\0' || raw_output == NULL)
|
||||
return NULL;
|
||||
proposals = g_ptr_array_new_with_free_func(osint_dns_proposal_free);
|
||||
lines = g_strsplit(raw_output, "\n", -1);
|
||||
for (gsize index = 0; lines != NULL && lines[index] != NULL; index++)
|
||||
{
|
||||
OsintDnsProposal *proposal = osint_dns_proposal_parse_line(
|
||||
target_value, lines[index]
|
||||
);
|
||||
if (proposal != NULL) g_ptr_array_add(proposals, proposal);
|
||||
}
|
||||
g_strfreev(lines);
|
||||
return proposals;
|
||||
}
|
||||
|
||||
const char *osint_dns_proposal_get_owner(const OsintDnsProposal *proposal)
|
||||
{ return proposal != NULL ? proposal->owner : NULL; }
|
||||
const char *osint_dns_proposal_get_record_type(const OsintDnsProposal *proposal)
|
||||
{ return proposal != NULL ? proposal->record_type : NULL; }
|
||||
const char *osint_dns_proposal_get_value(const OsintDnsProposal *proposal)
|
||||
{ return proposal != NULL ? proposal->value : NULL; }
|
||||
const char *osint_dns_proposal_get_target(const OsintDnsProposal *proposal)
|
||||
{ return proposal != NULL ? proposal->target : NULL; }
|
||||
|
|
@ -35,6 +35,36 @@ typedef struct
|
|||
gboolean completed;
|
||||
} ApplicationMessageDialogConfirmationContext;
|
||||
|
||||
/** @brief Données possédées par l'action d'un dialogue détaillé. */
|
||||
typedef struct
|
||||
{
|
||||
ApplicationMessageDialogActionCallback callback;
|
||||
gpointer user_data;
|
||||
GDestroyNotify user_data_destroy;
|
||||
} ApplicationMessageDialogActionContext;
|
||||
|
||||
/** @brief Libère les données possédées par une action détaillée. */
|
||||
static void application_message_dialog_action_context_free(gpointer data)
|
||||
{
|
||||
ApplicationMessageDialogActionContext *context = data;
|
||||
if (context == NULL) return;
|
||||
if (context->user_data_destroy != NULL)
|
||||
context->user_data_destroy(context->user_data);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/** @brief Déclenche l'action facultative du dialogue détaillé. */
|
||||
static void application_message_dialog_on_action_clicked(
|
||||
GtkButton *button,
|
||||
gpointer user_data
|
||||
)
|
||||
{
|
||||
ApplicationMessageDialogActionContext *context = user_data;
|
||||
(void) button;
|
||||
if (context != NULL && context->callback != NULL)
|
||||
context->callback(context->user_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retourne le libellé correspondant au type de message.
|
||||
*
|
||||
|
|
@ -355,12 +385,16 @@ void application_message_dialog_present(
|
|||
);
|
||||
}
|
||||
|
||||
void application_message_dialog_present_details(
|
||||
void application_message_dialog_present_details_action(
|
||||
GtkWindow *parent_window,
|
||||
ApplicationMessageDialogType message_type,
|
||||
const char *title,
|
||||
const char *message,
|
||||
const char *details
|
||||
const char *details,
|
||||
const char *action_label,
|
||||
ApplicationMessageDialogActionCallback action_callback,
|
||||
gpointer action_data,
|
||||
GDestroyNotify action_data_destroy
|
||||
)
|
||||
{
|
||||
GtkWindow *dialog_window = GTK_WINDOW(gtk_window_new());
|
||||
|
|
@ -374,6 +408,9 @@ void application_message_dialog_present_details(
|
|||
GtkWidget *scrolled_window = gtk_scrolled_window_new();
|
||||
GtkWidget *details_view = gtk_text_view_new();
|
||||
GtkWidget *close_button = gtk_button_new_with_label("Fermer");
|
||||
GtkWidget *button_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
GtkWidget *action_button = NULL;
|
||||
ApplicationMessageDialogActionContext *action_context = NULL;
|
||||
GtkTextBuffer *details_buffer = gtk_text_view_get_buffer(
|
||||
GTK_TEXT_VIEW(details_view)
|
||||
);
|
||||
|
|
@ -426,7 +463,7 @@ void application_message_dialog_present_details(
|
|||
details_view
|
||||
);
|
||||
|
||||
gtk_widget_set_halign(close_button, GTK_ALIGN_END);
|
||||
gtk_widget_set_halign(button_box, GTK_ALIGN_END);
|
||||
g_signal_connect(
|
||||
close_button,
|
||||
"clicked",
|
||||
|
|
@ -434,14 +471,65 @@ void application_message_dialog_present_details(
|
|||
dialog_window
|
||||
);
|
||||
|
||||
if (action_label != NULL && action_label[0] != '\0' &&
|
||||
action_callback != NULL)
|
||||
{
|
||||
action_context = g_new0(ApplicationMessageDialogActionContext, 1);
|
||||
action_context->callback = action_callback;
|
||||
action_context->user_data = action_data;
|
||||
action_context->user_data_destroy = action_data_destroy;
|
||||
g_object_set_data_full(
|
||||
G_OBJECT(dialog_window),
|
||||
"application-message-dialog-action-context",
|
||||
action_context,
|
||||
application_message_dialog_action_context_free
|
||||
);
|
||||
action_button = gtk_button_new_with_label(action_label);
|
||||
gtk_widget_add_css_class(action_button, "suggested-action");
|
||||
g_signal_connect(
|
||||
action_button,
|
||||
"clicked",
|
||||
G_CALLBACK(application_message_dialog_on_action_clicked),
|
||||
action_context
|
||||
);
|
||||
gtk_box_append(GTK_BOX(button_box), action_button);
|
||||
}
|
||||
else if (action_data_destroy != NULL)
|
||||
{
|
||||
action_data_destroy(action_data);
|
||||
}
|
||||
|
||||
gtk_box_append(GTK_BOX(button_box), close_button);
|
||||
|
||||
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_box_append(GTK_BOX(main_box), button_box);
|
||||
gtk_window_set_child(dialog_window, main_box);
|
||||
gtk_window_present(dialog_window);
|
||||
}
|
||||
|
||||
void application_message_dialog_present_details(
|
||||
GtkWindow *parent_window,
|
||||
ApplicationMessageDialogType message_type,
|
||||
const char *title,
|
||||
const char *message,
|
||||
const char *details
|
||||
)
|
||||
{
|
||||
application_message_dialog_present_details_action(
|
||||
parent_window,
|
||||
message_type,
|
||||
title,
|
||||
message,
|
||||
details,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Libère le contexte associé à une fenêtre de confirmation.
|
||||
*/
|
||||
|
|
|
|||
59
tests/test_osint_dns_proposal.c
Normal file
59
tests/test_osint_dns_proposal.c
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/******************************************************************************
|
||||
* @file test_osint_dns_proposal.c
|
||||
* @brief Tests des propositions structurées extraites de dig.
|
||||
******************************************************************************/
|
||||
|
||||
#include "models/osint_dns_proposal.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
static void test_parse_recognized_records(void)
|
||||
{
|
||||
const char *output =
|
||||
"example.org. 300 IN A 93.184.216.34\n"
|
||||
"example.org. 300 IN MX 10 mail.example.org.\n"
|
||||
"example.org. 300 IN TXT \"texte avec espaces\"\n";
|
||||
GPtrArray *proposals = osint_dns_proposal_parse("example.org", output);
|
||||
const OsintDnsProposal *first = g_ptr_array_index(proposals, 0);
|
||||
const OsintDnsProposal *third = g_ptr_array_index(proposals, 2);
|
||||
|
||||
g_assert_cmpuint(proposals->len, ==, 3);
|
||||
g_assert_cmpstr(osint_dns_proposal_get_owner(first), ==, "example.org.");
|
||||
g_assert_cmpstr(osint_dns_proposal_get_record_type(first), ==, "A");
|
||||
g_assert_cmpstr(osint_dns_proposal_get_value(first), ==, "93.184.216.34");
|
||||
g_assert_cmpstr(osint_dns_proposal_get_target(first), ==, "example.org");
|
||||
g_assert_cmpstr(
|
||||
osint_dns_proposal_get_value(third), ==, "\"texte avec espaces\""
|
||||
);
|
||||
g_ptr_array_unref(proposals);
|
||||
}
|
||||
|
||||
static void test_ignore_unrecognized_lines(void)
|
||||
{
|
||||
const char *output =
|
||||
";; comment\n"
|
||||
"invalid line\n"
|
||||
"example.org. nope IN A 93.184.216.34\n"
|
||||
"example.org. 60 CH A 93.184.216.34\n"
|
||||
"example.org. 60 IN TYPE999 opaque\n";
|
||||
GPtrArray *proposals = osint_dns_proposal_parse("example.org", output);
|
||||
g_assert_nonnull(proposals);
|
||||
g_assert_cmpuint(proposals->len, ==, 0);
|
||||
g_ptr_array_unref(proposals);
|
||||
}
|
||||
|
||||
static void test_invalid_arguments(void)
|
||||
{
|
||||
g_assert_null(osint_dns_proposal_parse(NULL, ""));
|
||||
g_assert_null(osint_dns_proposal_parse("", ""));
|
||||
g_assert_null(osint_dns_proposal_parse("example.org", NULL));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
g_test_init(&argc, &argv, NULL);
|
||||
g_test_add_func("/osint-dns-proposal/recognized", test_parse_recognized_records);
|
||||
g_test_add_func("/osint-dns-proposal/ignored", test_ignore_unrecognized_lines);
|
||||
g_test_add_func("/osint-dns-proposal/invalid", test_invalid_arguments);
|
||||
return g_test_run();
|
||||
}
|
||||
Loading…
Reference in a new issue