Ensure trailing slashes are always used for collections + tests

pull/2/head
rfc2822 10 years ago
parent b2bd53f36f
commit 1ec1db3045

@ -7,6 +7,8 @@
******************************************************************************/
package at.bitfire.davdroid;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -18,7 +20,32 @@ public class URIUtils {
private static final String TAG = "davdroid.URIUtils";
// handles invalid URLs/paths as good as possible
public static String ensureTrailingSlash(String href) {
if (!href.endsWith("/")) {
Log.d(TAG, "Implicitly appending trailing slash to collection " + href);
return href + "/";
} else
return href;
}
public static URI ensureTrailingSlash(URI href) {
if (!href.getPath().endsWith("/"))
try {
URI newURI = new URI(href.getScheme(), href.getAuthority(), href.getPath() + "/", href.getQuery(), null);
// "@" is the only character that is not encoded
newURI = new URI(newURI.toString().replaceAll("@", "%40"));
Log.d(TAG, "Implicitly appending trailing slash to collection " + href + " -> " + newURI);
return newURI;
} catch (URISyntaxException e) {
Log.e(TAG, "Couldn't append trailing slash to collection URI", e);
}
return href;
}
/** handles invalid URLs/paths as good as possible **/
public static String sanitize(String original) {
if (original == null)
return null;
@ -67,4 +94,5 @@ public class URIUtils {
}
}
}

@ -88,15 +88,17 @@ public class DavResourceFinder {
if (homeSetCalendars.getMembers() != null)
for (WebDavResource resource : homeSetCalendars.getMembers())
if (resource.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getRawPath());
Log.i(TAG, "Found calendar: " + resource.getLocation().getPath());
if (resource.getSupportedComponents() != null) {
// CALDAV:supported-calendar-component-set available
boolean supportsEvents = false;
for (String supportedComponent : resource.getSupportedComponents())
if (supportedComponent.equalsIgnoreCase("VEVENT"))
supportsEvents = true;
if (!supportsEvents) // ignore collections without VEVENT support
if (!supportsEvents) { // ignore collections without VEVENT support
Log.i(TAG, "Ignoring this calendar because of missing VEVENT support");
continue;
}
}
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,

@ -39,13 +39,16 @@ public class DavProp {
@Root(strict=false)
public static class ResourceType {
@Element(required=false)
@Getter private Addressbook addressbook;
@Element(required=false)
@Getter private Calendar calendar;
@Getter private Collection collection;
public static class Collection { }
@Element(required=false)
@Getter private Addressbook addressbook;
@Namespace(prefix="CD",reference="urn:ietf:params:xml:ns:carddav")
public static class Addressbook { }
@Element(required=false)
@Getter private Calendar calendar;
@Namespace(prefix="C",reference="urn:ietf:params:xml:ns:caldav")
public static class Calendar { }
}

@ -68,7 +68,8 @@ public class WebDavResource {
CURRENT_USER_PRINCIPAL, // resource detection
ADDRESSBOOK_HOMESET, CALENDAR_HOMESET,
CONTENT_TYPE, READ_ONLY, // WebDAV (common)
DISPLAY_NAME, DESCRIPTION, CTAG, ETAG,
DISPLAY_NAME, DESCRIPTION, ETAG,
IS_COLLECTION, CTAG, // collections
IS_CALENDAR, COLOR, TIMEZONE, // CalDAV
IS_ADDRESSBOOK, VCARD_VERSION // CardDAV
}
@ -452,6 +453,7 @@ public class WebDavResource {
// member list will be built from response
List<WebDavResource> members = new LinkedList<WebDavResource>();
// iterate through all resources (either ourselves or member)
for (DavResponse singleResponse : multiStatus.response) {
URI href;
try {
@ -461,42 +463,19 @@ public class WebDavResource {
continue;
}
Log.d(TAG, "Processing multi-status element: " + href);
// about which resource is this response?
WebDavResource referenced = null;
// "this" resource is either at "location" …
if (location.equals(href)) { // -> ourselves
referenced = this;
} else {
// … or at location + "/" (in case of a collection where the server has implicitly appended the trailing slash)
if (!location.getRawPath().endsWith("/")) // this is only possible if location doesn't have a trailing slash
try {
URI locationAsCollection = new URI(location.getScheme(), location.getAuthority(), location.getPath() + "/", location.getQuery(), null);
if (locationAsCollection.equals(href)) {
Log.d(TAG, "Server implicitly appended trailing slash to " + locationAsCollection);
referenced = this;
}
} catch (URISyntaxException e) {
Log.wtf(TAG, "Couldn't understand our own URI", e);
}
// otherwise, the referenced resource is a member
if (referenced == null) {
referenced = new WebDavResource(this, href);
members.add(referenced);
}
}
// process known properties
HashMap<Property, String> properties = new HashMap<Property, String>();
List<String> supportedComponents = null;
byte[] data = null;
for (DavPropstat singlePropstat : singleResponse.getPropstat()) {
StatusLine status = BasicLineParser.parseStatusLine(singlePropstat.status, new BasicLineParser());
// ignore information about missing properties etc.
if (status.getStatusCode()/100 != 1 && status.getStatusCode()/100 != 2)
continue;
DavProp prop = singlePropstat.prop;
HashMap<Property, String> properties = referenced.properties;
if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null)
properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href);
@ -520,15 +499,20 @@ public class WebDavResource {
}
if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
properties.put(Property.ADDRESSBOOK_HOMESET, prop.addressbookHomeSet.getHref().href);
properties.put(Property.ADDRESSBOOK_HOMESET, URIUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href));
if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null)
properties.put(Property.CALENDAR_HOMESET, prop.calendarHomeSet.getHref().href);
properties.put(Property.CALENDAR_HOMESET, URIUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href));
if (prop.displayname != null)
properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName());
if (prop.resourcetype != null) {
if (prop.resourcetype.getCollection() != null) {
properties.put(Property.IS_COLLECTION, "1");
// is a collection, ensure trailing slash
href = URIUtils.ensureTrailingSlash(href);
}
if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties
properties.put(Property.IS_ADDRESSBOOK, "1");
@ -554,9 +538,9 @@ public class WebDavResource {
properties.put(Property.TIMEZONE, Event.TimezoneDefToTzId(prop.calendarTimezone.getTimezone()));
if (prop.supportedCalendarComponentSet != null) {
referenced.supportedComponents = new LinkedList<String>();
supportedComponents = new LinkedList<String>();
for (Comp component : prop.supportedCalendarComponentSet)
referenced.supportedComponents.add(component.getName());
supportedComponents.add(component.getName());
}
}
}
@ -568,9 +552,26 @@ public class WebDavResource {
properties.put(Property.ETAG, prop.getetag.getETag());
if (prop.calendarData != null && prop.calendarData.ical != null)
referenced.content = prop.calendarData.ical.getBytes();
data = prop.calendarData.ical.getBytes();
else if (prop.addressData != null && prop.addressData.vcard != null)
referenced.content = prop.addressData.vcard.getBytes();
data = prop.addressData.vcard.getBytes();
}
// about which resource is this response?
// "this" resource is either at "location" …
if (location.equals(href)) { // -> ourselves
this.properties.putAll(properties);
if (supportedComponents != null)
this.supportedComponents = supportedComponents;
this.content = data;
} else {
WebDavResource member = new WebDavResource(this, href);
member.properties = properties;
member.supportedComponents = supportedComponents;
member.content = data;
members.add(member);
}
}

@ -35,7 +35,7 @@ exports.getBodyParts = function(conf) {
<CARD:addressbook/>\
</resourcetype>\
<CARD:addressbook-description>\
Address Book with absolute URL\
Address Book with absolute URL and at sign in path\
</CARD:addressbook-description>\
</prop>\
<status>HTTP/1.1 200 OK</status>\

@ -7,11 +7,24 @@
******************************************************************************/
package at.bitfire.davdroid.test;
import java.net.URI;
import java.net.URISyntaxException;
import junit.framework.TestCase;
import at.bitfire.davdroid.URIUtils;
public class URIUtilsTest extends TestCase {
public void testEnsureTrailingSlash() throws URISyntaxException {
assertEquals("/test/", URIUtils.ensureTrailingSlash("/test"));
assertEquals("/test/", URIUtils.ensureTrailingSlash("/test/"));
String withoutSlash = "http://www.test.at/dav/collection",
withSlash = withoutSlash + "/";
assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withoutSlash)));
assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withSlash)));
}
public void testSanitize() {
assertNull(URIUtils.sanitize(null));

@ -103,7 +103,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
public void testPropfindHomeSets() throws IOException, HttpException, DavException {
WebDavResource dav = new WebDavResource(davCollection, "principals/users/test");
dav.propfind(HttpPropfind.Mode.HOME_SETS);
assertEquals("/dav/addressbooks/test", dav.getAddressbookHomeSet());
assertEquals("/dav/addressbooks/test/", dav.getAddressbookHomeSet());
assertEquals("/dav/calendars/test/", dav.getCalendarHomeSet());
}
@ -221,7 +221,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
List<WebDavResource> members = dav.getMembers();
assertEquals(2, members.size());
assertEquals(Constants.ROBOHYDRA_BASE + "dav/addressbooks/user%40domain/My%20Contacts%3a1.vcf/", members.get(0).getLocation().toString());
assertEquals("HTTPS://example.com/user%40domain/absolute-url.vcf", members.get(1).getLocation().toString());
assertEquals("HTTPS://example.com/user%40domain/absolute-url.vcf/", members.get(1).getLocation().toString());
}
}

Loading…
Cancel
Save