feat: add external tool catalog and version detection
This commit is contained in:
parent
2ffbdec888
commit
f68c4f88d1
8 changed files with 3674 additions and 2 deletions
15
Makefile
15
Makefile
|
|
@ -45,6 +45,7 @@ TEST_TASK_MANAGER := tests/test_task_manager
|
|||
TEST_TOOL_REGISTRY := tests/test_tool_registry
|
||||
TEST_TOOL_PROCESS := tests/test_tool_process
|
||||
TEST_TOOL_TASK := tests/test_tool_task
|
||||
TEST_TOOL_CATALOG := tests/test_tool_catalog
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
|
|
@ -171,6 +172,13 @@ $(TEST_TOOL_TASK): \
|
|||
src/core/background_task.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
|
||||
$(TEST_TOOL_CATALOG): \
|
||||
tests/test_tool_catalog.c \
|
||||
src/core/tool_catalog.c \
|
||||
src/core/tool_registry.c \
|
||||
src/core/tool_process.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
|
||||
test: \
|
||||
$(TEST_NODE) \
|
||||
$(TEST_TREE_MODEL) \
|
||||
|
|
@ -187,7 +195,8 @@ test: \
|
|||
$(TEST_TASK_MANAGER) \
|
||||
$(TEST_TOOL_REGISTRY) \
|
||||
$(TEST_TOOL_PROCESS) \
|
||||
$(TEST_TOOL_TASK)
|
||||
$(TEST_TOOL_TASK) \
|
||||
$(TEST_TOOL_CATALOG)
|
||||
@echo "Exécution des tests..."
|
||||
@./$(TEST_NODE)
|
||||
@./$(TEST_TREE_MODEL)
|
||||
|
|
@ -205,6 +214,7 @@ test: \
|
|||
@$(TEST_TOOL_REGISTRY)
|
||||
@$(TEST_TOOL_PROCESS)
|
||||
@$(TEST_TOOL_TASK)
|
||||
@$(TEST_TOOL_CATALOG)
|
||||
@echo "Tous les tests sont valides."
|
||||
|
||||
%.o: %.c
|
||||
|
|
@ -230,6 +240,7 @@ clean:
|
|||
$(TEST_TASK_MANAGER) \
|
||||
$(TEST_TOOL_REGISTRY) \
|
||||
$(TEST_TOOL_PROCESS) \
|
||||
$(TEST_TOOL_TASK)
|
||||
$(TEST_TOOL_TASK) \
|
||||
$(TEST_TOOL_CATALOG)
|
||||
|
||||
.PHONY: clean run test
|
||||
|
|
|
|||
515
docs/tickets/closed/TICKET-039.md
Normal file
515
docs/tickets/closed/TICKET-039.md
Normal file
|
|
@ -0,0 +1,515 @@
|
|||
# TICKET-039 — Catalogue initial des outils externes et détection de leurs versions
|
||||
|
||||
## Statut
|
||||
|
||||
À faire
|
||||
|
||||
## Priorité
|
||||
|
||||
Haute
|
||||
|
||||
## Objectif
|
||||
|
||||
Créer le catalogue initial des outils externes utilisés par Labfy Investigation et fournir une détection fiable de leur version.
|
||||
|
||||
Ce ticket complète les modules déjà validés :
|
||||
|
||||
```text
|
||||
ToolRegistry
|
||||
ToolProcess
|
||||
ToolTask
|
||||
```
|
||||
|
||||
Le catalogue décrit les outils connus par l’application. Le registre décrit leur état sur la machine courante.
|
||||
|
||||
```text
|
||||
ToolCatalog
|
||||
description statique des outils connus
|
||||
↓
|
||||
ToolRegistry
|
||||
disponibilité, chemin résolu, version détectée
|
||||
↓
|
||||
ToolProcess
|
||||
exécution sécurisée de la commande de version
|
||||
```
|
||||
|
||||
## Contexte
|
||||
|
||||
`ToolRegistry` permet déjà d’enregistrer un outil, de rechercher son exécutable dans le `PATH`, de conserver son chemin résolu et sa version détectée, et de distinguer les états `UNKNOWN`, `AVAILABLE` et `MISSING`.
|
||||
|
||||
Il manque une source centrale définissant :
|
||||
|
||||
- les outils reconnus par l’application ;
|
||||
- leur identifiant interne ;
|
||||
- leur nom affiché ;
|
||||
- leur exécutable ;
|
||||
- leur importance ;
|
||||
- les arguments permettant de demander leur version ;
|
||||
- la manière de normaliser la sortie obtenue.
|
||||
|
||||
Sans catalogue central, chaque fonctionnalité finirait par enregistrer elle-même ses dépendances.
|
||||
|
||||
## Modules attendus
|
||||
|
||||
```text
|
||||
include/core/tool_catalog.h
|
||||
src/core/tool_catalog.c
|
||||
tests/test_tool_catalog.c
|
||||
```
|
||||
|
||||
Le ticket ne doit pas modifier l’API publique de `ToolRegistry`, sauf nécessité démontrée pendant l’implémentation.
|
||||
|
||||
## Responsabilités de ToolCatalog
|
||||
|
||||
Le module doit :
|
||||
|
||||
1. conserver une liste statique et immuable des outils connus ;
|
||||
2. garantir l’unicité de leurs identifiants ;
|
||||
3. exposer leurs informations descriptives ;
|
||||
4. enregistrer l’ensemble du catalogue dans un `ToolRegistry` ;
|
||||
5. définir les arguments de détection de version ;
|
||||
6. exécuter ces arguments avec `ToolProcess` ;
|
||||
7. normaliser la sortie de version ;
|
||||
8. retourner la version détectée sans modifier directement le registre ;
|
||||
9. gérer l’annulation ;
|
||||
10. rester indépendant de GTK.
|
||||
|
||||
Le module ne doit pas installer de dépendance, modifier le `PATH`, utiliser un shell, analyser un résultat OSINT ni modifier l’interface graphique.
|
||||
|
||||
## Catalogue initial
|
||||
|
||||
| Identifiant interne | Nom affiché | Exécutable | Importance | Arguments de version |
|
||||
|---|---|---|---|---|
|
||||
| `dns.dig` | `dig` | `dig` | optionnel | `-v` |
|
||||
| `dns.host` | `host` | `host` | optionnel | `-V` |
|
||||
| `network.whois` | `whois` | `whois` | optionnel | `--version` |
|
||||
| `http.curl` | `curl` | `curl` | optionnel | `--version` |
|
||||
| `tls.openssl` | `OpenSSL` | `openssl` | optionnel | `version` |
|
||||
|
||||
Tous ces outils sont optionnels au niveau de l’application entière.
|
||||
|
||||
Ne pas ajouter encore `nslookup`, `traceroute`, `jq`, `file`, `exiftool`, `nmap`, `subfinder`, `amass`, les outils Python ou les outils installés depuis GitHub.
|
||||
|
||||
## Structure ToolCatalogEntry
|
||||
|
||||
Créer une structure opaque :
|
||||
|
||||
```c
|
||||
typedef struct ToolCatalogEntry ToolCatalogEntry;
|
||||
```
|
||||
|
||||
Elle conserve au minimum :
|
||||
|
||||
```text
|
||||
identifier
|
||||
display_name
|
||||
executable_name
|
||||
requirement
|
||||
version_arguments
|
||||
```
|
||||
|
||||
Les données du catalogue sont statiques et immuables. Elles ne doivent jamais être libérées par l’appelant.
|
||||
|
||||
## Domaine d’erreur
|
||||
|
||||
```c
|
||||
#define TOOL_CATALOG_ERROR \
|
||||
tool_catalog_error_quark()
|
||||
```
|
||||
|
||||
```c
|
||||
typedef enum
|
||||
{
|
||||
TOOL_CATALOG_ERROR_INVALID_ARGUMENT,
|
||||
TOOL_CATALOG_ERROR_ENTRY_NOT_FOUND,
|
||||
TOOL_CATALOG_ERROR_REGISTRATION,
|
||||
TOOL_CATALOG_ERROR_TOOL_NOT_REGISTERED,
|
||||
TOOL_CATALOG_ERROR_TOOL_NOT_CHECKED,
|
||||
TOOL_CATALOG_ERROR_TOOL_MISSING,
|
||||
TOOL_CATALOG_ERROR_INVALID_TOOL_STATE,
|
||||
TOOL_CATALOG_ERROR_PROCESS,
|
||||
TOOL_CATALOG_ERROR_VERSION_COMMAND,
|
||||
TOOL_CATALOG_ERROR_VERSION_OUTPUT,
|
||||
TOOL_CATALOG_ERROR_CANCELLED
|
||||
} ToolCatalogError;
|
||||
```
|
||||
|
||||
```c
|
||||
GQuark tool_catalog_error_quark(void);
|
||||
```
|
||||
|
||||
## API publique proposée
|
||||
|
||||
### Consultation
|
||||
|
||||
```c
|
||||
gsize tool_catalog_get_count(void);
|
||||
```
|
||||
|
||||
```c
|
||||
const ToolCatalogEntry *tool_catalog_get_entry(
|
||||
gsize index
|
||||
);
|
||||
```
|
||||
|
||||
```c
|
||||
const ToolCatalogEntry *tool_catalog_find(
|
||||
const char *identifier
|
||||
);
|
||||
```
|
||||
|
||||
### Accesseurs
|
||||
|
||||
```c
|
||||
const char *tool_catalog_entry_get_identifier(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
```
|
||||
|
||||
```c
|
||||
const char *tool_catalog_entry_get_display_name(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
```
|
||||
|
||||
```c
|
||||
const char *tool_catalog_entry_get_executable_name(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
```
|
||||
|
||||
```c
|
||||
ToolRequirement tool_catalog_entry_get_requirement(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
```
|
||||
|
||||
```c
|
||||
gsize tool_catalog_entry_get_version_argument_count(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
```
|
||||
|
||||
```c
|
||||
const char *tool_catalog_entry_get_version_argument(
|
||||
const ToolCatalogEntry *entry,
|
||||
gsize index
|
||||
);
|
||||
```
|
||||
|
||||
### Enregistrement
|
||||
|
||||
```c
|
||||
gboolean tool_catalog_register_defaults(
|
||||
ToolRegistry *tool_registry,
|
||||
GError **error
|
||||
);
|
||||
```
|
||||
|
||||
### Détection d’une version
|
||||
|
||||
```c
|
||||
gboolean tool_catalog_detect_version(
|
||||
const ToolRegistry *tool_registry,
|
||||
const char *identifier,
|
||||
GCancellable *cancellable,
|
||||
char **out_version,
|
||||
GError **error
|
||||
);
|
||||
```
|
||||
|
||||
`out_version` reçoit une chaîne nouvellement allouée, à libérer avec `g_free()`.
|
||||
|
||||
La fonction ne doit pas appeler `tool_registry_set_version()`.
|
||||
|
||||
## Règles d’enregistrement
|
||||
|
||||
`tool_catalog_register_defaults()` doit vérifier avant toute insertion qu’aucun identifiant du catalogue n’existe déjà dans le registre.
|
||||
|
||||
En cas de doublon :
|
||||
|
||||
- aucune entrée ne doit être ajoutée ;
|
||||
- la fonction retourne `FALSE` ;
|
||||
- l’erreur vaut `TOOL_CATALOG_ERROR_REGISTRATION`.
|
||||
|
||||
Cette prévalidation évite une insertion partielle, car `ToolRegistry` ne fournit pas de suppression.
|
||||
|
||||
## Validation de la détection
|
||||
|
||||
La fonction doit refuser :
|
||||
|
||||
- un registre `NULL` ;
|
||||
- un identifiant `NULL` ou vide ;
|
||||
- un `out_version` égal à `NULL` ;
|
||||
- un `out_version` pointant déjà vers une chaîne ;
|
||||
- un `GError` déjà initialisé.
|
||||
|
||||
Au début d’un appel valide :
|
||||
|
||||
```c
|
||||
*out_version = NULL;
|
||||
```
|
||||
|
||||
Cas d’erreur :
|
||||
|
||||
```text
|
||||
entrée absente du catalogue → TOOL_CATALOG_ERROR_ENTRY_NOT_FOUND
|
||||
outil absent du registre → TOOL_CATALOG_ERROR_TOOL_NOT_REGISTERED
|
||||
état UNKNOWN → TOOL_CATALOG_ERROR_TOOL_NOT_CHECKED
|
||||
état MISSING → TOOL_CATALOG_ERROR_TOOL_MISSING
|
||||
AVAILABLE sans chemin résolu → TOOL_CATALOG_ERROR_INVALID_TOOL_STATE
|
||||
```
|
||||
|
||||
## Exécution de la commande de version
|
||||
|
||||
Utiliser exclusivement :
|
||||
|
||||
```c
|
||||
tool_process_run()
|
||||
```
|
||||
|
||||
avec :
|
||||
|
||||
```text
|
||||
executable_path = chemin résolu du ToolRegistry
|
||||
arguments = arguments statiques du ToolCatalogEntry
|
||||
working_directory = NULL
|
||||
cancellable = paramètre reçu
|
||||
```
|
||||
|
||||
Interdictions :
|
||||
|
||||
- `/bin/sh -c` ;
|
||||
- `system()` ;
|
||||
- `popen()` ;
|
||||
- concaténation d’une ligne de commande ;
|
||||
- redirections interprétées ;
|
||||
- ajout ou transformation des arguments.
|
||||
|
||||
## Annulation
|
||||
|
||||
Si `ToolProcess` retourne `TOOL_PROCESS_ERROR_CANCELLED`, la fonction doit produire :
|
||||
|
||||
```text
|
||||
G_IO_ERROR
|
||||
G_IO_ERROR_CANCELLED
|
||||
```
|
||||
|
||||
Aucune version partielle ne doit être retournée.
|
||||
|
||||
## Code de sortie
|
||||
|
||||
Pour une commande de version, un code de sortie non nul est un échec :
|
||||
|
||||
```text
|
||||
TOOL_CATALOG_ERROR_VERSION_COMMAND
|
||||
```
|
||||
|
||||
Une terminaison par signal produit la même catégorie d’erreur.
|
||||
|
||||
La sortie ne doit pas être interprétée lorsque le processus n’a pas terminé normalement avec le code zéro.
|
||||
|
||||
## Sélection de la sortie
|
||||
|
||||
Règle générique :
|
||||
|
||||
1. prendre la première ligne non vide de stdout ;
|
||||
2. si stdout ne contient rien d’exploitable, essayer stderr ;
|
||||
3. si aucun flux ne contient de texte exploitable, échouer.
|
||||
|
||||
Cette règle évite une logique spéciale par outil pendant ce premier ticket.
|
||||
|
||||
## Normalisation
|
||||
|
||||
Le catalogue ne doit pas extraire seulement un numéro sémantique.
|
||||
|
||||
La version conservée est la première ligne descriptive exploitable, par exemple :
|
||||
|
||||
```text
|
||||
DiG 9.20.8
|
||||
curl 8.14.1 (x86_64-pc-linux-gnu)
|
||||
OpenSSL 3.5.1 1 Jul 2025
|
||||
```
|
||||
|
||||
Algorithme :
|
||||
|
||||
1. obtenir les octets du flux ;
|
||||
2. inspecter au maximum 4096 octets par flux ;
|
||||
3. convertir les octets invalides avec `g_utf8_make_valid()` ;
|
||||
4. séparer les lignes ;
|
||||
5. ignorer les lignes vides ;
|
||||
6. choisir la première ligne non vide ;
|
||||
7. retirer espaces et tabulations en début et fin ;
|
||||
8. retirer `\r` et `\n` ;
|
||||
9. refuser un résultat vide ;
|
||||
10. retourner une nouvelle chaîne.
|
||||
|
||||
## Propriété des données
|
||||
|
||||
Les entrées et chaînes du catalogue sont empruntées et statiques.
|
||||
|
||||
La chaîne placée dans `*out_version` appartient à l’appelant et doit être libérée avec `g_free()`.
|
||||
|
||||
`ToolCatalog` ne devient jamais propriétaire du registre.
|
||||
|
||||
## Contraintes de thread
|
||||
|
||||
Le catalogue statique est immuable.
|
||||
|
||||
`tool_catalog_detect_version()` :
|
||||
|
||||
- ne modifie pas le catalogue ;
|
||||
- ne modifie pas le registre ;
|
||||
- copie le chemin nécessaire avant l’exécution ;
|
||||
- ne conserve aucun pointeur vers `ToolInfo` après la préparation.
|
||||
|
||||
Le stockage avec `tool_registry_set_version()` reste la responsabilité du propriétaire du registre, idéalement dans le thread principal.
|
||||
|
||||
## Tests unitaires obligatoires
|
||||
|
||||
Les tests utilisent uniquement de faux exécutables temporaires.
|
||||
|
||||
Ils ne doivent pas dépendre des outils réellement installés.
|
||||
|
||||
### Tests du catalogue
|
||||
|
||||
1. nombre exact d’entrées ;
|
||||
2. contenu exact des cinq entrées ;
|
||||
3. index invalide ;
|
||||
4. recherche par identifiant ;
|
||||
5. identifiant inconnu ;
|
||||
6. unicité des identifiants ;
|
||||
7. arguments de version ;
|
||||
8. enregistrement complet dans un registre vide ;
|
||||
9. état initial `UNKNOWN` ;
|
||||
10. prévalidation d’un doublon sans insertion partielle.
|
||||
|
||||
### Tests de détection
|
||||
|
||||
11. arguments invalides ;
|
||||
12. entrée inconnue ;
|
||||
13. outil non enregistré ;
|
||||
14. outil non vérifié ;
|
||||
15. outil absent ;
|
||||
16. version sur stdout ;
|
||||
17. version sur stderr ;
|
||||
18. première ligne non vide ;
|
||||
19. normalisation des espaces et fins de ligne ;
|
||||
20. sortie non UTF-8 ;
|
||||
21. sortie vide ;
|
||||
22. code de sortie non nul ;
|
||||
23. terminaison par signal ;
|
||||
24. annulation ;
|
||||
25. indépendance du registre ;
|
||||
26. plusieurs détections successives.
|
||||
|
||||
## Noms de tests suggérés
|
||||
|
||||
```text
|
||||
/tool_catalog/entries
|
||||
/tool_catalog/invalid_index
|
||||
/tool_catalog/find
|
||||
/tool_catalog/unique_entries
|
||||
/tool_catalog/register_defaults
|
||||
/tool_catalog/register_duplicate
|
||||
/tool_catalog/detect_invalid_arguments
|
||||
/tool_catalog/detect_unknown_entry
|
||||
/tool_catalog/detect_unregistered_tool
|
||||
/tool_catalog/detect_unchecked_tool
|
||||
/tool_catalog/detect_missing_tool
|
||||
/tool_catalog/version_stdout
|
||||
/tool_catalog/version_stderr
|
||||
/tool_catalog/version_first_nonempty_line
|
||||
/tool_catalog/version_normalization
|
||||
/tool_catalog/version_non_utf8
|
||||
/tool_catalog/version_empty
|
||||
/tool_catalog/version_nonzero_exit
|
||||
/tool_catalog/version_signaled
|
||||
/tool_catalog/version_cancelled
|
||||
/tool_catalog/version_registry_independence
|
||||
/tool_catalog/version_successive_runs
|
||||
```
|
||||
|
||||
## Makefile
|
||||
|
||||
```make
|
||||
TEST_TOOL_CATALOG := tests/test_tool_catalog
|
||||
```
|
||||
|
||||
```make
|
||||
$(TEST_TOOL_CATALOG): \
|
||||
tests/test_tool_catalog.c \
|
||||
src/core/tool_catalog.c \
|
||||
src/core/tool_registry.c \
|
||||
src/core/tool_process.c
|
||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
||||
```
|
||||
|
||||
Ajouter le binaire à `make test` et `make clean`.
|
||||
|
||||
## Vérifications
|
||||
|
||||
```bash
|
||||
make clean
|
||||
make
|
||||
make tests/test_tool_catalog
|
||||
./tests/test_tool_catalog
|
||||
make test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
## Vérification mémoire
|
||||
|
||||
```bash
|
||||
G_DEBUG=gc-friendly \
|
||||
G_SLICE=always-malloc \
|
||||
valgrind \
|
||||
--leak-check=full \
|
||||
--show-leak-kinds=all \
|
||||
./tests/test_tool_catalog
|
||||
```
|
||||
|
||||
## Critères d’acceptation
|
||||
|
||||
Le ticket est validé lorsque :
|
||||
|
||||
- le catalogue contient exactement les cinq outils prévus ;
|
||||
- toutes les entrées sont immuables ;
|
||||
- tous les identifiants sont uniques ;
|
||||
- le catalogue s’enregistre dans un registre vide ;
|
||||
- un doublon empêche toute insertion partielle ;
|
||||
- la détection utilise `ToolProcess` ;
|
||||
- aucun shell n’est utilisé ;
|
||||
- stdout et stderr sont pris en charge ;
|
||||
- la première ligne non vide est normalisée ;
|
||||
- une sortie invalide devient un UTF-8 valide ;
|
||||
- l’inspection est limitée à 4096 octets par flux ;
|
||||
- un code non nul ou un signal est refusé ;
|
||||
- l’annulation produit `G_IO_ERROR_CANCELLED` ;
|
||||
- la fonction ne modifie pas directement le registre ;
|
||||
- les versions successives sont indépendantes ;
|
||||
- les tests ne dépendent d’aucun outil installé ;
|
||||
- tous les tests passent ;
|
||||
- aucune fuite mémoire n’est détectée ;
|
||||
- le test est intégré au `Makefile`.
|
||||
|
||||
## Démonstration finale
|
||||
|
||||
Après validation :
|
||||
|
||||
1. créer un `ToolRegistry` ;
|
||||
2. appeler `tool_catalog_register_defaults()` ;
|
||||
3. appeler `tool_registry_refresh()` ;
|
||||
4. afficher les outils présents et absents ;
|
||||
5. détecter la version d’un outil disponible ;
|
||||
6. stocker explicitement la version avec `tool_registry_set_version()`.
|
||||
|
||||
Cette démonstration restera hors de l’interface GTK principale.
|
||||
|
||||
## Suite prévue
|
||||
|
||||
```text
|
||||
#040 — Initialisation des dépendances et analyse asynchrone au démarrage
|
||||
```
|
||||
|
||||
Ce ticket devra créer le registre global de l’application, enregistrer le catalogue, rafraîchir les disponibilités hors du thread GTK, détecter les versions et publier une tâche dans `TaskManager`.
|
||||
257
include/core/tool_catalog.h
Normal file
257
include/core/tool_catalog.h
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
/******************************************************************************
|
||||
* @file tool_catalog.h
|
||||
* @brief Catalogue statique des outils externes connus.
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef LABFY_INVESTIGATION_TOOL_CATALOG_H
|
||||
#define LABFY_INVESTIGATION_TOOL_CATALOG_H
|
||||
|
||||
#include "core/tool_registry.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
/**
|
||||
* @brief Erreurs produites par ToolCatalog.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
/**
|
||||
* Un argument transmis au module est invalide.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_INVALID_ARGUMENT,
|
||||
|
||||
/**
|
||||
* L'identifiant demandé n'existe pas dans le catalogue.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_ENTRY_NOT_FOUND,
|
||||
|
||||
/**
|
||||
* Le catalogue n'a pas pu être enregistré dans le registre.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_REGISTRATION,
|
||||
|
||||
/**
|
||||
* L'outil existe dans le catalogue, mais pas dans le registre.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_TOOL_NOT_REGISTERED,
|
||||
|
||||
/**
|
||||
* La disponibilité de l'outil n'a pas encore été vérifiée.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_TOOL_NOT_CHECKED,
|
||||
|
||||
/**
|
||||
* L'outil a été vérifié, mais son exécutable est absent.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_TOOL_MISSING,
|
||||
|
||||
/**
|
||||
* L'outil est disponible, mais son état interne est incohérent.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_INVALID_TOOL_STATE,
|
||||
|
||||
/**
|
||||
* ToolProcess n'a pas pu exécuter ou interroger l'outil.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_PROCESS,
|
||||
|
||||
/**
|
||||
* La commande de version s'est terminée avec un code non nul
|
||||
* ou à la suite d'un signal.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_VERSION_COMMAND,
|
||||
|
||||
/**
|
||||
* La commande n'a produit aucune version exploitable.
|
||||
*/
|
||||
TOOL_CATALOG_ERROR_VERSION_OUTPUT
|
||||
} ToolCatalogError;
|
||||
|
||||
/**
|
||||
* @brief Domaine d'erreur du catalogue d'outils.
|
||||
*/
|
||||
#define TOOL_CATALOG_ERROR \
|
||||
tool_catalog_error_quark()
|
||||
|
||||
/**
|
||||
* @brief Entrée opaque et immuable du catalogue.
|
||||
*
|
||||
* Les entrées sont conservées dans une table statique interne.
|
||||
* Elles ne doivent jamais être libérées par l'appelant.
|
||||
*/
|
||||
typedef struct ToolCatalogEntry ToolCatalogEntry;
|
||||
|
||||
/**
|
||||
* @brief Retourne le domaine d'erreur du catalogue.
|
||||
*
|
||||
* @return Quark GLib du domaine d'erreur.
|
||||
*/
|
||||
GQuark tool_catalog_error_quark(void);
|
||||
|
||||
/**
|
||||
* @brief Retourne le nombre d'outils connus du catalogue.
|
||||
*
|
||||
* @return Nombre d'entrées statiques.
|
||||
*/
|
||||
gsize tool_catalog_get_count(void);
|
||||
|
||||
/**
|
||||
* @brief Retourne une entrée à partir de son index.
|
||||
*
|
||||
* Le pointeur retourné est emprunté et statique.
|
||||
*
|
||||
* @param index Index de l'entrée.
|
||||
*
|
||||
* @return Entrée correspondante, ou NULL si l'index est invalide.
|
||||
*/
|
||||
const ToolCatalogEntry *tool_catalog_get_entry(
|
||||
gsize index
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Recherche une entrée par son identifiant interne.
|
||||
*
|
||||
* Le pointeur retourné est emprunté et statique.
|
||||
*
|
||||
* @param identifier Identifiant recherché.
|
||||
*
|
||||
* @return Entrée correspondante, ou NULL si elle n'existe pas.
|
||||
*/
|
||||
const ToolCatalogEntry *tool_catalog_find(
|
||||
const char *identifier
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne l'identifiant interne d'une entrée.
|
||||
*
|
||||
* La chaîne retournée est empruntée et statique.
|
||||
*
|
||||
* @param entry Entrée consultée.
|
||||
*
|
||||
* @return Identifiant de l'outil, ou NULL.
|
||||
*/
|
||||
const char *tool_catalog_entry_get_identifier(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le nom affiché d'une entrée.
|
||||
*
|
||||
* La chaîne retournée est empruntée et statique.
|
||||
*
|
||||
* @param entry Entrée consultée.
|
||||
*
|
||||
* @return Nom affiché, ou NULL.
|
||||
*/
|
||||
const char *tool_catalog_entry_get_display_name(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le nom de l'exécutable recherché dans le PATH.
|
||||
*
|
||||
* La chaîne retournée est empruntée et statique.
|
||||
*
|
||||
* @param entry Entrée consultée.
|
||||
*
|
||||
* @return Nom de l'exécutable, ou NULL.
|
||||
*/
|
||||
const char *tool_catalog_entry_get_executable_name(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne l'importance de la dépendance.
|
||||
*
|
||||
* @param entry Entrée consultée.
|
||||
*
|
||||
* @return Importance de l'outil.
|
||||
*/
|
||||
ToolRequirement tool_catalog_entry_get_requirement(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne le nombre d'arguments de la commande de version.
|
||||
*
|
||||
* Le chemin de l'exécutable n'est pas compté.
|
||||
*
|
||||
* @param entry Entrée consultée.
|
||||
*
|
||||
* @return Nombre d'arguments.
|
||||
*/
|
||||
gsize tool_catalog_entry_get_version_argument_count(
|
||||
const ToolCatalogEntry *entry
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Retourne un argument de la commande de version.
|
||||
*
|
||||
* La chaîne retournée est empruntée et statique.
|
||||
*
|
||||
* @param entry Entrée consultée.
|
||||
* @param index Index de l'argument.
|
||||
*
|
||||
* @return Argument correspondant, ou NULL si l'index est invalide.
|
||||
*/
|
||||
const char *tool_catalog_entry_get_version_argument(
|
||||
const ToolCatalogEntry *entry,
|
||||
gsize index
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Enregistre toutes les entrées du catalogue dans un registre.
|
||||
*
|
||||
* Avant toute insertion, la fonction vérifie qu'aucun identifiant du
|
||||
* catalogue n'existe déjà dans le registre.
|
||||
*
|
||||
* En cas de doublon, aucune entrée n'est ajoutée.
|
||||
*
|
||||
* @param tool_registry Registre cible.
|
||||
* @param error Emplacement facultatif pour l'erreur.
|
||||
*
|
||||
* @return TRUE si toutes les entrées ont été enregistrées.
|
||||
*/
|
||||
gboolean tool_catalog_register_defaults(
|
||||
ToolRegistry *tool_registry,
|
||||
GError **error
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Détecte la version d'un outil disponible.
|
||||
*
|
||||
* La fonction :
|
||||
*
|
||||
* - vérifie que l'outil existe dans le catalogue et le registre ;
|
||||
* - vérifie que son exécutable est disponible ;
|
||||
* - exécute sa commande de version avec ToolProcess ;
|
||||
* - sélectionne la première ligne non vide de stdout ou stderr ;
|
||||
* - retourne une chaîne UTF-8 normalisée.
|
||||
*
|
||||
* La fonction ne modifie pas la version conservée dans ToolRegistry.
|
||||
*
|
||||
* En cas d'annulation, l'erreur retournée appartient au domaine
|
||||
* G_IO_ERROR avec le code G_IO_ERROR_CANCELLED.
|
||||
*
|
||||
* @param tool_registry Registre contenant l'outil détecté.
|
||||
* @param identifier Identifiant de l'entrée du catalogue.
|
||||
* @param cancellable Objet d'annulation facultatif.
|
||||
* @param out_version Emplacement recevant une nouvelle chaîne.
|
||||
* @param error Emplacement facultatif pour l'erreur.
|
||||
*
|
||||
* @return TRUE si une version exploitable a été détectée.
|
||||
*/
|
||||
gboolean tool_catalog_detect_version(
|
||||
const ToolRegistry *tool_registry,
|
||||
const char *identifier,
|
||||
GCancellable *cancellable,
|
||||
char **out_version,
|
||||
GError **error
|
||||
);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
Binary file not shown.
872
src/core/tool_catalog.c
Normal file
872
src/core/tool_catalog.c
Normal file
|
|
@ -0,0 +1,872 @@
|
|||
/******************************************************************************
|
||||
* @file tool_catalog.c
|
||||
* @brief Catalogue statique des outils externes connus.
|
||||
******************************************************************************/
|
||||
|
||||
#include "core/tool_catalog.h"
|
||||
#include "core/tool_process.h"
|
||||
|
||||
#define TOOL_CATALOG_VERSION_OUTPUT_LIMIT 4096
|
||||
|
||||
/**
|
||||
* @struct ToolCatalogEntry
|
||||
* @brief Description statique d'un outil externe connu.
|
||||
*/
|
||||
struct ToolCatalogEntry
|
||||
{
|
||||
const char *identifier;
|
||||
const char *display_name;
|
||||
const char *executable_name;
|
||||
|
||||
ToolRequirement requirement;
|
||||
|
||||
const char *const *version_arguments;
|
||||
gsize version_argument_count;
|
||||
};
|
||||
|
||||
/*
|
||||
* Arguments de détection des versions.
|
||||
*
|
||||
* Chaque tableau est terminé par NULL afin de pouvoir être transmis
|
||||
* directement à ToolProcess.
|
||||
*/
|
||||
static const char *const tool_catalog_dig_version_arguments[] =
|
||||
{
|
||||
"-v",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const tool_catalog_host_version_arguments[] =
|
||||
{
|
||||
"-V",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const tool_catalog_whois_version_arguments[] =
|
||||
{
|
||||
"--version",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const tool_catalog_curl_version_arguments[] =
|
||||
{
|
||||
"--version",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *const tool_catalog_openssl_version_arguments[] =
|
||||
{
|
||||
"version",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Catalogue statique initial.
|
||||
*/
|
||||
static const ToolCatalogEntry tool_catalog_entries[] =
|
||||
{
|
||||
{
|
||||
.identifier = "dns.dig",
|
||||
.display_name = "dig",
|
||||
.executable_name = "dig",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments =
|
||||
tool_catalog_dig_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "dns.host",
|
||||
.display_name = "host",
|
||||
.executable_name = "host",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments =
|
||||
tool_catalog_host_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "network.whois",
|
||||
.display_name = "whois",
|
||||
.executable_name = "whois",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments =
|
||||
tool_catalog_whois_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "http.curl",
|
||||
.display_name = "curl",
|
||||
.executable_name = "curl",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments =
|
||||
tool_catalog_curl_version_arguments,
|
||||
.version_argument_count = 1
|
||||
},
|
||||
{
|
||||
.identifier = "tls.openssl",
|
||||
.display_name = "OpenSSL",
|
||||
.executable_name = "openssl",
|
||||
.requirement = TOOL_REQUIREMENT_OPTIONAL,
|
||||
.version_arguments =
|
||||
tool_catalog_openssl_version_arguments,
|
||||
.version_argument_count = 1
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Vérifie qu'une chaîne est définie et non vide.
|
||||
*/
|
||||
static gboolean tool_catalog_string_is_valid(
|
||||
const char *text
|
||||
)
|
||||
{
|
||||
return text != NULL &&
|
||||
text[0] != '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Encapsule une erreur provenant d'un autre module.
|
||||
*/
|
||||
static void tool_catalog_set_wrapped_error(
|
||||
GError **error,
|
||||
ToolCatalogError error_code,
|
||||
const char *context_message,
|
||||
const GError *cause
|
||||
)
|
||||
{
|
||||
if (cause == NULL ||
|
||||
cause->message == NULL)
|
||||
{
|
||||
g_set_error_literal(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
error_code,
|
||||
context_message
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
error_code,
|
||||
"%s : %s",
|
||||
context_message,
|
||||
cause->message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extrait la première ligne non vide d'une sortie.
|
||||
*
|
||||
* Au maximum TOOL_CATALOG_VERSION_OUTPUT_LIMIT octets sont inspectés.
|
||||
* Les séquences UTF-8 invalides sont remplacées.
|
||||
*
|
||||
* @param bytes Sortie brute du processus.
|
||||
*
|
||||
* @return Nouvelle chaîne normalisée, ou NULL.
|
||||
*/
|
||||
static char *tool_catalog_extract_first_nonempty_line(
|
||||
GBytes *bytes
|
||||
)
|
||||
{
|
||||
gconstpointer bytes_data = NULL;
|
||||
|
||||
gsize bytes_size = 0;
|
||||
gsize inspected_size = 0;
|
||||
gsize line_index = 0;
|
||||
|
||||
char *utf8_text = NULL;
|
||||
char **lines = NULL;
|
||||
char *version = NULL;
|
||||
char *trimmed_line = NULL;
|
||||
|
||||
if (bytes == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bytes_data = g_bytes_get_data(
|
||||
bytes,
|
||||
&bytes_size
|
||||
);
|
||||
|
||||
if (bytes_data == NULL ||
|
||||
bytes_size == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
inspected_size = MIN(
|
||||
bytes_size,
|
||||
(gsize) TOOL_CATALOG_VERSION_OUTPUT_LIMIT
|
||||
);
|
||||
|
||||
utf8_text = g_utf8_make_valid(
|
||||
bytes_data,
|
||||
(gssize) inspected_size
|
||||
);
|
||||
|
||||
if (utf8_text == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lines = g_strsplit(
|
||||
utf8_text,
|
||||
"\n",
|
||||
-1
|
||||
);
|
||||
|
||||
if (lines == NULL)
|
||||
{
|
||||
g_free(
|
||||
utf8_text
|
||||
);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (line_index = 0;
|
||||
lines[line_index] != NULL;
|
||||
line_index++)
|
||||
{
|
||||
/*
|
||||
* g_strstrip() retire notamment :
|
||||
*
|
||||
* - espaces ;
|
||||
* - tabulations ;
|
||||
* - retours chariot ;
|
||||
* - fins de ligne.
|
||||
*/
|
||||
trimmed_line = g_strstrip(
|
||||
lines[line_index]
|
||||
);
|
||||
|
||||
if (trimmed_line[0] == '\0')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
version = g_strdup(
|
||||
trimmed_line
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
g_strfreev(
|
||||
lines
|
||||
);
|
||||
|
||||
g_free(
|
||||
utf8_text
|
||||
);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
GQuark tool_catalog_error_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string(
|
||||
"labfy-investigation-tool-catalog-error"
|
||||
);
|
||||
}
|
||||
|
||||
gsize tool_catalog_get_count(void)
|
||||
{
|
||||
return G_N_ELEMENTS(
|
||||
tool_catalog_entries
|
||||
);
|
||||
}
|
||||
|
||||
const ToolCatalogEntry *tool_catalog_get_entry(
|
||||
gsize index
|
||||
)
|
||||
{
|
||||
if (index >= tool_catalog_get_count())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &tool_catalog_entries[index];
|
||||
}
|
||||
|
||||
const ToolCatalogEntry *tool_catalog_find(
|
||||
const char *identifier
|
||||
)
|
||||
{
|
||||
gsize entry_index = 0;
|
||||
|
||||
if (!tool_catalog_string_is_valid(
|
||||
identifier
|
||||
))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (entry_index = 0;
|
||||
entry_index < tool_catalog_get_count();
|
||||
entry_index++)
|
||||
{
|
||||
if (g_strcmp0(
|
||||
tool_catalog_entries[entry_index].identifier,
|
||||
identifier
|
||||
) == 0)
|
||||
{
|
||||
return &tool_catalog_entries[entry_index];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *tool_catalog_entry_get_identifier(
|
||||
const ToolCatalogEntry *entry
|
||||
)
|
||||
{
|
||||
if (entry == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return entry->identifier;
|
||||
}
|
||||
|
||||
const char *tool_catalog_entry_get_display_name(
|
||||
const ToolCatalogEntry *entry
|
||||
)
|
||||
{
|
||||
if (entry == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return entry->display_name;
|
||||
}
|
||||
|
||||
const char *tool_catalog_entry_get_executable_name(
|
||||
const ToolCatalogEntry *entry
|
||||
)
|
||||
{
|
||||
if (entry == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return entry->executable_name;
|
||||
}
|
||||
|
||||
ToolRequirement tool_catalog_entry_get_requirement(
|
||||
const ToolCatalogEntry *entry
|
||||
)
|
||||
{
|
||||
if (entry == NULL)
|
||||
{
|
||||
/*
|
||||
* Valeur neutre et non bloquante pour un appel invalide.
|
||||
*/
|
||||
return TOOL_REQUIREMENT_OPTIONAL;
|
||||
}
|
||||
|
||||
return entry->requirement;
|
||||
}
|
||||
|
||||
gsize tool_catalog_entry_get_version_argument_count(
|
||||
const ToolCatalogEntry *entry
|
||||
)
|
||||
{
|
||||
if (entry == NULL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return entry->version_argument_count;
|
||||
}
|
||||
|
||||
const char *tool_catalog_entry_get_version_argument(
|
||||
const ToolCatalogEntry *entry,
|
||||
gsize index
|
||||
)
|
||||
{
|
||||
if (entry == NULL ||
|
||||
entry->version_arguments == NULL ||
|
||||
index >= entry->version_argument_count)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return entry->version_arguments[index];
|
||||
}
|
||||
|
||||
gboolean tool_catalog_register_defaults(
|
||||
ToolRegistry *tool_registry,
|
||||
GError **error
|
||||
)
|
||||
{
|
||||
const ToolCatalogEntry *entry = NULL;
|
||||
|
||||
GError *registration_error = NULL;
|
||||
|
||||
gsize entry_index = 0;
|
||||
|
||||
gboolean registration_success = FALSE;
|
||||
|
||||
g_return_val_if_fail(
|
||||
error == NULL || *error == NULL,
|
||||
FALSE
|
||||
);
|
||||
|
||||
if (tool_registry == NULL)
|
||||
{
|
||||
g_set_error_literal(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_INVALID_ARGUMENT,
|
||||
"Le registre fourni au catalogue est invalide."
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prévalidation complète.
|
||||
*
|
||||
* Aucun outil ne doit être ajouté lorsqu'au moins un identifiant
|
||||
* existe déjà dans le registre.
|
||||
*/
|
||||
for (entry_index = 0;
|
||||
entry_index < tool_catalog_get_count();
|
||||
entry_index++)
|
||||
{
|
||||
entry = tool_catalog_get_entry(
|
||||
entry_index
|
||||
);
|
||||
|
||||
if (entry == NULL)
|
||||
{
|
||||
g_set_error_literal(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_REGISTRATION,
|
||||
"Le catalogue contient une entrée invalide."
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (tool_registry_find(
|
||||
tool_registry,
|
||||
entry->identifier
|
||||
) != NULL)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_REGISTRATION,
|
||||
"L'outil '%s' existe déjà dans le registre.",
|
||||
entry->identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Aucun doublon n'a été trouvé. Les entrées peuvent maintenant
|
||||
* être enregistrées.
|
||||
*/
|
||||
for (entry_index = 0;
|
||||
entry_index < tool_catalog_get_count();
|
||||
entry_index++)
|
||||
{
|
||||
entry = tool_catalog_get_entry(
|
||||
entry_index
|
||||
);
|
||||
|
||||
registration_success =
|
||||
tool_registry_register(
|
||||
tool_registry,
|
||||
entry->identifier,
|
||||
entry->display_name,
|
||||
entry->executable_name,
|
||||
entry->requirement,
|
||||
®istration_error
|
||||
);
|
||||
|
||||
if (!registration_success)
|
||||
{
|
||||
tool_catalog_set_wrapped_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR_REGISTRATION,
|
||||
"Une entrée du catalogue n'a pas pu être enregistrée",
|
||||
registration_error
|
||||
);
|
||||
|
||||
g_clear_error(
|
||||
®istration_error
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean tool_catalog_detect_version(
|
||||
const ToolRegistry *tool_registry,
|
||||
const char *identifier,
|
||||
GCancellable *cancellable,
|
||||
char **out_version,
|
||||
GError **error
|
||||
)
|
||||
{
|
||||
const ToolCatalogEntry *catalog_entry = NULL;
|
||||
const ToolInfo *tool_info = NULL;
|
||||
|
||||
ToolAvailability availability;
|
||||
|
||||
const char *resolved_path = NULL;
|
||||
|
||||
char *executable_path = NULL;
|
||||
char *detected_version = NULL;
|
||||
|
||||
ToolProcessResult *process_result = NULL;
|
||||
|
||||
GBytes *stdout_bytes = NULL;
|
||||
GBytes *stderr_bytes = NULL;
|
||||
|
||||
GError *process_error = NULL;
|
||||
|
||||
gboolean process_success = FALSE;
|
||||
|
||||
g_return_val_if_fail(
|
||||
error == NULL || *error == NULL,
|
||||
FALSE
|
||||
);
|
||||
|
||||
if (tool_registry == NULL ||
|
||||
!tool_catalog_string_is_valid(
|
||||
identifier
|
||||
) ||
|
||||
out_version == NULL ||
|
||||
*out_version != NULL)
|
||||
{
|
||||
g_set_error_literal(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_INVALID_ARGUMENT,
|
||||
"Les arguments de détection de version sont invalides."
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*out_version = NULL;
|
||||
|
||||
catalog_entry = tool_catalog_find(
|
||||
identifier
|
||||
);
|
||||
|
||||
if (catalog_entry == NULL)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_ENTRY_NOT_FOUND,
|
||||
"L'identifiant '%s' n'existe pas dans le catalogue.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
tool_info = tool_registry_find(
|
||||
tool_registry,
|
||||
identifier
|
||||
);
|
||||
|
||||
if (tool_info == NULL)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_TOOL_NOT_REGISTERED,
|
||||
"L'outil '%s' n'est pas enregistré dans le registre.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
availability = tool_info_get_availability(
|
||||
tool_info
|
||||
);
|
||||
|
||||
if (availability ==
|
||||
TOOL_AVAILABILITY_UNKNOWN)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_TOOL_NOT_CHECKED,
|
||||
"La disponibilité de l'outil '%s' n'a pas été vérifiée.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (availability ==
|
||||
TOOL_AVAILABILITY_MISSING)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_TOOL_MISSING,
|
||||
"L'outil '%s' est absent de la machine.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (availability !=
|
||||
TOOL_AVAILABILITY_AVAILABLE)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_INVALID_TOOL_STATE,
|
||||
"L'outil '%s' possède un état de disponibilité invalide.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
resolved_path = tool_info_get_resolved_path(
|
||||
tool_info
|
||||
);
|
||||
|
||||
if (!tool_catalog_string_is_valid(
|
||||
resolved_path
|
||||
))
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_INVALID_TOOL_STATE,
|
||||
"L'outil '%s' est disponible mais ne possède aucun chemin.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* La copie permet de ne plus dépendre du ToolInfo pendant
|
||||
* l'exécution du processus.
|
||||
*/
|
||||
executable_path = g_strdup(
|
||||
resolved_path
|
||||
);
|
||||
|
||||
process_success = tool_process_run(
|
||||
executable_path,
|
||||
catalog_entry->version_arguments,
|
||||
NULL,
|
||||
cancellable,
|
||||
&process_result,
|
||||
&process_error
|
||||
);
|
||||
|
||||
g_free(
|
||||
executable_path
|
||||
);
|
||||
|
||||
executable_path = NULL;
|
||||
|
||||
if (!process_success)
|
||||
{
|
||||
if (process_error != NULL &&
|
||||
g_error_matches(
|
||||
process_error,
|
||||
TOOL_PROCESS_ERROR,
|
||||
TOOL_PROCESS_ERROR_CANCELLED
|
||||
))
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
G_IO_ERROR,
|
||||
G_IO_ERROR_CANCELLED,
|
||||
"%s",
|
||||
process_error->message != NULL
|
||||
? process_error->message
|
||||
: "La détection de version a été annulée."
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
tool_catalog_set_wrapped_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR_PROCESS,
|
||||
"La commande de version n'a pas pu être exécutée",
|
||||
process_error
|
||||
);
|
||||
}
|
||||
|
||||
g_clear_error(
|
||||
&process_error
|
||||
);
|
||||
|
||||
tool_process_result_free(
|
||||
process_result
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (process_result == NULL)
|
||||
{
|
||||
g_set_error_literal(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_PROCESS,
|
||||
"La commande de version n'a produit aucun résultat."
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Une commande de version doit se terminer normalement avec
|
||||
* le code zéro.
|
||||
*/
|
||||
if (!tool_process_result_exited_normally(
|
||||
process_result
|
||||
))
|
||||
{
|
||||
if (tool_process_result_was_signaled(
|
||||
process_result
|
||||
))
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_VERSION_COMMAND,
|
||||
"La commande de version de '%s' a été terminée "
|
||||
"par le signal %d.",
|
||||
identifier,
|
||||
tool_process_result_get_termination_signal(
|
||||
process_result
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_VERSION_COMMAND,
|
||||
"La commande de version de '%s' ne s'est pas "
|
||||
"terminée normalement.",
|
||||
identifier
|
||||
);
|
||||
}
|
||||
|
||||
tool_process_result_free(
|
||||
process_result
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (tool_process_result_get_exit_status(
|
||||
process_result
|
||||
) != 0)
|
||||
{
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_VERSION_COMMAND,
|
||||
"La commande de version de '%s' a retourné le code %d.",
|
||||
identifier,
|
||||
tool_process_result_get_exit_status(
|
||||
process_result
|
||||
)
|
||||
);
|
||||
|
||||
tool_process_result_free(
|
||||
process_result
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
stdout_bytes =
|
||||
tool_process_result_ref_stdout(
|
||||
process_result
|
||||
);
|
||||
|
||||
detected_version =
|
||||
tool_catalog_extract_first_nonempty_line(
|
||||
stdout_bytes
|
||||
);
|
||||
|
||||
/*
|
||||
* Certains outils écrivent leur version sur stderr.
|
||||
*/
|
||||
if (detected_version == NULL)
|
||||
{
|
||||
stderr_bytes =
|
||||
tool_process_result_ref_stderr(
|
||||
process_result
|
||||
);
|
||||
|
||||
detected_version =
|
||||
tool_catalog_extract_first_nonempty_line(
|
||||
stderr_bytes
|
||||
);
|
||||
}
|
||||
|
||||
g_clear_pointer(
|
||||
&stdout_bytes,
|
||||
g_bytes_unref
|
||||
);
|
||||
|
||||
g_clear_pointer(
|
||||
&stderr_bytes,
|
||||
g_bytes_unref
|
||||
);
|
||||
|
||||
tool_process_result_free(
|
||||
process_result
|
||||
);
|
||||
|
||||
process_result = NULL;
|
||||
|
||||
if (detected_version == NULL ||
|
||||
detected_version[0] == '\0')
|
||||
{
|
||||
g_clear_pointer(
|
||||
&detected_version,
|
||||
g_free
|
||||
);
|
||||
|
||||
g_set_error(
|
||||
error,
|
||||
TOOL_CATALOG_ERROR,
|
||||
TOOL_CATALOG_ERROR_VERSION_OUTPUT,
|
||||
"L'outil '%s' n'a produit aucune version exploitable.",
|
||||
identifier
|
||||
);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
*out_version = detected_version;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
BIN
tests/test_tool_catalog
Executable file
BIN
tests/test_tool_catalog
Executable file
Binary file not shown.
2017
tests/test_tool_catalog.c
Normal file
2017
tests/test_tool_catalog.c
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue