io.milton.http.report.SyncCollectionReport 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
package io.milton.http.report;
import io.milton.common.Utils;
import io.milton.http.HttpManager;
import io.milton.http.Response;
import io.milton.http.XmlWriter;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.webdav.PropFindPropertyBuilder;
import io.milton.http.webdav.PropFindResponse;
import io.milton.http.webdav.PropFindXmlFooter;
import io.milton.http.webdav.PropFindXmlGenerator;
import io.milton.http.webdav.PropertiesRequest;
import io.milton.http.webdav.WebDavProtocol;
import io.milton.resource.PropFindableResource;
import io.milton.resource.RemovedResource;
import io.milton.resource.Resource;
import io.milton.resource.SyncCollectionResource;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.namespace.QName;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
public class SyncCollectionReport implements Report {
protected final Namespace NS_DAV = Namespace.getNamespace(WebDavProtocol.NS_DAV.getPrefix(), WebDavProtocol.NS_DAV.getName());
private enum SyncLevel {
One,
Infinite
}
private final PropFindPropertyBuilder propertyBuilder;
private final PropFindXmlGenerator xmlGenerator;
public SyncCollectionReport(PropFindPropertyBuilder propertyBuilder, PropFindXmlGenerator xmlGenerator) {
this.propertyBuilder = propertyBuilder;
this.xmlGenerator = xmlGenerator;
}
@Override
public String getName() {
return "sync-collection";
}
@Override
public String process(String host, String path, Resource r, Document doc)
throws BadRequestException, NotAuthorizedException {
if (!(r instanceof SyncCollectionResource)) {
throw new BadRequestException(r, "This resource does not support sync-token.");
}
SyncCollectionResource syncCollectionResource = (SyncCollectionResource) r;
/*
* This report is only defined when the Depth header has value "0";
other values result in a 400 (Bad Request) error response. Note
that [RFC3253], Section 3.6, states that if the Depth header is
not present, it defaults to a value of "0".
3.3. Depth Behavior
Servers MUST support only Depth:0 behavior with the
DAV:sync-collection report, i.e., the report targets only the
collection being synchronized in a single request.
*/
if (HttpManager.request().getDepthHeader() != 0) {
//throw new BadRequestException(r, "Depth header must be 0");
// iOS 8.1 sends depth=1 with sync-collection, so ignore the spec
}
/* However, clients
do need to "scope" the synchronization to different levels within
that collection -- specifically, immediate children (level "1") and
all children at any depth (level "infinite"). To specify which level
to use, clients MUST include a DAV:sync-level XML element in the
request. */
Element syncLevelElm = ReportUtils.find(doc.getRootElement(), "sync-level", NS_DAV);
/* To specify which level
to use, clients MUST include a DAV:sync-level XML element in the
request.*/
if (syncLevelElm == null) {
throw new BadRequestException(r, "DAV:sync-level must be included in the request.");
}
String syncLevel = syncLevelElm.getText(); // 1 or infinite
SyncLevel lv = SyncLevel.One;
if ("1".equals(syncLevel)) {
/* When the client specifies the DAV:sync-level XML element with a
value of "1", only appropriate internal member URLs (immediate
children) of the collection specified as the request-URI are
reported. */
lv = SyncLevel.One;
} else if ("infinite".equals(syncLevel)) {
/* When the client specifies the DAV:sync-level XML element with a
value of "infinite", all appropriate member URLs of the collection
specified as the request-URI are reported, provided child
collections themselves also support the DAV:sync-collection
report. */
lv = SyncLevel.Infinite;
} else {
throw new BadRequestException(r, String.format("Unsupported DAV:sync-level: \"%s\", must be either \"1\" or \"infinite\".", syncLevel));
}
URI syncToken = null;
Element syncTokenElm = ReportUtils.find(doc.getRootElement(), "sync-token", NS_DAV);
if (syncTokenElm != null)
{
String syncTokenText = syncTokenElm.getText();
if (syncTokenText != null && !syncTokenText.isEmpty())
{
try
{
syncToken = new URI(syncTokenText);
}
catch (URISyntaxException e)
{
throw new BadRequestException(r, "sync-token must be a valid URI.");
}
}
}
String parentHref = HttpManager.request().getAbsolutePath();
parentHref = Utils.suffixSlash(parentHref);
List respProps = new ArrayList<>();
findResources(syncCollectionResource, doc, syncToken, lv, parentHref, respProps);
final URI nextSyncToken = syncCollectionResource.getSyncToken();
/*
http://example.org/sync/1414342005182
1
*/
/*For members that have changed (i.e., are new or have had their
mapped resource modified), the DAV:response MUST contain at
least one DAV:propstat element and MUST NOT contain any
DAV:status element.
For members that have been removed, the DAV:response MUST
contain one DAV:status with a value set to '404 Not Found' and
MUST NOT contain any DAV:propstat element.
For members that are collections and are unable to support the
DAV:sync-collection report, the DAV:response MUST contain one
DAV:status with a value set to '403 Forbidden', a DAV:error
containing DAV:supported-report or DAV:sync-traversal-supported
(see Section 3.3 for which is appropriate) and MUST NOT contain
any DAV:propstat element.
*/
//List
return xmlGenerator.generate(respProps, new PropFindXmlFooter() {
@Override
public void footer(XmlWriter writer) {
writer.writeProperty(WebDavProtocol.NS_DAV.getPrefix(), "sync-token", nextSyncToken.toString());
}
});
}
private void findResources(SyncCollectionResource parent, Document doc, URI syncToken, SyncLevel syncLevel, String parentHref, List respProps) throws NotAuthorizedException, BadRequestException {
Map children = parent.findResourcesBySyncToken(syncToken);
for (String href : children.keySet()) {
Resource r = children.get(href);
//hrefs.add(parentHref + r.getName());
if (r instanceof RemovedResource) {
/* 3.4. Types of Changes Reported on Initial Synchronization
When the DAV:sync-collection request contains an empty DAV:sync-token
element, the server MUST return all member URLs of the collection
(taking account of the DAV:sync-level XML element value as per
Section 3.3, and optional truncation of the result set as per
Section 3.6) and it MUST NOT return any removed member URLs. All
types of member (collection or non-collection) MUST be reported.
*/
if (syncToken != null) {
/* For members that have been removed, the DAV:response MUST
contain one DAV:status with a value set to '404 Not Found' and
MUST NOT contain any DAV:propstat element. */
PropFindResponse resp = new PropFindResponse(href, Response.Status.SC_NOT_FOUND);
// TODO 404 must be in just under the response
respProps.add(resp);
}
} else if (r instanceof PropFindableResource) {
Set props = getProps(doc);
PropertiesRequest parseResult = PropertiesRequest.toProperties(props);
PropFindableResource pfr = (PropFindableResource) r;
try {
respProps.addAll(propertyBuilder.buildProperties(pfr, 0, parseResult, href));
} catch (URISyntaxException ex) {
throw new RuntimeException("There was an unencoded url requested: " + href, ex);
}
} else {
//log.warn("requested href is for a non PropFindableResource: " + r.getClass() + " - " + href);
}
if (syncLevel == SyncLevel.Infinite) {
/* When the client specifies the DAV:sync-level XML element with a
value of "infinite", all appropriate member URLs of the collection
specified as the request-URI are reported, provided child
collections themselves also support the DAV:sync-collection
report. */
if (r instanceof SyncCollectionResource) {
String currentHref = Utils.suffixSlash(parentHref + r.getName());
findResources((SyncCollectionResource) r, doc, syncToken, syncLevel, currentHref, respProps);
}
/* For members that are collections and are unable to support the
DAV:sync-collection report, the DAV:response MUST contain one
DAV:status with a value set to '403 Forbidden', a DAV:error
containing DAV:supported-report or DAV:sync-traversal-supported
(see Section 3.3 for which is appropriate) and MUST NOT contain
any DAV:propstat element.
*/
}
}
}
private Set getProps(Document doc) {
Element elProp = doc.getRootElement().getChild("prop", NS_DAV);
if (elProp == null) {
throw new RuntimeException("No prop element");
}
Set set = new HashSet<>();
for (Object o : elProp.getChildren()) {
if (o instanceof Element) {
Element el = (Element) o;
String local = el.getName();
String ns = el.getNamespaceURI();
set.add(new QName(ns, local, el.getNamespacePrefix()));
}
}
return set;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy