Premiere commit - Omika
This commit is contained in:
@@ -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>
|
||||
@@ -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 <img> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user