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_REGISTRY := tests/test_tool_registry
|
||||||
TEST_TOOL_PROCESS := tests/test_tool_process
|
TEST_TOOL_PROCESS := tests/test_tool_process
|
||||||
TEST_TOOL_TASK := tests/test_tool_task
|
TEST_TOOL_TASK := tests/test_tool_task
|
||||||
|
TEST_TOOL_CATALOG := tests/test_tool_catalog
|
||||||
|
|
||||||
all: $(TARGET)
|
all: $(TARGET)
|
||||||
|
|
||||||
|
|
@ -171,6 +172,13 @@ $(TEST_TOOL_TASK): \
|
||||||
src/core/background_task.c
|
src/core/background_task.c
|
||||||
$(CC) $(TEST_CFLAGS) $^ -o $@ $(TEST_LDFLAGS)
|
$(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: \
|
||||||
$(TEST_NODE) \
|
$(TEST_NODE) \
|
||||||
$(TEST_TREE_MODEL) \
|
$(TEST_TREE_MODEL) \
|
||||||
|
|
@ -187,7 +195,8 @@ test: \
|
||||||
$(TEST_TASK_MANAGER) \
|
$(TEST_TASK_MANAGER) \
|
||||||
$(TEST_TOOL_REGISTRY) \
|
$(TEST_TOOL_REGISTRY) \
|
||||||
$(TEST_TOOL_PROCESS) \
|
$(TEST_TOOL_PROCESS) \
|
||||||
$(TEST_TOOL_TASK)
|
$(TEST_TOOL_TASK) \
|
||||||
|
$(TEST_TOOL_CATALOG)
|
||||||
@echo "Exécution des tests..."
|
@echo "Exécution des tests..."
|
||||||
@./$(TEST_NODE)
|
@./$(TEST_NODE)
|
||||||
@./$(TEST_TREE_MODEL)
|
@./$(TEST_TREE_MODEL)
|
||||||
|
|
@ -205,6 +214,7 @@ test: \
|
||||||
@$(TEST_TOOL_REGISTRY)
|
@$(TEST_TOOL_REGISTRY)
|
||||||
@$(TEST_TOOL_PROCESS)
|
@$(TEST_TOOL_PROCESS)
|
||||||
@$(TEST_TOOL_TASK)
|
@$(TEST_TOOL_TASK)
|
||||||
|
@$(TEST_TOOL_CATALOG)
|
||||||
@echo "Tous les tests sont valides."
|
@echo "Tous les tests sont valides."
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
|
|
@ -230,6 +240,7 @@ clean:
|
||||||
$(TEST_TASK_MANAGER) \
|
$(TEST_TASK_MANAGER) \
|
||||||
$(TEST_TOOL_REGISTRY) \
|
$(TEST_TOOL_REGISTRY) \
|
||||||
$(TEST_TOOL_PROCESS) \
|
$(TEST_TOOL_PROCESS) \
|
||||||
$(TEST_TOOL_TASK)
|
$(TEST_TOOL_TASK) \
|
||||||
|
$(TEST_TOOL_CATALOG)
|
||||||
|
|
||||||
.PHONY: clean run test
|
.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