Support OPTIONS, improve handling of illegal multi-status responses + tests

parent a22eb4eb
......@@ -48,6 +48,25 @@ public class DavResourceTest extends TestCase {
}
public void testOptions() throws InterruptedException, IOException, HttpException, DavException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setHeader("DAV", "1,2,3, hyperactive-access"));
dav.options();
assertTrue(dav.capabilities.contains("1"));
assertTrue(dav.capabilities.contains("2"));
assertTrue(dav.capabilities.contains("3"));
assertTrue(dav.capabilities.contains("hyperactive-access"));
mockServer.enqueue(new MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK));
dav.options();
assertTrue(dav.capabilities.isEmpty());
}
public void testGet() throws InterruptedException, IOException, HttpException, DavException {
HttpUrl url = sampleUrl();
DavResource dav = new DavResource(httpClient, url);
......@@ -422,6 +441,35 @@ public class DavResourceTest extends TestCase {
}
for (boolean singleOK : ok)
assertTrue(singleOK);
/*** SPECIAL CASES ***/
// same property is sent as 200 OK and 404 Not Found in same <response> (seen in iCloud)
mockServer.enqueue(new MockResponse()
.setResponseCode(207)
.setHeader("Content-Type", "application/xml; charset=utf-8")
.setBody("<multistatus xmlns='DAV:'>" +
" <response>" +
" <href>" + url.toString() + "</href>" +
" <propstat>" +
" <prop>" +
" <resourcetype><collection/></resourcetype>" +
" <displayname>My DAV Collection</displayname>" +
" </prop>" +
" <status>HTTP/1.1 200 OK</status>" +
" </propstat>" +
" <propstat>" +
" <prop>" +
" <resourcetype/>" +
" </prop>" +
" <status>HTTP/1.1 404 Not Found</status>" +
" </propstat>" +
" </response>" +
"</multistatus>"));
dav.propfind(0, ResourceType.NAME, DisplayName.NAME);
assertTrue(((ResourceType) dav.properties.get(ResourceType.NAME)).types.contains(ResourceType.COLLECTION));
assertEquals("My DAV Collection", ((DisplayName)dav.properties.get(DisplayName.NAME)).displayName);
}
public void testPropfindUpdateProperties() throws IOException, HttpException, DavException {
......
......@@ -44,6 +44,7 @@ public class DavAddressBook extends DavResource {
serializer.setOutput(writer);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-query");
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_WEBDAV, "getetag");
......@@ -81,6 +82,7 @@ public class DavAddressBook extends DavResource {
serializer.setOutput(writer);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
serializer.startTag(XmlUtils.NS_CARDDAV, "addressbook-multiget");
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_WEBDAV, "getcontenttype"); // to determine the character set
......
......@@ -49,6 +49,7 @@ public class DavCalendar extends DavResource {
serializer.setOutput(writer);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV);
serializer.startTag(XmlUtils.NS_CALDAV, "calendar-query");
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_WEBDAV, "getetag");
......@@ -91,6 +92,7 @@ public class DavCalendar extends DavResource {
serializer.setOutput(writer);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV);
serializer.startTag(XmlUtils.NS_CALDAV, "calendar-multiget");
serializer.startTag(XmlUtils.NS_WEBDAV, "prop");
serializer.startTag(XmlUtils.NS_WEBDAV, "getcontenttype"); // to determine the character set
......
......@@ -57,6 +57,7 @@ public class DavResource {
protected static final int MAX_REDIRECTS = 5;
public HttpUrl location;
public final Set<String> capabilities = new HashSet<>();
public final PropertyCollection properties = new PropertyCollection();
public final Set<DavResource> members = new HashSet<>();
......@@ -88,6 +89,22 @@ public class DavResource {
}
public void options() throws IOException, HttpException, DavException {
capabilities.clear();
Response response = httpClient.newCall(new Request.Builder()
.method("OPTIONS", null)
.url(location)
.build()).execute();
checkStatus(response);
String dav = response.header("DAV");
if (dav != null)
for (String capability : TextUtils.split(dav, ","))
capabilities.add(capability.trim());
}
/**
* Sends a GET request to the resource. Note that this method expects the server to
* return an ETag (which is required for CalDAV and CardDAV, but not for WebDAV in general).
......@@ -183,7 +200,6 @@ public class DavResource {
checkStatus(response);
}
/**
* Sends a PROPFIND request to the resource. Expects and processes a 207 multi-status response.
* #{@link #properties} are updated according to the multi-status response.
......@@ -197,6 +213,8 @@ public class DavResource {
StringWriter writer = new StringWriter();
serializer.setOutput(writer);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.setPrefix("CAL", XmlUtils.NS_CALDAV);
serializer.setPrefix("CARD", XmlUtils.NS_CARDDAV);
serializer.startDocument("UTF-8", null);
serializer.setPrefix("", XmlUtils.NS_WEBDAV);
serializer.startTag(XmlUtils.NS_WEBDAV, "propfind");
......@@ -452,7 +470,7 @@ public class DavResource {
if (target != null)
target.properties.merge(properties, true);
else
Constants.log.warn("Received <response> for resource that was not requested");
Constants.log.warn("Received <response> not for self and not for member resource");
}
private PropertyCollection parseMultiStatus_PropStat(XmlPullParser parser) throws IOException, XmlPullParserException {
......
......@@ -78,14 +78,31 @@ public class PropertyCollection {
}
/**
* Merges another #{@link PropertyCollection} into #{@link #properties}.
* Existing properties will be overwritten.
* @param another property collection to take the properties from
* @param removeNullValues Indicates how "another" properties with null values should be treated.
* <ul>
* <li>#{@code true}: If the "another" property value is #{@code null}, the property will be removed in #{@link #properties}.</li>
* <li>#{@code false}: If the "another" property value is #{@code null}, the property in #{@link #properties} will be set to null, too,
but only if it doesn't exist yet. This means values in #{@link #properties} will never be overwritten by #{@code null}.</li>
* </ul>
*/
public void merge(PropertyCollection another, boolean removeNullValues) {
Map<Property.Name, Property> properties = another.getMap();
for (Property.Name name : properties.keySet()) {
Property prop = properties.get(name);
if (!removeNullValues || prop != null)
if (prop != null)
put(name, prop);
else
remove(name);
else {
// prop == null
if (removeNullValues)
remove(name);
else if (get(name) == null) // never overwrite non-null values
put(name, null);
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment