diff --git a/.gitignore b/.gitignore
index 2c05782..d613452 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,9 @@ examples/fileDav/dub.selections.json
.buildpath
.settings/org.dsource.ddt.ide.core.prefs
+
+examples/fileDav/dub.selections.json
+
+examples/fileDav/dub.selections.json
+
+examples/fileDav/dub.selections.json
diff --git a/README.md b/README.md
index 2d011ff..b44cd1a 100644
--- a/README.md
+++ b/README.md
@@ -83,3 +83,4 @@ auto router = new URLRouter;
* Improve XML support (eg: change xml node format from "name:DAV:" to "{DAV:}name")
* Add DB support
* Add migration tools from https://github.com/Kozea/Radicale
+* Maybe update the @ResourceProperty... Structs to something more general like @ResourceProperty!"%value"()
diff --git a/dub.json b/dub.json
index 4c938db..054b322 100644
--- a/dub.json
+++ b/dub.json
@@ -5,7 +5,7 @@
"authors": ["Szabo Bogdan"],
"license": "MIT",
"dependencies": {
- "vibe-d": "~>0.7.22",
+ "vibe-d": "~>0.7.23",
"tested": "~>0.9.2"
},
diff --git a/examples/webDav/dub.json b/examples/webDav/dub.json
index f22e65c..a38c500 100644
--- a/examples/webDav/dub.json
+++ b/examples/webDav/dub.json
@@ -4,7 +4,7 @@
"copyright": "Copyright © 2015, Szabo Bogdan",
"authors": ["Szabo Bogdan"],
"dependencies": {
- "vibe-d": "~>0.7.19",
+ "vibe-d": "~>0.7.23",
"vibe-dav": {
"version": "~>0.2.0",
"path": "../.."
diff --git a/examples/webDav/public/calendar/admin/personal/6837CB82-AE37-4E57-9C89-D39D62BBBC01.ics b/examples/webDav/public/calendar/admin/personal/6837CB82-AE37-4E57-9C89-D39D62BBBC01.ics
deleted file mode 100644
index 30c467a..0000000
--- a/examples/webDav/public/calendar/admin/personal/6837CB82-AE37-4E57-9C89-D39D62BBBC01.ics
+++ /dev/null
@@ -1,41 +0,0 @@
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//Mac OS X 10.10.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:Europe/Stockholm
-BEGIN:DAYLIGHT
-TZOFFSETFROM:+0100
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
-DTSTART:19810329T020000
-TZNAME:CEST
-TZOFFSETTO:+0200
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:+0200
-RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
-DTSTART:19961027T030000
-TZNAME:CET
-TZOFFSETTO:+0100
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-TRANSP:OPAQUE
-DTEND;TZID=Europe/Stockholm:20150408T170000
-PRIORITY:0
-UID:6837CB82-AE37-4E57-9C89-D39D62BBBC01
-DTSTAMP:20150407T131647Z
-X-YAHOO-USER-STATUS:BUSY
-X-YAHOO-USER-STATUS:BUSY
-STATUS:CONFIRMED
-SEQUENCE:0
-CLASS:PUBLIC
-X-YAHOO-YID:szabobogdan
-X-YAHOO-YID:szabobogdan
-X-YAHOO-EVENT-STATUS:BUSY
-X-YAHOO-EVENT-STATUS:BUSY
-SUMMARY:test event
-DTSTART;TZID=Europe/Stockholm:20150407T160000
-CREATED:20150407T131647Z
-END:VEVENT
-END:VCALENDAR
diff --git a/examples/webDav/public/calendar/admin/personal/7B508AD5-FE51-4C7B-8E21-0104F5412923.ics b/examples/webDav/public/calendar/admin/personal/7B508AD5-FE51-4C7B-8E21-0104F5412923.ics
deleted file mode 100644
index 8d3926d..0000000
--- a/examples/webDav/public/calendar/admin/personal/7B508AD5-FE51-4C7B-8E21-0104F5412923.ics
+++ /dev/null
@@ -1,32 +0,0 @@
-BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//Mac OS X 10.10.3//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:Europe/Stockholm
-BEGIN:DAYLIGHT
-TZOFFSETFROM:+0100
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
-DTSTART:19810329T020000
-TZNAME:CEST
-TZOFFSETTO:+0200
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:+0200
-RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
-DTSTART:19961027T030000
-TZNAME:CET
-TZOFFSETTO:+0100
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20150407T194949Z
-UID:7B508AD5-FE51-4C7B-8E21-0104F5412923
-DTEND;TZID=Europe/Stockholm:20150422T100000
-TRANSP:OPAQUE
-SUMMARY:cucu
-DTSTART;TZID=Europe/Stockholm:20150422T090000
-DTSTAMP:20150407T194949Z
-SEQUENCE:0
-END:VEVENT
-END:VCALENDAR
diff --git a/examples/webDav/public/calendar/admin/personal/main.ics b/examples/webDav/public/calendar/admin/personal/main.ics
deleted file mode 100644
index 2012da3..0000000
--- a/examples/webDav/public/calendar/admin/personal/main.ics
+++ /dev/null
@@ -1,77 +0,0 @@
-BEGIN:VCALENDAR
-METHOD:PUBLISH
-VERSION:2.0
-X-WR-CALNAME:Home
-X-WR-CALDESC:
-X-APPLE-CALENDAR-COLOR:#1BADF8
-X-WR-TIMEZONE:Europe/Bucharest
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:Europe/Bucharest
-BEGIN:DAYLIGHT
-TZOFFSETFROM:+0200
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
-DTSTART:19970330T030000
-TZNAME:EEST
-TZOFFSETTO:+0300
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:+0300
-RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
-DTSTART:19971026T040000
-TZNAME:EET
-TZOFFSETTO:+0200
-END:STANDARD
-END:VTIMEZONE
-BEGIN:VEVENT
-CREATED:20140214T081455Z
-UID:489F8C95-B5B3-49E3-BA4D-0CE0F820E022
-DTEND;TZID=Europe/Bucharest:20140214T120000
-TRANSP:OPAQUE
-SUMMARY:New Event
-DTSTART;TZID=Europe/Bucharest:20140214T110000
-DTSTAMP:20140214T081455Z
-SEQUENCE:1
-END:VEVENT
-BEGIN:VEVENT
-CREATED:20140912T181025Z
-UID:BAF9FA4C-C758-4E73-87C2-847A879AC884
-DTEND;TZID=Europe/Bucharest:20200724T130000
-TRANSP:OPAQUE
-SUMMARY:New Event
-DTSTART;TZID=Europe/Bucharest:20200724T120000
-DTSTAMP:20140911T200404Z
-LAST-MODIFIED:20140131T143908Z
-SEQUENCE:0
-END:VEVENT
-BEGIN:VEVENT
-CREATED:20140912T181025Z
-UID:349C7C65-CD2F-44B6-8467-FF9C22F58832
-RRULE:FREQ=WEEKLY;COUNT=1
-DTEND;TZID=Europe/Bucharest:20140212T130000
-TRANSP:OPAQUE
-SUMMARY:New Event
-DTSTART;TZID=Europe/Bucharest:20140212T120000
-DTSTAMP:20140911T200404Z
-LAST-MODIFIED:20140201T082515Z
-SEQUENCE:0
-BEGIN:VALARM
-X-WR-ALARMUID:95787692-6F0A-48A3-B4FE-B4C4CB0540F7
-UID:95787692-6F0A-48A3-B4FE-B4C4CB0540F7
-TRIGGER:-PT30M
-DESCRIPTION:Event reminder
-ACTION:DISPLAY
-END:VALARM
-END:VEVENT
-BEGIN:VEVENT
-CREATED:20140912T181025Z
-UID:8FC79E15-43C5-409A-B541-4DD21F2E9C89
-DTEND;TZID=Europe/Bucharest:20131230T130000
-TRANSP:OPAQUE
-SUMMARY:Eveniment nou
-DTSTART;TZID=Europe/Bucharest:20131230T120000
-DTSTAMP:20140911T200404Z
-LAST-MODIFIED:20140131T124833Z
-SEQUENCE:0
-END:VEVENT
-END:VCALENDAR
diff --git a/examples/webDav/public/principals/admin/calendars/Personal/4AF119BE-84A2-462C-9F8A-529F01454B18.ics b/examples/webDav/public/principals/admin/calendars/Personal/4AF119BE-84A2-462C-9F8A-529F01454B18.ics
new file mode 100644
index 0000000..1824c02
--- /dev/null
+++ b/examples/webDav/public/principals/admin/calendars/Personal/4AF119BE-84A2-462C-9F8A-529F01454B18.ics
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//Mac OS X 10.10.3//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:Europe/Bucharest
+BEGIN:DAYLIGHT
+TZOFFSETFROM:+0200
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
+DTSTART:19970330T030000
+TZNAME:EEST
+TZOFFSETTO:+0300
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:+0300
+RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
+DTSTART:19971026T040000
+TZNAME:EET
+TZOFFSETTO:+0200
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20150426T104512Z
+UID:4AF119BE-84A2-462C-9F8A-529F01454B18
+DTEND;TZID=Europe/Bucharest:20150415T100000
+TRANSP:OPAQUE
+SUMMARY:New Event
+DTSTART;TZID=Europe/Bucharest:20150415T090000
+DTSTAMP:20150426T104512Z
+SEQUENCE:0
+END:VEVENT
+END:VCALENDAR
diff --git a/examples/webDav/source/app.d b/examples/webDav/source/app.d
index bd0c876..2007a1d 100644
--- a/examples/webDav/source/app.d
+++ b/examples/webDav/source/app.d
@@ -4,28 +4,17 @@
* License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
* Copyright: Public Domain
*/
-import vibe.core.file;
-import vibe.core.log;
-import vibe.inet.message;
-import vibe.inet.mimetypes;
import vibe.http.router : URLRouter;
import vibe.http.server;
-import vibe.http.fileserver;
import vibe.http.auth.basic_auth;
import vibedav.filedav;
+import vibedav.acldav;
import vibedav.caldav;
-import vibedav.user;
-import vibedav.prop;
-import vibedav.userhome;
+import vibedav.syncdav;
import core.time;
-import std.conv : to;
import std.stdio;
-import std.file;
-import std.path;
-import std.digest.md;
-import std.datetime;
import std.functional : toDelegate;
bool checkPassword(string user, string password)
@@ -35,29 +24,21 @@ bool checkPassword(string user, string password)
shared static this()
{
- writeln("Starting WebDav server.");
+ writeln("Start Kangal server");
auto router = new URLRouter;
// now any request is matched and checked for authentication:
- router.any("/calendar/*", performBasicAuth("Site Realm", toDelegate(&checkPassword)));
+ router.any("/principals/*", performBasicAuth("Site Realm", toDelegate(&checkPassword)));
- auto userConnection = new BaseCalDavUserCollection;
+ auto dav = router.serveFileDav("", "public");
- alias factory = FileDavResourceFactory!(
- "calendar", "public/calendar",
- "", FileDavCollection, FileDavResource,
- ":user/", FileDavCollection, FileDavResource,
- ":user/inbox", FileDavCollection, FileDavResource,
- ":user/outbox", FileDavCollection, FileDavResource,
- ":user/personal", FileDavCalendarCollection, FileDavCalendarResource
- );
-
- router.serveFileDav!("/files/", "public/files/")(userConnection);
- router.serveFileDav!factory(userConnection);
+ new ACLDavPlugin(dav);
+ new CalDavPlugin(dav);
+ new SyncDavPlugin(dav);
auto settings = new HTTPServerSettings;
settings.port = 8080;
- settings.bindAddresses = ["::1", "127.0.0.1"];
+ settings.bindAddresses = ["::1", "127.0.0.1", "192.168.0.13", "192.168.0.100"];
listenHTTP(settings, router);
}
diff --git a/source/vibedav/base.d b/source/vibedav/base.d
index bd8cb87..fd9840d 100644
--- a/source/vibedav/base.d
+++ b/source/vibedav/base.d
@@ -57,6 +57,67 @@ class DavException : Exception {
}
}
+struct DavReport {
+ string name;
+ string ns;
+}
+
+string reportName(DavProp reportBody) {
+ if(reportBody[0].name == "?xml")
+ return reportBody[1].tagName ~ ":" ~ reportBody[1].namespace;
+
+ return reportBody[0].tagName ~ ":" ~ reportBody[0].namespace;
+}
+
+DavReport getReportProperty(T...)() {
+ static if(T.length == 0)
+ static assert(false, "There is no `@DavReport` attribute.");
+ else static if( is(typeof(T[0]) == DavReport) )
+ return T[0];
+ else
+ return getResourceProperty!(T[1..$]);
+}
+
+bool hasDavReport(I)(string key) {
+ bool result = false;
+
+ void keyExist(T...)() {
+ static if(T.length > 0) {
+ enum val = getReportProperty!(__traits(getAttributes, __traits(getMember, I, T[0])));
+ enum staticKey = val.name ~ ":" ~ val.ns;
+
+ if(staticKey == key)
+ result = true;
+
+ keyExist!(T[1..$])();
+ }
+ }
+
+ keyExist!(__traits(allMembers, I))();
+
+ return result;
+}
+
+void getDavReport(I)(I plugin, DavRequest request, DavResponse response) {
+ string key = request.content.reportName;
+
+ void getProp(T...)() {
+ static if(T.length > 0) {
+ enum val = getReportProperty!(__traits(getAttributes, __traits(getMember, I, T[0])));
+ enum staticKey = val.name ~ ":" ~ val.ns;
+
+
+ if(staticKey == key) {
+ __traits(getMember, plugin, T[0])(request, response);
+ }
+
+ getProp!(T[1..$])();
+ }
+ }
+
+ getProp!(__traits(allMembers, I))();
+}
+
interface IDavResourceAccess {
bool exists(URL url, string username);
bool canCreateCollection(URL url, string username);
@@ -71,6 +132,12 @@ interface IDavResourceAccess {
}
interface IDavPlugin : IDavResourceAccess {
+
+ bool hasReport(URL url, string username, string name);
+ void report(DavRequest request, DavResponse response);
+
+ void notice(string action, DavResource resource);
+
@property {
IDav dav();
string name();
@@ -86,9 +153,10 @@ interface IDavPluginHub {
abstract class BaseDavPlugin : IDavPlugin {
- private IDav _dav;
+ protected IDav _dav;
this(IDav dav) {
+ dav.registerPlugin(this);
_dav = dav;
}
@@ -122,6 +190,18 @@ abstract class BaseDavPlugin : IDavPlugin {
void bindResourcePlugins(DavResource resource) { }
+ bool hasReport(URL url, string username, string name) {
+ return false;
+ }
+
+ void report(DavRequest request, DavResponse response) {
+ throw new DavException(HTTPStatus.internalServerError, "Can't get report.");
+ }
+
+ void notice(string action, DavResource resource) {
+
+ }
+
@property {
IDav dav() {
return _dav;
@@ -146,6 +226,10 @@ interface IDav : IDavResourceAccess, IDavPluginHub {
void copy(DavRequest request, DavResponse response);
void report(DavRequest request, DavResponse response);
+ void notice(string action, DavResource resource);
+
+ DavResource[] getResources(URL url, ulong depth, string username);
+
@property
Path rootUrl();
}
@@ -241,20 +325,23 @@ class Dav : IDav {
void removeResource(URL url, string username) {
foreach_reverse(plugin; plugins)
- if(plugin.exists(url, username))
+ if(plugin.exists(url, username)) {
+ notice("deleted", getResource(url, username));
return plugin.removeResource(url, username);
+ }
throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found.");
}
DavResource getResource(URL url, string username) {
- foreach_reverse(plugin; plugins)
+ foreach_reverse(plugin; plugins) {
if(plugin.exists(url, username)) {
auto res = plugin.getResource(url, username);
bindResourcePlugins(res);
return res;
}
+ }
throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found.");
}
@@ -292,6 +379,8 @@ class Dav : IDav {
auto res = plugin.createCollection(url, username);
bindResourcePlugins(res);
+ notice("created", res);
+
return res;
}
@@ -304,13 +393,14 @@ class Dav : IDav {
auto res = plugin.createResource(url, username);
bindResourcePlugins(res);
+ notice("created", res);
+
return res;
}
throw new DavException(HTTPStatus.methodNotAllowed, "No plugin available.");
}
-
void bindResourcePlugins(DavResource resource) {
foreach(plugin; plugins)
plugin.bindResourcePlugins(resource);
@@ -374,6 +464,36 @@ class Dav : IDav {
response.flush;
}
+ void proppatch(DavRequest request, DavResponse response) {
+ auto ifHeader = request.ifCondition;
+ response.statusCode = HTTPStatus.ok;
+
+ DavStorage.locks.check(request.url, ifHeader);
+ DavResource resource = getResource(request.url, request.username);
+
+ notice("changed", resource);
+
+ auto xmlString = resource.propPatch(request.content);
+
+ response.content = xmlString;
+ response.flush;
+ }
+
+ void report(DavRequest request, DavResponse response) {
+ auto ifHeader = request.ifCondition;
+
+ string report = "";
+
+ foreach_reverse(plugin; plugins) {
+ if(plugin.hasReport(request.url, request.username, request.content.reportName)) {
+ plugin.report(request, response);
+ return;
+ }
+ }
+
+ throw new DavException(HTTPStatus.notFound, "There is no report.");
+ }
+
void lock(DavRequest request, DavResponse response) {
auto ifHeader = request.ifCondition;
@@ -464,6 +584,7 @@ class Dav : IDav {
DavStorage.locks.check(request.url, request.ifCondition);
resource.setContent(request.stream, request.contentLength);
+ notice("changed", resource);
DavStorage.locks.setETag(resource.url, resource.eTag);
@@ -472,19 +593,6 @@ class Dav : IDav {
response.flush;
}
- void proppatch(DavRequest request, DavResponse response) {
- auto ifHeader = request.ifCondition;
- response.statusCode = HTTPStatus.ok;
-
- DavStorage.locks.check(request.url, ifHeader);
- DavResource resource = getResource(request.url, request.username);
-
- auto xmlString = resource.propPatch(request.content);
-
- response.content = xmlString;
- response.flush;
- }
-
void mkcol(DavRequest request, DavResponse response) {
auto ifHeader = request.ifCondition;
@@ -498,6 +606,7 @@ class Dav : IDav {
response.statusCode = HTTPStatus.created;
createCollection(request.url, request.username);
+ notice("created", getResource(request.url, request.username));
response.flush;
}
@@ -533,10 +642,6 @@ class Dav : IDav {
response.flush;
}
- void report(DavRequest request, DavResponse response) {
-
- }
-
void copy(DavRequest request, DavResponse response) {
string username = request.username;
@@ -608,9 +713,15 @@ class Dav : IDav {
}
localCopy(source, destination);
+ notice("changed", destination);
response.flush;
}
+
+ void notice(string action, DavResource resource) {
+ foreach_reverse(plugin; plugins)
+ plugin.notice(action, resource);
+ }
}
/// Hook vibe.d requests to the right DAV method
diff --git a/source/vibedav/caldav.d b/source/vibedav/caldav.d
index a14adb8..99c8277 100644
--- a/source/vibedav/caldav.d
+++ b/source/vibedav/caldav.d
@@ -8,6 +8,7 @@ module vibedav.caldav;
public import vibedav.base;
import vibedav.filedav;
+import vibedav.syncdav;
import vibe.core.file;
import vibe.http.server;
@@ -53,7 +54,6 @@ interface ICalDavProperties {
}
}
-
interface ICalDavCollectionProperties {
@property {
@@ -85,21 +85,40 @@ interface ICalDavCollectionProperties {
@ResourceProperty("max-attendees-per-instance", "urn:ietf:params:xml:ns:caldav")
ulong maxAttendeesPerInstance(DavResource resource);
+ }
+}
- /*
-
-
-
-
- */
+interface ICalDavResourceProperties {
+
+ @property {
+ @ResourceProperty("calendar-data", "urn:ietf:params:xml:ns:caldav")
+ string calendarData(DavResource resource);
}
}
+interface ICalDavReports {
+ @DavReport("free-busy-query", "urn:ietf:params:xml:ns:caldav")
+ void freeBusyQuery(DavRequest request, DavResponse response);
+
+ @DavReport("calendar-query", "urn:ietf:params:xml:ns:caldav")
+ void calendarQuery(DavRequest request, DavResponse response);
+
+ @DavReport("calendar-multiget", "urn:ietf:params:xml:ns:caldav")
+ void calendarMultiget(DavRequest request, DavResponse response);
+}
+
interface ICalDavSchedulingProperties {
// for Inbox
//
+
+ /*
+
+
+
+
+ */
}
private bool matchPluginUrl(URL url, string username) {
@@ -117,7 +136,7 @@ private bool matchPluginUrl(URL url, string username) {
return false;
}
-class CalDavResourcePlugin : BaseDavResourcePlugin, ICalDavProperties, IDavReportSetProperties, IDavBindingProperties {
+class CalDavDataPlugin : BaseDavResourcePlugin, ICalDavProperties, IDavReportSetProperties, IDavBindingProperties {
string[] calendarHomeSet(DavResource resource) {
@@ -201,6 +220,53 @@ class CalDavResourcePlugin : BaseDavResourcePlugin, ICalDavProperties, IDavRepor
}
+class CalDavResourcePlugin : BaseDavResourcePlugin, ICalDavResourceProperties {
+ string calendarData(DavResource resource) {
+
+ auto content = resource.stream;
+ string data;
+
+ while(!content.empty) {
+ auto leastSize = content.leastSize;
+ ubyte[] buf;
+
+ buf.length = leastSize;
+ content.read(buf);
+
+ data ~= buf;
+ }
+
+ return data;
+ }
+
+ override {
+
+ bool canGetProperty(DavResource resource, string name) {
+ if(matchPluginUrl(resource.url, resource.username) && hasDavInterfaceProperty!ICalDavResourceProperties(name))
+ return true;
+
+ return false;
+ }
+
+ DavProp property(DavResource resource, string name) {
+ if(!matchPluginUrl(resource.url, resource.username))
+ throw new DavException(HTTPStatus.internalServerError, "Can't get property.");
+
+ if(hasDavInterfaceProperty!ICalDavResourceProperties(name))
+ return getDavInterfaceProperty!ICalDavResourceProperties(name, this, resource);
+
+ throw new DavException(HTTPStatus.internalServerError, "Can't get property.");
+ }
+ }
+
+
+ @property {
+ string name() {
+ return "CalDavResourcePlugin";
+ }
+ }
+}
+
class CalDavCollectionPlugin : BaseDavResourcePlugin, ICalDavCollectionProperties {
string calendarDescription(DavResource resource) {
@@ -271,24 +337,86 @@ class CalDavCollectionPlugin : BaseDavResourcePlugin, ICalDavCollectionPropertie
}
}
-class CalDavPlugin : BaseDavPlugin {
+class CalDavPlugin : BaseDavPlugin, ICalDavReports {
this(IDav dav) {
super(dav);
}
+ override {
+
+ void bindResourcePlugins(DavResource resource) {
- override void bindResourcePlugins(DavResource resource) {
+ if(!matchPluginUrl(resource.url, resource.username))
+ return;
+
+ resource.registerPlugin(new CalDavDataPlugin);
+ auto path = resource.url.path.toString.stripSlashes;
- if(!matchPluginUrl(resource.url, resource.username))
- return;
+ if(resource.isCollection && path != "principals/" ~ resource.username ~ "/calendars") {
+ resource.resourceType ~= "calendar:urn:ietf:params:xml:ns:caldav";
+ resource.registerPlugin(new CalDavCollectionPlugin);
+ } else if(!resource.isCollection && path.length > 4 && path[$-4..$].toLower == ".ics") {
+ resource.registerPlugin(new CalDavResourcePlugin);
+ }
+ }
- resource.registerPlugin(new CalDavResourcePlugin);
+ bool hasReport(URL url, string username, string name) {
+
+ if(!matchPluginUrl(url, username))
+ return false;
- if(resource.isCollection && resource.url.path.toString.stripSlashes != "principals/" ~ resource.username ~ "/calendars") {
- resource.resourceType ~= "calendar:urn:ietf:params:xml:ns:caldav";
- resource.registerPlugin(new CalDavCollectionPlugin);
+ if(hasDavReport!ICalDavReports(name))
+ return true;
+
+ return false;
}
+
+ void report(DavRequest request, DavResponse response) {
+ if(!matchPluginUrl(request.url, request.username) || !hasDavReport!ICalDavReports(request.content.reportName))
+ throw new DavException(HTTPStatus.internalServerError, "Can't get report.");
+
+ getDavReport!ICalDavReports(this, request, response);
+ }
+ }
+
+ void freeBusyQuery(DavRequest request, DavResponse response) {
+ throw new DavException(HTTPStatus.internalServerError, "Not Implemented");
+ }
+
+ void calendarQuery(DavRequest request, DavResponse response) {
+ throw new DavException(HTTPStatus.internalServerError, "Not Implemented");
+ }
+
+ void calendarMultiget(DavRequest request, DavResponse response) {
+ response.mimeType = "application/xml";
+ response.statusCode = HTTPStatus.multiStatus;
+ auto reportData = request.content;
+
+ bool[string] requestedProperties;
+
+ foreach(name, p; reportData["calendar-multiget"]["prop"])
+ requestedProperties[name] = true;
+
+ DavResource[] list;
+
+ auto hrefList = [ reportData["calendar-multiget"] ].getTagChilds("href");
+
+ HTTPStatus[string] resourceStatus;
+
+ foreach(p; hrefList) {
+ string path = p.value;
+
+ try {
+ list ~= dav.getResource(URL(path), request.username);
+ resourceStatus[path] = HTTPStatus.ok;
+ } catch(DavException e) {
+ resourceStatus[path] = e.status;
+ }
+ }
+
+ response.setPropContent(list, requestedProperties, resourceStatus);
+ response.flush;
}
@property {
diff --git a/source/vibedav/davresource.d b/source/vibedav/davresource.d
index 55817cd..8a77b9c 100644
--- a/source/vibedav/davresource.d
+++ b/source/vibedav/davresource.d
@@ -688,7 +688,6 @@ class DavResource : IDavResourcePluginHub {
foreach(prop; removeList)
foreach(string key, p; prop) {
auto status = removeProperty(key);
-
result ~= `` ~ p.toString ~ ``;
result ~= `HTTP/1.1 ` ~ status.to!int.to!string ~ ` ` ~ status.to!string ~ ``;
}
@@ -743,7 +742,7 @@ class DavResource : IDavResourcePluginHub {
return;
}
- throw new DavException(HTTPStatus.methodNotAllowed, "No plugin support.");
+ throw new DavException(HTTPStatus.methodNotAllowed, "setContent No plugin support.");
}
void setContent(InputStream content, ulong size) {
@@ -755,7 +754,7 @@ class DavResource : IDavResourcePluginHub {
return;
}
- throw new DavException(HTTPStatus.methodNotAllowed, "No plugin support.");
+ throw new DavException(HTTPStatus.methodNotAllowed, "setContent No plugin support.");
}
@property {
@@ -764,7 +763,7 @@ class DavResource : IDavResourcePluginHub {
if(plugin.canGetStream(this))
return plugin.stream(this);
- throw new DavException(HTTPStatus.methodNotAllowed, "No plugin support.");
+ throw new DavException(HTTPStatus.methodNotAllowed, "stream No plugin support.");
}
pure nothrow bool isCollection() {
diff --git a/source/vibedav/filedav.d b/source/vibedav/filedav.d
index 9f2fa09..cac3037 100644
--- a/source/vibedav/filedav.d
+++ b/source/vibedav/filedav.d
@@ -313,16 +313,15 @@ class FileResourcePlugin : IDavResourcePlugin {
}
/// File Dav impplementation
-class FileDav : IDavPlugin {
+class FileDav : BaseDavPlugin {
private {
- IDav _dav;
Path baseUrlPath;
Path basePath;
}
this(IDav dav, Path baseUrlPath, Path basePath) {
- _dav = dav;
+ super(dav);
this.baseUrlPath = baseUrlPath;
this.basePath = basePath;
}
@@ -347,87 +346,91 @@ class FileDav : IDavPlugin {
return getFilePath(baseUrlPath, basePath, url);
}
- bool exists(URL url, string username) {
- auto filePath = filePath(url);
+ override {
+ bool exists(URL url, string username) {
+ auto filePath = filePath(url);
- return filePath.toString.exists;
- }
+ return filePath.toString.exists;
+ }
- bool canCreateResource(URL url, string username) {
- return !exists(url, username);
- }
+ bool canCreateResource(URL url, string username) {
+ return !exists(url, username);
+ }
- bool canCreateCollection(URL url, string username) {
- return !exists(url, username);
- }
+ bool canCreateCollection(URL url, string username) {
+ return !exists(url, username);
+ }
- void removeResource(URL url, string username) {
- if(!exists(url, username))
- throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found.");
+ void removeResource(URL url, string username) {
+ if(!exists(url, username))
+ throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found.");
- auto filePath = filePath(url).toString;
+ auto filePath = filePath(url).toString;
- if(filePath.isDir)
- filePath.rmdirRecurse;
- else
- filePath.remove;
+ if(filePath.isDir)
+ filePath.rmdirRecurse;
+ else
+ filePath.remove;
+ }
- }
+ DavResource getResource(URL url, string username) {
+ if(!exists(url, username))
+ throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found.");
- DavResource getResource(URL url, string username) {
- if(!exists(url, username))
- throw new DavException(HTTPStatus.notFound, "`" ~ url.toString ~ "` not found.");
+ auto filePath = filePath(url);
- auto filePath = filePath(url);
+ DavResource resource = new DavResource(_dav, url);
+ resource.username = username;
+ setResourceProperties(resource);
- DavResource resource = new DavResource(_dav, url);
- resource.username = username;
- setResourceProperties(resource);
+ return resource;
+ }
- return resource;
- }
+ DavResource createResource(URL url, string username) {
+ auto filePath = filePath(url).toString;
+
+ File(filePath, "w");
- DavResource createResource(URL url, string username) {
- auto filePath = filePath(url).toString;
+ return getResource(url, username);
+ }
- File(filePath, "w");
+ DavResource createCollection(URL url, string username) {
+ auto filePath = filePath(url);
- return getResource(url, username);
- }
+ if(filePath.toString.exists)
+ throw new DavException(HTTPStatus.methodNotAllowed, "Resource already exists.");
- DavResource createCollection(URL url, string username) {
- auto filePath = filePath(url);
+ filePath.toString.mkdirRecurse;
- if(filePath.toString.exists)
- throw new DavException(HTTPStatus.methodNotAllowed, "Resource already exists.");
+ return getResource(url, username);
+ }
- filePath.toString.mkdirRecurse;
+ void bindResourcePlugins(DavResource resource) {
+ if(resource.isCollection)
+ resource.registerPlugin(new DirectoryResourcePlugin(baseUrlPath, basePath));
+ else
+ resource.registerPlugin(new FileResourcePlugin(baseUrlPath, basePath));
- return getResource(url, username);
- }
+ resource.registerPlugin(new ResourceCustomProperties);
+ resource.registerPlugin(new ResourceBasicProperties);
+ }
- void bindResourcePlugins(DavResource resource) {
- if(resource.isCollection)
- resource.registerPlugin(new DirectoryResourcePlugin(baseUrlPath, basePath));
- else
- resource.registerPlugin(new FileResourcePlugin(baseUrlPath, basePath));
- resource.registerPlugin(new ResourceCustomProperties);
- resource.registerPlugin(new ResourceBasicProperties);
+ @property {
+ IDav dav() {
+ return _dav;
+ }
+
+ string[] support(URL url, string username) {
+ return ["1", "2", "3"];
+ }
+ }
}
@property {
string name() {
return "FileDav";
}
-
- IDav dav() {
- return _dav;
- }
-
- string[] support(URL url, string username) {
- return ["1", "2", "3"];
- }
}
}
diff --git a/source/vibedav/http.d b/source/vibedav/http.d
index 67fa192..c1e5d08 100644
--- a/source/vibedav/http.d
+++ b/source/vibedav/http.d
@@ -109,6 +109,10 @@ struct DavResponse {
_content = value;
}
+ void content(DavProp value) {
+ content(value.toString);
+ }
+
void mimeType(string value) {
response.headers["Content-Type"] = value;
}
@@ -162,23 +166,40 @@ struct DavResponse {
response.writeRawBody(resource.stream);
}
- void setPropContent (DavResource[] list, bool[string] props) {
+ void setPropContent (DavResource[] list, bool[string] props, HTTPStatus[string] responseCodes = null) {
statusCode = HTTPStatus.multiStatus;
mimeType = "application/xml";
string str = ``;
- auto response = parseXMLProp(``);
+ auto multistatus = new DavProp;
+ multistatus.addNamespace("d", "DAV:");
+ multistatus.name = "d:multistatus";
foreach(item; list)
- item.filterProps(response["d:multistatus"], props);
+ item.filterProps(multistatus, props);
+
+ if(responseCodes !is null) {
+ foreach(string path, HTTPStatus status; responseCodes) {
+ DavProp element = new DavProp;
+ multistatus.addChild(element);
+
+ element.name = "response";
+ element.namespaceAttr = "DAV:";
+ element[`d:href`] = path;
+ element[`d:status`] = "HTTP/1.1 " ~ status.to!int.to!string ~ " " ~ status.to!string;
+ }
+ }
- _content = str ~ response.toString;
+ _content = str ~ multistatus.toString;
}
}
/// The HTTP request wrapper
struct DavRequest {
- private HTTPServerRequest request;
+ private {
+ HTTPServerRequest request;
+ DavProp document;
+ }
this(HTTPServerRequest req) {
request = req;
@@ -220,15 +241,15 @@ struct DavRequest {
}
DavProp content() {
- DavProp document;
+
+ if(document !is null)
+ return document;
if(request.bodyReader is null)
return document;
string requestXml = cast(string)request.bodyReader.readAllUTF8;
- debug writeln("requestXml:", requestXml);
-
if(requestXml.length > 0) {
try document = requestXml.parseXMLProp;
catch (DavPropException e)
diff --git a/source/vibedav/syncdav.d b/source/vibedav/syncdav.d
new file mode 100644
index 0000000..7431943
--- /dev/null
+++ b/source/vibedav/syncdav.d
@@ -0,0 +1,216 @@
+/**
+ * Authors: Szabo Bogdan
+ * Date: 4 23, 2015
+ * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
+ * Copyright: Public Domain
+ */
+module vibedav.syncdav;
+
+import std.datetime;
+
+import vibedav.base;
+import vibedav.davresource;
+import vibe.core.file;
+
+import vibe.http.server;
+
+
+interface ISyncDavProperties {
+ @property {
+
+ /// rfc6578 - 4
+ @ResourceProperty("sync-token", "DAV:")
+ string syncToken(DavResource resource);
+ }
+}
+
+interface ISyncDavReports {
+
+ /// rfc6578 - 3.2
+ @DavReport("sync-collection", "DAV:")
+ void syncCollection(DavRequest request, DavResponse response);
+}
+
+class SyncDavDataPlugin : BaseDavResourcePlugin, ISyncDavProperties {
+
+ private SyncDavPlugin _syncPlugin;
+
+ this(SyncDavPlugin syncPlugin) {
+ _syncPlugin = syncPlugin;
+ }
+
+ string syncToken(DavResource resource) {
+ return SyncDavPlugin.prefix ~ _syncPlugin.currentChangeNr.to!string;
+ }
+
+ override {
+ bool canGetProperty(DavResource resource, string name) {
+ if(hasDavInterfaceProperty!ISyncDavProperties(name))
+ return true;
+
+ return false;
+ }
+
+ DavProp property(DavResource resource, string name) {
+ if(hasDavInterfaceProperty!ISyncDavProperties(name))
+ return getDavInterfaceProperty!ISyncDavProperties(name, this, resource);
+
+ throw new DavException(HTTPStatus.internalServerError, "Can't get property.");
+ }
+ }
+
+ @property
+ string name() {
+ return "SyncDavDataPlugin";
+ }
+}
+
+// todo: add ISyncDavReports
+class SyncDavPlugin : BaseDavPlugin, ISyncDavReports {
+ enum string prefix = "http://vibedav/ns/sync/";
+
+ struct Change {
+ Path path;
+ string type;
+ SysTime time;
+ }
+
+ private {
+ Change[] log;
+ ulong changeNr = 1;
+ }
+
+ this(IDav dav) {
+ super(dav);
+ }
+
+ private {
+
+ ulong getToken(DavProp[] syncTokenList) {
+ if(syncTokenList.length == 0)
+ return 0;
+
+ if(syncTokenList[0].tagName != "sync-token")
+ return 0;
+
+ string value = syncTokenList[0].value;
+
+ if(value.length == 0)
+ return 0;
+
+ if(value.length <= prefix.length)
+ return 0;
+
+ if(value[0..prefix.length] != prefix)
+ return 0;
+
+ value = value[prefix.length..$];
+
+ try {
+ return value.to!ulong;
+ } catch(Exception e) {
+ throw new DavException(HTTPStatus.internalServerError, "invalid sync-token");
+ }
+ }
+
+ ulong getLevel(DavProp[] syncLevelList) {
+ if(syncLevelList.length == 0)
+ return 0;
+
+ if(syncLevelList[0].name != "sync-level")
+ return 0;
+
+ try {
+ return syncLevelList[0].value.to!ulong;
+ } catch(Exception e) {
+ throw new DavException(HTTPStatus.internalServerError, "invalid sync-level");
+ }
+ }
+
+ bool[string] getChangesFrom(ulong token) {
+ if(token > changeNr)
+ throw new DavException(HTTPStatus.forbidden, "Invalid token.");
+
+ bool[string] wasRemoved;
+
+ foreach(i; token..changeNr-1) {
+ auto change = log[i];
+
+ wasRemoved[change.path.toString] = (change.type == "deleted");
+ }
+
+ return wasRemoved;
+ }
+ }
+
+ void syncCollection(DavRequest request, DavResponse response) {
+ response.mimeType = "application/xml";
+ response.statusCode = HTTPStatus.multiStatus;
+ auto reportData = request.content;
+
+ bool[string] requestedProperties;
+ HTTPStatus[string] responseCodes;
+
+ foreach(name, p; reportData["sync-collection"]["prop"])
+ requestedProperties[name] = true;
+
+ auto syncTokenList = [ reportData["sync-collection"] ].getTagChilds("sync-token");
+ auto syncLevelList = [ reportData["sync-collection"] ].getTagChilds("sync-level");
+
+ ulong token = getToken(syncTokenList);
+ ulong level = getLevel(syncLevelList);
+
+ DavResource[] list;
+ auto changes = getChangesFrom(token);
+
+ foreach(string path, bool wasRemoved; changes) {
+ if(wasRemoved) {
+ responseCodes[path] = HTTPStatus.notFound;
+ } else {
+ list ~= _dav.getResource(URL(path), request.username);
+ }
+ }
+
+ response.setPropContent(list, requestedProperties, responseCodes);
+ response.flush;
+ }
+
+ override {
+ bool hasReport(URL url, string username, string name) {
+
+ if(hasDavReport!ISyncDavReports(name))
+ return true;
+
+ return false;
+ }
+
+ void report(DavRequest request, DavResponse response) {
+ if(!hasDavReport!ISyncDavReports(request.content.reportName))
+ throw new DavException(HTTPStatus.internalServerError, "Can't get report.");
+
+ getDavReport!ISyncDavReports(this, request, response);
+ }
+
+ void bindResourcePlugins(DavResource resource) {
+ if(resource.isCollection)
+ resource.registerPlugin(new SyncDavDataPlugin(this));
+ }
+
+ void notice(string action, DavResource resource) {
+ if(action == "created" || action == "deleted" || action == "changed") {
+ changeNr++;
+ log ~= Change(resource.url.path, action, Clock.currTime);
+ }
+ }
+ }
+
+ @property {
+ ulong currentChangeNr() {
+ return changeNr;
+ }
+
+ string name() {
+ return "SyncDavPlugin";
+ }
+ }
+}