From 6f160d1b1e1bfdd06414d5b6c49a0c9d9fc8a830 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Tue, 12 Sep 2023 23:49:01 +0200
Subject: Complete the output of scan results (text or JSON).

---
 plugins/pychrysalide/analysis/scan/scanner.c | 125 ++++++++++++++++++++++++++
 src/analysis/scan/matches/bytes.c            |  55 +++++++++++-
 src/analysis/scan/pattern.c                  | 128 +++++++++++++++++++++++++--
 src/analysis/scan/pattern.h                  |   4 +-
 src/analysis/scan/rule.c                     | 128 +++++++++++++++++++++++++--
 src/analysis/scan/rule.h                     |   4 +-
 src/analysis/scan/scanner.c                  | 128 +++++++++++++++++++++++++--
 src/analysis/scan/scanner.h                  |   4 +-
 8 files changed, 550 insertions(+), 26 deletions(-)

diff --git a/plugins/pychrysalide/analysis/scan/scanner.c b/plugins/pychrysalide/analysis/scan/scanner.c
index befbc80..e2d5a18 100644
--- a/plugins/pychrysalide/analysis/scan/scanner.c
+++ b/plugins/pychrysalide/analysis/scan/scanner.c
@@ -25,6 +25,7 @@
 #include "scanner.h"
 
 
+#include <malloc.h>
 #include <pygobject.h>
 
 
@@ -34,6 +35,7 @@
 #include <analysis/scan/scanner-int.h>
 
 
+#include "context.h"
 #include "options.h"
 #include "../content.h"
 #include "../../access.h"
@@ -49,6 +51,12 @@ static int py_content_scanner_init(PyObject *, PyObject *, PyObject *);
 /* Lance une analyse d'un contenu binaire. */
 static PyObject *py_content_scanner_analyze(PyObject *, PyObject *);
 
+/* Convertit un gestionnaire de recherches en JSON. */
+static PyObject *py_content_scanner_convert_to_json(PyObject *, PyObject *);
+
+/* Indique le chemin d'un éventuel fichier de source. */
+static PyObject *py_content_scanner_get_filename(PyObject *, void *);
+
 
 
 /******************************************************************************
@@ -188,6 +196,121 @@ static PyObject *py_content_scanner_analyze(PyObject *self, PyObject *args)
 
 /******************************************************************************
 *                                                                             *
+*  Paramètres  : self = classe représentant un format.                        *
+*                args = arguments fournis à l'appel.                          *
+*                                                                             *
+*  Description : Convertit un gestionnaire de recherches en texte.            *
+*                                                                             *
+*  Retour      : Données textuelles ou None en cas d'erreur.                  *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_content_scanner_convert_to_text(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Contexte de suivi à renvoyer*/
+    GScanContext *context;                  /* Contexte d'analyse          */
+    int ret;                                /* Bilan de lecture des args.  */
+    GContentScanner *scanner;               /* Encadrement de recherche    */
+    char *out;                              /* Données en sortie           */
+
+#define CONTENT_SCANNER_CONVERT_TO_TEXT_METHOD PYTHON_METHOD_DEF            \
+(                                                                           \
+    convert_to_text, "$self, context, /",                                   \
+    METH_VARARGS, py_content_scanner,                                       \
+    "Output a scan results as text.\n"                                      \
+    "\n"                                                                    \
+    "The *context* argument is a pychrysalide.analysis.scan.ScanContext"    \
+    " instance provided by a previous call to *self.analyze()*. This"       \
+    " context stores all the scan results.\n"                               \
+    "\n"                                                                    \
+    "The method returns a string value, or *None* in case of failure."      \
+)
+
+    ret = PyArg_ParseTuple(args, "O&", convert_to_scan_context, &context);
+    if (!ret) return NULL;
+
+    scanner = G_CONTENT_SCANNER(pygobject_get(self));
+
+    out = g_content_scanner_convert_to_text(scanner, context);
+
+    if (out != NULL)
+    {
+        result = PyUnicode_FromString(out);
+        free(out);
+    }
+    else
+    {
+        result = Py_None;
+        Py_INCREF(result);
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = classe représentant un format.                        *
+*                args = arguments fournis à l'appel.                          *
+*                                                                             *
+*  Description : Convertit un gestionnaire de recherches en JSON.             *
+*                                                                             *
+*  Retour      : Données textuelles au format JSON ou None en cas d'erreur.   *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_content_scanner_convert_to_json(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Contexte de suivi à renvoyer*/
+    GScanContext *context;                  /* Contexte d'analyse          */
+    int ret;                                /* Bilan de lecture des args.  */
+    GContentScanner *scanner;               /* Encadrement de recherche    */
+    char *out;                              /* Données en sortie           */
+
+#define CONTENT_SCANNER_CONVERT_TO_JSON_METHOD PYTHON_METHOD_DEF            \
+(                                                                           \
+    convert_to_json, "$self, context, /",                                   \
+    METH_VARARGS, py_content_scanner,                                       \
+    "Output a scan results as JSON data.\n"                                 \
+    "\n"                                                                    \
+    "The *context* argument is a pychrysalide.analysis.scan.ScanContext"    \
+    " instance provided by a previous call to *self.analyze()*. This"       \
+    " context stores all the scan results.\n"                               \
+    "\n"                                                                    \
+    "The method returns JSON data as a string value, or *None* in case"     \
+    " of failure."                                                          \
+)
+
+    ret = PyArg_ParseTuple(args, "O&", convert_to_scan_context, &context);
+    if (!ret) return NULL;
+
+    scanner = G_CONTENT_SCANNER(pygobject_get(self));
+
+    out = g_content_scanner_convert_to_json(scanner, context);
+
+    if (out != NULL)
+    {
+        result = PyUnicode_FromString(out);
+        free(out);
+    }
+    else
+    {
+        result = Py_None;
+        Py_INCREF(result);
+    }
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
 *  Paramètres  : self    = objet Python concerné par l'appel.                 *
 *                closure = non utilisé ici.                                   *
 *                                                                             *
@@ -247,6 +370,8 @@ PyTypeObject *get_python_content_scanner_type(void)
 {
     static PyMethodDef py_content_scanner_methods[] = {
         CONTENT_SCANNER_ANALYZE_METHOD,
+        CONTENT_SCANNER_CONVERT_TO_TEXT_METHOD,
+        CONTENT_SCANNER_CONVERT_TO_JSON_METHOD,
         { NULL }
     };
 
diff --git a/src/analysis/scan/matches/bytes.c b/src/analysis/scan/matches/bytes.c
index 043170e..ccd73a1 100644
--- a/src/analysis/scan/matches/bytes.c
+++ b/src/analysis/scan/matches/bytes.c
@@ -449,7 +449,7 @@ static void g_scan_bytes_match_output_to_json(const GScanBytesMatch *match, cons
 
     write(fd, ",\n", 2);
 
-    /* Affichage du contenu */
+    /* Affichage du contenu brut */
 
     for (i = 0; i < level; i++)
         write(fd, padding->data, padding->len);
@@ -462,12 +462,61 @@ static void g_scan_bytes_match_output_to_json(const GScanBytesMatch *match, cons
 
     for (k = 0; k < match->len; k++)
     {
-        if (isprint(data[k]))
+        if (data[k] == '\\')
+            write(fd, "\\\\", 2);
+
+        else if (isprint(data[k]))
             write(fd, &data[k], 1);
 
         else
         {
-            write(fd, "\\x", 2);
+            write(fd, "\\u", 2);
+
+            /**
+             * Cf. https://datatracker.ietf.org/doc/html/rfc8259#section-7
+             */
+            ret = snprintf(value, ULLONG_MAXLEN, "%04hhx", data[k]);
+
+            if (ret > 0)
+            {
+                assert(ret == 4);
+                write(fd, value, ret);
+            }
+
+            else
+            {
+                log_simple_message(LMT_EXT_ERROR, "Error while converting data!");
+                write(fd, "??", 2);
+            }
+
+        }
+
+    }
+
+    write(fd, "\",\n", 3);
+
+    /* Affichage du contenu en version humainement lisible */
+
+    for (i = 0; i < level; i++)
+        write(fd, padding->data, padding->len);
+
+    write(fd, "\"content_str\": \"", 16);
+
+    init_vmpa(&pos, match->start, VMPA_NO_VIRTUAL);
+
+    data = g_binary_content_get_raw_access(match->content, &pos, match->len);
+
+    for (k = 0; k < match->len; k++)
+    {
+        if (data[k] == '\\')
+            write(fd, "\\\\", 2);
+
+        else if (isprint(data[k]))
+            write(fd, &data[k], 1);
+
+        else
+        {
+            write(fd, "\\\\x", 3);
 
             ret = snprintf(value, ULLONG_MAXLEN, "%02hhx", data[k]);
 
diff --git a/src/analysis/scan/pattern.c b/src/analysis/scan/pattern.c
index 5b966d2..797f4ac 100644
--- a/src/analysis/scan/pattern.c
+++ b/src/analysis/scan/pattern.c
@@ -25,10 +25,15 @@
 
 
 #include <malloc.h>
+#include <stdio.h>
 #include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
 
 
 #include "pattern-int.h"
+#include "../../core/logs.h"
 
 
 
@@ -216,14 +221,69 @@ void g_search_pattern_output_to_text(const GSearchPattern *pattern, GScanContext
 *                                                                             *
 *  Description : Convertit un motif de recherche en texte.                    *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Données textuelles ou NULL en cas d'erreur.                  *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_search_pattern_convert_as_text(const GSearchPattern *pattern, GScanContext *context)
+char *g_search_pattern_convert_as_text(const GSearchPattern *pattern, GScanContext *context)
 {
+    char *result;                           /* Données à retourner         */
+    char *name;                             /* Nom "unique" pour le canal  */
+    int ret;                                /* Bilan de création de nom    */
+    int fd;                                 /* Canal d'écriture            */
+    struct stat info;                       /* Infos. incluant une taille  */
+    ssize_t got;                            /* Données effectivement relues*/
+
+    static unsigned long long counter = 0;
+
+    result = NULL;
+
+    ret = asprintf(&name, "rost-pattern2text-%llu", counter++);
+    if (ret == -1) goto exit;
+
+    fd = memfd_create(name, MFD_CLOEXEC);
+    if (fd == -1)
+    {
+        LOG_ERROR_N("memfd_create");
+        goto exit_with_name;
+    }
+
+    g_search_pattern_output_to_text(pattern, context, fd);
+
+    ret = fstat(fd, &info);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("fstat");
+        goto exit_with_name_and_fd;
+    }
+
+    result = malloc((info.st_size + 1) * sizeof(char));
+
+    lseek(fd, SEEK_SET, 0);
+
+    got = read(fd, result, info.st_size);
+    if (got != info.st_size)
+    {
+        LOG_ERROR_N("read");
+        free(result);
+        goto exit_with_name_and_fd;
+    }
+
+    result[info.st_size] = '\0';
+
+ exit_with_name_and_fd:
+
+    close(fd);
+
+ exit_with_name:
+
+    free(name);
+
+ exit:
+
+    return result;
 
 }
 
@@ -294,14 +354,72 @@ void g_search_pattern_output_to_json(const GSearchPattern *pattern, GScanContext
 *                                                                             *
 *  Description : Convertit un motif de recherche en JSON.                     *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Données textuelles au format JSON ou NULL en cas d'erreur.   *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_search_pattern_convert_as_json(const GSearchPattern *pattern, GScanContext *context)
+char *g_search_pattern_convert_as_json(const GSearchPattern *pattern, GScanContext *context)
 {
-    /* TODO */
+    char *result;                           /* Données à retourner         */
+    char *name;                             /* Nom "unique" pour le canal  */
+    int ret;                                /* Bilan de création de nom    */
+    int fd;                                 /* Canal d'écriture            */
+    sized_string_t padding;                 /* Bourrage pour le JSON       */
+    struct stat info;                       /* Infos. incluant une taille  */
+    ssize_t got;                            /* Données effectivement relues*/
+
+    static unsigned long long counter = 0;
+
+    result = NULL;
+
+    ret = asprintf(&name, "rost-pattern2json-%llu", counter++);
+    if (ret == -1) goto exit;
+
+    fd = memfd_create(name, MFD_CLOEXEC);
+    if (fd == -1)
+    {
+        LOG_ERROR_N("memfd_create");
+        goto exit_with_name;
+    }
+
+    padding.data = "   ";
+    padding.len = 3;
+
+    g_search_pattern_output_to_json(pattern, context, &padding, 0, fd, false);
+
+    ret = fstat(fd, &info);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("fstat");
+        goto exit_with_name_and_fd;
+    }
+
+    result = malloc((info.st_size + 1) * sizeof(char));
+
+    lseek(fd, SEEK_SET, 0);
+
+    got = read(fd, result, info.st_size);
+    if (got != info.st_size)
+    {
+        LOG_ERROR_N("read");
+        free(result);
+        goto exit_with_name_and_fd;
+    }
+
+    result[info.st_size] = '\0';
+
+ exit_with_name_and_fd:
+
+    close(fd);
+
+ exit_with_name:
+
+    free(name);
+
+ exit:
+
+    return result;
 
 }
diff --git a/src/analysis/scan/pattern.h b/src/analysis/scan/pattern.h
index 0e4327d..72f87e4 100644
--- a/src/analysis/scan/pattern.h
+++ b/src/analysis/scan/pattern.h
@@ -66,13 +66,13 @@ void g_search_pattern_set_name(GSearchPattern *, const char *, size_t);
 void g_search_pattern_output_to_text(const GSearchPattern *, GScanContext *, int);
 
 /* Convertit un motif de recherche en texte. */
-void g_search_pattern_convert_as_text(const GSearchPattern *, GScanContext *);
+char *g_search_pattern_convert_as_text(const GSearchPattern *, GScanContext *);
 
 /* Affiche un motif de recherche au format JSON. */
 void g_search_pattern_output_to_json(const GSearchPattern *, GScanContext *, const sized_string_t *, unsigned int, int, bool);
 
 /* Convertit un motif de recherche en JSON. */
-void g_search_pattern_convert_as_json(const GSearchPattern *, GScanContext *);
+char *g_search_pattern_convert_as_json(const GSearchPattern *, GScanContext *);
 
 
 
diff --git a/src/analysis/scan/rule.c b/src/analysis/scan/rule.c
index 4ef1e3c..a1fcfcb 100644
--- a/src/analysis/scan/rule.c
+++ b/src/analysis/scan/rule.c
@@ -26,7 +26,11 @@
 
 #include <assert.h>
 #include <regex.h>
+#include <stdio.h>
 #include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
 
 
 #include "rule-int.h"
@@ -561,15 +565,69 @@ void g_scan_rule_output_to_text(const GScanRule *rule, GScanContext *context, bo
 *                                                                             *
 *  Description : Convertit une règle en texte.                                *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Données textuelles ou NULL en cas d'erreur.                  *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context)
+char *g_scan_rule_convert_as_text(const GScanRule *rule, GScanContext *context)
 {
-    /* TODO */
+    char *result;                           /* Données à retourner         */
+    char *name;                             /* Nom "unique" pour le canal  */
+    int ret;                                /* Bilan de création de nom    */
+    int fd;                                 /* Canal d'écriture            */
+    struct stat info;                       /* Infos. incluant une taille  */
+    ssize_t got;                            /* Données effectivement relues*/
+
+    static unsigned long long counter = 0;
+
+    result = NULL;
+
+    ret = asprintf(&name, "rost-rule2text-%llu", counter++);
+    if (ret == -1) goto exit;
+
+    fd = memfd_create(name, MFD_CLOEXEC);
+    if (fd == -1)
+    {
+        LOG_ERROR_N("memfd_create");
+        goto exit_with_name;
+    }
+
+    g_scan_rule_output_to_text(rule, context, true, fd);
+
+    ret = fstat(fd, &info);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("fstat");
+        goto exit_with_name_and_fd;
+    }
+
+    result = malloc((info.st_size + 1) * sizeof(char));
+
+    lseek(fd, SEEK_SET, 0);
+
+    got = read(fd, result, info.st_size);
+    if (got != info.st_size)
+    {
+        LOG_ERROR_N("read");
+        free(result);
+        goto exit_with_name_and_fd;
+    }
+
+    result[info.st_size] = '\0';
+
+ exit_with_name_and_fd:
+
+    close(fd);
+
+ exit_with_name:
+
+    free(name);
+
+ exit:
+
+    return result;
 
 }
 
@@ -668,14 +726,72 @@ void g_scan_rule_output_to_json(const GScanRule *rule, GScanContext *context, co
 *                                                                             *
 *  Description : Convertit une règle en JSON.                                 *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Données textuelles au format JSON ou NULL en cas d'erreur.   *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_scan_rule_convert_as_json(const GScanRule *rule, GScanContext *context)
+char *g_scan_rule_convert_as_json(const GScanRule *rule, GScanContext *context)
 {
-    /* TODO */
+    char *result;                           /* Données à retourner         */
+    char *name;                             /* Nom "unique" pour le canal  */
+    int ret;                                /* Bilan de création de nom    */
+    int fd;                                 /* Canal d'écriture            */
+    sized_string_t padding;                 /* Bourrage pour le JSON       */
+    struct stat info;                       /* Infos. incluant une taille  */
+    ssize_t got;                            /* Données effectivement relues*/
+
+    static unsigned long long counter = 0;
+
+    result = NULL;
+
+    ret = asprintf(&name, "rost-rule2json-%llu", counter++);
+    if (ret == -1) goto exit;
+
+    fd = memfd_create(name, MFD_CLOEXEC);
+    if (fd == -1)
+    {
+        LOG_ERROR_N("memfd_create");
+        goto exit_with_name;
+    }
+
+    padding.data = "   ";
+    padding.len = 3;
+
+    g_scan_rule_output_to_json(rule, context, &padding, 0, fd, false);
+
+    ret = fstat(fd, &info);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("fstat");
+        goto exit_with_name_and_fd;
+    }
+
+    result = malloc((info.st_size + 1) * sizeof(char));
+
+    lseek(fd, SEEK_SET, 0);
+
+    got = read(fd, result, info.st_size);
+    if (got != info.st_size)
+    {
+        LOG_ERROR_N("read");
+        free(result);
+        goto exit_with_name_and_fd;
+    }
+
+    result[info.st_size] = '\0';
+
+ exit_with_name_and_fd:
+
+    close(fd);
+
+ exit_with_name:
+
+    free(name);
+
+ exit:
+
+    return result;
 
 }
diff --git a/src/analysis/scan/rule.h b/src/analysis/scan/rule.h
index 7ade51b..6ab5105 100644
--- a/src/analysis/scan/rule.h
+++ b/src/analysis/scan/rule.h
@@ -84,13 +84,13 @@ void g_scan_rule_check(GScanRule *, GEngineBackend *, GScanContext *);
 void g_scan_rule_output_to_text(const GScanRule *, GScanContext *, bool, int);
 
 /* Convertit une règle en texte. */
-void g_scan_rule_convert_as_text(const GScanRule *, GScanContext *);
+char *g_scan_rule_convert_as_text(const GScanRule *, GScanContext *);
 
 /* Affiche une règle au format JSON. */
 void g_scan_rule_output_to_json(const GScanRule *, GScanContext *, const sized_string_t *, unsigned int, int, bool);
 
 /* Convertit une règle en JSON. */
-void g_scan_rule_convert_as_json(const GScanRule *, GScanContext *);
+char *g_scan_rule_convert_as_json(const GScanRule *, GScanContext *);
 
 
 
diff --git a/src/analysis/scan/scanner.c b/src/analysis/scan/scanner.c
index 29f47eb..41fa0de 100644
--- a/src/analysis/scan/scanner.c
+++ b/src/analysis/scan/scanner.c
@@ -27,6 +27,10 @@
 #include <assert.h>
 #include <libgen.h>
 #include <malloc.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
 
 
 #include "decl.h"
@@ -556,15 +560,69 @@ void g_content_scanner_output_to_text(const GContentScanner *scanner, GScanConte
 *                                                                             *
 *  Description : Convertit un gestionnaire de recherches en texte.            *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Données textuelles ou NULL en cas d'erreur.                  *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_content_scanner_convert_as_text(const GContentScanner *scanner, GScanContext *context)
+char *g_content_scanner_convert_to_text(const GContentScanner *scanner, GScanContext *context)
 {
-    /* TODO */
+    char *result;                           /* Données à retourner         */
+    char *name;                             /* Nom "unique" pour le canal  */
+    int ret;                                /* Bilan de création de nom    */
+    int fd;                                 /* Canal d'écriture            */
+    struct stat info;                       /* Infos. incluant une taille  */
+    ssize_t got;                            /* Données effectivement relues*/
+
+    static unsigned long long counter = 0;
+
+    result = NULL;
+
+    ret = asprintf(&name, "rost-scanner2text-%llu", counter++);
+    if (ret == -1) goto exit;
+
+    fd = memfd_create(name, MFD_CLOEXEC);
+    if (fd == -1)
+    {
+        LOG_ERROR_N("memfd_create");
+        goto exit_with_name;
+    }
+
+    g_content_scanner_output_to_text(scanner, context, true, fd);
+
+    ret = fstat(fd, &info);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("fstat");
+        goto exit_with_name_and_fd;
+    }
+
+    result = malloc((info.st_size + 1) * sizeof(char));
+
+    lseek(fd, SEEK_SET, 0);
+
+    got = read(fd, result, info.st_size);
+    if (got != info.st_size)
+    {
+        LOG_ERROR_N("read");
+        free(result);
+        goto exit_with_name_and_fd;
+    }
+
+    result[info.st_size] = '\0';
+
+ exit_with_name_and_fd:
+
+    close(fd);
+
+ exit_with_name:
+
+    free(name);
+
+ exit:
+
+    return result;
 
 }
 
@@ -624,14 +682,72 @@ void g_content_scanner_output_to_json(const GContentScanner *scanner, GScanConte
 *                                                                             *
 *  Description : Convertit un gestionnaire de recherches en JSON.             *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Données textuelles au format JSON ou NULL en cas d'erreur.   *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_content_scanner_convert_as_json(const GContentScanner *scanner, GScanContext *context)
+char *g_content_scanner_convert_to_json(const GContentScanner *scanner, GScanContext *context)
 {
-    /* TODO */
+    char *result;                           /* Données à retourner         */
+    char *name;                             /* Nom "unique" pour le canal  */
+    int ret;                                /* Bilan de création de nom    */
+    int fd;                                 /* Canal d'écriture            */
+    sized_string_t padding;                 /* Bourrage pour le JSON       */
+    struct stat info;                       /* Infos. incluant une taille  */
+    ssize_t got;                            /* Données effectivement relues*/
+
+    static unsigned long long counter = 0;
+
+    result = NULL;
+
+    ret = asprintf(&name, "rost-scanner2json-%llu", counter++);
+    if (ret == -1) goto exit;
+
+    fd = memfd_create(name, MFD_CLOEXEC);
+    if (fd == -1)
+    {
+        LOG_ERROR_N("memfd_create");
+        goto exit_with_name;
+    }
+
+    padding.data = "   ";
+    padding.len = 3;
+
+    g_content_scanner_output_to_json(scanner, context, &padding, 0, fd);
+
+    ret = fstat(fd, &info);
+    if (ret != 0)
+    {
+        LOG_ERROR_N("fstat");
+        goto exit_with_name_and_fd;
+    }
+
+    result = malloc((info.st_size + 1) * sizeof(char));
+
+    lseek(fd, SEEK_SET, 0);
+
+    got = read(fd, result, info.st_size);
+    if (got != info.st_size)
+    {
+        LOG_ERROR_N("read");
+        free(result);
+        goto exit_with_name_and_fd;
+    }
+
+    result[info.st_size] = '\0';
+
+ exit_with_name_and_fd:
+
+    close(fd);
+
+ exit_with_name:
+
+    free(name);
+
+ exit:
+
+    return result;
 
 }
diff --git a/src/analysis/scan/scanner.h b/src/analysis/scan/scanner.h
index d2b5dc1..e03ecda 100644
--- a/src/analysis/scan/scanner.h
+++ b/src/analysis/scan/scanner.h
@@ -76,13 +76,13 @@ GScanContext *g_content_scanner_analyze(GContentScanner *, GScanOptions *, GBinC
 void g_content_scanner_output_to_text(const GContentScanner *, GScanContext *, bool, int);
 
 /* Convertit un gestionnaire de recherches en texte. */
-void g_content_scanner_convert_as_text(const GContentScanner *, GScanContext *);
+char *g_content_scanner_convert_to_text(const GContentScanner *, GScanContext *);
 
 /* Affiche un gestionnaire de recherches au format JSON. */
 void g_content_scanner_output_to_json(const GContentScanner *, GScanContext *, const sized_string_t *, unsigned int, int);
 
 /* Convertit un gestionnaire de recherches en JSON. */
-void g_content_scanner_convert_as_json(const GContentScanner *, GScanContext *);
+char *g_content_scanner_convert_to_json(const GContentScanner *, GScanContext *);
 
 
 
-- 
cgit v0.11.2-87-g4458