Bonjour,
Je viens de me rendre compte que lorsqu'un utilisateur répond à un mail de notification, cela crée systématiquement un nouveau ticket au lieu d'ajouter au suivi du ticket ouvert.
J'ai installé la Beta 3.2.62.
[Corrigé 3.2.62] Réponse à un mail de notification crée un nouveau ticket
Bonjour,
pouvez-vous transmettre une impression écran des paramètres de votre connecteur ?
Cdt
pouvez-vous transmettre une impression écran des paramètres de votre connecteur ?
Cdt
GestSup: 3.2.53 | Debian: 12 | Apache: 2.4.59 | MariaDB: 11.5.2 | PHP: 8.3 | https://doc.gestsup.fr/
Bonjour,
je ne reproduis pas ce problème de mon côté, pouvez-vous le reproduire sur un hébergement d'essai gratuit ?
Cdt
je ne reproduis pas ce problème de mon côté, pouvez-vous le reproduire sur un hébergement d'essai gratuit ?
Cdt
- Fichiers joints
-
- 2026-08-27 16_04_39-Greenshot.png (33.83 Kio) Vu 104 fois
GestSup: 3.2.53 | Debian: 12 | Apache: 2.4.59 | MariaDB: 11.5.2 | PHP: 8.3 | https://doc.gestsup.fr/
GestSup: 3.2.53 | Debian: 12 | Apache: 2.4.59 | MariaDB: 11.5.2 | PHP: 8.3 | https://doc.gestsup.fr/
Bonjour Flox,
Je complète ce sujet avec une analyse complète. Deux défauts distincts, dans la même construction (celle commentée "encoding fix #6068"), avec un correctif que j'ai appliqué et validé en production ce jour. L'environnement détaillé est en fin de message : ma page Système s'affiche mal derrière mon reverse proxy, j'ai donc tout listé à la main.
1. Symptômes
Deux comportements, que j'ai longtemps confondus de mon côté :
a) Le titre des tickets créés par mail contient un bloc d'en-tête brut :
(tronqué à 100 caractères par tincidents.title varchar(100))
b) Le titre perd tous ses caractères non-ASCII, et toute réponse à une notification crée un nouveau ticket au lieu d'alimenter l'existant :
Aucun des deux n'est systématique : le comportement dépend de la longueur de l'objet, donc du repliement de l'en-tête Subject. C'est ce qui rend le diagnostic difficile.
2. Le code concerné — core/imap_oauth.php
Défaut 1 — la capture de find() déborde sur tout l'en-tête
[^\"]* est gourmand, et une classe de caractères négative matche les retours à la ligne. Sur un en-tête Subject non replié, la capture avale donc tout le bloc d'en-tête jusqu'au prochain guillemet double — chez moi le boundary=" du Content-Type. Ce bloc est ensuite stocké tel quel comme titre du ticket.
Défaut 2 — double décodage RFC 2047
La variable $subject peut contenir deux représentations différentes :
- via find() : l'en-tête brut, encoded-words RFC 2047 inclus. iconv_mime_decode() est le bon traitement.
- via getSubject() : l'objet DÉJÀ décodé par Webklex. iconv_mime_decode() reçoit alors du texte 8 bits, illégal dans un en-tête RFC 2047, et avec ICONV_MIME_DECODE_CONTINUE_ON_ERROR il supprime silencieusement chaque octet non-ASCII au lieu d'échouer.
Le décodage étant appliqué inconditionnellement aux deux branches, la seconde est détruite.
3. Quelle branche est empruntée, et pourquoi ça change tout
Le motif de find() exige "Subject: " avec une espace littérale après les deux-points. Quand le client replie l'en-tête sur des lignes de continuation, il ne correspond plus :
Donc : objet court non replié -> défaut 1 (bloc d'en-tête dans le titre)
objet long replié -> défaut 2 (non-ASCII détruits)
Or l'objet d'une notification GestSup est long par construction. core/mail.php ligne 364 :
Chez moi cet objet fait 105 octets, au-delà de la limite de 78 caractères par ligne du RFC 5322. Le repliement est inévitable, et la réponse y ajoute encore "Re: ". Toute réponse à une notification emprunte donc la branche du défaut 2.
4. Conséquence sur le rattachement au ticket
Le motif exige le caractère ° (U+00B0, octets C2 B0), que le défaut 2 vient précisément de supprimer. Aucune correspondance, donc passage dans la branche "case create new ticket". Un défaut d'encodage produit une duplication de tickets.
5. Preuve côté base (tincidents.title, même colonne, même connexion)
La base n'est pas en cause : elle transporte C3 89 sans perte sur la ligne 8306.
6. Correctif proposé — deux modifications localisées
Défaut 1 : borner la capture à une seule ligne d'en-tête.
Défaut 2 : ne décoder que si des encoded-words sont réellement présents. Idempotent, sans effet sur la branche find().
7. Validation en production
Appliqué sur mon instance 3.2.62 le 01/09/2026, php -l OK.
Avant correctif :
Après correctif :
Objet propre, accents conservés, rattachement rétabli.
8. Périmètre
core/imap_basic.php ne contient pas ce double décodage (ligne 204 : htmlspecialchars directement). Les deux défauts ne concernent donc que le chemin Webklex, c'est-à-dire les modes d'authentification oauth_azure, login2 et oauth_google. Le mode login historique n'est pas affecté.
9. Deux suggestions annexes
a) Message de debug ambigu. La branche de rattachement teste deux conditions :
mais affiche "create new ticket" dans tous les cas d'échec. Distinguer "numéro de ticket introuvable dans l'objet" de "option Gérer les réponses aux mails désactivée" ferait gagner beaucoup de temps en diagnostic.
b) Risque de faux positif sur $c_reg. Le motif /n°(.*?):/i s'applique à tout mail entrant, y compris à un mail neuf. Un objet du type "Facture n°4521 : relance" sera rattaché au ticket 4521 s'il existe, ou insérera un thread orphelin s'il n'existe pas — le SELECT sur tincidents ne vérifie pas que la ligne existe. En français, "n°" est très courant en correspondance commerciale. Deux pistes : vérifier la présence d'un en-tête In-Reply-To ou References, ou ancrer le motif sur le libellé mail_object configuré plutôt que sur "n°" seul.
10. Environnement
Je n'ai pas pu déterminer à quelle version la construction #6068 a été introduite. Le correctif ci-dessus est en place chez moi, je reste disponible pour tester une version amont.
Cordialement,
Tom
Je complète ce sujet avec une analyse complète. Deux défauts distincts, dans la même construction (celle commentée "encoding fix #6068"), avec un correctif que j'ai appliqué et validé en production ce jour. L'environnement détaillé est en fin de message : ma page Système s'affiche mal derrière mon reverse proxy, j'ai donc tout listé à la main.
1. Symptômes
Deux comportements, que j'ai longtemps confondus de mon côté :
a) Le titre des tickets créés par mail contient un bloc d'en-tête brut :
Code : Tout sélectionner
TEST
Thread-Topic: TEST
Thread-Index: AQHdOh+HVnL2t5QAR0a0KMEYtYfOWQ==
Date: Tue, 1 Sep 2026 16:38:2
b) Le titre perd tous ses caractères non-ASCII, et toute réponse à une notification crée un nouveau ticket au lieu d'alimenter l'existant :
Code : Tout sélectionner
attendu : Re: Notification de déclaration pour le ticket n°8306 : TEST RÉPONSE
obtenu : Re: Notification de dclaration pour le ticket n8306 : TEST RPONSE
2. Le code concerné — core/imap_oauth.php
Code : Tout sélectionner
//get subject with encoding fix #6068
$header=$imap_mail->getHeader();
$subject=$header->find("/Subject: \"?([^\"]*)[\";\s]/");
if(!$subject) { $subject=$imap_mail->getSubject();}
if(!$subject){$subject=T_('(Sans objet)');}
#convert encoding
$subject = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
#sanitize subject
$subject=htmlspecialchars($subject, ENT_QUOTES, 'UTF-8');
[^\"]* est gourmand, et une classe de caractères négative matche les retours à la ligne. Sur un en-tête Subject non replié, la capture avale donc tout le bloc d'en-tête jusqu'au prochain guillemet double — chez moi le boundary=" du Content-Type. Ce bloc est ensuite stocké tel quel comme titre du ticket.
Défaut 2 — double décodage RFC 2047
La variable $subject peut contenir deux représentations différentes :
- via find() : l'en-tête brut, encoded-words RFC 2047 inclus. iconv_mime_decode() est le bon traitement.
- via getSubject() : l'objet DÉJÀ décodé par Webklex. iconv_mime_decode() reçoit alors du texte 8 bits, illégal dans un en-tête RFC 2047, et avec ICONV_MIME_DECODE_CONTINUE_ON_ERROR il supprime silencieusement chaque octet non-ASCII au lieu d'échouer.
Le décodage étant appliqué inconditionnellement aux deux branches, la seconde est détruite.
3. Quelle branche est empruntée, et pourquoi ça change tout
Le motif de find() exige "Subject: " avec une espace littérale après les deux-points. Quand le client replie l'en-tête sur des lignes de continuation, il ne correspond plus :
Code : Tout sélectionner
Subject:
=?utf-8?B?UmU6IE5vdGlmaWNhdGlvbiBkZSBkw6ljbGFyYXRpb24gcG91ciBsZSB0aWNr?=
=?utf-8?B?ZXQgbsKwODMwNyA6IFJlOiBOb3RpZmljYXRpb24gZGUgZGNsYXJhdGlvbiBw?=
=?utf-8?Q?our_le_ticket_n8306_:_TEST_RPONSE_2?=
objet long replié -> défaut 2 (non-ASCII détruits)
Or l'objet d'une notification GestSup est long par construction. core/mail.php ligne 364 :
Code : Tout sélectionner
$object=T_($robject['mail_object']).' '.T_('pour le ticket').' n°'.$db_id.' : '.htmlspecialchars_decode($globalrow['title'], ENT_QUOTES);
4. Conséquence sur le rattachement au ticket
Code : Tout sélectionner
$c_reg = "/n°(.*?):/i"; //regex to find ticket number
5. Preuve côté base (tincidents.title, même colonne, même connexion)
Code : Tout sélectionner
id 8306 objet court, non replié
title : TEST RÉPONSE 2
hex : 544553542052C389504F4E53452032
^^^^ C3 89 = É conservé
id 8307 objet long, replié
title : Re: Notification de dclaration pour le ticket n8306 : TEST RPONSE 2
hex : 52653A...2064636C61726174696F6E...206E38333036...
aucun C3 A9 (é), aucun C2 B0 (°)
6. Correctif proposé — deux modifications localisées
Défaut 1 : borner la capture à une seule ligne d'en-tête.
Code : Tout sélectionner
$subject=$header->find("/Subject: \"?([^\"\r\n]*)[\";\s]/");
Code : Tout sélectionner
#convert encoding - only when RFC 2047 encoded-words are actually present
if (preg_match('/=\?[^?]+\?[BQbq]\?/', $subject)) {
$subject = iconv_mime_decode($subject, ICONV_MIME_DECODE_CONTINUE_ON_ERROR, "UTF-8");
}
Appliqué sur mon instance 3.2.62 le 01/09/2026, php -l OK.
Avant correctif :
Code : Tout sélectionner
[mail 1] mail data : subject="TEST
Thread-Topic: TEST
Thread-Index: AQHdOh+HVnL2t5QAR0a0KMEYtYfOWQ==
Date: Tue, 1 Sep 2026 16:38:27 +0200
[...tout le bloc d'en-tête jusqu'au boundary="...]"
[mail 1] create new ticket
[mail 1] ticket created : 8317
[mail 1] mail data : subject="Re: Notification de dclaration pour le ticket n8317 : TESTThread-Topic:..."
[mail 1] create new ticket
[mail 1] ticket created : 8318
Code : Tout sélectionner
[mail 1] mail data : subject="TEST ENCOR ENCORE V2 OU 3 JE SAIS PLUS"
[mail 1] create new ticket
[mail 1] ticket created : 8320
[mail 1] mail data : subject="Re: Notification de déclaration pour le ticket n°8320 : TEST ENCOR ENCORE V2 OU 3 JE SAIS PLUS"
[mail 1] update existing ticket : 8320
8. Périmètre
core/imap_basic.php ne contient pas ce double décodage (ligne 204 : htmlspecialchars directement). Les deux défauts ne concernent donc que le chemin Webklex, c'est-à-dire les modes d'authentification oauth_azure, login2 et oauth_google. Le mode login historique n'est pas affecté.
9. Deux suggestions annexes
a) Message de debug ambigu. La branche de rattachement teste deux conditions :
Code : Tout sélectionner
if($find_ticket_number && $rparameters['imap_reply'])
b) Risque de faux positif sur $c_reg. Le motif /n°(.*?):/i s'applique à tout mail entrant, y compris à un mail neuf. Un objet du type "Facture n°4521 : relance" sera rattaché au ticket 4521 s'il existe, ou insérera un thread orphelin s'il n'existe pas — le SELECT sur tincidents ne vérifie pas que la ligne existe. En français, "n°" est très courant en correspondance commerciale. Deux pistes : vérifier la présence d'un en-tête In-Reply-To ou References, ou ancrer le motif sur le libellé mail_object configuré plutôt que sur "n°" seul.
10. Environnement
Code : Tout sélectionner
GestSup : 3.2.62 (beta), montée depuis 3.2.61 le 01/09/2026
core/imap_oauth.php : @Update 19/08/2026, @Version 3.2.62
Langue serveur : fr_FR
OS : Debian 12 (conteneur Docker)
Déploiement : Docker Compose, deux conteneurs (gestsup + mariadb),
reverse proxy en frontal
Serveur web : Apache 2.4, libapache2-mod-php, écoute sur 8081
PHP : 8.2.7 (mod_php)
Extensions PHP : common, curl, gd, imap, intl, ldap, mbstring, mysql, xml, zip
php.ini : max_execution_time=480, memory_limit=512M,
upload_max_filesize=8M, date.timezone=Europe/Paris,
expose_php=Off, default_charset=UTF-8
Base : MariaDB (image officielle), conteneur séparé
innodb_buffer_pool_size=4G, skip-name-resolve
Charsets : character_set_client = utf8mb3
character_set_connection = utf8mb3
character_set_database = utf8mb3
character_set_results = utf8mb3
character_set_server = utf8mb4
Table : tincidents DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci
tincidents.title varchar(100) NOT NULL
Connecteur IMAP : outlook.office365.com:993 (IMAP sécurisé)
Vérification SSL : activée
Dossier racine : INBOX
Date de création du ticket : date du mail
Authentification : XOAUTH2 Azure (Entra ID)
Gérer les réponses aux mails : activé
Multi boîte aux lettres par service : désactivé
Création automatique des utilisateurs : désactivée
Action post-traitement : passer en lu
Connecteur SMTP : smtp.office365.com:587 (TLS)
Vérification SSL : activée
Classe : isSMTP()
Authentification : XOAUTH2 Azure (Entra ID)
Appel mail2ticket : cron externe sur l'hôte
docker exec -d gestsup php /var/www/html/mail2ticket.php
toutes les minutes, 6h-20h, du lundi au vendredi
Clients expéditeurs: Microsoft Outlook / Exchange Online (Microsoft 365)
Cordialement,
Tom
