caldav-adapter
Version:
CalDAV server for Node.js and Koa. Modernized and maintained for Forward Email.
453 lines (432 loc) • 14.1 kB
JavaScript
const { isEmail } = require('validator');
const { buildTag, href, response, status } = require('./x-build');
const winston = require('./winston');
const dav = 'DAV:';
const cal = 'urn:ietf:params:xml:ns:caldav';
const cs = 'http://calendarserver.org/ns/';
const ical = 'http://apple.com/ns/ical/';
module.exports = function (options) {
const log = winston({ ...options, label: 'tags' });
const tags = {
[dav]: {
'current-user-principal': {
doc: 'https://tools.ietf.org/html/rfc5397#section-3',
async resp({ ctx }) {
return {
[buildTag(dav, 'current-user-principal')]: href(
ctx.state.principalUrl
)
};
}
},
'current-user-privilege-set': {
doc: 'https://tools.ietf.org/html/rfc3744#section-5.4',
async resp({ resource, calendar }) {
if (resource === 'calendar') {
const privileges = [{ [buildTag(dav, 'read')]: '' }];
if (!calendar.readonly) {
privileges.push(
{ [buildTag(dav, 'read')]: '' },
{ [buildTag(dav, 'read-acl')]: '' },
{ [buildTag(dav, 'read-current-user-privilege-set')]: '' },
{ [buildTag(dav, 'write')]: '' },
{ [buildTag(dav, 'write-content')]: '' },
{ [buildTag(dav, 'write-properties')]: '' },
{ [buildTag(dav, 'bind')]: '' }, // PUT - https://tools.ietf.org/html/rfc3744#section-3.9
{ [buildTag(dav, 'unbind')]: '' }, // DELETE - https://tools.ietf.org/html/rfc3744#section-3.10
{ [buildTag(cal, 'read-free-busy')]: '' } // https://tools.ietf.org/html/rfc4791#section-6.1.1
);
}
return {
[buildTag(dav, 'current-user-privilege-set')]: {
[buildTag(dav, 'privilege')]: privileges
}
};
}
}
},
displayname: {
doc: 'https://tools.ietf.org/html/rfc4918#section-15.2',
async resp({ resource, ctx, calendar }) {
if (resource === 'principal') {
return {
[buildTag(dav, 'displayname')]: ctx.state.user.principalName
};
}
if (resource === 'calendar')
return {
[buildTag(dav, 'displayname')]: calendar.name
};
if (resource === 'calendarProppatch')
return response(ctx.url, status[200], [
{
[buildTag(dav, 'displayname')]: calendar.name
}
]);
}
},
getcontenttype: {
doc: 'https://tools.ietf.org/html/rfc2518#section-13.5',
async resp({ resource }) {
if (resource === 'calendar') {
return {
[buildTag(dav, 'getcontenttype')]:
'text/calendar; charset=utf-8; component=VEVENT'
};
}
if (resource === 'event') {
return {
[buildTag(dav, 'getcontenttype')]:
'text/calendar; charset=utf-8; component=VEVENT'
};
}
}
},
getetag: {
doc: 'https://tools.ietf.org/html/rfc4791#section-5.3.4',
async resp({ resource, ctx, event }) {
if (resource === 'event') {
return {
[buildTag(dav, 'getetag')]: options.data.getETag(ctx, event)
};
}
}
},
owner: {
doc: 'https://tools.ietf.org/html/rfc3744#section-5.1',
async resp({ resource, ctx }) {
if (resource === 'calendar') {
return {
[buildTag(dav, 'owner')]: href(ctx.state.principalUrl)
};
}
}
},
'principal-collection-set': {
doc: 'https://tools.ietf.org/html/rfc3744#section-5.8',
async resp({ resource, ctx }) {
if (resource === 'principal') {
return {
[buildTag(dav, 'principal-collection-set')]: href(
ctx.state.principalRootUrl
)
};
}
}
},
'principal-URL': {
doc: 'https://tools.ietf.org/html/rfc3744#section-4.2',
async resp({ ctx }) {
return {
[buildTag(dav, 'principal-URL')]: href(ctx.state.principalUrl)
};
}
},
'resource-id': {
doc: 'https://tools.ietf.org/html/rfc5842#section-3.1'
},
resourcetype: {
doc: 'https://tools.ietf.org/html/rfc4791#section-4.2',
async resp({ resource }) {
if (resource === 'calCollection') {
return {
[buildTag(dav, 'resourcetype')]: {
[buildTag(dav, 'collection')]: ''
}
};
}
if (resource === 'calendar') {
return {
[buildTag(dav, 'resourcetype')]: {
[buildTag(dav, 'collection')]: '',
[buildTag(cal, 'calendar')]: ''
}
};
}
if (resource === 'principal') {
return {
[buildTag(dav, 'resourcetype')]: {
[buildTag(dav, 'principal')]: ''
}
};
}
}
},
'supported-report-set': {
doc: 'https://tools.ietf.org/html/rfc3253#section-3.1.5',
async resp({ resource }) {
if (resource === 'calCollection') {
return {
[buildTag(dav, 'supported-report-set')]: {
[buildTag(dav, 'supported-report')]: {
[buildTag(dav, 'report')]: {
[buildTag(cal, 'sync-collection')]: ''
}
}
}
};
}
if (resource === 'calendar') {
return {
[buildTag(dav, 'supported-report-set')]: {
[buildTag(dav, 'supported-report')]: [
{
[buildTag(dav, 'report')]: {
[buildTag(cal, 'calendar-query')]: ''
}
},
{
[buildTag(dav, 'report')]: {
[buildTag(cal, 'calendar-multiget')]: ''
}
},
{
[buildTag(dav, 'report')]: {
[buildTag(cal, 'sync-collection')]: ''
}
}
]
}
};
}
}
},
'sync-token': {
doc: 'https://tools.ietf.org/html/rfc6578#section-3',
async resp({ resource, calendar }) {
if (resource === 'calendar') {
return {
[buildTag(dav, 'sync-token')]: calendar.synctoken
};
}
}
}
},
[cal]: {
'calendar-data': {
doc: 'https://tools.ietf.org/html/rfc4791#section-9.6',
async resp({ event, ctx, calendar }) {
const ics = await options.data.buildICS(ctx, event, calendar);
return {
[buildTag(cal, 'calendar-data')]: ics
};
}
},
'calendar-home-set': {
doc: 'https://tools.ietf.org/html/rfc4791#section-6.2.1',
async resp({ resource, ctx }) {
if (resource === 'principal') {
return {
[buildTag(cal, 'calendar-home-set')]: href(
ctx.state.calendarHomeUrl
)
};
}
}
},
'calendar-description': {
doc: 'https://tools.ietf.org/html/rfc4791#section-5.2.1',
async resp({ resource, ctx, calendar }) {
if (resource === 'calendar')
return {
[buildTag(cal, 'calendar-description')]: calendar.description
};
if (resource === 'calendarProppatch')
return response(ctx.url, status[200], [
{
[buildTag(cal, 'calendar-description')]: calendar.description
}
]);
}
},
'calendar-timezone': {
doc: 'https://tools.ietf.org/html/rfc4791#section-5.2.2',
async resp({ resource, ctx, calendar }) {
if (resource === 'calendar')
return {
[buildTag(cal, 'calendar-timezone')]: calendar.timezone
};
if (resource === 'calendarProppatch')
return response(ctx.url, status[200], [
{
[buildTag(cal, 'calendar-timezone')]: calendar.timezone
}
]);
}
},
'calendar-user-address-set': {
doc: 'https://tools.ietf.org/html/rfc6638#section-2.4.1',
async resp({ ctx }) {
if (isEmail(ctx.state.user.principalName))
return {
[buildTag(cal, 'calendar-user-address-set')]: href(
`mailto:${ctx.state.user.principalName}`
)
};
if (isEmail(ctx.state.user.email))
return {
[buildTag(cal, 'calendar-user-address-set')]: href(
`mailto:${ctx.state.user.email}`
)
};
return {
[buildTag(cal, 'calendar-user-address-set')]: ''
};
}
},
'default-alarm-vevent-date': {
doc: 'https://tools.ietf.org/id/draft-daboo-valarm-extensions-01.html#rfc.section.9',
async resp({ resource, ctx }) {
if (resource === 'calCollectionProppatch') {
return response(ctx.url, status[403], [
{
[buildTag(cal, 'default-alarm-vevent-date')]: ''
}
]);
}
}
},
'default-alarm-vevent-datetime': {
doc: 'https://tools.ietf.org/id/draft-daboo-valarm-extensions-01.html#rfc.section.9',
async resp({ resource, ctx }) {
if (resource === 'calCollectionProppatch') {
return response(ctx.url, status[403], [
{
[buildTag(cal, 'default-alarm-vevent-datetime')]: ''
}
]);
}
}
},
'schedule-inbox-URL': {
doc: 'https://tools.ietf.org/html/rfc6638#section-2.2',
async resp() {
return {
[buildTag(cal, 'schedule-inbox-URL')]: href('')
};
}
},
'schedule-outbox-URL': {
doc: 'https://tools.ietf.org/html/rfc6638#section-2.1',
async resp() {
return {
[buildTag(cal, 'schedule-outbox-URL')]: href('')
};
}
},
'supported-calendar-component-set': {
doc: 'https://tools.ietf.org/html/rfc4791#section-5.2.3',
async resp({ resource }) {
if (resource === 'calendar') {
return {
[buildTag(cal, 'supported-calendar-component-set')]: {
[buildTag(cal, 'comp')]: {
'@name': 'VEVENT'
}
}
};
}
}
}
},
[cs]: {
'allowed-sharing-modes': {
doc: 'https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-sharing.txt',
async resp({ resource }) {
if (resource === 'calendar') {
return {
[buildTag(cs, 'allowed-sharing-modes')]: ''
};
}
}
},
'checksum-versions': {},
'dropbox-home-URL': {},
'email-address-set': {
async resp({ resource, ctx }) {
if (
resource === 'calendar' &&
(ctx?.state?.user?.email || ctx?.state?.user?.username)
) {
return {
[buildTag(cs, 'email-address-set')]:
ctx?.state?.user?.email || ctx?.state?.user?.username
};
}
}
},
getctag: {
// DEPRECATED
doc: 'https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-ctag.txt',
async resp({ resource, calendar }) {
if (resource === 'calendar') {
return {
[buildTag(cs, 'getctag')]: calendar.synctoken
};
}
}
},
'notification-URL': {
doc: 'https://github.com/apple/ccs-calendarserver/blob/master/doc/Extensions/caldav-notifications.txt'
}
},
[ical]: {
'calendar-color': {
async resp({ resource, ctx, calendar }) {
if (resource === 'calendar')
return {
[buildTag(ical, 'calendar-color')]: calendar.color
};
if (resource === 'calendarProppatch')
return response(ctx.url, status[200], [
{
[buildTag(ical, 'calendar-color')]: calendar.color
}
]);
}
},
'calendar-order': {
async resp({ resource, ctx, calendar }) {
if (resource === 'calendar')
return {
[buildTag(ical, 'calendar-order')]: calendar.order
};
if (resource === 'calendarProppatch')
return response(ctx.url, status[200], [
{
[buildTag(ical, 'calendar-order')]: calendar.order
}
]);
}
}
}
};
const getResponse = async ({ resource, child, ctx, calendar, event }) => {
if (!child.namespaceURI) {
return null;
}
if (!tags[child.namespaceURI]) {
log.debug(`Namespace miss: ${child.namespaceURI}`);
return null;
}
const tagAction = tags[child.namespaceURI][child.localName];
if (!tagAction) {
log.debug(`Tag miss: ${buildTag(child.namespaceURI, child.localName)}`);
return null;
}
if (!tagAction.resp) {
log.debug(
`Tag no response: ${buildTag(child.namespaceURI, child.localName)}`
);
return null;
}
return tagAction.resp({
resource,
ctx,
calendar,
event,
text: child.textContent
});
};
return { tags, getResponse };
};