Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>Omika.MessageExterneProbe</AssemblyName>
<RootNamespace>Omika.MessageExterneProbe</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Data.OleDb" />
</ItemGroup>
</Project>
+802
View File
@@ -0,0 +1,802 @@
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<string, string>(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 <COLLAB_PKID> [--db ...]");
Console.Error.WriteLine(" --smtp-config <UT_PKID> [--db ...]");
Console.Error.WriteLine(" --smtp-resolution <COLLAB_PKID> [--db ...]");
Console.Error.WriteLine(" --image-dims <ME_PKID> [--db ...]");
Console.Error.WriteLine(" --message-fields <ME_PKID> [--db ...]");
Console.Error.WriteLine(" --smtp-keys <UT_PKID> [--db ...]");
Console.Error.WriteLine(" --analyze <ME_PKID> [--db ...]");
Console.Error.WriteLine(" --model-structure <MM_PKID> [--db ...]");
Console.Error.WriteLine(" --dump-message <ME_PKID> <file> [--db ...]");
Console.Error.WriteLine(" --dump-model <MM_PKID> <file> [--db ...]");
return 2;
// ------------------------------------------------------------
/// <summary>Résout l'utilisateur lié à un collaborateur (UT_PKID) + nom du collaborateur.</summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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<string>();
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;
}
/// <summary>
/// Dimensions de l'image inline d'un message (lecture seule) : attributs width/height
/// de la balise &lt;img&gt; vs dimensions intrinsèques du PNG/JPEG embarqué (data URI).
/// N'affiche JAMAIS l'image ni son contenu base64, uniquement des entiers.
/// </summary>
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, "<img\\b[^>]*>", 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;
}
/// <summary>Champs MessageExterne (lecture seule) — adresses masquées, corps jamais affiché.</summary>
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;
}
/// <summary>Clés JSON (structure, sans valeur) des configs SMTP d'un utilisateur.</summary>
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<JsonElement> 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<string>();
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;
}
/// <summary>Audit structurel (sans secret) des configs SMTP d'un utilisateur.</summary>
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, "<html") ? "YES" : "NO"));
Console.WriteLine("HAS HEAD = " + (Contains(lower, "<head") ? "YES" : "NO"));
Console.WriteLine("HAS STYLE = " + (Contains(lower, "<style") ? "YES" : "NO"));
Console.WriteLine("HAS SCRIPT = " + (Contains(lower, "<script") ? "YES" : "NO"));
Console.WriteLine("HAS BODY = " + (Contains(lower, "<body") ? "YES" : "NO"));
Console.WriteLine("HAS META = " + (Contains(lower, "<meta") ? "YES" : "NO"));
Console.WriteLine("HAS TITLE = " + (Contains(lower, "<title") ? "YES" : "NO"));
Console.WriteLine("HAS LINK = " + (Contains(lower, "<link") ? "YES" : "NO"));
Console.WriteLine("TABLE COUNT = " + Regex.Matches(msg, "<table", RegexOptions.IgnoreCase).Count);
Console.WriteLine("TR COUNT = " + Regex.Matches(msg, "<tr", RegexOptions.IgnoreCase).Count);
Console.WriteLine("TD COUNT = " + Regex.Matches(msg, "<td", RegexOptions.IgnoreCase).Count);
Console.WriteLine("TH COUNT = " + Regex.Matches(msg, "<th", RegexOptions.IgnoreCase).Count);
Console.WriteLine("TBODY COUNT = " + Regex.Matches(msg, "<tbody", RegexOptions.IgnoreCase).Count);
Console.WriteLine("IMG COUNT = " + Regex.Matches(msg, "<img", RegexOptions.IgnoreCase).Count);
Console.WriteLine("P COUNT = " + Regex.Matches(msg, "<p", RegexOptions.IgnoreCase).Count);
Console.WriteLine("BR COUNT = " + Regex.Matches(msg, "<br", RegexOptions.IgnoreCase).Count);
Console.WriteLine("SPAN COUNT = " + Regex.Matches(msg, "<span", RegexOptions.IgnoreCase).Count);
Console.WriteLine("DIV COUNT = " + Regex.Matches(msg, "<div", RegexOptions.IgnoreCase).Count);
Console.WriteLine("COL COUNT = " + Regex.Matches(msg, "<col", RegexOptions.IgnoreCase).Count);
var classes = Regex.Matches(msg, "class\\s*=\\s*\"([^\"]+)\"", RegexOptions.IgnoreCase)
.Select(m => 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 <style> générique (CSS WinDev, non privé) — affiché pour la spec.
var styles = Regex.Matches(msg, "<style[^>]*>(.*?)</style>", 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, "<w:p[ >]").Count);
Console.WriteLine("TABLE COUNT = " + Regex.Matches(xml, "<w:tbl>").Count);
Console.WriteLine("ROW COUNT = " + Regex.Matches(xml, "<w:tr>").Count);
Console.WriteLine("CELL COUNT = " + Regex.Matches(xml, "<w:tc>").Count);
Console.WriteLine("DRAWING/IMAGE COUNT = " + Regex.Matches(xml, "<w:drawing>").Count);
Console.WriteLine("TEXT COUNT = " + Regex.Matches(xml, "<w:t[ >]").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("<html") && (l.Contains("<head") || l.Contains("<body") || l.Contains("<style")))
return "WINFULL";
if (l.Contains("<table") || l.Contains("<p")) return "OMIKA-FRAGMENT";
return "OTHER";
}
static string Skeleton(string html)
{
// Masque les textes entre balises et les valeurs src/href (aucun contenu privé).
string s = Regex.Replace(html, "(src|href)\\s*=\\s*\"[^\"]*\"", "$1=\"{MASKED}\"", RegexOptions.IgnoreCase);
s = Regex.Replace(s, ">([^<>]+)<", 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 <w:t> (texte Word) uniquement — structure conservée.
string s = Regex.Replace(xml, "<w:t[^>]*>([^<]*)</w:t>", m => "<w:t>{T" + m.Groups[1].Value.Length + "}</w:t>");
return s;
}