From 35ef86d5f05c1ba6d90bb62c170f49a1d86f6b1f Mon Sep 17 00:00:00 2001 From: Patrick Simpson Date: Wed, 15 Mar 2017 09:23:47 +0100 Subject: [PATCH] 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(); + } +}