Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb3b273741 | ||
|
|
f04de700d9 |
No files matched your search
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using Outlook = Microsoft.Office.Interop.Outlook;
|
using Outlook = Microsoft.Office.Interop.Outlook;
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ namespace BulkArchiveMailer.AddIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
var path = ResolveSignaturePath(signatureName);
|
var path = ResolveSignaturePath(signatureName);
|
||||||
return File.Exists(path) ? File.ReadAllText(path) : null;
|
return File.Exists(path) ? ReadSignatureHtml(path) : null;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -119,5 +121,141 @@ namespace BulkArchiveMailer.AddIn
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads a signature .htm file with the encoding it was actually
|
||||||
|
/// written in, instead of assuming UTF-8 the way
|
||||||
|
/// <see cref="File.ReadAllText(string)"/> does for BOM-less files.
|
||||||
|
///
|
||||||
|
/// Outlook writes signature .htm files in the system ANSI code
|
||||||
|
/// page (windows-1251 on a Russian Windows) without a BOM and
|
||||||
|
/// usually without a reliable <meta charset> tag, so blindly
|
||||||
|
/// reading as UTF-8 mangles Cyrillic into replacement characters.
|
||||||
|
///
|
||||||
|
/// Detection order:
|
||||||
|
/// 1. BOM (UTF-8 / UTF-16 LE / UTF-16 BE / UTF-32 LE / UTF-32 BE) - trusted if present.
|
||||||
|
/// 2. <meta charset=...> or <meta http-equiv="Content-Type" content="...charset=...">
|
||||||
|
/// found in the first ~1KB, if the named encoding is recognized.
|
||||||
|
/// 3. Encoding.Default (the system ANSI code page) - matches
|
||||||
|
/// Outlook's legacy signature file format.
|
||||||
|
///
|
||||||
|
/// Never throws: any failure along the way falls back to the
|
||||||
|
/// previous behavior, <see cref="File.ReadAllText(string)"/>.
|
||||||
|
/// </summary>
|
||||||
|
internal static string ReadSignatureHtml(string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytes = File.ReadAllBytes(path);
|
||||||
|
|
||||||
|
var bomEncoding = DetectBomEncoding(bytes, out var preambleLength);
|
||||||
|
if (bomEncoding != null)
|
||||||
|
{
|
||||||
|
return bomEncoding.GetString(bytes, preambleLength, bytes.Length - preambleLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
var metaEncoding = TryDetectMetaCharsetEncoding(bytes);
|
||||||
|
if (metaEncoding != null)
|
||||||
|
{
|
||||||
|
return metaEncoding.GetString(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Encoding.Default.GetString(bytes, 0, bytes.Length);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return File.ReadAllText(path);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Encoding DetectBomEncoding(byte[] bytes, out int preambleLength)
|
||||||
|
{
|
||||||
|
// UTF-32 BOMs must be checked before UTF-16 ones: the UTF-32 LE
|
||||||
|
// BOM (FF FE 00 00) starts with the same two bytes as the
|
||||||
|
// UTF-16 LE BOM (FF FE).
|
||||||
|
if (bytes.Length >= 4 && bytes[0] == 0x00 && bytes[1] == 0x00 && bytes[2] == 0xFE && bytes[3] == 0xFF)
|
||||||
|
{
|
||||||
|
preambleLength = 4;
|
||||||
|
return new UTF32Encoding(bigEndian: true, byteOrderMark: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.Length >= 4 && bytes[0] == 0xFF && bytes[1] == 0xFE && bytes[2] == 0x00 && bytes[3] == 0x00)
|
||||||
|
{
|
||||||
|
preambleLength = 4;
|
||||||
|
return new UTF32Encoding(bigEndian: false, byteOrderMark: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF)
|
||||||
|
{
|
||||||
|
preambleLength = 3;
|
||||||
|
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.Length >= 2 && bytes[0] == 0xFE && bytes[1] == 0xFF)
|
||||||
|
{
|
||||||
|
preambleLength = 2;
|
||||||
|
return new UnicodeEncoding(bigEndian: true, byteOrderMark: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.Length >= 2 && bytes[0] == 0xFF && bytes[1] == 0xFE)
|
||||||
|
{
|
||||||
|
preambleLength = 2;
|
||||||
|
return new UnicodeEncoding(bigEndian: false, byteOrderMark: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
preambleLength = 0;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Regex MetaCharsetRegex = new Regex(
|
||||||
|
@"<meta\b[^>]*charset\s*=\s*[""']?\s*([A-Za-z0-9_\-]+)",
|
||||||
|
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
|
private static Encoding TryDetectMetaCharsetEncoding(byte[] bytes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// <meta charset=...> tags are always plain ASCII, so
|
||||||
|
// Latin1 (single byte, 1:1 with the raw bytes) is a safe,
|
||||||
|
// lossless way to look for them regardless of the file's
|
||||||
|
// real encoding.
|
||||||
|
var headLength = Math.Min(bytes.Length, 1024);
|
||||||
|
var head = Encoding.GetEncoding("ISO-8859-1").GetString(bytes, 0, headLength);
|
||||||
|
|
||||||
|
var match = MetaCharsetRegex.Match(head);
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var charsetName = match.Groups[1].Value.Trim();
|
||||||
|
if (string.IsNullOrEmpty(charsetName))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Encoding.GetEncoding(charsetName);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Unrecognized charset name - let the caller fall back
|
||||||
|
// to Encoding.Default instead.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
private System.Windows.Forms.Button btnCheckAllReady;
|
private System.Windows.Forms.Button btnCheckAllReady;
|
||||||
private System.Windows.Forms.Button btnUncheckAll;
|
private System.Windows.Forms.Button btnUncheckAll;
|
||||||
private System.Windows.Forms.Button btnRescan;
|
private System.Windows.Forms.Button btnRescan;
|
||||||
|
private System.Windows.Forms.Button btnSettings;
|
||||||
|
|
||||||
private System.Windows.Forms.ProgressBar progressSend;
|
private System.Windows.Forms.ProgressBar progressSend;
|
||||||
private System.Windows.Forms.Label lblProgressStatus;
|
private System.Windows.Forms.Label lblProgressStatus;
|
||||||
@@ -56,6 +57,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
this.btnCheckAllReady = new System.Windows.Forms.Button();
|
this.btnCheckAllReady = new System.Windows.Forms.Button();
|
||||||
this.btnUncheckAll = new System.Windows.Forms.Button();
|
this.btnUncheckAll = new System.Windows.Forms.Button();
|
||||||
this.btnRescan = new System.Windows.Forms.Button();
|
this.btnRescan = new System.Windows.Forms.Button();
|
||||||
|
this.btnSettings = new System.Windows.Forms.Button();
|
||||||
|
|
||||||
this.progressSend = new System.Windows.Forms.ProgressBar();
|
this.progressSend = new System.Windows.Forms.ProgressBar();
|
||||||
this.lblProgressStatus = new System.Windows.Forms.Label();
|
this.lblProgressStatus = new System.Windows.Forms.Label();
|
||||||
@@ -70,7 +72,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
// lblPasswordReminder
|
// lblPasswordReminder
|
||||||
//
|
//
|
||||||
this.lblPasswordReminder.Location = new System.Drawing.Point(12, 10);
|
this.lblPasswordReminder.Location = new System.Drawing.Point(12, 10);
|
||||||
this.lblPasswordReminder.Size = new System.Drawing.Size(900, 20);
|
this.lblPasswordReminder.Size = new System.Drawing.Size(1018, 20);
|
||||||
this.lblPasswordReminder.Text = "Напоминание: пароль от архивов сообщается получателям отдельно (вручную, не через это письмо и не через этот инструмент).";
|
this.lblPasswordReminder.Text = "Напоминание: пароль от архивов сообщается получателям отдельно (вручную, не через это письмо и не через этот инструмент).";
|
||||||
this.lblPasswordReminder.ForeColor = System.Drawing.Color.DarkRed;
|
this.lblPasswordReminder.ForeColor = System.Drawing.Color.DarkRed;
|
||||||
this.lblPasswordReminder.Font = new System.Drawing.Font(this.lblPasswordReminder.Font, System.Drawing.FontStyle.Bold);
|
this.lblPasswordReminder.Font = new System.Drawing.Font(this.lblPasswordReminder.Font, System.Drawing.FontStyle.Bold);
|
||||||
@@ -80,7 +82,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
// lblSignatureWarning
|
// lblSignatureWarning
|
||||||
//
|
//
|
||||||
this.lblSignatureWarning.Location = new System.Drawing.Point(12, 32);
|
this.lblSignatureWarning.Location = new System.Drawing.Point(12, 32);
|
||||||
this.lblSignatureWarning.Size = new System.Drawing.Size(900, 20);
|
this.lblSignatureWarning.Size = new System.Drawing.Size(1018, 20);
|
||||||
this.lblSignatureWarning.Text = "Подпись не найдена — письма будут отправлены без подписи. Настройте .htm-файл вручную в Настройках.";
|
this.lblSignatureWarning.Text = "Подпись не найдена — письма будут отправлены без подписи. Настройте .htm-файл вручную в Настройках.";
|
||||||
this.lblSignatureWarning.ForeColor = System.Drawing.Color.DarkOrange;
|
this.lblSignatureWarning.ForeColor = System.Drawing.Color.DarkOrange;
|
||||||
this.lblSignatureWarning.Visible = false;
|
this.lblSignatureWarning.Visible = false;
|
||||||
@@ -90,7 +92,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
// grid
|
// grid
|
||||||
//
|
//
|
||||||
this.grid.Location = new System.Drawing.Point(12, 58);
|
this.grid.Location = new System.Drawing.Point(12, 58);
|
||||||
this.grid.Size = new System.Drawing.Size(900, 420);
|
this.grid.Size = new System.Drawing.Size(1018, 420);
|
||||||
this.grid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.grid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
@@ -215,24 +217,34 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
this.btnUncheckAll.UseVisualStyleBackColor = true;
|
this.btnUncheckAll.UseVisualStyleBackColor = true;
|
||||||
this.btnUncheckAll.Click += new System.EventHandler(this.btnUncheckAll_Click);
|
this.btnUncheckAll.Click += new System.EventHandler(this.btnUncheckAll_Click);
|
||||||
|
|
||||||
|
//
|
||||||
|
// btnSettings
|
||||||
|
//
|
||||||
|
this.btnSettings.Location = new System.Drawing.Point(416, 540);
|
||||||
|
this.btnSettings.Size = new System.Drawing.Size(110, 27);
|
||||||
|
this.btnSettings.Text = "Настройки...";
|
||||||
|
this.btnSettings.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.btnSettings.UseVisualStyleBackColor = true;
|
||||||
|
this.btnSettings.Click += new System.EventHandler(this.btnSettings_Click);
|
||||||
|
|
||||||
//
|
//
|
||||||
// progressSend
|
// progressSend
|
||||||
//
|
//
|
||||||
this.progressSend.Location = new System.Drawing.Point(430, 543);
|
this.progressSend.Location = new System.Drawing.Point(548, 543);
|
||||||
this.progressSend.Size = new System.Drawing.Size(300, 20);
|
this.progressSend.Size = new System.Drawing.Size(300, 20);
|
||||||
this.progressSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.progressSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
|
||||||
//
|
//
|
||||||
// lblProgressStatus
|
// lblProgressStatus
|
||||||
//
|
//
|
||||||
this.lblProgressStatus.Location = new System.Drawing.Point(430, 565);
|
this.lblProgressStatus.Location = new System.Drawing.Point(548, 565);
|
||||||
this.lblProgressStatus.Size = new System.Drawing.Size(482, 20);
|
this.lblProgressStatus.Size = new System.Drawing.Size(482, 20);
|
||||||
this.lblProgressStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
this.lblProgressStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
|
||||||
//
|
//
|
||||||
// btnClose
|
// btnClose
|
||||||
//
|
//
|
||||||
this.btnClose.Location = new System.Drawing.Point(720, 540);
|
this.btnClose.Location = new System.Drawing.Point(838, 540);
|
||||||
this.btnClose.Size = new System.Drawing.Size(90, 30);
|
this.btnClose.Size = new System.Drawing.Size(90, 30);
|
||||||
this.btnClose.Text = "Закрыть";
|
this.btnClose.Text = "Закрыть";
|
||||||
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
@@ -243,7 +255,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
//
|
//
|
||||||
// btnSend
|
// btnSend
|
||||||
//
|
//
|
||||||
this.btnSend.Location = new System.Drawing.Point(816, 540);
|
this.btnSend.Location = new System.Drawing.Point(934, 540);
|
||||||
this.btnSend.Size = new System.Drawing.Size(96, 30);
|
this.btnSend.Size = new System.Drawing.Size(96, 30);
|
||||||
this.btnSend.Text = "Отправить";
|
this.btnSend.Text = "Отправить";
|
||||||
this.btnSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.btnSend.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
@@ -256,8 +268,8 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.ClientSize = new System.Drawing.Size(924, 582);
|
this.ClientSize = new System.Drawing.Size(1042, 582);
|
||||||
this.MinimumSize = new System.Drawing.Size(760, 420);
|
this.MinimumSize = new System.Drawing.Size(878, 420);
|
||||||
this.Controls.Add(this.lblPasswordReminder);
|
this.Controls.Add(this.lblPasswordReminder);
|
||||||
this.Controls.Add(this.lblSignatureWarning);
|
this.Controls.Add(this.lblSignatureWarning);
|
||||||
this.Controls.Add(this.grid);
|
this.Controls.Add(this.grid);
|
||||||
@@ -266,6 +278,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
this.Controls.Add(this.btnRescan);
|
this.Controls.Add(this.btnRescan);
|
||||||
this.Controls.Add(this.btnCheckAllReady);
|
this.Controls.Add(this.btnCheckAllReady);
|
||||||
this.Controls.Add(this.btnUncheckAll);
|
this.Controls.Add(this.btnUncheckAll);
|
||||||
|
this.Controls.Add(this.btnSettings);
|
||||||
this.Controls.Add(this.progressSend);
|
this.Controls.Add(this.progressSend);
|
||||||
this.Controls.Add(this.lblProgressStatus);
|
this.Controls.Add(this.lblProgressStatus);
|
||||||
this.Controls.Add(this.btnClose);
|
this.Controls.Add(this.btnClose);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(settings.SignatureHtmlPath))
|
if (!string.IsNullOrWhiteSpace(settings.SignatureHtmlPath))
|
||||||
{
|
{
|
||||||
return File.Exists(settings.SignatureHtmlPath) ? File.ReadAllText(settings.SignatureHtmlPath) : null;
|
return File.Exists(settings.SignatureHtmlPath) ? SignatureReader.ReadSignatureHtml(settings.SignatureHtmlPath) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return SignatureReader.TryAutoDetectSignatureHtml(outlookApplication);
|
return SignatureReader.TryAutoDetectSignatureHtml(outlookApplication);
|
||||||
@@ -172,6 +172,34 @@ namespace BulkArchiveMailer.AddIn.UI
|
|||||||
RunScan();
|
RunScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void btnSettings_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using (var settingsForm = new SettingsForm(outlookApplication))
|
||||||
|
{
|
||||||
|
settingsForm.ShowDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regardless of DialogResult (OK or Cancel), reload settings and
|
||||||
|
// rescan so a changed archive folder, domain, or templates are
|
||||||
|
// reflected immediately without a separate "Пересканировать"
|
||||||
|
// click. A closed-without-saving dialog just reloads the same
|
||||||
|
// settings again, which is harmless.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
settings = AppSettings.Load();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show(this, "Не удалось прочитать настройки: " + ex.ToString(), "BulkArchiveMailer", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureHtml = ResolveSignatureHtml();
|
||||||
|
lblSignatureWarning.Visible = string.IsNullOrEmpty(signatureHtml);
|
||||||
|
|
||||||
|
RunScan();
|
||||||
|
}
|
||||||
|
|
||||||
private void btnCheckAllReady_Click(object sender, EventArgs e)
|
private void btnCheckAllReady_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
foreach (var row in rows)
|
foreach (var row in rows)
|
||||||
|
|||||||
Reference in new issue
Block a user