From 45fed7fcd60742bbd9bcdbfa1be8b889f386190b Mon Sep 17 00:00:00 2001 From: grayTerminal-sh Date: Wed, 22 Jul 2026 23:53:12 +0200 Subject: [PATCH] feat(evidence): extract file metadata with ExifTool --- Makefile | 9 ++- include/core/exiftool_metadata.h | 27 +++++++ include/views/main_window.h | 6 ++ include/views/metadata_analysis_dialog.h | 25 ++++++ include/widgets/workspace.h | 6 ++ src/core/application.c | 99 ++++++++++++++++++++++++ src/core/exiftool_metadata.c | 82 ++++++++++++++++++++ src/core/tool_catalog.c | 13 ++++ src/views/main_window.c | 20 +++++ src/views/metadata_analysis_dialog.c | 66 ++++++++++++++++ src/widgets/workspace.c | 42 ++++++++++ tests/test_exiftool_metadata.c | 33 ++++++++ tests/test_tool_catalog.c | 6 ++ 13 files changed, 433 insertions(+), 1 deletion(-) create mode 100644 include/core/exiftool_metadata.h create mode 100644 include/views/metadata_analysis_dialog.h create mode 100644 src/core/exiftool_metadata.c create mode 100644 src/views/metadata_analysis_dialog.c create mode 100644 tests/test_exiftool_metadata.c diff --git a/Makefile b/Makefile index fd2e2fe..d1b2f38 100644 --- a/Makefile +++ b/Makefile @@ -126,6 +126,7 @@ TEST_SOCIAL_PLATFORM := tests/test_social_platform TEST_PERSON_ENTITY_SERVICE := tests/test_person_entity_service TEST_EML_ANALYZER := tests/test_eml_analyzer TEST_IBAN_ANALYZER := tests/test_iban_analyzer +TEST_EXIFTOOL_METADATA := tests/test_exiftool_metadata all: $(TARGET) @@ -651,6 +652,10 @@ $(TEST_EML_ANALYZER): tests/test_eml_analyzer.c src/core/eml_analyzer.c $(TEST_IBAN_ANALYZER): tests/test_iban_analyzer.c src/core/iban_analyzer.c $(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS) +$(TEST_EXIFTOOL_METADATA): tests/test_exiftool_metadata.c \ + src/core/exiftool_metadata.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 \ @@ -731,7 +736,8 @@ test: \ $(TEST_SOCIAL_PLATFORM) \ $(TEST_PERSON_ENTITY_SERVICE) \ $(TEST_EML_ANALYZER) \ - $(TEST_IBAN_ANALYZER) + $(TEST_IBAN_ANALYZER) \ + $(TEST_EXIFTOOL_METADATA) @echo "Exécution des tests..." @./$(TEST_NODE) @./$(TEST_TREE_MODEL) @@ -792,6 +798,7 @@ test: \ @$(TEST_PERSON_ENTITY_SERVICE) @$(TEST_EML_ANALYZER) @$(TEST_IBAN_ANALYZER) + @$(TEST_EXIFTOOL_METADATA) @echo "Tous les tests sont valides." %.o: %.c diff --git a/include/core/exiftool_metadata.h b/include/core/exiftool_metadata.h new file mode 100644 index 0000000..8714f3d --- /dev/null +++ b/include/core/exiftool_metadata.h @@ -0,0 +1,27 @@ +/****************************************************************************** + * @file exiftool_metadata.h + * @brief Extraction locale des métadonnées techniques d'une preuve. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_EXIFTOOL_METADATA_H +#define LABFY_INVESTIGATION_EXIFTOOL_METADATA_H + +#include + +G_BEGIN_DECLS + +/** + * @brief Extrait une synthèse lisible et la sortie JSON brute avec ExifTool. + * @param file_path Chemin du fichier de travail à analyser. + * @param summary Emplacement recevant la synthèse nouvellement allouée. + * @param json Emplacement recevant le JSON nouvellement alloué. + * @param version Emplacement facultatif recevant la version de l'outil. + * @param error Emplacement facultatif pour l'erreur. + * @return TRUE lorsque les deux extractions ont réussi. + */ +gboolean exiftool_metadata_extract(const char *file_path, char **summary, + char **json, char **version, GError **error); + +G_END_DECLS + +#endif diff --git a/include/views/main_window.h b/include/views/main_window.h index cd24217..6c362b4 100644 --- a/include/views/main_window.h +++ b/include/views/main_window.h @@ -104,6 +104,9 @@ typedef void (*MainWindowAnalyzeEmlCallback)(const char *evidence_identifier, /** @brief Callback appelé pour analyser un RIB par OCR. */ typedef void (*MainWindowAnalyzeRibCallback)(const char *evidence_identifier, gpointer user_data); +/** @brief Callback appelé pour extraire les métadonnées d'une preuve. */ +typedef void (*MainWindowExtractMetadataCallback)(const char *evidence_identifier, + gpointer user_data); /** * @brief Callback appelé après le déplacement effectif d'un nœud. @@ -218,6 +221,9 @@ void main_window_set_analyze_eml_callback(MainWindow *main_window, /** @brief Définit le callback d'analyse OCR d'un RIB. */ void main_window_set_analyze_rib_callback(MainWindow *main_window, MainWindowAnalyzeRibCallback callback, gpointer user_data); +/** @brief Définit le callback d'extraction locale des métadonnées. */ +void main_window_set_extract_metadata_callback(MainWindow *main_window, + MainWindowExtractMetadataCallback callback, gpointer user_data); /** * @brief Définit le callback de fin de déplacement d'un nœud. diff --git a/include/views/metadata_analysis_dialog.h b/include/views/metadata_analysis_dialog.h new file mode 100644 index 0000000..dbe2341 --- /dev/null +++ b/include/views/metadata_analysis_dialog.h @@ -0,0 +1,25 @@ +/****************************************************************************** + * @file metadata_analysis_dialog.h + * @brief Fenêtre de consultation des métadonnées extraites. + ******************************************************************************/ + +#ifndef LABFY_INVESTIGATION_METADATA_ANALYSIS_DIALOG_H +#define LABFY_INVESTIGATION_METADATA_ANALYSIS_DIALOG_H + +#include + +G_BEGIN_DECLS + +/** + * @brief Affiche une synthèse ExifTool dans une fenêtre défilable. + * @param parent Fenêtre parente facultative. + * @param summary Métadonnées lisibles empruntées. + * @param json_path Chemin emprunté de la sortie JSON conservée. + * @param version Version empruntée d'ExifTool. + */ +void metadata_analysis_dialog_present(GtkWindow *parent, const char *summary, + const char *json_path, const char *version); + +G_END_DECLS + +#endif diff --git a/include/widgets/workspace.h b/include/widgets/workspace.h index 8f85dec..098e6df 100644 --- a/include/widgets/workspace.h +++ b/include/widgets/workspace.h @@ -138,6 +138,9 @@ typedef void (*WorkspaceAnalyzeEmlCallback)(const char *evidence_identifier, /** @brief Callback appelé pour analyser un RIB par OCR. */ typedef void (*WorkspaceAnalyzeRibCallback)(const char *evidence_identifier, gpointer user_data); +/** @brief Callback appelé pour extraire les métadonnées d'une preuve. */ +typedef void (*WorkspaceExtractMetadataCallback)(const char *evidence_identifier, + gpointer user_data); /** * @brief Crée une nouvelle zone de travail. @@ -274,6 +277,9 @@ void workspace_set_analyze_eml_callback(Workspace *workspace, /** @brief Définit le callback d'analyse OCR d'un RIB. */ void workspace_set_analyze_rib_callback(Workspace *workspace, WorkspaceAnalyzeRibCallback callback, gpointer user_data); +/** @brief Définit le callback d'extraction locale des métadonnées. */ +void workspace_set_extract_metadata_callback(Workspace *workspace, + WorkspaceExtractMetadataCallback 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 b0cbfc5..bec1fd8 100644 --- a/src/core/application.c +++ b/src/core/application.c @@ -63,6 +63,8 @@ #include "views/rib_ocr_review_dialog.h" #include "core/rib_ocr.h" #include "core/iban_analyzer.h" +#include "core/exiftool_metadata.h" +#include "views/metadata_analysis_dialog.h" #include #include @@ -3527,6 +3529,101 @@ cleanup: evidence_record_free(record); evidence_dao_free(dao); } +/** @brief Analyse avec ExifTool une copie de travail de la preuve. */ +static void application_on_extract_metadata_requested( + const char *evidence_identifier, gpointer user_data) +{ + Application *application = user_data; + const InvestigationProject *project = NULL; + EvidenceDao *dao = NULL; + EvidenceRecord *record = NULL; + char *canonical_root = NULL; + char *candidate_path = NULL; + char *source_path = NULL; + char *output_directory = NULL; + char *copy_name = NULL; + char *copy_path = NULL; + char *json_name = NULL; + char *json_path = NULL; + char *summary = NULL; + char *json = NULL; + char *version = NULL; + GFile *source_file = NULL; + GFile *copy_file = NULL; + GError *error = NULL; + + if (application == NULL || application->session == NULL || + evidence_identifier == NULL) return; + dao = evidence_dao_new( + investigation_session_get_database(application->session), &error); + if (dao != NULL) + record = evidence_dao_find_by_identifier(dao, evidence_identifier, + &error); + project = investigation_session_get_project(application->session); + if (record == NULL || project == NULL) goto failure; + canonical_root = g_canonicalize_filename( + investigation_project_get_root_path(project), NULL); + candidate_path = g_build_filename(canonical_root, + evidence_record_get_relative_path(record), NULL); + source_path = g_canonicalize_filename(candidate_path, NULL); + if (source_path == NULL || !g_str_has_prefix(source_path, canonical_root) || + (source_path[strlen(canonical_root)] != G_DIR_SEPARATOR && + source_path[strlen(canonical_root)] != '\0')) + { + g_set_error_literal(&error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED, + "Le chemin de la preuve sort du dossier de l'enquête."); + goto failure; + } + output_directory = g_build_filename(canonical_root, + "02_Preuves_Traitees", "Extractions", "Metadata", NULL); + if (g_mkdir_with_parents(output_directory, 0750) != 0) + { + g_set_error(&error, G_IO_ERROR, g_io_error_from_errno(errno), + "Impossible de créer le dossier des métadonnées : %s", + g_strerror(errno)); + goto failure; + } + copy_name = g_strdup_printf("%s-%s", evidence_identifier, + evidence_record_get_original_name(record)); + copy_path = g_build_filename(output_directory, copy_name, NULL); + source_file = g_file_new_for_path(source_path); + copy_file = g_file_new_for_path(copy_path); + if (!g_file_copy(source_file, copy_file, G_FILE_COPY_OVERWRITE, + NULL, NULL, NULL, &error)) goto failure; + if (!exiftool_metadata_extract(copy_path, &summary, &json, &version, + &error)) goto failure; + json_name = g_strdup_printf("%s-exiftool.json", evidence_identifier); + json_path = g_build_filename(output_directory, json_name, NULL); + if (!g_file_set_contents(json_path, json, -1, &error)) goto failure; + metadata_analysis_dialog_present( + main_window_get_window(application->main_window), summary, json_path, + version); + main_window_set_status(application->main_window, + "Métadonnées extraites localement et sortie JSON conservée."); + goto cleanup; +failure: + application_present_error(application, "Extraction impossible", + error != NULL ? error->message : + "La preuve n'a pas pu être analysée avec ExifTool."); +cleanup: + g_clear_error(&error); + g_clear_object(&source_file); + g_clear_object(©_file); + g_free(canonical_root); + g_free(candidate_path); + g_free(source_path); + g_free(output_directory); + g_free(copy_name); + g_free(copy_path); + g_free(json_name); + g_free(json_path); + g_free(summary); + g_free(json); + g_free(version); + evidence_record_free(record); + evidence_dao_free(dao); +} + /** * @brief Prépare et démarre l’import asynchrone d’un fichier. */ @@ -7098,6 +7195,8 @@ static void application_on_activate( application_on_analyze_eml_requested, application); main_window_set_analyze_rib_callback(application->main_window, application_on_analyze_rib_requested, application); + main_window_set_extract_metadata_callback(application->main_window, + application_on_extract_metadata_requested, application); main_window_set_graph_node_moved_callback( application->main_window, diff --git a/src/core/exiftool_metadata.c b/src/core/exiftool_metadata.c new file mode 100644 index 0000000..9205364 --- /dev/null +++ b/src/core/exiftool_metadata.c @@ -0,0 +1,82 @@ +/****************************************************************************** + * @file exiftool_metadata.c + * @brief Extraction locale des métadonnées techniques d'une preuve. + ******************************************************************************/ + +#include "core/exiftool_metadata.h" + +#include + +/** @brief Exécute ExifTool et retourne sa sortie standard en UTF-8. */ +static gboolean exiftool_metadata_run(const char *const *arguments, + char **output, GError **error) +{ + GSubprocess *process = NULL; + char *stdout_text = NULL; + char *stderr_text = NULL; + gboolean success = FALSE; + + process = g_subprocess_newv(arguments, + G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDERR_PIPE, error); + if (process == NULL) return FALSE; + if (!g_subprocess_communicate_utf8(process, NULL, NULL, &stdout_text, + &stderr_text, error)) goto cleanup; + if (!g_subprocess_get_successful(process)) + { + g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, + "ExifTool a échoué : %s", stderr_text != NULL ? stderr_text : + "aucun détail disponible"); + goto cleanup; + } + if (stdout_text == NULL || stdout_text[0] == '\0') + { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, + "ExifTool n'a retourné aucune métadonnée."); + goto cleanup; + } + *output = g_steal_pointer(&stdout_text); + success = TRUE; +cleanup: + g_free(stdout_text); + g_free(stderr_text); + g_object_unref(process); + return success; +} + +gboolean exiftool_metadata_extract(const char *file_path, char **summary, + char **json, char **version, GError **error) +{ + const char *summary_arguments[] = + { "exiftool", "-a", "-G1", "-s", file_path, NULL }; + const char *json_arguments[] = + { "exiftool", "-j", "-a", "-G1", "-s", file_path, NULL }; + const char *version_arguments[] = { "exiftool", "-ver", NULL }; + char *local_summary = NULL; + char *local_json = NULL; + char *local_version = NULL; + + if (file_path == NULL || file_path[0] == '\0' || summary == NULL || + json == NULL) + { + g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "Le fichier ou les sorties ExifTool sont absents."); + return FALSE; + } + *summary = NULL; + *json = NULL; + if (version != NULL) *version = NULL; + if (!exiftool_metadata_run(summary_arguments, &local_summary, error) || + !exiftool_metadata_run(json_arguments, &local_json, error)) + goto failure; + if (version != NULL && !exiftool_metadata_run(version_arguments, + &local_version, error)) goto failure; + *summary = g_steal_pointer(&local_summary); + *json = g_steal_pointer(&local_json); + if (version != NULL) *version = g_steal_pointer(&local_version); + return TRUE; +failure: + g_free(local_summary); + g_free(local_json); + g_free(local_version); + return FALSE; +} diff --git a/src/core/tool_catalog.c b/src/core/tool_catalog.c index 8d840b3..cbd29f2 100644 --- a/src/core/tool_catalog.c +++ b/src/core/tool_catalog.c @@ -64,6 +64,11 @@ static const char *const tool_catalog_tesseract_version_arguments[] = "--version", NULL }; +static const char *const tool_catalog_exiftool_version_arguments[] = +{ + "-ver", + NULL +}; /** * @brief Catalogue statique initial. @@ -122,6 +127,14 @@ static const ToolCatalogEntry tool_catalog_entries[] = .requirement = TOOL_REQUIREMENT_OPTIONAL, .version_arguments = tool_catalog_tesseract_version_arguments, .version_argument_count = 1 + }, + { + .identifier = "metadata.exiftool", + .display_name = "ExifTool", + .executable_name = "exiftool", + .requirement = TOOL_REQUIREMENT_OPTIONAL, + .version_arguments = tool_catalog_exiftool_version_arguments, + .version_argument_count = 1 } }; diff --git a/src/views/main_window.c b/src/views/main_window.c index 76947bf..243e9a7 100644 --- a/src/views/main_window.c +++ b/src/views/main_window.c @@ -112,6 +112,8 @@ struct MainWindow gpointer analyze_eml_user_data; MainWindowAnalyzeRibCallback analyze_rib_callback; gpointer analyze_rib_user_data; + MainWindowExtractMetadataCallback extract_metadata_callback; + gpointer extract_metadata_user_data; MainWindowGraphNodeMovedCallback graph_node_moved_callback; @@ -212,6 +214,15 @@ static void main_window_on_analyze_rib_requested(const char *identifier, if (window != NULL && window->analyze_rib_callback != NULL) window->analyze_rib_callback(identifier, window->analyze_rib_user_data); } +/** @brief Relaie la demande d'extraction des métadonnées. */ +static void main_window_on_extract_metadata_requested(const char *identifier, + gpointer data) +{ + MainWindow *window = data; + if (window != NULL && window->extract_metadata_callback != NULL) + window->extract_metadata_callback(identifier, + window->extract_metadata_user_data); +} /** * @brief Ouvre dans le workspace l'entité choisie dans la sidebar. @@ -975,6 +986,8 @@ MainWindow *main_window_new( main_window_on_analyze_eml_requested, main_window); workspace_set_analyze_rib_callback(main_window->workspace, main_window_on_analyze_rib_requested, main_window); + workspace_set_extract_metadata_callback(main_window->workspace, + main_window_on_extract_metadata_requested, main_window); workspace_widget = workspace_get_widget( main_window->workspace @@ -1530,6 +1543,13 @@ void main_window_set_analyze_rib_callback(MainWindow *main_window, main_window->analyze_rib_callback = callback; main_window->analyze_rib_user_data = user_data; } +void main_window_set_extract_metadata_callback(MainWindow *main_window, + MainWindowExtractMetadataCallback callback, gpointer user_data) +{ + if (main_window == NULL) return; + main_window->extract_metadata_callback = callback; + main_window->extract_metadata_user_data = user_data; +} void main_window_set_tree_selection_callback( MainWindow *main_window, diff --git a/src/views/metadata_analysis_dialog.c b/src/views/metadata_analysis_dialog.c new file mode 100644 index 0000000..312367e --- /dev/null +++ b/src/views/metadata_analysis_dialog.c @@ -0,0 +1,66 @@ +/****************************************************************************** + * @file metadata_analysis_dialog.c + * @brief Fenêtre de consultation des métadonnées extraites. + ******************************************************************************/ + +#include "views/metadata_analysis_dialog.h" + +void metadata_analysis_dialog_present(GtkWindow *parent, const char *summary, + const char *json_path, const char *version) +{ + GtkWidget *window = NULL; + GtkWidget *content = NULL; + GtkWidget *heading = NULL; + GtkWidget *scrolled = NULL; + GtkWidget *text_view = NULL; + GtkTextBuffer *buffer = NULL; + GtkWidget *path_label = NULL; + GtkWidget *close_button = NULL; + char *heading_text = NULL; + char *path_text = NULL; + + window = gtk_window_new(); + gtk_window_set_title(GTK_WINDOW(window), "Métadonnées de la preuve"); + gtk_window_set_default_size(GTK_WINDOW(window), 760, 560); + gtk_window_set_modal(GTK_WINDOW(window), TRUE); + if (parent != NULL) gtk_window_set_transient_for(GTK_WINDOW(window), parent); + content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12); + gtk_widget_set_margin_top(content, 16); + gtk_widget_set_margin_bottom(content, 16); + gtk_widget_set_margin_start(content, 16); + gtk_widget_set_margin_end(content, 16); + heading_text = g_strdup_printf("Extraction locale — ExifTool %s", + version != NULL ? version : "version inconnue"); + heading = gtk_label_new(heading_text); + gtk_widget_set_halign(heading, GTK_ALIGN_START); + gtk_box_append(GTK_BOX(content), heading); + scrolled = gtk_scrolled_window_new(); + gtk_widget_set_vexpand(scrolled, TRUE); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + text_view = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(text_view), FALSE); + gtk_text_view_set_monospace(GTK_TEXT_VIEW(text_view), TRUE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(text_view), GTK_WRAP_WORD_CHAR); + buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); + gtk_text_buffer_set_text(buffer, summary != NULL ? summary : + "Aucune métadonnée exploitable.", -1); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrolled), text_view); + gtk_box_append(GTK_BOX(content), scrolled); + path_text = g_strdup_printf("Sortie JSON conservée : %s", + json_path != NULL ? json_path : "indisponible"); + path_label = gtk_label_new(path_text); + gtk_label_set_wrap(GTK_LABEL(path_label), TRUE); + gtk_label_set_selectable(GTK_LABEL(path_label), TRUE); + gtk_widget_set_halign(path_label, GTK_ALIGN_START); + gtk_box_append(GTK_BOX(content), path_label); + close_button = gtk_button_new_with_label("Fermer"); + gtk_widget_set_halign(close_button, GTK_ALIGN_END); + g_signal_connect_swapped(close_button, "clicked", + G_CALLBACK(gtk_window_destroy), window); + gtk_box_append(GTK_BOX(content), close_button); + gtk_window_set_child(GTK_WINDOW(window), content); + gtk_window_present(GTK_WINDOW(window)); + g_free(heading_text); + g_free(path_text); +} diff --git a/src/widgets/workspace.c b/src/widgets/workspace.c index 2fbf35f..0e2cb3b 100644 --- a/src/widgets/workspace.c +++ b/src/widgets/workspace.c @@ -93,6 +93,7 @@ struct Workspace GtkWidget *edit_evidence_button; GtkWidget *analyze_eml_button; GtkWidget *analyze_rib_button; + GtkWidget *extract_metadata_button; GtkWidget *evidence_preview_stack; GtkWidget *evidence_preview_status; GtkWidget *evidence_preview_picture; @@ -115,6 +116,8 @@ struct Workspace gpointer analyze_eml_user_data; WorkspaceAnalyzeRibCallback analyze_rib_callback; gpointer analyze_rib_user_data; + WorkspaceExtractMetadataCallback extract_metadata_callback; + gpointer extract_metadata_user_data; WorkspaceGraphNodeMovedCallback graph_node_moved_callback; @@ -1092,6 +1095,19 @@ static void workspace_on_analyze_rib_clicked(GtkButton *button, gpointer data) workspace->analyze_rib_user_data); } +/** @brief Transmet la demande d'extraction des métadonnées. */ +static void workspace_on_extract_metadata_clicked(GtkButton *button, + gpointer data) +{ + Workspace *workspace = data; + (void) button; + if (workspace != NULL && workspace->extract_metadata_callback != NULL && + workspace->selected_evidence_identifier != NULL) + workspace->extract_metadata_callback( + workspace->selected_evidence_identifier, + workspace->extract_metadata_user_data); +} + Workspace *workspace_new(void) { GtkWidget *evidence_content = NULL; @@ -1468,6 +1484,16 @@ Workspace *workspace_new(void) g_signal_connect(workspace->analyze_rib_button, "clicked", G_CALLBACK(workspace_on_analyze_rib_clicked), workspace); gtk_box_append(GTK_BOX(evidence_content), workspace->analyze_rib_button); + workspace->extract_metadata_button = gtk_button_new_with_label( + "Extraire les métadonnées"); + gtk_widget_set_halign(workspace->extract_metadata_button, GTK_ALIGN_START); + gtk_widget_set_sensitive(workspace->extract_metadata_button, FALSE); + gtk_widget_set_tooltip_text(workspace->extract_metadata_button, + "Analyser localement une copie avec ExifTool"); + g_signal_connect(workspace->extract_metadata_button, "clicked", + G_CALLBACK(workspace_on_extract_metadata_clicked), workspace); + gtk_box_append(GTK_BOX(evidence_content), + workspace->extract_metadata_button); evidence_separator = gtk_separator_new( @@ -2454,6 +2480,8 @@ void workspace_set_selected_node( gtk_widget_set_sensitive(workspace->analyze_eml_button, FALSE); if (workspace->analyze_rib_button != NULL) gtk_widget_set_sensitive(workspace->analyze_rib_button, FALSE); + if (workspace->extract_metadata_button != NULL) + gtk_widget_set_sensitive(workspace->extract_metadata_button, FALSE); if (node == NULL) { @@ -2622,6 +2650,13 @@ void workspace_set_selected_evidence( gtk_widget_set_sensitive(workspace->analyze_rib_button, lower != NULL && (g_str_has_suffix(lower, ".jpg") || g_str_has_suffix(lower, ".jpeg") || g_str_has_suffix(lower, ".png"))); + gtk_widget_set_sensitive(workspace->extract_metadata_button, + lower != NULL && (g_str_has_suffix(lower, ".jpg") || + g_str_has_suffix(lower, ".jpeg") || + g_str_has_suffix(lower, ".png") || + g_str_has_suffix(lower, ".mov") || + g_str_has_suffix(lower, ".mp4") || + g_str_has_suffix(lower, ".pdf"))); g_free(lower); } @@ -3257,6 +3292,13 @@ void workspace_set_analyze_rib_callback(Workspace *workspace, workspace->analyze_rib_callback = callback; workspace->analyze_rib_user_data = user_data; } +void workspace_set_extract_metadata_callback(Workspace *workspace, + WorkspaceExtractMetadataCallback callback, gpointer user_data) +{ + if (workspace == NULL) return; + workspace->extract_metadata_callback = callback; + workspace->extract_metadata_user_data = user_data; +} void workspace_set_graph_node_moved_callback( Workspace *workspace, diff --git a/tests/test_exiftool_metadata.c b/tests/test_exiftool_metadata.c new file mode 100644 index 0000000..056a10c --- /dev/null +++ b/tests/test_exiftool_metadata.c @@ -0,0 +1,33 @@ +/****************************************************************************** + * @file test_exiftool_metadata.c + * @brief Tests de validation de l'extraction ExifTool. + ******************************************************************************/ + +#include "core/exiftool_metadata.h" + +#include + +/** @brief Vérifie le rejet des arguments incomplets. */ +static void test_exiftool_metadata_invalid_arguments(void) +{ + GError *error = NULL; + char *summary = NULL; + char *json = NULL; + + g_assert_false(exiftool_metadata_extract(NULL, &summary, &json, NULL, + &error)); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_clear_error(&error); + g_assert_false(exiftool_metadata_extract("preuve.jpg", NULL, &json, NULL, + &error)); + g_assert_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT); + g_clear_error(&error); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + g_test_add_func("/exiftool-metadata/invalid-arguments", + test_exiftool_metadata_invalid_arguments); + return g_test_run(); +} diff --git a/tests/test_tool_catalog.c b/tests/test_tool_catalog.c index a80c613..aac68de 100644 --- a/tests/test_tool_catalog.c +++ b/tests/test_tool_catalog.c @@ -62,6 +62,12 @@ static const ExpectedToolCatalogEntry expected_catalog_entries[] = .display_name = "Tesseract OCR", .executable_name = "tesseract", .version_argument = "--version" + }, + { + .identifier = "metadata.exiftool", + .display_name = "ExifTool", + .executable_name = "exiftool", + .version_argument = "-ver" } };