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();
+ }
+}