io.milton.http.carddav.CardDavProtocol Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of milton-server-ent Show documentation
Show all versions of milton-server-ent Show documentation
Milton Enterprise: Supports DAV level 2 and above, including Caldav and Carddav. Available on AGPL or
commercial licenses
/*
* Copyright 2012 McEvoy Software Ltd.
*
*/
package io.milton.http.carddav;
import io.milton.principal.CardDavPrincipal;
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.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.common.LogUtils;
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;
import io.milton.resource.AddressResource;
import io.milton.resource.AddressBookResource;
import io.milton.http.values.Pair;
import io.milton.http.values.AddressDataTypeList;
import io.milton.http.webdav.PropFindPropertyBuilder;
import io.milton.http.caldav.ExpandPropertyReport;
import io.milton.principal.DirectoryGatewayCardDavPrincipal;
import io.milton.resource.DigestResource;
import io.milton.resource.GetableResource;
import io.milton.resource.PropFindableResource;
import io.milton.resource.Resource;
/**
*
* @author bradm
*/
public class CardDavProtocol implements HttpExtension, PropertySource, WellKnownHandler {
private static final Logger log = LoggerFactory.getLogger(CardDavProtocol.class);
// Standard caldav properties
public static final String CARDDAV_NS = "urn:ietf:params:xml:ns:carddav";
private final Set handlers;
private final PropertyMap propertyMapCardDav;
public CardDavProtocol(ResourceFactory resourceFactory, WebDavResponseHandler responseHandler, HandlerHelper handlerHelper, WebDavProtocol webDavProtocol, PropFindXmlGenerator gen, PropFindPropertyBuilder propertyBuilder) {
propertyMapCardDav = new PropertyMap(CARDDAV_NS);
propertyMapCardDav.add(new AddressBookHomeSetProperty());
propertyMapCardDav.add(new AddressBookDescriptionProperty());
propertyMapCardDav.add(new SupportedAddressData());
propertyMapCardDav.add(new PrincipalAddress());
propertyMapCardDav.add(new DirectoryGateway());
propertyMapCardDav.add(new AddressDataProperty());
handlers = new HashSet<>();
webDavProtocol.addPropertySource(this);
webDavProtocol.addReport(new AddressBookMultiGetReport(resourceFactory, propertyBuilder, gen));
webDavProtocol.addReport(new AddressBookQueryReport(resourceFactory, propertyBuilder, gen));
webDavProtocol.addReport(new ExpandPropertyReport(resourceFactory, propertyBuilder, gen));
}
@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 (propertyMapCardDav.hasProperty(name)) {
o = propertyMapCardDav.getProperty(name, r);
} else {
o = null;
}
log.debug("result : " + o);
return o;
}
@Override
public void setProperty(QName name, Object value, Resource r) {
log.trace("setProperty: {}", name.getLocalPart());
if (propertyMapCardDav.hasProperty(name)) {
propertyMapCardDav.setProperty(name, r, value);
}
}
@Override
public PropertyMetaData getPropertyMetaData(QName name, Resource r) {
PropertyMetaData md;
if (propertyMapCardDav.hasProperty(name)) {
md = propertyMapCardDav.getPropertyMetaData(name, r);
} else {
md = null;
}
log.trace("getPropertyMetaData: {} - returned: {}", name.getLocalPart(), md);
return md;
}
@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<>(propertyMapCardDav.getAllPropertyNames(r));
return list;
}
@Override
public List getCustomPostHandlers() {
return null;
}
// (CARDDAV:supported-address-data-conversion -- requires implemenation
/**
* When used in an address book REPORT request, the CARDDAV:address-data XML
* element specifies which parts of address object resources need to be
* returned in the response. If the CARDDAV:address-data XML element doesn’t
* contain any CARDDAV:prop elements, address object resources will be
* returned in their entirety. Additionally, a media type and version can be
* specified to request that the server return the data in that format if
* possible. Finally, when used in an address book REPORT response, the
* CARDDAV:address-data XML element specifies the content of an address
* object resource. Given that XML parsers normalize the two-character
* sequence CRLF (US-ASCII decimal 13 and US-ASCII decimal 10) to a single
* LF character (US-ASCII decimal 10), the CR character (US-ASCII decimal
* 13) MAY be omitted in address object resources specified in the
* CARDDAV:address-data XML element. Furthermore, address object resources
* specified in the CARDDAV:address-data XML element MAY be invalid per
* their media type specification if the CARDDAV:address-data XML element
* part of the address book REPORT request did not specify required vCard
* properties (e.g., UID, etc.) or specified a CARDDAV:prop XML element with
* the "novalue" attribute set to "yes".
*
* Note: The CARDDAV:address-data XML element is specified in requests and
* responses inside the DAV:prop XML element as if it were a WebDAV
* property. However, the CARDDAV:address-data XML element is not a WebDAV
* property and as such it is not returned in PROPFIND responses nor used in
* PROPPATCH requests.
*
* Note: The address data embedded within the CARDDAV:address-data XML
* element MUST follow the standard XML character data encoding rules,
* including use of <, >, & etc., entity encoding or the use of a
* construct. In the latter case, the vCard data cannot
* contain the character sequence "]]>", which is the end delimiter for the
* CDATA section.
*
* Definition:
*
* when nested in the DAV:prop XML element in an address book REPORT request
* to specify which parts of address object resources should be returned in
* the response;
*
*
* when nested in the DAV:prop XML element in an address book REPORT
* response to specify the content of a returned address object resource.
*
*
*
* attributes can be used on each variant of the CALDAV:address-data XML
* element.
*/
static class AddressDataProperty implements StandardProperty {
@Override
public String fieldName() {
return "address-data";
}
@Override
public String getValue(PropFindableResource res) {
if (res instanceof AddressResource) {
AddressResource resource = (AddressResource) res;
return resource.getAddressData();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return String.class;
}
}
/**
* This property is meant to allow users to easily find the address book
* collections owned by the principal. Typically, users will group all the
* address book collections that they own under a common collection. This
* property specifies the URL of collections that are either address book
* collections or ordinary collections that have child or descendant address
* book collections owned by the principal.
*
* Definition:
*
*
* Example:
*
* /bernard/addresses/
*
*/
static class AddressBookHomeSetProperty implements StandardProperty {
@Override
public String fieldName() {
return "addressbook-home-set";
}
@Override
public HrefList getValue(PropFindableResource res) {
if (res instanceof CardDavPrincipal) {
return ((CardDavPrincipal) res).getAddressBookHomeSet();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return HrefList.class;
}
}
/*
* This property contains a description of the address book collection that
* is suitable for presentation to a user. The xml:lang attribute can be
* used to add a language tag for the value of this property.
*
* Definition:
*
*
*
* Example:
* Adresses de Oliver Daboo
*/
static class AddressBookDescriptionProperty implements StandardProperty {
// todo - add support of internationalization so the protocol can allow
// multiple language description, this can be accomplished by either
// add a method(getAttributes) to StandardProperty interface or we can
// have an InternationalizedStandardProperty interface that would extend
// from StandardProperty and have an additional method (getLanguage)
@Override
public String fieldName() {
return "addressbook-description";
}
@Override
public String getValue(PropFindableResource res) {
if (res instanceof AddressBookResource) {
AddressBookResource addressBookResource = (AddressBookResource) res;
return addressBookResource.getDescription().getValue();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return String.class;
}
}
/**
* This property is used to specify the media type supported for the address
* object resources contained in a given address book collection (e.g.,
* vCard version 3.0). Any attempt by the client to store address object
* resources with a media type not listed in this property MUST result in an
* error, with the CARDDAV:supported-address-data precondition (Section
* 6.3.2.1) being violated. In the absence of this property, the server MUST
* only accept data with the media type "text/vcard" and vCard version 3.0,
* and clients can assume that is all the server will accept.
*
* Definition:
*
*
*
*
*
*
* Example:
*
*
*
*/
static class SupportedAddressData implements StandardProperty>> {
@Override
public String fieldName() {
return "supported-address-data";
}
@Override
public List> getValue(PropFindableResource res) {
if (res instanceof AddressBookResource) {
return ((AddressBookResource) res).getSupportedAddressData();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return AddressDataTypeList.class;
}
}
/**
* This property is used to specify a numeric value that represents the
* maximum size in octets that the server is willing to accept when an
* address object resource is stored in an address book collection. Any
* attempt to store an address book object resource exceeding this size MUST
* result in an error, with the CARDDAV:max-resource-size precondition
* (Section 6.3.2.1) being violated. In the absence of this property, the
* client can assume that the server will allow storing a resource of any
* reasonable size.
*
* Definition:
*
*
*
* Example:
* 102400
*
*/
static class MaxResourceSize implements StandardProperty {
@Override
public String fieldName() {
return "max-resource-size";
}
@Override
public Long getValue(PropFindableResource res) {
if (res instanceof AddressBookResource) {
return ((AddressBookResource) res).getMaxResourceSize();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return Long.class;
}
}
/**
* This property is meant to allow users to easily find contact information
* for users represented by principals on the system. This property
* specifies the URL of the resource containing the corresponding contact
* information. The resource could be an address object resource in an
* address book collection, or it could be a resource in a "regular"
* collection.
*
* Definition:
*
*
* Example:
*
* /system/cyrus.vcf
*
*
*/
static class PrincipalAddress implements StandardProperty {
@Override
public String fieldName() {
return "principal-address";
}
@Override
public String getValue(PropFindableResource res) {
if (res instanceof CardDavPrincipal) {
return ((CardDavPrincipal) res).getAddress();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return Long.class;
}
}
/**
* The CARDDAV:directory-gateway identifies address book collection
* resources that are directory gateway address books for the server.
*
* Definition:
*
* Example: /directory
*
*
*/
static class DirectoryGateway implements StandardProperty {
@Override
public String fieldName() {
return "directory-gateway";
}
@Override
public HrefList getValue(PropFindableResource res) {
if (res instanceof DirectoryGatewayCardDavPrincipal) {
return ((DirectoryGatewayCardDavPrincipal) res).getDirectoryGateway();
} else {
return null;
}
}
@Override
public Class getValueClass() {
return HrefList.class;
}
}
@Override
public String getWellKnownName() {
return "carddav";
}
@Override
public Resource locateWellKnownResource(Resource host) {
log.trace("found a carddav well-known resource");
return new CardDavWellKnownResource(host);
}
public class CardDavWellKnownResource implements DigestResource, GetableResource, PropFindableResource {
private final Resource host;
public CardDavWellKnownResource(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 addressBookHomes;
String first;
if (auth != null && auth.getTag() != null) {
if (auth.getTag() instanceof CardDavPrincipal) {
CardDavPrincipal p = (CardDavPrincipal) auth.getTag();
addressBookHomes = p.getAddressBookHomeSet();
if (addressBookHomes == null || addressBookHomes.isEmpty()) {
log.error("can't redirect, CalDavPrincipal.getCalendatHomeSet did not return an address. Check implementation class: " + p.getClass());
return null;
} else {
first = addressBookHomes.get(0); // just use first
LogUtils.debug(log, "well-known: checkRedirect. redirecting to:", first);
return first;
}
} else {
log.warn("can't redirect, auth.getTag is not a CardDavPrincipal, is a: " + auth.getTag().getClass() + " To use CARDDAV, the user object returned from authenticate must be a " + CardDavPrincipal.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