labfy-investigation/src/core/eml_pipeline_task.c
2026-07-28 07:15:49 +02:00

270 lines
9.5 KiB
C

/******************************************************************************
* @file eml_pipeline_task.c
* @brief Pipeline d'analyse asynchrone complète pour fichier EML.
******************************************************************************/
#include "core/eml_pipeline_task.h"
#include "core/file_hash.h"
#include <gio/gio.h>
#include <glib.h>
#include <string.h>
typedef struct
{
char *eml_path;
char *processed_evidence_dir;
char *evidence_id;
char *exiftool;
char *tesseract;
char *pdfinfo;
char *pdftotext;
char *pdftoppm;
} EmlPipelineTaskData;
static void eml_pipeline_task_data_free(gpointer user_data)
{
EmlPipelineTaskData *data = user_data;
if (data == NULL)
return;
g_free(data->eml_path);
g_free(data->processed_evidence_dir);
g_free(data->evidence_id);
g_free(data->exiftool);
g_free(data->tesseract);
g_free(data->pdfinfo);
g_free(data->pdftotext);
g_free(data->pdftoppm);
g_free(data);
}
void eml_pipeline_result_free(EmlPipelineResult *res)
{
if (res == NULL)
return;
if (res->analysis != NULL)
eml_analysis_free(res->analysis);
if (res->mime_result != NULL)
eml_mime_result_free(res->mime_result);
if (res->bank_proposals != NULL)
g_ptr_array_unref(res->bank_proposals);
if (res->document_analyses != NULL)
g_ptr_array_unref(res->document_analyses);
if (res->warnings != NULL)
g_ptr_array_unref(res->warnings);
g_free(res);
}
static gboolean eml_pipeline_task_worker(BackgroundTask *task,
GCancellable *cancellable,
gpointer worker_data,
gpointer *out_result,
GError **error)
{
EmlPipelineTaskData *data = worker_data;
g_return_val_if_fail(data != NULL, FALSE);
background_task_report_progress(task, 0.10, "Analyse des en-têtes EML...");
if (g_cancellable_is_cancelled(cancellable))
return FALSE;
EmlAnalysis *analysis = eml_analyzer_analyze_file(data->eml_path, error);
if (analysis == NULL)
return FALSE;
background_task_report_progress(task, 0.30, "Calcul d'empreinte SHA-256...");
char *eml_hash = NULL;
guint64 size = 0;
file_hash_compute_sha256(data->eml_path, cancellable, &eml_hash, &size,
NULL);
char *target_dir = g_build_filename(data->processed_evidence_dir, "eml_attachments",
eml_hash != NULL ? eml_hash : "default", NULL);
g_free(eml_hash);
background_task_report_progress(task, 0.50, "Extraction sécurisée des pièces jointes...");
if (g_cancellable_is_cancelled(cancellable))
{
g_free(target_dir);
eml_analysis_free(analysis);
return FALSE;
}
EmlMimeResult *mime_res = eml_mime_extract_attachments_cancellable(
data->eml_path,
target_dir,
cancellable,
error
);
g_free(target_dir);
if (mime_res == NULL)
{
if (g_cancellable_is_cancelled(cancellable))
{
eml_analysis_free(analysis);
return FALSE;
}
/* Si l'extraction MIME échoue, on conserve quand même l'analyse des en-têtes (résultat partiel) */
g_clear_error(error);
mime_res = g_new0(EmlMimeResult, 1);
mime_res->attachments = g_ptr_array_new_with_free_func((GDestroyNotify) eml_attachment_free);
mime_res->warnings = g_ptr_array_new_with_free_func(g_free);
g_ptr_array_add(mime_res->warnings, g_strdup("L'extraction MIME a échoué ou ne contient aucune pièce jointe."));
}
background_task_report_progress(task, 0.75, "Analyse OCR et détection bancaire...");
GPtrArray *bank_proposals = g_ptr_array_new_with_free_func((GDestroyNotify) bank_proposal_free);
GPtrArray *document_analyses = g_ptr_array_new_with_free_func(
(GDestroyNotify) document_file_analysis_free);
for (guint i = 0; mime_res->attachments != NULL && i < mime_res->attachments->len; i++)
{
if (document_analyses->len >=
DOCUMENT_ANALYSIS_MAX_PIPELINE_ITEMS)
break;
if (g_cancellable_is_cancelled(cancellable))
{
eml_analysis_free(analysis);
eml_mime_result_free(mime_res);
g_ptr_array_unref(bank_proposals);
g_ptr_array_unref(document_analyses);
g_set_error_literal(
error,
G_IO_ERROR,
G_IO_ERROR_CANCELLED,
"L'analyse EML a été annulée."
);
return FALSE;
}
EmlAttachment *att = g_ptr_array_index(mime_res->attachments, i);
if (att->extracted_path == NULL)
continue;
/* Analyse bancaire sur les fichiers texte/images compatibles */
if (g_str_has_suffix(att->extracted_path, ".txt") || g_str_has_suffix(att->extracted_path, ".eml"))
{
char *content = NULL;
if (g_file_get_contents(att->extracted_path, &content, NULL, NULL))
{
BankProposal *bp = bank_proposal_analyze_text(content, data->evidence_id);
if (bp != NULL)
{
bp->extraction_id = g_strdup(att->part_index);
g_ptr_array_add(bank_proposals, bp);
}
g_free(content);
}
}
else if (ocr_analysis_mime_is_compatible(att->detected_mime) ||
g_strcmp0(att->detected_mime, "application/pdf") == 0 ||
g_strcmp0(att->content_type, "application/pdf") == 0)
{
DocumentAnalysisTools tools = {
.exiftool = data->exiftool,
.tesseract = data->tesseract,
.pdfinfo = data->pdfinfo,
.pdftotext = data->pdftotext,
.pdftoppm = data->pdftoppm
};
GError *analysis_error = NULL;
DocumentFileAnalysis *document =
document_file_analysis_run(&tools, att->extracted_path,
att->content_type, att->detected_mime, TRUE,
"fra+eng", cancellable, &analysis_error);
if (document == NULL &&
analysis_error != NULL &&
g_error_matches(analysis_error, G_IO_ERROR,
G_IO_ERROR_CANCELLED))
{
g_propagate_error(error, analysis_error);
eml_analysis_free(analysis);
eml_mime_result_free(mime_res);
g_ptr_array_unref(bank_proposals);
g_ptr_array_unref(document_analyses);
return FALSE;
}
g_clear_error(&analysis_error);
if (document == NULL)
continue;
g_ptr_array_add(document_analyses, document);
const char *ocr_text = document->ocr != NULL
? document->ocr->text
: NULL;
if (ocr_text != NULL)
{
BankProposal *bp = bank_proposal_analyze_text(ocr_text, data->evidence_id);
if (bp != NULL)
{
bp->extraction_id = g_strdup(att->part_index);
g_ptr_array_add(bank_proposals, bp);
}
}
}
}
background_task_report_progress(task, 1.0, "Analyse EML terminée avec succès.");
EmlPipelineResult *res = g_new0(EmlPipelineResult, 1);
res->analysis = analysis;
res->mime_result = mime_res;
res->bank_proposals = bank_proposals;
res->document_analyses = document_analyses;
res->warnings = g_ptr_array_new_with_free_func(g_free);
if (out_result != NULL)
*out_result = res;
return TRUE;
}
BackgroundTask *eml_pipeline_task_new(const char *eml_path,
const char *processed_evidence_dir,
const char *evidence_id)
{
DocumentAnalysisTools tools = {
.exiftool = "exiftool",
.tesseract = "tesseract",
.pdfinfo = "pdfinfo",
.pdftotext = "pdftotext",
.pdftoppm = "pdftoppm"
};
return eml_pipeline_task_new_with_tools(eml_path,
processed_evidence_dir, evidence_id, &tools);
}
BackgroundTask *eml_pipeline_task_new_with_tools(
const char *eml_path,
const char *processed_evidence_dir,
const char *evidence_id,
const DocumentAnalysisTools *tools)
{
if (eml_path == NULL || processed_evidence_dir == NULL || tools == NULL)
return NULL;
EmlPipelineTaskData *data = g_new0(EmlPipelineTaskData, 1);
data->eml_path = g_strdup(eml_path);
data->processed_evidence_dir = g_strdup(processed_evidence_dir);
data->evidence_id = g_strdup(evidence_id);
data->exiftool = g_strdup(tools->exiftool);
data->tesseract = g_strdup(tools->tesseract);
data->pdfinfo = g_strdup(tools->pdfinfo);
data->pdftotext = g_strdup(tools->pdftotext);
data->pdftoppm = g_strdup(tools->pdftoppm);
BackgroundTask *task = background_task_new(
"Analyse du message EML et de ses pièces jointes");
GError *start_error = NULL;
if (task == NULL || !background_task_start(task, eml_pipeline_task_worker,
data, eml_pipeline_task_data_free, (GDestroyNotify)
eml_pipeline_result_free, NULL, NULL, NULL, &start_error))
{
if (task != NULL)
background_task_unref(task);
else
eml_pipeline_task_data_free(data);
g_clear_error(&start_error);
return NULL;
}
return task;
}