using System.Data.OleDb; using System.IO.Compression; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; // ============================================================ // OMIKA MESSAGE EXTERNE PROBE — READ-ONLY DB // Audit structurel des corps HTML (MessageExterne.ME_Message) et des // modèles DOCX (MailModele.MM_MsgDoc) pour la compatibilité WinDev ↔ Omika. // Ne modifie AUCUNE donnée. N'affiche JAMAIS de contenu privé : le squelette // du body est affiché avec le texte masqué (seul le CSS générique WinDev et // la structure des balises/attributs sont montrés). // ============================================================ const string server = "127.0.0.1"; const int port = 4900; const string user = "susana"; const string pass = "123"; var dbMap = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["OMIKA_TEST"] = "A1BAF17C65BC565F2927BF1A85DAFEED", ["BATIK_TEST"] = "E2FC3072E6145E1B20DA7321755CD607", }; int dbIdx = Array.IndexOf(args, "--db"); string db = dbIdx >= 0 && dbIdx + 1 < args.Length ? args[dbIdx + 1] : "OMIKA_TEST"; if (!dbMap.TryGetValue(db, out string? stdPwd)) { Console.Error.WriteLine($"ERROR: unknown database '{db}'."); return 2; } string connStr = $"Provider=PCSOFT.HFSQL;Data Source={server}:{port};Initial Catalog={db};" + $"User ID={user};Password={pass};" + $"Extended Properties=\"Password=ParametreUtilisateur:{stdPwd}\""; if (args.Contains("--list-messages")) return ListMessages(connStr, db); if (args.Contains("--list-models")) return ListModels(connStr, db); int ipos = Array.IndexOf(args, "--collab-identity"); if (ipos >= 0 && ipos + 1 < args.Length) return CollabIdentity(connStr, long.Parse(args[ipos + 1])); ipos = Array.IndexOf(args, "--smtp-config"); if (ipos >= 0 && ipos + 1 < args.Length) return SmtpConfig(db, stdPwd, long.Parse(args[ipos + 1])); ipos = Array.IndexOf(args, "--smtp-resolution"); if (ipos >= 0 && ipos + 1 < args.Length) return SmtpResolution(connStr, long.Parse(args[ipos + 1])); ipos = Array.IndexOf(args, "--image-dims"); if (ipos >= 0 && ipos + 1 < args.Length) return ImageDims(connStr, long.Parse(args[ipos + 1])); ipos = Array.IndexOf(args, "--message-fields"); if (ipos >= 0 && ipos + 1 < args.Length) return MessageFields(connStr, long.Parse(args[ipos + 1])); ipos = Array.IndexOf(args, "--smtp-keys"); if (ipos >= 0 && ipos + 1 < args.Length) return SmtpKeys(connStr, long.Parse(args[ipos + 1])); int pos = Array.IndexOf(args, "--analyze"); if (pos >= 0 && pos + 1 < args.Length) return AnalyzeMessage(connStr, long.Parse(args[pos + 1])); pos = Array.IndexOf(args, "--model-structure"); if (pos >= 0 && pos + 1 < args.Length) return ModelStructure(connStr, long.Parse(args[pos + 1])); pos = Array.IndexOf(args, "--dump-message"); if (pos >= 0 && pos + 2 < args.Length) return DumpMessage(connStr, long.Parse(args[pos + 1]), args[pos + 2]); pos = Array.IndexOf(args, "--dump-model"); if (pos >= 0 && pos + 2 < args.Length) return DumpModel(connStr, long.Parse(args[pos + 1]), args[pos + 2]); Console.Error.WriteLine("USAGE:"); Console.Error.WriteLine(" --list-messages [--db OMIKA_TEST|BATIK_TEST]"); Console.Error.WriteLine(" --list-models [--db ...]"); Console.Error.WriteLine(" --collab-identity [--db ...]"); Console.Error.WriteLine(" --smtp-config [--db ...]"); Console.Error.WriteLine(" --smtp-resolution [--db ...]"); Console.Error.WriteLine(" --image-dims [--db ...]"); Console.Error.WriteLine(" --message-fields [--db ...]"); Console.Error.WriteLine(" --smtp-keys [--db ...]"); Console.Error.WriteLine(" --analyze [--db ...]"); Console.Error.WriteLine(" --model-structure [--db ...]"); Console.Error.WriteLine(" --dump-message [--db ...]"); Console.Error.WriteLine(" --dump-model [--db ...]"); return 2; // ------------------------------------------------------------ /// Résout l'utilisateur lié à un collaborateur (UT_PKID) + nom du collaborateur. static int CollabIdentity(string connStr, long collabPkId) { try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT UT_PKID, UT_Nom, UT_Prenom, UT_Login FROM Utilisateur WHERE UT_IDCollaborateur = {collabPkId}"; using var r = cmd.ExecuteReader(); if (!r.Read()) { Console.WriteLine("UTILISATEUR LIE AU COLLABORATEUR = NO"); return 0; } Console.WriteLine("UTILISATEUR LIE AU COLLABORATEUR = YES"); Console.WriteLine("UT_PKID = " + (r.IsDBNull(0) ? "(null)" : r.GetValue(0)?.ToString())); Console.WriteLine("UT_NOM = " + (r.IsDBNull(1) ? "(null)" : r.GetValue(1)?.ToString())); Console.WriteLine("UT_PRENOM = " + (r.IsDBNull(2) ? "(null)" : r.GetValue(2)?.ToString())); Console.WriteLine("UT_LOGIN = " + (r.IsDBNull(3) ? "(null)" : r.GetValue(3)?.ToString())); } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT * FROM Collaborateur WHERE CL_PKID = {collabPkId}"; using var r = cmd.ExecuteReader(); if (r.Read()) { for (int i = 0; i < r.FieldCount; i++) { string? name = r.GetName(i); if (name is "CL_Nom" or "CL_Prenom" or "CL_PKID") { Console.WriteLine(name + " = " + (r.IsDBNull(i) ? "(null)" : r.GetValue(i)?.ToString())); } } } else { Console.WriteLine("COLLABORATEUR = NOT FOUND (ou colonne PK différente de CL_PKID)"); } } catch (Exception ex) { Console.WriteLine("COLLAB QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); } return 0; } /// /// Reproduit EXACTEMENT la résolution SMTP côté API (lecture seule) : /// Collaborateur (CO_PKID, GUID) → UT_PKID (UT_IDCollaborateur) → ParametreUtilisateur /// (PU_IDUtilisateur, PU_Code LIKE 'EMAIL_ENVOI%'). N'affiche jamais le contenu /// des valeurs JSON ni aucun secret. /// static int SmtpResolution(string connStr, long collabPkId) { Console.WriteLine("COLLAB PKID = " + collabPkId); string? guid = null; long utPk = 0; try { using var conn = new OleDbConnection(connStr); conn.Open(); // 1. GUID physique du collaborateur (résolution Omika : WHERE GUID = ?). using (var cmd = conn.CreateCommand()) { cmd.CommandText = $"SELECT CO_PKID, GUID FROM Collaborateur WHERE CO_PKID = {collabPkId}"; using var r = cmd.ExecuteReader(); if (!r.Read()) { Console.WriteLine("COLLABORATEUR FOUND = NO (CO_PKID introuvable)"); return 0; } guid = r.IsDBNull(1) ? null : r.GetValue(1)?.ToString(); Console.WriteLine("COLLABORATEUR FOUND = YES"); Console.WriteLine("CO_PKID = " + (r.IsDBNull(0) ? "(null)" : r.GetValue(0)?.ToString())); Console.WriteLine("GUID RAW = " + (guid is null ? "(null)" : $"[{guid}] len={guid.Length}")); } // Guid.TryParse + reserialize "D" = ce que l'API fait (PhysicalGuidParser → ToString("D")). string? guidD = null; if (guid is not null && Guid.TryParse(guid, out Guid g)) { guidD = g.ToString("D"); Console.WriteLine("GUID PARSED (D) = " + guidD); } else { Console.WriteLine("GUID PARSED (D) = (non analysable)"); } // 2. Résolution GUID → CO_PKID (requête de ResolveCollaborateurPkAsync). using (var cmd = conn.CreateCommand()) { cmd.CommandText = "SELECT CO_PKID FROM Collaborateur WHERE GUID = ?"; cmd.Parameters.Add(new OleDbParameter("?", guidD ?? (object)DBNull.Value)); object? v = cmd.ExecuteScalar(); Console.WriteLine("RESOLVE GUID→CO_PKID = " + (v is null or DBNull ? "NO MATCH" : v.ToString())); } // 3. Résolution UT_PKID (UT_IDCollaborateur = CO_PKID). using (var cmd = conn.CreateCommand()) { cmd.CommandText = $"SELECT UT_PKID FROM Utilisateur WHERE UT_IDCollaborateur = {collabPkId}"; object? v = cmd.ExecuteScalar(); utPk = v is null or DBNull ? 0 : Convert.ToInt64(v); Console.WriteLine("RESOLVE UT_PKID = " + (utPk > 0 ? utPk.ToString() : "NO MATCH")); } // 4. Configurations SMTP (PU_IDUtilisateur = UT_PKID). if (utPk <= 0) { Console.WriteLine("SMTP ROWS = 0 (UT_PKID non résolu)"); return 0; } using (var cmd = conn.CreateCommand()) { cmd.CommandText = $"SELECT PU_Code FROM ParametreUtilisateur WHERE PU_IDUtilisateur = {utPk} AND PU_Code LIKE 'EMAIL_ENVOI%' ORDER BY PU_Code"; using var r = cmd.ExecuteReader(); var codes = new List(); while (r.Read()) { codes.Add(r.GetValue(0)?.ToString() ?? ""); } Console.WriteLine("SMTP ROWS = " + codes.Count); Console.WriteLine("SMTP CODES = " + string.Join(", ", codes)); } } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } return 0; } /// /// Dimensions de l'image inline d'un message (lecture seule) : attributs width/height /// de la balise <img> vs dimensions intrinsèques du PNG/JPEG embarqué (data URI). /// N'affiche JAMAIS l'image ni son contenu base64, uniquement des entiers. /// static int ImageDims(string connStr, long pk) { string? msg = ReadMessage(connStr, pk); if (msg is null) { Console.WriteLine("MESSAGE FOUND = NO"); return 4; } Match img = Regex.Match(msg, "]*>", RegexOptions.IgnoreCase); if (!img.Success) { Console.WriteLine("IMG TAG = NO"); return 0; } Console.WriteLine("IMG TAG = YES"); Match w = Regex.Match(img.Value, "width\\s*=\\s*[\"']?(\\d+)", RegexOptions.IgnoreCase); Match h = Regex.Match(img.Value, "height\\s*=\\s*[\"']?(\\d+)", RegexOptions.IgnoreCase); Console.WriteLine("IMG TAG WIDTH = " + (w.Success ? w.Groups[1].Value : "(absent)")); Console.WriteLine("IMG TAG HEIGHT = " + (h.Success ? h.Groups[1].Value : "(absent)")); Match src = Regex.Match(img.Value, "src\\s*=\\s*\"(data:[^\"]+)\"", RegexOptions.IgnoreCase); if (!src.Success) { Console.WriteLine("DATA URI = NO"); return 0; } string dataUri = src.Groups[1].Value; int comma = dataUri.IndexOf(','); string meta = comma > 0 ? dataUri[..comma] : ""; bool isPng = meta.Contains("png", StringComparison.OrdinalIgnoreCase); bool isJpeg = meta.Contains("jpeg", StringComparison.OrdinalIgnoreCase) || meta.Contains("jpg", StringComparison.OrdinalIgnoreCase); Console.WriteLine("IMAGE FORMAT = " + (isPng ? "PNG" : isJpeg ? "JPEG" : "AUTRE")); try { byte[] bytes = Convert.FromBase64String(dataUri[(comma + 1)..]); if (isPng && bytes.Length >= 24) { int wpx = (bytes[16] << 24) | (bytes[17] << 16) | (bytes[18] << 8) | bytes[19]; int hpx = (bytes[20] << 24) | (bytes[21] << 16) | (bytes[22] << 8) | bytes[23]; Console.WriteLine("PNG INTRINSIC WIDTH = " + wpx); Console.WriteLine("PNG INTRINSIC HEIGHT = " + hpx); } else { Console.WriteLine("INTRINSIC DIMS = (non extraites, format non PNG ou trop court)"); } } catch (FormatException) { Console.WriteLine("BASE64 DECODE = FAIL"); } return 0; } /// Champs MessageExterne (lecture seule) — adresses masquées, corps jamais affiché. static int MessageFields(string connStr, long pk) { try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT ME_PKID, ME_IDIntervention, ME_AdrExp, ME_AdrDestinataires, ME_AdrCC, ME_AdrCCI, " + "ME_Objet, ME_Envoyer, ME_Brouillon, ME_PJ, ME_IDDocPJ, ME_EnvoyerMob, CreationUser, CreationDate, ME_Message " + "FROM MessageExterne WHERE ME_PKID = " + pk; using var r = cmd.ExecuteReader(); if (!r.Read()) { Console.WriteLine("MESSAGE FOUND = NO"); return 4; } Console.WriteLine("MESSAGE FOUND = YES"); Console.WriteLine("ME_PKID = " + Val(r, 0)); Console.WriteLine("ME_IDIntervention = " + Val(r, 1)); Console.WriteLine("ME_AdrExp = " + MaskEmails(Val(r, 2))); Console.WriteLine("ME_AdrDestinataires = " + MaskEmails(Val(r, 3))); Console.WriteLine("ME_AdrCC = " + MaskEmails(Val(r, 4))); Console.WriteLine("ME_AdrCCI = " + MaskEmails(Val(r, 5))); Console.WriteLine("ME_Objet = " + MaskText(Val(r, 6) ?? "")); Console.WriteLine("ME_Envoyer = " + BoolVal(r, 7)); Console.WriteLine("ME_Brouillon = " + BoolVal(r, 8)); Console.WriteLine("ME_PJ = " + BoolVal(r, 9)); Console.WriteLine("ME_IDDocPJ = " + MaskPj(Val(r, 10))); Console.WriteLine("ME_EnvoyerMob = " + BoolVal(r, 11)); Console.WriteLine("CreationUser = " + Val(r, 12)); Console.WriteLine("CreationDate = " + Val(r, 13)); string? msg = r.IsDBNull(14) ? null : r.GetValue(14)?.ToString(); Console.WriteLine("BODY LENGTH = " + (msg?.Length ?? 0)); Console.WriteLine("HAS SIGNATURE = " + HasSignature(msg)); } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } return 0; } /// Clés JSON (structure, sans valeur) des configs SMTP d'un utilisateur. static int SmtpKeys(string connStr, long userId) { try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT PU_Code, PU_Valeur FROM ParametreUtilisateur WHERE PU_IDUtilisateur = {userId} AND PU_Code LIKE 'EMAIL_ENVOI%' ORDER BY PU_Code"; using var r = cmd.ExecuteReader(); int n = 0; while (r.Read()) { string code = (r.GetValue(0)?.ToString() ?? "").Trim(); string? valeur = r.IsDBNull(1) ? null : r.GetValue(1)?.ToString(); n++; Console.WriteLine($"--- ROW {n}: PU_Code={code} ---"); if (string.IsNullOrWhiteSpace(valeur)) { Console.WriteLine("VALUE = (empty)"); continue; } try { using JsonDocument doc = JsonDocument.Parse(valeur); JsonElement root = doc.RootElement; IEnumerable items = root.ValueKind == JsonValueKind.Array ? root.EnumerateArray() : [root]; int i = 0; foreach (JsonElement el in items) { if (el.ValueKind != JsonValueKind.Object) { Console.WriteLine($" [{i}] = non-objet"); i++; continue; } Console.WriteLine($" [{i}] keys = " + string.Join(", ", el.EnumerateObject().Select(p => p.Name))); if (el.TryGetProperty("Compte", out JsonElement compte) && compte.ValueKind == JsonValueKind.Object) Console.WriteLine($" [{i}] Compte keys = " + string.Join(", ", compte.EnumerateObject().Select(p => p.Name))); if (el.TryGetProperty("SMTP", out JsonElement smtp) && smtp.ValueKind == JsonValueKind.Object) { Console.WriteLine($" [{i}] SMTP keys = " + string.Join(", ", smtp.EnumerateObject().Select(p => p.Name))); foreach (JsonProperty p in smtp.EnumerateObject()) { string kind = p.Value.ValueKind switch { JsonValueKind.Array => "array", JsonValueKind.Object => "object", JsonValueKind.String => "string", JsonValueKind.True => "bool(true)", JsonValueKind.False => "bool(false)", JsonValueKind.Number => "number", JsonValueKind.Null => "null", _ => "other" }; int len = p.Value.ValueKind == JsonValueKind.String ? p.Value.GetString()?.Length ?? 0 : p.Value.ValueKind == JsonValueKind.Array ? p.Value.GetArrayLength() : -1; string extra = ""; if (p.Name == "bufSignature" && p.Value.ValueKind == JsonValueKind.String) { string? sv = p.Value.GetString(); string prefix = sv is { Length: > 0 } ? sv[..Math.Min(sv.Length, 16)] : ""; extra = $" prefix=\"{prefix.Replace("\n", "\\n").Replace("\r", "\\r")}\""; } else if (p.Name == "tabCC_CopieEmail" && p.Value.ValueKind == JsonValueKind.Array) { var elems = new List(); int idx = 0; foreach (JsonElement e in p.Value.EnumerateArray()) { if (e.ValueKind == JsonValueKind.String) elems.Add("s:" + MaskEmails(e.GetString() ?? "")); else if (e.ValueKind == JsonValueKind.Object) elems.Add("obj{" + string.Join(",", e.EnumerateObject().Select(x => x.Name)) + "}"); else elems.Add(e.ValueKind.ToString()); idx++; } extra = $" elements=[{string.Join(" | ", elems)}]"; } Console.WriteLine($" SMTP.{p.Name} = {kind} len={len}{extra}"); } } i++; } } catch (JsonException) { Console.WriteLine("JSON = invalid"); } } if (n == 0) Console.WriteLine("NO ROWS"); } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } return 0; } /// Audit structurel (sans secret) des configs SMTP d'un utilisateur. static int SmtpConfig(string db, string? stdPwd, long userId) { string connStr2 = $"Provider=PCSOFT.HFSQL;Data Source={server}:{port};Initial Catalog={db};" + $"User ID={user};Password={pass};" + $"Extended Properties=\"Password=ParametreUtilisateur:{stdPwd}\""; Console.WriteLine("PU_IDUTILISATEUR = " + userId); try { using var conn = new OleDbConnection(connStr2); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT PU_Code, PU_Valeur FROM ParametreUtilisateur WHERE PU_IDUtilisateur = {userId} AND PU_Code LIKE 'EMAIL_ENVOI%' ORDER BY PU_Code"; using var r = cmd.ExecuteReader(); int n = 0; while (r.Read()) { string code = (r.GetValue(0)?.ToString() ?? "").Trim(); string? valeur = r.IsDBNull(1) ? null : r.GetValue(1)?.ToString(); n++; Console.WriteLine($"--- ROW {n}: PU_Code={code} ---"); Console.WriteLine("DEV SMTP CONFIG FOUND = YES"); Console.WriteLine("VALUE PRESENT = " + (string.IsNullOrWhiteSpace(valeur) ? "NO" : "YES")); if (string.IsNullOrWhiteSpace(valeur)) { Console.WriteLine("STANDARD CONFIG FOUND = NO"); continue; } // Structure JSON (jamais le contenu sMdp/sMdpStandard lui-même). try { using JsonDocument doc = JsonDocument.Parse(valeur); var smtp = doc.RootElement.ValueKind == JsonValueKind.Array ? doc.RootElement.EnumerateArray().Select(e => e.TryGetProperty("SMTP", out JsonElement s) ? s : default).FirstOrDefault(s => s.ValueKind == JsonValueKind.Object) : (doc.RootElement.TryGetProperty("SMTP", out JsonElement s0) ? s0 : default); if (smtp.ValueKind == JsonValueKind.Object) { bool hasStd = smtp.TryGetProperty("sMdpStandard", out JsonElement e) && e.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(e.GetString()); bool hasMdp = smtp.TryGetProperty("sMdp", out JsonElement e2) && e2.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(e2.GetString()); string host = smtp.TryGetProperty("sAdresseServeur", out JsonElement h) ? h.GetString() ?? "" : ""; string port = smtp.TryGetProperty("sPortServeur", out JsonElement p) ? p.GetString() ?? "" : ""; int cryptage = smtp.TryGetProperty("nCryptage", out JsonElement c) ? c.GetInt32() : 0; string login = smtp.TryGetProperty("sLogin", out JsonElement l) ? l.GetString() ?? "" : ""; Console.WriteLine("STANDARD CONFIG FOUND = " + (hasStd ? "YES" : "NO")); Console.WriteLine("sMdpStandard PRESENT = " + (hasStd ? "YES" : "NO")); Console.WriteLine("LEGACY sMdp PRESENT = " + (hasMdp ? "YES" : "NO")); Console.WriteLine("HOST PRESENT = " + (string.IsNullOrWhiteSpace(host) ? "NO" : "YES")); Console.WriteLine("PORT PRESENT = " + (string.IsNullOrWhiteSpace(port) ? "NO" : "YES")); Console.WriteLine("CRYPTAGE = " + cryptage); Console.WriteLine("LOGIN PRESENT = " + (string.IsNullOrWhiteSpace(login) ? "NO" : "YES")); Console.WriteLine("CONFIGURATION VALID = " + ((hasStd && !string.IsNullOrWhiteSpace(host) && !string.IsNullOrWhiteSpace(port) && cryptage is >= 1 and <= 3 && !string.IsNullOrWhiteSpace(login)) ? "YES" : "NO")); } else { Console.WriteLine("STANDARD CONFIG FOUND = NO (SMTP absent du JSON)"); } } catch (JsonException) { Console.WriteLine("STANDARD CONFIG FOUND = NO (JSON invalide ou legacy/binaire)"); } } if (n == 0) { Console.WriteLine("DEV SMTP CONFIG FOUND = NO"); } } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } return 0; } static int ListMessages(string connStr, string db) { Console.WriteLine("DATABASE = " + db); try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT ME_PKID, ME_Objet, ME_Message, CreationDate, CreationUser FROM MessageExterne ORDER BY CreationDate DESC"; using var r = cmd.ExecuteReader(); int i = 0; while (r.Read()) { long pk = Convert.ToInt64(r.GetValue(0)); string? objet = r.IsDBNull(1) ? null : r.GetValue(1)?.ToString(); string? msg = r.IsDBNull(2) ? null : r.GetValue(2)?.ToString(); string? created = r.IsDBNull(3) ? null : r.GetValue(3)?.ToString(); string? creator = r.IsDBNull(4) ? null : r.GetValue(4)?.ToString(); string kind = Classify(msg); string obj = (objet ?? "").Replace("\n", " ").Replace("\r", " "); if (obj.Length > 60) obj = obj[..60]; Console.WriteLine($"[{i}] PK={pk} KIND={kind} LEN={(msg ?? "").Length} OBJ=\"{obj}\" CREATED={created} USER={creator}"); i++; } } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } return 0; } static int ListModels(string connStr, string db) { Console.WriteLine("DATABASE = " + db); try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT MM_PKID, MM_Nom, MM_Sujet, MM_MsgDoc FROM MailModele ORDER BY MM_PKID"; using var r = cmd.ExecuteReader(); int i = 0; while (r.Read()) { long pk = Convert.ToInt64(r.GetValue(0)); string? nom = r.IsDBNull(1) ? null : r.GetValue(1)?.ToString(); string? sujet = r.IsDBNull(2) ? null : r.GetValue(2)?.ToString(); int docLen = r.IsDBNull(3) ? 0 : (r.GetValue(3) as byte[])?.Length ?? 0; Console.WriteLine($"[{i}] PK={pk} NOM=\"{nom}\" SUJET=\"{sujet}\" DOC_LEN={docLen}"); i++; } } catch (Exception ex) { Console.WriteLine("QUERY FAILED = " + ex.GetType().Name + " : " + ex.Message); return 3; } return 0; } static int AnalyzeMessage(string connStr, long pk) { string? msg = ReadMessage(connStr, pk); if (msg is null) { Console.WriteLine("MESSAGE FOUND = NO"); return 4; } Console.WriteLine("MESSAGE FOUND = YES"); Console.WriteLine($"PK = {pk}"); Console.WriteLine($"BODY LENGTH = {msg.Length}"); string lower = msg.ToLowerInvariant(); Console.WriteLine("FULL HTML DOCUMENT = " + (Contains(lower, " m.Groups[1].Value) .SelectMany(v => v.Split(' ', StringSplitOptions.RemoveEmptyEntries)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(c => c, StringComparer.OrdinalIgnoreCase) .ToList(); Console.WriteLine("UNIQUE CLASSES = " + string.Join(", ", classes)); // Bloc ", RegexOptions.Singleline | RegexOptions.IgnoreCase); Console.WriteLine("STYLE BLOCKS = " + styles.Count); for (int i = 0; i < styles.Count; i++) { Console.WriteLine($"--- STYLE BLOCK {i} (len={styles[i].Groups[1].Value.Length}) ---"); Console.WriteLine(styles[i].Groups[1].Value.Trim()); } Console.WriteLine("--- BODY SKELETON (texte masqué, src/href masqués) ---"); Console.WriteLine(Skeleton(msg)); return 0; } static int ModelStructure(string connStr, long pk) { byte[]? docx = ReadModel(connStr, pk); if (docx is null) { Console.WriteLine("MODEL FOUND = NO"); return 4; } Console.WriteLine("MODEL FOUND = YES"); Console.WriteLine($"PK = {pk}"); Console.WriteLine($"DOC LENGTH = {docx.Length}"); Console.WriteLine("ZIP MAGIC (DOCX) = " + (docx.Length >= 2 && docx[0] == 0x50 && docx[1] == 0x4B ? "YES" : "NO")); if (!(docx.Length >= 2 && docx[0] == 0x50 && docx[1] == 0x4B)) { Console.WriteLine("NON-DOCX MODEL — first 16 bytes hex: " + BitConverter.ToString(docx.Take(16).ToArray())); return 0; } try { using var ms = new MemoryStream(docx); using var zip = new ZipArchive(ms, ZipArchiveMode.Read); var entries = zip.Entries.Select(e => e.FullName).ToList(); Console.WriteLine("ZIP ENTRIES = " + string.Join(", ", entries)); var documentEntry = zip.Entries.FirstOrDefault(e => e.FullName == "word/document.xml"); if (documentEntry is null) { Console.WriteLine("document.xml ABSENT"); return 0; } using var sr = new StreamReader(documentEntry.Open(), Encoding.UTF8); string xml = sr.ReadToEnd(); Console.WriteLine("document.xml LENGTH = " + xml.Length); Console.WriteLine("PARAGRAPH COUNT = " + Regex.Matches(xml, "]").Count); Console.WriteLine("TABLE COUNT = " + Regex.Matches(xml, "").Count); Console.WriteLine("ROW COUNT = " + Regex.Matches(xml, "").Count); Console.WriteLine("CELL COUNT = " + Regex.Matches(xml, "").Count); Console.WriteLine("DRAWING/IMAGE COUNT = " + Regex.Matches(xml, "").Count); Console.WriteLine("TEXT COUNT = " + Regex.Matches(xml, "]").Count); Console.WriteLine("--- DOCUMENT.XML SKELETON (texte masqué) ---"); Console.WriteLine(SkeletonXml(xml)); } catch (Exception ex) { Console.WriteLine("DOCX PARSE FAILED = " + ex.GetType().Name + " : " + ex.Message); } return 0; } static int DumpMessage(string connStr, long pk, string file) { string? msg = ReadMessage(connStr, pk); if (msg is null) { Console.WriteLine("MESSAGE FOUND = NO"); return 4; } File.WriteAllText(file, msg, Encoding.UTF8); Console.WriteLine($"WROTE {msg.Length} chars to {file}"); return 0; } static int DumpModel(string connStr, long pk, string file) { byte[]? docx = ReadModel(connStr, pk); if (docx is null) { Console.WriteLine("MODEL FOUND = NO"); return 4; } File.WriteAllBytes(file, docx); Console.WriteLine($"WROTE {docx.Length} bytes to {file}"); return 0; } static string? ReadMessage(string connStr, long pk) { try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT ME_Message FROM MessageExterne WHERE ME_PKID = {pk}"; object? v = cmd.ExecuteScalar(); return v is DBNull or null ? null : v.ToString(); } catch (Exception ex) { Console.WriteLine("READ MESSAGE FAILED = " + ex.GetType().Name + " : " + ex.Message); return null; } } static byte[]? ReadModel(string connStr, long pk) { try { using var conn = new OleDbConnection(connStr); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = $"SELECT MM_MsgDoc FROM MailModele WHERE MM_PKID = {pk}"; object? v = cmd.ExecuteScalar(); return v switch { byte[] b => b, DBNull or null => null, _ => Encoding.UTF8.GetBytes(v.ToString() ?? "") }; } catch (Exception ex) { Console.WriteLine("READ MODEL FAILED = " + ex.GetType().Name + " : " + ex.Message); return null; } } static bool Contains(string haystack, string needle) => haystack.Contains(needle, StringComparison.OrdinalIgnoreCase); static string Classify(string? msg) { if (string.IsNullOrWhiteSpace(msg)) return "EMPTY"; string l = msg.ToLowerInvariant(); if (l.Contains("([^<>]+)<", m => ">" + MaskText(m.Groups[1].Value) + "<"); return s; } static string MaskText(string text) { string t = text.Trim(); if (t.Length == 0) return text; return "{T" + t.Length + "}"; } static string Val(System.Data.OleDb.OleDbDataReader r, int i) => r.IsDBNull(i) ? "(null)" : (r.GetValue(i)?.ToString() ?? "(null)"); static string BoolVal(System.Data.OleDb.OleDbDataReader r, int i) => r.IsDBNull(i) ? "(null)" : (Convert.ToBoolean(r.GetValue(i)) ? "TRUE" : "FALSE"); static string MaskEmails(string? s) { if (string.IsNullOrWhiteSpace(s)) return "(vide)"; return Regex.Replace(s, @"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", "[email]", RegexOptions.IgnoreCase); } static string MaskPj(string? s) { if (string.IsNullOrWhiteSpace(s)) return "(vide)"; // Les références de PJ peuvent être des GUID/IDs/chemins : on masque les GUID // et chemins, mais on conserve la STRUCTURE (séparateurs + nombre d'éléments). string masked = Regex.Replace(s, @"[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}", "[guid]", RegexOptions.IgnoreCase); masked = Regex.Replace(masked, @"[A-Za-z]:\\[^;]*", "[path]", RegexOptions.IgnoreCase); return masked; } static string HasSignature(string? msg) { if (string.IsNullOrWhiteSpace(msg)) return "NO (body vide)"; string l = msg.ToLowerInvariant(); bool sig = l.Contains("signature") || l.Contains("cordialement") || l.Contains("salutations"); return sig ? "YES (marqueur trouvé)" : "NO (aucun marqueur)"; } static string SkeletonXml(string xml) { // Masque le contenu des (texte Word) uniquement — structure conservée. string s = Regex.Replace(xml, "]*>([^<]*)", m => "{T" + m.Groups[1].Value.Length + "}"); return s; }