From 35ef86d5f05c1ba6d90bb62c170f49a1d86f6b1f Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 15 Mar 2017 09:23:47 +0100
Subject: [PATCH 1/4] Small clean up to FreeBusy webserver to allow it to be
used for different purposes in the future.
---
.../AcaciaZPushPlugin.csproj | 2 +
.../Features/FreeBusy/FeatureFreeBusy.cs | 7 +-
.../Features/FreeBusy/FreeBusyServer.cs | 90 +++----------------
.../Features/FreeBusy/FreeBusyServlet.cs | 84 +++++++++++++++++
.../Features/FreeBusy/Servlet.cs | 49 ++++++++++
5 files changed, 152 insertions(+), 80 deletions(-)
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServlet.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/Servlet.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index 1f3a556..5a2c5e5 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -241,6 +241,8 @@
+
+
Form
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs
index 3601e42..869ef06 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FeatureFreeBusy.cs
@@ -111,9 +111,9 @@ namespace Acacia.Features.FreeBusy
private const string REG_KEY = @"Options\Calendar\Internet Free/Busy";
private const string REG_VALUE = @"Read URL";
- internal const string URL_IDENTIFIER = "/zpush/";
+ internal const string URL_IDENTIFIER = "zpush";
private const int DEFAULT_PORT = 18632;
- private const string URL_PREFIX = @"http://127.0.0.1:{0}" + URL_IDENTIFIER;
+ private const string URL_PREFIX = @"http://127.0.0.1:{0}/" + URL_IDENTIFIER + "/";
private const string URL = URL_PREFIX + "%NAME%@%SERVER%";
private void Worker()
@@ -125,8 +125,9 @@ namespace Acacia.Features.FreeBusy
{
if (key != null)
{
+ // Set only if empty or already our URL
string oldURL = key.GetValueString(REG_VALUE);
- if (string.IsNullOrWhiteSpace(oldURL) || oldURL.Contains(URL_IDENTIFIER))
+ if (string.IsNullOrWhiteSpace(oldURL) || oldURL.Contains("/" + URL_IDENTIFIER + "/"))
key.SetValue(REG_VALUE, string.Format(URL, Port));
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServer.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServer.cs
index e5265c0..9ba69c2 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServer.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServer.cs
@@ -32,15 +32,15 @@ namespace Acacia.Features.FreeBusy
{
public class FreeBusyServer
{
- private readonly FeatureFreeBusy _freeBusy;
- private readonly int _port;
private readonly Regex _httpRequest;
+ public delegate Servlet ServletFactory();
+ private readonly Dictionary _servlets = new Dictionary();
+
public FreeBusyServer(FeatureFreeBusy freeBusy)
{
- this._freeBusy = freeBusy;
- this._port = freeBusy.Port;
- this._httpRequest = new Regex(@"^GET " + FeatureFreeBusy.URL_IDENTIFIER + @"([^ ]+) HTTP/(\d.\d)$");
+ this._httpRequest = new Regex(@"^GET /([^/]+)/([^ ]*) HTTP/(\d.\d)$");
+ _servlets.Add(FeatureFreeBusy.URL_IDENTIFIER, () => new FreeBusyServlet(freeBusy));
}
public void HandleRequest(TcpClient client)
@@ -61,19 +61,17 @@ namespace Acacia.Features.FreeBusy
Logger.Instance.Trace(this, "Invalid request: {0}", s);
throw new InvalidOperationException();
}
- string username = m.Groups[1].Value;
- Logger.Instance.Trace(this, "REQUEST: {0} -> {1}, {2}", s, m.Groups[1], m.Groups[2]);
-
- // Headers
- for (;;)
+ string app = m.Groups[1].Value;
+ ServletFactory factory;
+ if (!_servlets.TryGetValue(app, out factory))
{
- s = reader.ReadLine();
- if (string.IsNullOrEmpty(s))
- break;
+ Logger.Instance.Trace(this, "Unknown servlet: {0} -> {1}", s, app);
+ throw new InvalidOperationException();
}
- // Write response
- FetchData(username, writer);
+ Servlet servlet = factory();
+ servlet.Init(s, m.Groups[2].Value, reader, writer);
+ servlet.Process();
}
catch (InvalidOperationException)
{
@@ -92,67 +90,5 @@ namespace Acacia.Features.FreeBusy
Logger.Instance.Error(this, "Error in FreeBusy worker: {0}", e);
}
}
-
- private void FetchData(string username, StreamWriter output)
- {
- // Request the data from the ZPush server
- ZPushConnection connection = new ZPushConnection(_freeBusy.FindZPushAccount(username), new System.Threading.CancellationToken(false));
-
- // Include yesterday in the request, outlook shows it by default
- var request = new ActiveSync.ResolveRecipientsRequest(username,
- DateTime.Today.AddDays(-1),
- DateTime.Today.AddMonths(6));
- var response = connection.Execute(request);
-
- // If there is no FreeBusy data, return 404
- if (response?.FreeBusy == null)
- {
- throw new InvalidOperationException();
- }
-
- Logger.Instance.Trace(this, "Writing response");
- // Encode the response in vcard format
- output.WriteLine("HTTP/1.0 200 OK");
- output.WriteLine("Content-Type: text/vcard");
- output.WriteLine("Connection: close");
- output.WriteLine("");
-
-
- output.WriteLine("BEGIN:VCALENDAR");
- output.WriteLine("PRODID:-//ZPush//EN");
- output.WriteLine("VERSION:2.0");
- output.WriteLine("BEGIN:VFREEBUSY");
- output.WriteLine("ORGANIZER:" + username);
- output.WriteLine(string.Format("URL:http://127.0.0.1:{0}{1}{2}", _port, FeatureFreeBusy.URL_IDENTIFIER, username));
- output.WriteLine(string.Format("DTSTAMP:{0:" + Constants.DATE_ISO_8601 + "}", DateTime.Now));
- output.WriteLine(string.Format("DTSTART:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.StartTime));
- output.WriteLine(string.Format("DTEND:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.EndTime));
-
- foreach(ActiveSync.FreeBusyData data in response.FreeBusy)
- {
- if (data.Type != ActiveSync.FreeBusyType.Free)
- {
- string freeBusy = string.Format("FREEBUSY;FBTYPE={2}:{0:" + Constants.DATE_ISO_8601 + "}/{1:" + Constants.DATE_ISO_8601 + "}",
- data.Start, data.End, MapType(data.Type));
- output.WriteLine(freeBusy);
- }
- }
-
- output.WriteLine("END:VFREEBUSY");
- output.WriteLine("END:VCALENDAR");
- }
-
- private object MapType(ActiveSync.FreeBusyType type)
- {
- switch(type)
- {
- case ActiveSync.FreeBusyType.Free: return "FREE";
- case ActiveSync.FreeBusyType.Busy: return "BUSY";
- case ActiveSync.FreeBusyType.Tentative: return "BUSY-TENTATIVE";
- case ActiveSync.FreeBusyType.OutOfOffice: return "BUSY-UNAVAILABLE";
- default:
- return "BUSY";
- }
- }
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServlet.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServlet.cs
new file mode 100644
index 0000000..8bed6c2
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/FreeBusyServlet.cs
@@ -0,0 +1,84 @@
+using Acacia.Utils;
+using Acacia.ZPush.Connect;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Features.FreeBusy
+{
+ public class FreeBusyServlet : Servlet
+ {
+ private readonly FeatureFreeBusy _freeBusy;
+
+ public FreeBusyServlet(FeatureFreeBusy freeBusy)
+ {
+ this._freeBusy = freeBusy;
+ }
+
+ protected override void ProcessRequest()
+ {
+ string username = Url;
+ // Request the data from the ZPush server
+ ZPushConnection connection = new ZPushConnection(_freeBusy.FindZPushAccount(username), new System.Threading.CancellationToken(false));
+
+ // Include yesterday in the request, outlook shows it by default
+ var request = new ActiveSync.ResolveRecipientsRequest(username,
+ DateTime.Today.AddDays(-1),
+ DateTime.Today.AddMonths(6));
+ var response = connection.Execute(request);
+
+ // If there is no FreeBusy data, return 404
+ if (response?.FreeBusy == null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ Logger.Instance.Trace(this, "Writing response");
+ // Encode the response in vcard format
+ Out.WriteLine("HTTP/1.0 200 OK");
+ Out.WriteLine("Content-Type: text/vcard");
+ Out.WriteLine("Connection: close");
+ Out.WriteLine("");
+
+
+ Out.WriteLine("BEGIN:VCALENDAR");
+ Out.WriteLine("PRODID:-//ZPush//EN");
+ Out.WriteLine("VERSION:2.0");
+ Out.WriteLine("BEGIN:VFREEBUSY");
+ Out.WriteLine("ORGANIZER:" + username);
+ Out.WriteLine(string.Format("URL:http://127.0.0.1:{0}/{1}/{2}", _freeBusy.Port, FeatureFreeBusy.URL_IDENTIFIER, username));
+ Out.WriteLine(string.Format("DTSTAMP:{0:" + Constants.DATE_ISO_8601 + "}", DateTime.Now));
+ Out.WriteLine(string.Format("DTSTART:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.StartTime));
+ Out.WriteLine(string.Format("DTEND:{0:" + Constants.DATE_ISO_8601 + "}", response.FreeBusy.EndTime));
+
+ foreach (ActiveSync.FreeBusyData data in response.FreeBusy)
+ {
+ if (data.Type != ActiveSync.FreeBusyType.Free)
+ {
+ string freeBusy = string.Format("FREEBUSY;FBTYPE={2}:{0:" + Constants.DATE_ISO_8601 + "}/{1:" + Constants.DATE_ISO_8601 + "}",
+ data.Start, data.End, MapType(data.Type));
+ Out.WriteLine(freeBusy);
+ }
+ }
+
+ Out.WriteLine("END:VFREEBUSY");
+ Out.WriteLine("END:VCALENDAR");
+ }
+
+ private object MapType(ActiveSync.FreeBusyType type)
+ {
+ switch (type)
+ {
+ case ActiveSync.FreeBusyType.Free: return "FREE";
+ case ActiveSync.FreeBusyType.Busy: return "BUSY";
+ case ActiveSync.FreeBusyType.Tentative: return "BUSY-TENTATIVE";
+ case ActiveSync.FreeBusyType.OutOfOffice: return "BUSY-UNAVAILABLE";
+ default:
+ return "BUSY";
+ }
+ }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/Servlet.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/Servlet.cs
new file mode 100644
index 0000000..12b993d
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/FreeBusy/Servlet.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Features.FreeBusy
+{
+ abstract public class Servlet
+ {
+ private string _request;
+ protected string Url { get; private set; }
+ protected StreamReader In { get; private set; }
+ protected StreamWriter Out { get; private set; }
+
+ public void Init(string request, string url, StreamReader reader, StreamWriter writer)
+ {
+ this._request = request;
+ this.Url = url;
+ this.In = reader;
+ this.Out = writer;
+ }
+
+ public void Process()
+ {
+ ProcessHeaders();
+ ProcessRequest();
+ }
+
+ virtual protected void ProcessHeaders()
+ {
+ for (;;)
+ {
+ string s = In.ReadLine();
+ if (string.IsNullOrEmpty(s))
+ break;
+ ProcessHeader(s);
+ }
+ }
+
+ virtual protected void ProcessHeader(string s)
+ {
+
+ }
+
+ abstract protected void ProcessRequest();
+ }
+}
From a1117d6b44cac521a183c50ca178eaaa6271e10b Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 15 Mar 2017 11:22:19 +0100
Subject: [PATCH 2/4] Removed whitespace
---
.../AcaciaZPushPlugin/Features/GAB/GABHandler.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs
index c6092c9..a55c4a3 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/GAB/GABHandler.cs
@@ -580,7 +580,6 @@ namespace Acacia.Features.GAB
// Update the groups
AddItemToGroups(contact, id, value, index);
}
-
}
private void CreateGroup(string id, Dictionary value, ChunkIndex index)
From 70d9d00ea8a922497423fcabc26d15fe3d6b628b Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 15 Mar 2017 11:27:35 +0100
Subject: [PATCH 3/4] [KOE-87] Changed the event used to initialize the plugin.
The original event was sometimes invoked after the ribbon was initialised,
which meant buttons did not show up.
---
.../AcaciaZPushPlugin/ThisAddIn.cs | 34 +++++++------------
1 file changed, 12 insertions(+), 22 deletions(-)
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs
index bc60279..c622f44 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs
@@ -29,6 +29,7 @@ using System.Globalization;
using Acacia.UI.Outlook;
using Acacia.Stubs;
using Acacia.Stubs.OutlookWrappers;
+using Microsoft.Office.Tools.Ribbon;
namespace Acacia
{
@@ -77,7 +78,16 @@ namespace Acacia
#region Startup / Shutdown
- private void ThisAddIn_Startup(object sender, System.EventArgs args)
+ private void InternalStartup()
+ {
+ // Do nothing
+ }
+
+ ///
+ /// [KOE-87] Use this event rather than Startup, as that is invoked after the ribbon UI is queried, which leads
+ /// to an empty - and therefore invisible - ribbon.
+ ///
+ public override void BeginInit()
{
try
{
@@ -145,7 +155,6 @@ namespace Acacia
Acacia.Features.DebugSupport.Statistics.StartupTime.Stop();
foreach (Feature feature in Features)
feature.AfterStartup();
-
}
catch (System.Exception e)
{
@@ -171,12 +180,6 @@ namespace Acacia
}
}
- private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
- {
- // Note: Outlook no longer raises this event. If you have code that
- // must run when Outlook shuts down, see http://go.microsoft.com/fwlink/?LinkId=506785
- }
-
#endregion
#region Ribbons
@@ -209,19 +212,6 @@ namespace Acacia
}
#endregion
-
- #region VSTO generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InternalStartup()
- {
- this.Startup += new System.EventHandler(ThisAddIn_Startup);
- this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
- }
-
- #endregion
+
}
}
From df356741d52bc6ba8ab181ea5693247a6e16260c Mon Sep 17 00:00:00 2001
From: Patrick Simpson
Date: Wed, 15 Mar 2017 16:13:15 +0100
Subject: [PATCH 4/4] [KOE-23] BCC field is now parsed from the transport
headers and displayed in sent items
---
.../AcaciaZPushPlugin.csproj | 3 +
.../Features/BCC/FeatureBCC.cs | 156 ++++++++++++++++++
.../AcaciaZPushPlugin/Features/Features.cs | 1 +
.../AcaciaZPushPlugin/Stubs/Enums.cs | 9 +
.../AcaciaZPushPlugin/Stubs/IMailItem.cs | 17 ++
.../AcaciaZPushPlugin/Stubs/IRecipient.cs | 3 +
.../AcaciaZPushPlugin/Stubs/IRecipients.cs | 15 ++
.../Stubs/OutlookWrappers/AddInWrapper.cs | 5 +-
.../Stubs/OutlookWrappers/MailItemWrapper.cs | 35 ++++
.../Stubs/OutlookWrappers/RecipientWrapper.cs | 11 ++
.../OutlookWrappers/RecipientsWrapper.cs | 41 +++++
.../AcaciaZPushPlugin/Utils/StringUtil.cs | 56 +++++--
.../ZPush/FolderRegistration.cs | 26 +++
13 files changed, 365 insertions(+), 13 deletions(-)
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/BCC/FeatureBCC.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipients.cs
create mode 100644 src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientsWrapper.cs
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
index 5a2c5e5..b253358 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/AcaciaZPushPlugin.csproj
@@ -241,6 +241,7 @@
+
@@ -311,6 +312,7 @@
+
@@ -325,6 +327,7 @@
+
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/BCC/FeatureBCC.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/BCC/FeatureBCC.cs
new file mode 100644
index 0000000..1d7b615
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/BCC/FeatureBCC.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Acacia.Stubs;
+using static Acacia.DebugOptions;
+using Acacia.ZPush;
+using System.Text.RegularExpressions;
+using System.Net.Mail;
+using Acacia.Utils;
+
+namespace Acacia.Features.BCC
+{
+ [AcaciaOption("Displays the BCC field on sent items.")]
+ public class FeatureBCC : Feature
+ {
+ private readonly FolderRegistration _folderRegistration;
+
+ public FeatureBCC()
+ {
+ _folderRegistration = new FolderRegistrationDefault(this, DefaultFolder.SentMail);
+ }
+
+ public override void Startup()
+ {
+ // TODO: this is very similar to ReplyFlags
+
+ if (UpdateEvents)
+ {
+ // Watch the sent mail folder
+ Watcher.WatchFolder(_folderRegistration,
+ (folder) => Watcher.WatchItems(folder, CheckBCC, false)
+ );
+ }
+
+ if (ReadEvent)
+ {
+ // As a fallback, add an event handler to update the message when displaying it
+ if (MailEvents != null)
+ {
+ MailEvents.Read += (mail) =>
+ {
+ // Check we're in the SentMail folder
+ using (IFolder folder = mail.Parent)
+ {
+ if (_folderRegistration.IsApplicable(folder))
+ CheckBCC(mail);
+ }
+ };
+ }
+ }
+ }
+
+ private static readonly Regex RE_BCC = new Regex("(?m)^Bcc:[ \t]*(([^\r\n]|\r\n[ \t]+)*)\r\n");
+ private static readonly Regex RE_BCC_NAME_EMAIL = new Regex("([^<>]*)[ \t]*<(.*)>");
+
+ private void CheckBCC(IMailItem mail)
+ {
+ // If the item already has a BCC, assume it's correct
+ if (!string.IsNullOrEmpty(mail.BCC))
+ return;
+
+ // Grab the transport headers
+ string headers = (string)mail.GetProperty(OutlookConstants.PR_TRANSPORT_MESSAGE_HEADERS);
+ if (string.IsNullOrEmpty(headers))
+ return;
+
+ // Check if there's a bcc header
+ Match match = RE_BCC.Match(headers);
+ if (match.Groups.Count < 2)
+ return;
+ string bcc = match.Groups[1].Value;
+ if (string.IsNullOrEmpty(bcc))
+ return;
+
+ // Add the recipient
+ string decoded = bcc.DecodeQuotedPrintable();
+ try
+ {
+ using (IRecipients recipients = mail.Recipients)
+ {
+ using (IRecipient recip = CreateRecipient(recipients, decoded))
+ {
+ recip.Type = MailRecipientType.BCC;
+ }
+ }
+ }
+ finally
+ {
+ mail.Save();
+ }
+ }
+
+ private IRecipient CreateRecipient(IRecipients recipients, string decoded)
+ {
+ // First try to resolve directly
+ IRecipient recipient = recipients.Add(decoded);
+ if (recipient.Resolve())
+ return recipient;
+
+ // Nope, remove and create with email
+ recipient.Dispose();
+ recipient = null;
+ recipients.Remove(recipients.Count - 1);
+
+ string displayName;
+ string email = ParseBCCHeader(decoded, out displayName);
+
+ // TODO: is it possible to use the display name?
+ recipient = recipients.Add(email);
+ recipient.Resolve();
+ return recipient;
+ }
+
+ // TODO: this is probably generally useful
+ private string ParseBCCHeader(string bcc, out string displayName)
+ {
+ Match match = RE_BCC_NAME_EMAIL.Match(bcc);
+ if (match.Groups.Count > 1)
+ {
+ displayName = match.Groups[1].Value;
+ return match.Groups[2].Value;
+ }
+ else
+ {
+ displayName = null;
+ return bcc;
+ }
+ }
+
+ #region Debug options
+
+ [AcaciaOption("Enables or disables the handling of read events on mail items. If this is enabled, " +
+ "the BCC field is checked. This is almost guaranteed to work, but has the downside " +
+ "of only setting the BCC field when an email is opened.")]
+ public bool ReadEvent
+ {
+ get { return GetOption(OPTION_READ_EVENT); }
+ set { SetOption(OPTION_READ_EVENT, value); }
+ }
+ private static readonly BoolOption OPTION_READ_EVENT = new BoolOption("ReadEvents", true);
+
+ [AcaciaOption("Enables or disables the handling of update events to mail items. When a mail item is " +
+ "updated, it is checked to see if the BCC field needs to be set. This is the main " +
+ "mechanism for setting the BCC field.")]
+ public bool UpdateEvents
+ {
+ get { return GetOption(OPTION_UPDATE_EVENTS); }
+ set { SetOption(OPTION_UPDATE_EVENTS, value); }
+ }
+ private static readonly BoolOption OPTION_UPDATE_EVENTS = new BoolOption("FolderEvents", true);
+
+ #endregion
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Features.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Features.cs
index 1b8c6a3..fad252a 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Features.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Features.cs
@@ -27,6 +27,7 @@ namespace Acacia.Features
public static readonly Type[] FEATURES =
{
typeof(ReplyFlags.FeatureReplyFlags),
+ typeof(BCC.FeatureBCC),
typeof(OutOfOffice.FeatureOutOfOffice),
typeof(SharedFolders.FeatureSharedFolders),
typeof(WebApp.FeatureWebApp),
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs
index 4c13a37..deadd05 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/Enums.cs
@@ -75,4 +75,13 @@ namespace Acacia.Stubs
EAS,
Other
}
+
+ // Replacement for OlMailRecipientType
+ public enum MailRecipientType
+ {
+ Originator = 0,
+ To = 1,
+ CC = 2,
+ BCC = 3
+ }
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IMailItem.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IMailItem.cs
index 5f775fe..7f33495 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IMailItem.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IMailItem.cs
@@ -27,9 +27,15 @@ namespace Acacia.Stubs
///
public interface IMailItem : IItem
{
+ #region Reply verbs
+
DateTime? AttrLastVerbExecutionTime { get; set; }
int AttrLastVerbExecuted { get; set; }
+ #endregion
+
+ #region Sender
+
string SenderEmailAddress { get; }
string SenderName { get; }
@@ -38,5 +44,16 @@ namespace Acacia.Stubs
///
/// The address. The caller is responsible for disposing.
void SetSender(IAddressEntry addressEntry);
+
+ #endregion
+
+ #region Recipients
+
+ string To { get; set; }
+ string CC { get; set; }
+ string BCC { get; set; }
+ IRecipients Recipients { get; }
+
+ #endregion
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipient.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipient.cs
index 0127eb9..27ca079 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipient.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipient.cs
@@ -24,11 +24,14 @@ namespace Acacia.Stubs
{
public interface IRecipient : IComWrapper
{
+ bool Resolve();
bool IsResolved { get; }
string Name { get; }
string Address { get; }
+ MailRecipientType Type { get; set; }
+
///
/// Returns the address entry. The caller is responsible for disposing it.
///
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipients.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipients.cs
new file mode 100644
index 0000000..b3eeff6
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/IRecipients.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Acacia.Stubs
+{
+ public interface IRecipients : IComWrapper, IEnumerable
+ {
+ int Count { get; }
+ void Remove(int index);
+ IRecipient Add(string name);
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs
index 64c72c5..ab6daca 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/AddInWrapper.cs
@@ -347,8 +347,9 @@ namespace Acacia.Stubs.OutlookWrappers
NSOutlook.Recipient recipient = com.Add(session.CreateRecipient(name));
if (recipient == null)
return null;
- recipient.Resolve();
- return Mapping.Wrap(com.Remove(recipient));
+ IRecipient wrapped = Mapping.Wrap(com.Remove(recipient));
+ wrapped.Resolve();
+ return wrapped;
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs
index 9572987..02e126f 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/MailItemWrapper.cs
@@ -34,6 +34,8 @@ namespace Acacia.Stubs.OutlookWrappers
#region IMailItem implementation
+ #region Reply verbs
+
public DateTime? AttrLastVerbExecutionTime
{
get
@@ -58,6 +60,10 @@ namespace Acacia.Stubs.OutlookWrappers
}
}
+ #endregion
+
+ #region Sender
+
public string SenderEmailAddress
{
get
@@ -88,6 +94,35 @@ namespace Acacia.Stubs.OutlookWrappers
#endregion
+ #region Recipients
+
+ public string To
+ {
+ get { return _item.To; }
+ set { _item.To = value; }
+ }
+
+ public string CC
+ {
+ get { return _item.CC; }
+ set { _item.CC = value; }
+ }
+
+ public string BCC
+ {
+ get { return _item.BCC; }
+ set { _item.BCC = value; }
+ }
+
+ public IRecipients Recipients
+ {
+ get { return new RecipientsWrapper(_item.Recipients); }
+ }
+
+ #endregion
+
+ #endregion
+
#region Wrapper methods
protected override NSOutlook.UserProperties GetUserProperties()
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientWrapper.cs
index b589f76..82749b9 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientWrapper.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientWrapper.cs
@@ -32,6 +32,17 @@ namespace Acacia.Stubs.OutlookWrappers
internal NSOutlook.Recipient RawItem { get { return _item; } }
+ public MailRecipientType Type
+ {
+ get { return (MailRecipientType)_item.Type; }
+ set { _item.Type = (int)value; }
+ }
+
+ public bool Resolve()
+ {
+ return _item.Resolve();
+ }
+
public bool IsResolved
{
get
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientsWrapper.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientsWrapper.cs
new file mode 100644
index 0000000..31d85c2
--- /dev/null
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Stubs/OutlookWrappers/RecipientsWrapper.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using NSOutlook = Microsoft.Office.Interop.Outlook;
+
+namespace Acacia.Stubs.OutlookWrappers
+{
+ class RecipientsWrapper : ComWrapper, IRecipients
+ {
+ public RecipientsWrapper(NSOutlook.Recipients item) : base(item)
+ {
+ }
+
+ public int Count { get { return _item.Count; } }
+
+ public void Remove(int index)
+ {
+ _item.Remove(index + 1);
+ }
+
+ public IRecipient Add(string name)
+ {
+ return new RecipientWrapper(_item.Add(name));
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ foreach (NSOutlook.Recipient recipient in _item)
+ yield return new RecipientWrapper(recipient);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ foreach (NSOutlook.Recipient recipient in _item)
+ yield return new RecipientWrapper(recipient);
+ }
+ }
+}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs
index 6d990c0..4d4055b 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/StringUtil.cs
@@ -49,6 +49,7 @@ namespace Acacia.Utils
return _this;
}
+
#endregion
#region Hex strings
@@ -112,19 +113,43 @@ namespace Acacia.Utils
public static string ReplaceStringTokens(this string s, string open, string close, TokenReplacer replacer)
{
- return Regex.Replace(s, Regex.Escape(open) + @"(\w+)" + Regex.Escape(close), (m) =>
+ StringBuilder replaced = new StringBuilder();
+
+ int start = 0;
+ for(;;)
{
- var key = m.Groups[1].Value;
+ // Find open token
+ int newStart = s.IndexOf(open, start);
+
+ // Not found, append rest and done
+ if (newStart < 0)
+ {
+ replaced.Append(s.Substring(start));
+ break;
+ }
+
+ // Append current text
+ replaced.Append(s.Substring(start, newStart - start));
+
+ // Find the close token
+ int keyStart = newStart + open.Length;
+ int newClose = s.IndexOf(close, keyStart);
+ if (newClose < 0)
+ {
+ break;
+ }
+
+ // Add the replacement
+ string key = s.Substring(keyStart, newClose - keyStart);
string replacement = replacer(key);
- if (replacement == null)
- {
- return m.Groups[0].Value;
- }
- else
- {
- return replacement;
- }
- });
+ if (replacement != null)
+ replaced.Append(replacement);
+
+ // Next
+ start = newClose + close.Length;
+ }
+
+ return replaced.ToString();
}
public static string ReplaceStringTokens(this string s, string open, string close, Dictionary replacements)
@@ -138,5 +163,14 @@ namespace Acacia.Utils
}
#endregion
+
+
+ public static string DecodeQuotedPrintable(this string _this)
+ {
+ return ReplaceStringTokens(_this, "=?", "?=", (token) =>
+ System.Net.Mail.Attachment.CreateAttachmentFromString("", "=?" + token + "?=").Name
+ );
+ }
+
}
}
diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/FolderRegistration.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/FolderRegistration.cs
index d3f6938..ecb9175 100644
--- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/FolderRegistration.cs
+++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ZPush/FolderRegistration.cs
@@ -57,4 +57,30 @@ namespace Acacia.ZPush
return Feature.Name + ":" + _itemType.ToString();
}
}
+
+ public class FolderRegistrationDefault : FolderRegistration
+ {
+ private readonly DefaultFolder _folder;
+
+ public FolderRegistrationDefault(Feature feature, DefaultFolder folder)
+ :
+ base(feature)
+ {
+ this._folder = folder;
+ }
+
+ public override bool IsApplicable(IFolder folder)
+ {
+ // TODO: cache folder id per store
+ using (IStore store = folder.GetStore())
+ {
+ return folder.EntryID == store.GetDefaultFolderId(_folder);
+ }
+ }
+
+ public override string ToString()
+ {
+ return Feature.Name + ":" + _folder.ToString();
+ }
+ }
}