Small clean up to FreeBusy webserver to allow it to be used for different purposes in the future.

This commit is contained in:
Patrick Simpson 2017-03-15 09:23:47 +01:00
parent 0fa8b2b949
commit 35ef86d5f0
5 changed files with 152 additions and 80 deletions

View File

@ -241,6 +241,8 @@
<Compile Include="Controls\KUITask.cs" />
<Compile Include="Controls\KUIUtil.cs" />
<Compile Include="DebugOptions.cs" />
<Compile Include="Features\FreeBusy\FreeBusyServlet.cs" />
<Compile Include="Features\FreeBusy\Servlet.cs" />
<Compile Include="Features\SecondaryContacts\FeatureSecondaryContacts.cs" />
<Compile Include="Features\DebugSupport\AboutDialog.cs">
<SubType>Form</SubType>

View File

@ -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));
}
}

View File

@ -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<string, ServletFactory> _servlets = new Dictionary<string, ServletFactory>();
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";
}
}
}
}

View File

@ -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";
}
}
}
}

View File

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