All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.milton.http.caldav.CalDavProtocol Maven / Gradle / Ivy

Go to download

Milton Enterprise: Supports DAV level 2 and above, including Caldav and Carddav. Available on AGPL or commercial licenses

There is a newer version: 4.0.5.2400
Show newest version
/*
 * Copyright 2012 McEvoy Software Ltd.
 *
 */
package io.milton.http.caldav;

import io.milton.webdav.utils.CalendarDataProperty;
import io.milton.http.values.SupportedCalendarComponentList;
import io.milton.principal.CalDavPrincipal;
import io.milton.common.LogUtils;
import io.milton.http.Auth;
import io.milton.http.Handler;
import io.milton.http.HandlerHelper;
import io.milton.http.HttpExtension;
import io.milton.http.Range;
import io.milton.http.Request;
import io.milton.http.Request.Method;
import io.milton.http.ResourceFactory;
import io.milton.http.WellKnownResourceFactory.WellKnownHandler;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.exceptions.NotFoundException;
import io.milton.http.http11.CustomPostHandler;
import io.milton.http.http11.auth.DigestResponse;
import io.milton.http.values.HrefList;
import io.milton.http.values.WrappedHref;
import io.milton.http.webdav.PropFindPropertyBuilder;
import io.milton.http.webdav.PropFindXmlGenerator;
import io.milton.http.webdav.PropertyMap;
import io.milton.http.webdav.PropertyMap.StandardProperty;
import io.milton.http.webdav.WebDavProtocol;
import io.milton.http.webdav.WebDavResponseHandler;
import io.milton.property.PropertySource;
import io.milton.resource.CalendarCollection;
import io.milton.resource.CalendarResource;
import io.milton.http.acl.ACLHandler;
import io.milton.http.values.SupportedCalendarComponentListsSet;
import io.milton.http.webdav.PropertyMap.WritableStandardProperty;
import io.milton.resource.DigestResource;
import io.milton.resource.GetableResource;
import io.milton.resource.PropFindableResource;
import io.milton.resource.Resource;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author brad
 */
public class CalDavProtocol implements HttpExtension, PropertySource, WellKnownHandler {

    private static final Logger log = LoggerFactory.getLogger(CalDavProtocol.class);
    // Standard caldav properties
    public static final String CALDAV_NS = "urn:ietf:params:xml:ns:caldav";
    // For extension properties
    public static final String CALSERVER_NS = "http://calendarserver.org/ns/";
    public static final String APPLE_ICAL_NS = "http://apple.com/ns/ical/";
    private final Set handlers;
    private final PropertyMap propertyMapCalDav;
    private final PropertyMap propertyMapCalServer;
    private final PropertyMap propertyMapAppleCal;
    //private final PropertyMap propertyMapDav;
    private final CalendarSearchService calendarSearchService;
    private final List customPostHandlers;
    private ResourceFactory resourceFactory;

    public CalDavProtocol(ResourceFactory resourceFactory, WebDavResponseHandler responseHandler, HandlerHelper handlerHelper, WebDavProtocol webDavProtocol, PropFindXmlGenerator gen, PropFindPropertyBuilder propertyBuilder, CalendarSearchService calendarSearchService) {
        if (resourceFactory == null) {
            throw new NullPointerException("resourceFactory is null");
        }
        this.resourceFactory = resourceFactory;
        this.calendarSearchService = calendarSearchService;
        propertyMapCalDav = new PropertyMap(CALDAV_NS);
        propertyMapCalDav.add(new CalenderDescriptionProperty());
        propertyMapCalDav.add(new CalenderUserTypeProperty());
        propertyMapCalDav.add(new CalendarOrderProperty());
        propertyMapCalDav.add(new CalendarDataProperty());
        propertyMapCalDav.add(new CalenderHomeSetProperty());
        propertyMapCalDav.add(new CalenderUserAddressSetProperty());
        propertyMapCalDav.add(new SupportedCalendarComponentSetProperty());
        propertyMapCalDav.add(new SupportedCalendarComponentSetsProperty());
        propertyMapCalDav.add(new ScheduleInboxProperty());
        propertyMapCalDav.add(new ScheduleOutboxProperty());

        propertyMapCalServer = new PropertyMap(CALSERVER_NS);
        //propertyMapDav = new PropertyMap(WebDavProtocol.NS_DAV.getName());
        propertyMapCalServer.add(new CTagProperty());
//        propertyMapCalServer.add(new XMPPProperty());
        //propertyMapCalServer.add(new DropBoxProperty());
        //propertyMapCalServer.add(new NotificationProperty());
//        propertyMapCalServer.add(new NotificationsProperty());

        propertyMapAppleCal = new PropertyMap(APPLE_ICAL_NS);
        propertyMapAppleCal.add(new ColorProperty());
        propertyMapAppleCal.add(new CalendarOrderProperty());

        handlers = new HashSet<>();
        handlers.add(new ACLHandler(responseHandler, handlerHelper));

        handlers.add(new MkCalendarHandler(webDavProtocol.getMkColHandler(), webDavProtocol.getPropPatchHandler()));

        webDavProtocol.addPropertySource(this);

        //Adding supported reports
        webDavProtocol.addReport(new MultiGetReport(resourceFactory, propertyBuilder, gen));
        webDavProtocol.addReport(new ACLPrincipalPropSetReport());
        webDavProtocol.addReport(new PrincipalMatchReport());
        //webDavProtocol.addReport(new ExpandPropertyReport());
        webDavProtocol.addReport(new CalendarQueryReport(propertyBuilder, gen, calendarSearchService));

        customPostHandlers = Collections.EMPTY_LIST;

    }

    @Override
    public Set getHandlers() {
        return Collections.unmodifiableSet(handlers);
    }

    //TODO: remove debug logging once it's working
    @Override
    public Object getProperty(QName name, Resource r) {
        log.trace("getProperty: {}", name.getLocalPart());
        Object o;
        if (propertyMapCalDav.hasProperty(name)) {
            o = propertyMapCalDav.getProperty(name, r);
        } else if (propertyMapAppleCal.hasProperty(name)) {
            o = propertyMapAppleCal.getProperty(name, r);
        } else {
            o = propertyMapCalServer.getProperty(name, r);
        }
        if (log.isTraceEnabled()) {
            log.trace("getProperty result : " + o + " for property: " + name.getLocalPart());
        }
        return o;
    }

    @Override
    public void setProperty(QName name, Object value, Resource r) {
        log.trace("setProperty: {}", name.getLocalPart());
        if (propertyMapCalDav.hasProperty(name)) {
            propertyMapCalDav.setProperty(name, r, value);
        } else if (propertyMapAppleCal.hasProperty(name)) {
            propertyMapAppleCal.setProperty(name, r, value);
        } else {
            propertyMapCalServer.setProperty(name, r, value);
        }
    }

    @Override
    public PropertyMetaData getPropertyMetaData(QName name, Resource r) {
        log.trace("getPropertyMetaData: {}", name.getLocalPart());
        if (propertyMapCalDav.hasProperty(name)) {
            return propertyMapCalDav.getPropertyMetaData(name, r);
        } else if (propertyMapAppleCal.hasProperty(name)) {
            return propertyMapAppleCal.getPropertyMetaData(name, r);
        } else {
            return propertyMapCalServer.getPropertyMetaData(name, r);
        }
    }

    @Override
    public void clearProperty(QName name, Resource r) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public List getAllPropertyNames(Resource r) {
        log.trace("getAllPropertyNames");
        List list = new ArrayList<>();
        list.addAll(propertyMapCalDav.getAllPropertyNames(r));
        list.addAll(propertyMapCalServer.getAllPropertyNames(r));
        list.addAll(propertyMapAppleCal.getAllPropertyNames(r));

        return list;
    }

    @Override
    public List getCustomPostHandlers() {
        return customPostHandlers;
    }

    /*
     
     */
    static class CalenderDescriptionProperty implements StandardProperty {

        @Override
        public String fieldName() {
            return "calendar-description";
        }

        @Override
        public String getValue(PropFindableResource res) {
            if (res instanceof CalendarResource) {
                CalendarResource ical = (CalendarResource) res;
                return ical.getCalendarDescription();
            } else {
                log.warn("getValue: not a ICalResource");
                return null;
            }

        }

        @Override
        public Class getValueClass() {
            return String.class;
        }
    }

    /*
     
     /calendars/__uids__/admin
          
     */
    static class CalenderHomeSetProperty implements StandardProperty {

        @Override
        public String fieldName() {
            return "calendar-home-set";
        }

        @Override
        public HrefList getValue(PropFindableResource res) {
            if (res instanceof CalDavPrincipal) {
                return ((CalDavPrincipal) res).getCalendarHomeSet();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return HrefList.class;
        }
    }

    /* Scheduling support
     see : http://ietfreport.isoc.org/idref/draft-desruisseaux-caldav-sched/
     for details
    
     
     http://polaris.home.j2anywhere.com:8008/principals/users/admin/
     urn:uuid:admin
     http://polaris.home.j2anywhere.com:8008/principals/__uids__/admin/
     /principals/__uids__/admin/
     /principals/users/admin/
     
     */
    static class CalenderUserAddressSetProperty implements StandardProperty {

        @Override
        public String fieldName() {
            return "calendar-user-address-set";
        }

        /**
         * 
         * mailto:[email protected]
         * mailto:[email protected]
         * 
         *
         * @param res
         * @return
         */
        @Override
        public HrefList getValue(PropFindableResource res) {
            if (res instanceof CalDavPrincipal) {
                return ((CalDavPrincipal) res).getCalendarUserAddressSet();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return HrefList.class;
        }
    }

    /*
     
     /calendars/__uids__/admin/inbox/
     
     */
    class ScheduleInboxProperty implements StandardProperty {

        @Override
        public String fieldName() {
            return "schedule-inbox-URL";
        }

        @Override
        public WrappedHref getValue(PropFindableResource res) {
            if (res instanceof CalDavPrincipal) {
                CalDavPrincipal p = (CalDavPrincipal) res;
                String s = p.getPrincipalURL() + calendarSearchService.getSchedulingColName() + "/" + calendarSearchService.getSchedulingInboxColName() + "/";
                return new WrappedHref(s);
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return WrappedHref.class;
        }
    }

    /*
     
     /calendars/__uids__/admin/outbox/
     
     */
    class ScheduleOutboxProperty implements StandardProperty {

        @Override
        public String fieldName() {
            return "schedule-outbox-URL";
        }

        @Override
        public WrappedHref getValue(PropFindableResource res) {
            if (res instanceof CalDavPrincipal) {
                CalDavPrincipal p = (CalDavPrincipal) res;
                String s = p.getPrincipalURL() + calendarSearchService.getSchedulingColName() + "/" + calendarSearchService.getSchedulingOutboxColName() + "/";
                return new WrappedHref(s);
            } else {
                return null;
            }

        }

        @Override
        public Class getValueClass() {
            return WrappedHref.class;
        }
    }

    static class CalenderUserTypeProperty implements StandardProperty {

        public CalenderUserTypeProperty() {
        }

        @Override
        public String fieldName() {
            return "calendar-user-type";
        }

        @Override
        public Object getValue(PropFindableResource res) {
            if (res instanceof CalDavPrincipal) {
                CalDavPrincipal p = (CalDavPrincipal) res;
                return p.getCalendarUserType();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return String.class;
        }
    }



    /**
     * CalendarServer support
     *
     * https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-ctag.txt\
     * http://code.google.com/p/sabredav/wiki/ICal
     *
     *
     * 4.1. getctag WebDAV Property 173 174	Name: getctag 175 176	Namespace:
     * http://calendarserver.org/ns/ 177 178	Purpose: Specifies a
     * "synchronization" token used to indicate when 179	the contents of a
     * calendar or scheduling Inbox or Outbox 180	collection have changed. 181
     * 182	Conformance: This property MUST be defined on a calendar or 183
     * scheduling Inbox or Outbox collection resource. It MUST be 184	protected
     * and SHOULD be returned by a PROPFIND DAV:allprop request 185	(as defined
     * in Section 12.14.1 of [RFC2518]). 186 187	Description: The CS:getctag
     * property allows clients to quickly 188	determine if the contents of a
     * calendar or scheduling Inbox or 189	Outbox collection have changed since
     * the last time a 190	"synchronization" operation was done. The CS:getctag
     * property 191	value MUST change each time the contents of the calendar or
     * 192	scheduling Inbox or Outbox collection change, and each change MUST
     * 193	result in a value that is different from any other used with that 194
     * collection URI. 195 196	Definition: 197 198	
     * 199 200	Example: 201 202	ABCD-GUID-IN-THIS-COLLECTION-20070228T122324010340
     */
    static class CTagProperty implements StandardProperty {

        @Override
        public String fieldName() {
            return "getctag";
        }

        @Override
        public String getValue(PropFindableResource res) {
            if (res instanceof CalendarCollection) {
                CalendarCollection ccol = (CalendarCollection) res;
                return ccol.getCTag();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return String.class;
        }
    }

    static class ColorProperty implements PropertyMap.WritableStandardProperty {

        @Override
        public String fieldName() {
            return "calendar-color";
        }

        @Override
        public String getValue(PropFindableResource res) {
            if (res instanceof CalendarResource) {
                CalendarResource ccol = (CalendarResource) res;
                return ccol.getColor();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return String.class;
        }

        @Override
        public void setValue(PropFindableResource res, String value) {
            if (res instanceof CalendarResource) {
                CalendarResource ccol = (CalendarResource) res;
                ccol.setColor(value);
            }
        }
    }

    static class CalendarOrderProperty implements WritableStandardProperty {

        public CalendarOrderProperty() {
        }

        @Override
        public String fieldName() {
            return "calendar-order";
        }

        @Override
        public String getValue(PropFindableResource res) {
            if (res instanceof CalendarResource) {
                CalendarResource ccol = (CalendarResource) res;
                return ccol.getCalendarOrder();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return String.class; // could be an int, i suppose...
        }

        @Override
        public void setValue(PropFindableResource res, String value) {
            if (res instanceof CalendarResource) {
                CalendarResource ccol = (CalendarResource) res;
                ccol.setCalendarOrder(value);
            }
        }
    }

    /**
     * Implemented on CalDavPrincpials
     *
     * See http://tools.ietf.org/html/draft-daboo-caldav-extensions-01#page-7
     *
     * If servers apply restrictions on the allowed calendar component sets used
     * when creating a calendar, then those servers SHOULD advertise this
     * property on each calendar home collection within which the restrictions
     * apply. In the absence of this property, clients cannot assume anything
     * about whether the server will enforce a set of restrictions or not - in
     * that case clients need to handle the server rejecting certain
     * combinations of restricted component sets. If this property is present,
     * but contains no child XML elements, then clients can assume that the
     * server imposes no restrictions on the combinations of component types it
     * is willing to accept. If present, each CALDAV:supported-
     * calendar-component-set element represents a valid restriction the client
     * can use in an MKCALENDAR or extended MKCOL request when creating a
     * calendar.
     */
    static class SupportedCalendarComponentSetsProperty implements PropertyMap.WritableStandardProperty {

        @Override
        public String fieldName() {
            return "supported-calendar-component-sets";
        }

        @Override
        public SupportedCalendarComponentListsSet getValue(PropFindableResource res) {
            if (res instanceof CalDavPrincipal) {
                CalDavPrincipal ccol = (CalDavPrincipal) res;
                return ccol.getSupportedComponentSets();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return SupportedCalendarComponentListsSet.class;
        }

        @Override
        public void setValue(PropFindableResource res, SupportedCalendarComponentListsSet value) {
        }
    }

    /**
     * See http://www.ietf.org/rfc/rfc4791.txt
     *
     */
    static class SupportedCalendarComponentSetProperty implements PropertyMap.WritableStandardProperty {

        @Override
        public String fieldName() {
            return "supported-calendar-component-set";
        }

        @Override
        public SupportedCalendarComponentList getValue(PropFindableResource res) {
            if (res instanceof CalendarResource) {
                CalendarResource ccol = (CalendarResource) res;
                return ccol.getSupportedComponentSet();
            } else {
                return null;
            }
        }

        @Override
        public Class getValueClass() {
            return SupportedCalendarComponentList.class;
        }

        @Override
        public void setValue(PropFindableResource res, SupportedCalendarComponentList value) {
        }
    }

    @Override
    public String getWellKnownName() {
        return "caldav";
    }

    @Override
    public Resource locateWellKnownResource(Resource host) {
        log.trace("found a caldav well-known resource");
        return new CaldavWellKnownResource(host);
    }

    public ResourceFactory getResourceFactory() {
        return resourceFactory;
    }

    public class CaldavWellKnownResource implements DigestResource, GetableResource, PropFindableResource {

        private final Resource host;

        public CaldavWellKnownResource(Resource host) {
            this.host = host;
        }

        @Override
        public String getUniqueId() {
            return null;
        }

        @Override
        public String getName() {
            return getWellKnownName();
        }

        @Override
        public Object authenticate(String user, String password) {
            return host.authenticate(user, password);
        }

        @Override
        public boolean authorise(Request request, Method method, Auth auth) {
            // we require a user, so we know where to redirect to
            return (auth != null);
        }

        @Override
        public String getRealm() {
            return host.getRealm();
        }

        @Override
        public Date getModifiedDate() {
            return null; // no caching
        }

        @Override
        public String checkRedirect(Request request) {
            log.trace("well-known: checkRedirect");
            Auth auth = request.getAuthorization();
            HrefList calendars;
            String first;
            if (auth != null && auth.getTag() != null) {
                if (auth.getTag() instanceof CalDavPrincipal) {
                    CalDavPrincipal p = (CalDavPrincipal) auth.getTag();
                    calendars = p.getCalendarHomeSet();
                    if (calendars == null || calendars.isEmpty()) {
                        log.warn("can't redirect, CalDavPrincipal.getCalendatHomeSet did not return an address. Check implementation class: " + p.getClass());
                        return null;
                    } else {
                        first = calendars.get(0); // just use first
                        LogUtils.trace(log, "well-known: checkRedirect. redirecting to:", first);
                        return first;
                    }
                } else {
                    log.warn("can't redirect, auth.getTag is not a CalDavPrincipal, is a: " + auth.getTag().getClass() + " To use CALDAV, the user object returned from authenticate must be a " + CalDavPrincipal.class);
                    return null;
                }
            } else {
                log.trace("can't redirect, no authorisation");
                return null;
            }
        }

        @Override
        public Object authenticate(DigestResponse digestRequest) {
            if (host instanceof DigestResource) {
                DigestResource dr = (DigestResource) host;
                return dr.authenticate(digestRequest);
            } else {
                return null;
            }
        }

        @Override
        public boolean isDigestAllowed() {
            if (host instanceof DigestResource) {
                DigestResource dr = (DigestResource) host;
                return dr.isDigestAllowed();
            } else {
                return false;
            }
        }

        @Override
        public void sendContent(OutputStream out, Range range, Map params, String contentType) throws IOException, NotAuthorizedException, BadRequestException, NotFoundException {
            throw new UnsupportedOperationException("Not supported yet.");
        }

        @Override
        public Long getMaxAgeSeconds(Auth auth) {
            return null; // no caching
        }

        @Override
        public String getContentType(String accepts) {
            return null;
        }

        @Override
        public Long getContentLength() {
            return null;
        }

        @Override
        public Date getCreateDate() {
            return null;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy