diff --git a/src/at/bitfire/davdroid/Constants.java b/src/at/bitfire/davdroid/Constants.java index 6fb34050..df4c136b 100644 --- a/src/at/bitfire/davdroid/Constants.java +++ b/src/at/bitfire/davdroid/Constants.java @@ -9,7 +9,7 @@ package at.bitfire.davdroid; public class Constants { public static final String - APP_VERSION = "0.3.6-alpha", + APP_VERSION = "0.3.7-alpha", ACCOUNT_TYPE = "bitfire.at.davdroid", diff --git a/src/at/bitfire/davdroid/Utils.java b/src/at/bitfire/davdroid/URIUtils.java similarity index 76% rename from src/at/bitfire/davdroid/Utils.java rename to src/at/bitfire/davdroid/URIUtils.java index 872f678b..e937948e 100644 --- a/src/at/bitfire/davdroid/Utils.java +++ b/src/at/bitfire/davdroid/URIUtils.java @@ -3,8 +3,8 @@ package at.bitfire.davdroid; import java.net.URI; import java.net.URISyntaxException; -public class Utils { - public static boolean isSameURL(URI a, URI b) { +public class URIUtils { + public static boolean isSame(URI a, URI b) { try { a = new URI(a.getScheme(), null, a.getHost(), a.getPort(), a.getPath(), a.getQuery(), a.getFragment()); b = new URI(b.getScheme(), null, b.getHost(), b.getPort(), b.getPath(), b.getQuery(), b.getFragment()); @@ -14,8 +14,8 @@ public class Utils { } } - public static URI resolveURI(URI parent, String member) { - if (!member.startsWith("/") && !member.startsWith("http:") && !member.startsWith("https://")) + public static URI resolve(URI parent, String member) { + if (!member.startsWith("/") && !member.startsWith("http:") && !member.startsWith("https:")) member = "./" + member; return parent.resolve(member); diff --git a/src/at/bitfire/davdroid/resource/CalDavCalendar.java b/src/at/bitfire/davdroid/resource/CalDavCalendar.java index 2e3ac11c..e0e496ce 100644 --- a/src/at/bitfire/davdroid/resource/CalDavCalendar.java +++ b/src/at/bitfire/davdroid/resource/CalDavCalendar.java @@ -7,10 +7,9 @@ ******************************************************************************/ package at.bitfire.davdroid.resource; -import java.io.IOException; import java.net.URISyntaxException; -import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType; +import at.bitfire.davdroid.webdav.WebDavResource.MultigetType; public class CalDavCalendar extends RemoteCollection { //private final static String TAG = "davdroid.CalDavCalendar"; diff --git a/src/at/bitfire/davdroid/resource/CardDavAddressBook.java b/src/at/bitfire/davdroid/resource/CardDavAddressBook.java index e839ded7..1f59fcf5 100644 --- a/src/at/bitfire/davdroid/resource/CardDavAddressBook.java +++ b/src/at/bitfire/davdroid/resource/CardDavAddressBook.java @@ -9,7 +9,7 @@ package at.bitfire.davdroid.resource; import java.net.URISyntaxException; -import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType; +import at.bitfire.davdroid.webdav.WebDavResource.MultigetType; public class CardDavAddressBook extends RemoteCollection { //private final static String TAG = "davdroid.CardDavAddressBook"; diff --git a/src/at/bitfire/davdroid/resource/RemoteCollection.java b/src/at/bitfire/davdroid/resource/RemoteCollection.java index 97243c79..eedab73c 100644 --- a/src/at/bitfire/davdroid/resource/RemoteCollection.java +++ b/src/at/bitfire/davdroid/resource/RemoteCollection.java @@ -23,22 +23,21 @@ import org.apache.http.HttpException; import android.util.Log; import at.bitfire.davdroid.webdav.HttpPropfind; import at.bitfire.davdroid.webdav.InvalidDavResponseException; -import at.bitfire.davdroid.webdav.WebDavCollection; -import at.bitfire.davdroid.webdav.WebDavCollection.MultigetType; import at.bitfire.davdroid.webdav.WebDavResource; +import at.bitfire.davdroid.webdav.WebDavResource.MultigetType; import at.bitfire.davdroid.webdav.WebDavResource.PutMode; public abstract class RemoteCollection { private static final String TAG = "davdroid.RemoteCollection"; - @Getter WebDavCollection collection; + @Getter WebDavResource collection; abstract protected String memberContentType(); abstract protected MultigetType multiGetType(); abstract protected ResourceType newResourceSkeleton(String name, String ETag); public RemoteCollection(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException { - collection = new WebDavCollection(new URI(baseURL), user, password, preemptiveAuth); + collection = new WebDavResource(new URI(baseURL), user, password, preemptiveAuth, true); } @@ -58,9 +57,12 @@ public abstract class RemoteCollection { collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG); List resources = new LinkedList(); - for (WebDavResource member : collection.getMembers()) - resources.add(newResourceSkeleton(member.getName(), member.getETag())); - return resources.toArray(new Resource[0]); + if (collection.getMembers() != null) { + for (WebDavResource member : collection.getMembers()) + resources.add(newResourceSkeleton(member.getName(), member.getETag())); + return resources.toArray(new Resource[0]); + } else + return null; } @SuppressWarnings("unchecked") @@ -78,19 +80,23 @@ public abstract class RemoteCollection { collection.multiGet(names.toArray(new String[0]), multiGetType()); LinkedList foundResources = new LinkedList(); - for (WebDavResource member : collection.getMembers()) { - ResourceType resource = newResourceSkeleton(member.getName(), member.getETag()); - try { - InputStream is = member.getContent(); - if (is != null) { - resource.parseEntity(is); - foundResources.add(resource); - } else - Log.e(TAG, "Ignoring entity without content"); - } catch (ParserException ex) { - Log.e(TAG, "Ignoring unparseable entity in multi-response", ex); + if (collection.getMembers() != null) + for (WebDavResource member : collection.getMembers()) { + ResourceType resource = newResourceSkeleton(member.getName(), member.getETag()); + try { + InputStream is = member.getContent(); + if (is != null) { + resource.parseEntity(is); + foundResources.add(resource); + } else + Log.e(TAG, "Ignoring entity without content"); + } catch (ParserException ex) { + Log.e(TAG, "Ignoring unparseable entity in multi-response", ex); + } } - } + else + return null; + return foundResources.toArray(new Resource[0]); } catch (ParserException ex) { Log.w(TAG, "Couldn't parse single multi-get entity", ex); @@ -113,16 +119,22 @@ public abstract class RemoteCollection { WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag()); member.setContentType(memberContentType()); member.put(res.toEntity().getBytes("UTF-8"), PutMode.ADD_DONT_OVERWRITE); + + collection.invalidateCTag(); } public void delete(Resource res) throws IOException, HttpException { WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag()); member.delete(); + + collection.invalidateCTag(); } public void update(Resource res) throws IOException, HttpException, ValidationException { WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag()); member.setContentType(memberContentType()); member.put(res.toEntity().getBytes("UTF-8"), PutMode.UPDATE_DONT_OVERWRITE); + + collection.invalidateCTag(); } } diff --git a/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java b/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java index f3a31458..ea94e666 100644 --- a/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java +++ b/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java @@ -30,7 +30,6 @@ import android.widget.Toast; import at.bitfire.davdroid.R; import at.bitfire.davdroid.resource.IncapableResourceException; import at.bitfire.davdroid.webdav.HttpPropfind.Mode; -import at.bitfire.davdroid.webdav.WebDavCollection; import at.bitfire.davdroid.webdav.WebDavResource; public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks { @@ -110,13 +109,15 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC try { // (1/5) detect capabilities - WebDavCollection base = new WebDavCollection(new URI(serverInfo.getBaseURL()), serverInfo.getUserName(), - serverInfo.getPassword(), serverInfo.isAuthPreemptive()); + WebDavResource base = new WebDavResource(new URI(serverInfo.getBaseURL()), serverInfo.getUserName(), + serverInfo.getPassword(), serverInfo.isAuthPreemptive(), true); base.options(); serverInfo.setCardDAV(base.supportsDAV("addressbook")); serverInfo.setCalDAV(base.supportsDAV("calendar-access")); - if (!base.supportsMethod("PROPFIND") || (!serverInfo.isCalDAV() && !serverInfo.isCardDAV())) + if (!base.supportsMethod("PROPFIND") || !base.supportsMethod("GET") || + !base.supportsMethod("PUT") || !base.supportsMethod("DELETE") || + (!serverInfo.isCalDAV() && !serverInfo.isCardDAV())) throw new IncapableResourceException(getContext().getString(R.string.neither_caldav_nor_carddav)); // (2/5) get principal URL @@ -129,7 +130,7 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC throw new IncapableResourceException(getContext().getString(R.string.error_principal_path)); // (3/5) get home sets - WebDavCollection principal = new WebDavCollection(base, principalPath); + WebDavResource principal = new WebDavResource(base, principalPath); principal.propfind(Mode.HOME_SETS); String pathAddressBooks = null; @@ -152,42 +153,44 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC // (4/5) get address books if (serverInfo.isCardDAV()) { - WebDavCollection homeSetAddressBooks = new WebDavCollection(principal, pathAddressBooks); + WebDavResource homeSetAddressBooks = new WebDavResource(principal, pathAddressBooks); homeSetAddressBooks.propfind(Mode.MEMBERS_COLLECTIONS); List addressBooks = new LinkedList(); - for (WebDavResource resource : homeSetAddressBooks.getMembers()) - if (resource.isAddressBook()) { - Log.i(TAG, "Found address book: " + resource.getLocation().getPath()); - ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo( - ServerInfo.ResourceInfo.Type.ADDRESS_BOOK, - resource.getLocation().getPath(), - resource.getDisplayName(), - resource.getDescription() - ); - addressBooks.add(info); - } + if (homeSetAddressBooks.getMembers() != null) + for (WebDavResource resource : homeSetAddressBooks.getMembers()) + if (resource.isAddressBook()) { + Log.i(TAG, "Found address book: " + resource.getLocation().getPath()); + ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo( + ServerInfo.ResourceInfo.Type.ADDRESS_BOOK, + resource.getLocation().getPath(), + resource.getDisplayName(), + resource.getDescription() + ); + addressBooks.add(info); + } serverInfo.setAddressBooks(addressBooks); } // (5/5) get calendars if (serverInfo.isCalDAV()) { - WebDavCollection homeSetCalendars = new WebDavCollection(principal, pathCalendars); + WebDavResource homeSetCalendars = new WebDavResource(principal, pathCalendars); homeSetCalendars.propfind(Mode.MEMBERS_COLLECTIONS); List calendars = new LinkedList(); - for (WebDavResource resource : homeSetCalendars.getMembers()) - if (resource.isCalendar()) { - Log.i(TAG, "Found calendar: " + resource.getLocation().getPath()); - ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo( - ServerInfo.ResourceInfo.Type.CALENDAR, - resource.getLocation().getPath(), - resource.getDisplayName(), - resource.getDescription() - ); - calendars.add(info); - } + if (homeSetCalendars.getMembers() != null) + for (WebDavResource resource : homeSetCalendars.getMembers()) + if (resource.isCalendar()) { + Log.i(TAG, "Found calendar: " + resource.getLocation().getPath()); + ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo( + ServerInfo.ResourceInfo.Type.CALENDAR, + resource.getLocation().getPath(), + resource.getDisplayName(), + resource.getDescription() + ); + calendars.add(info); + } serverInfo.setCalendars(calendars); } diff --git a/src/at/bitfire/davdroid/webdav/HttpPropfind.java b/src/at/bitfire/davdroid/webdav/HttpPropfind.java index 5edb501a..d742ea0b 100644 --- a/src/at/bitfire/davdroid/webdav/HttpPropfind.java +++ b/src/at/bitfire/davdroid/webdav/HttpPropfind.java @@ -74,8 +74,8 @@ public class HttpPropfind extends HttpEntityEnclosingRequestBase { setEntity(new StringEntity(writer.toString(), "UTF-8")); Log.d(TAG, "Prepared PROPFIND request: " + writer.toString()); - } catch(Exception e) { - Log.w(TAG, e.getMessage()); + } catch(Exception ex) { + Log.e(TAG, "Couldn't prepare PROPFIND request", ex); abort(); } } diff --git a/src/at/bitfire/davdroid/webdav/WebDavCollection.java b/src/at/bitfire/davdroid/webdav/WebDavCollection.java deleted file mode 100644 index c2fefc87..00000000 --- a/src/at/bitfire/davdroid/webdav/WebDavCollection.java +++ /dev/null @@ -1,232 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013 Richard Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - ******************************************************************************/ -package at.bitfire.davdroid.webdav; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import lombok.Getter; - -import org.apache.commons.io.input.TeeInputStream; -import org.apache.http.HttpException; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.StatusLine; -import org.apache.http.message.BasicLineParser; -import org.simpleframework.xml.Serializer; -import org.simpleframework.xml.core.Persister; - -import android.util.Log; -import at.bitfire.davdroid.Utils; - -public class WebDavCollection extends WebDavResource { - private static final String TAG = "davdroid.WebDavCollection"; - - public enum MultigetType { - ADDRESS_BOOK, - CALENDAR - } - - /* list of resource members, empty until filled by propfind() or multiGet() */ - @Getter protected List members = new LinkedList(); - - - public WebDavCollection(URI baseURL, String username, String password, boolean preemptiveAuth) throws URISyntaxException { - super(baseURL, username, password, preemptiveAuth, true); - } - - public WebDavCollection(WebDavCollection parent, URI member) { - super(parent, member); - } - - public WebDavCollection(WebDavCollection parent, String member) { - super(parent, member); - } - - - /* collection operations */ - - public boolean propfind(HttpPropfind.Mode mode) throws IOException, InvalidDavResponseException, HttpException { - HttpPropfind propfind = new HttpPropfind(location, mode); - HttpResponse response = client.execute(propfind); - checkResponse(response); - - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) { - DavMultistatus multistatus; - try { - Serializer serializer = new Persister(); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream is = new TeeInputStream(response.getEntity().getContent(), baos); - multistatus = serializer.read(DavMultistatus.class, is, false); - - Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8")); - } catch (Exception ex) { - Log.w(TAG, "Invalid PROPFIND XML response", ex); - throw new InvalidDavResponseException(); - } - processMultiStatus(multistatus); - return true; - - } else - return false; - } - - public boolean multiGet(String[] names, MultigetType type) throws IOException, InvalidDavResponseException, HttpException { - DavMultiget multiget = (type == MultigetType.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget(); - - multiget.prop = new DavProp(); - multiget.prop.getetag = new DavProp.DavPropGetETag(); - - if (type == MultigetType.ADDRESS_BOOK) - multiget.prop.addressData = new DavProp.DavPropAddressData(); - else if (type == MultigetType.CALENDAR) - multiget.prop.calendarData = new DavProp.DavPropCalendarData(); - - multiget.hrefs = new ArrayList(names.length); - for (String name : names) { - if (!name.startsWith("/")) name = "./" + name; // allow colons in relative URLs (see issue #45) - multiget.hrefs.add(new DavHref(Utils.resolveURI(location, name).getPath())); - } - - Serializer serializer = new Persister(); - StringWriter writer = new StringWriter(); - try { - serializer.write(multiget, writer); - } catch (Exception e) { - Log.e(TAG, e.getLocalizedMessage()); - return false; - } - - HttpReport report = new HttpReport(location, writer.toString()); - HttpResponse response = client.execute(report); - checkResponse(response); - - if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) { - DavMultistatus multistatus; - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - InputStream is = new TeeInputStream(response.getEntity().getContent(), baos); - multistatus = serializer.read(DavMultistatus.class, is, false); - - Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8")); - } catch (Exception e) { - Log.e(TAG, e.getLocalizedMessage()); - return false; - } - processMultiStatus(multistatus); - - } else - throw new InvalidDavResponseException(); - return true; - } - - - /* member operations */ - - @Override - public void put(byte[] data, PutMode mode) throws IOException, HttpException { - properties.remove(Property.CTAG); - super.put(data, mode); - } - - @Override - public void delete() throws IOException, HttpException { - properties.remove(Property.CTAG); - super.delete(); - } - - - /* HTTP support */ - - protected void processMultiStatus(DavMultistatus multistatus) throws HttpException { - if (multistatus.response == null) // empty response - return; - - // member list will be built from response - members.clear(); - - for (DavResponse singleResponse : multistatus.response) { - URI href; - try { - href = Utils.resolveURI(location, singleResponse.getHref().href); - } catch(IllegalArgumentException ex) { - Log.w(TAG, "Ignoring illegal member URI in multi-status response", ex); - continue; - } - - // about which resource is this response? - WebDavResource referenced = null; - if (Utils.isSameURL(location, href)) { // -> ourselves - referenced = this; - - } else { // -> about a member - referenced = new WebDavResource(this, href); - members.add(referenced); - } - - 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; - - if (prop.currentUserPrincipal != null) - referenced.properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href); - - if (prop.addressbookHomeSet != null) - referenced.properties.put(Property.ADDRESSBOOK_HOMESET, prop.addressbookHomeSet.getHref().href); - - if (singlePropstat.prop.calendarHomeSet != null) - referenced.properties.put(Property.CALENDAR_HOMESET, prop.calendarHomeSet.getHref().href); - - if (prop.displayname != null) - referenced.properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName()); - - if (prop.resourcetype != null) { - if (prop.resourcetype.getAddressbook() != null) { - referenced.properties.put(Property.IS_ADDRESSBOOK, "1"); - - if (prop.addressbookDescription != null) - referenced.properties.put(Property.DESCRIPTION, prop.addressbookDescription.getDescription()); - } else - referenced.properties.remove(Property.IS_ADDRESSBOOK); - - if (prop.resourcetype.getCalendar() != null) { - referenced.properties.put(Property.IS_CALENDAR, "1"); - - if (prop.calendarDescription != null) - referenced.properties.put(Property.DESCRIPTION, prop.calendarDescription.getDescription()); - } else - referenced.properties.remove(Property.IS_CALENDAR); - } - - if (prop.getctag != null) - referenced.properties.put(Property.CTAG, prop.getctag.getCTag()); - - if (prop.getetag != null) - referenced.properties.put(Property.ETAG, prop.getetag.getETag()); - - if (prop.calendarData != null) - referenced.content = new ByteArrayInputStream(prop.calendarData.ical.getBytes()); - else if (prop.addressData != null) - referenced.content = new ByteArrayInputStream(prop.addressData.vcard.getBytes()); - } - } - } -} diff --git a/src/at/bitfire/davdroid/webdav/WebDavResource.java b/src/at/bitfire/davdroid/webdav/WebDavResource.java index 22445b0e..3e3a05cb 100644 --- a/src/at/bitfire/davdroid/webdav/WebDavResource.java +++ b/src/at/bitfire/davdroid/webdav/WebDavResource.java @@ -7,18 +7,25 @@ ******************************************************************************/ package at.bitfire.davdroid.webdav; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.StringWriter; import java.net.URI; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import lombok.Getter; import lombok.ToString; +import org.apache.commons.io.input.TeeInputStream; import org.apache.commons.lang.StringUtils; import org.apache.http.Header; import org.apache.http.HttpException; @@ -35,11 +42,14 @@ import org.apache.http.client.methods.HttpPut; import org.apache.http.client.params.ClientPNames; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicLineParser; import org.apache.http.params.CoreProtocolPNames; +import org.simpleframework.xml.Serializer; +import org.simpleframework.xml.core.Persister; import android.util.Log; import at.bitfire.davdroid.Constants; -import at.bitfire.davdroid.Utils; +import at.bitfire.davdroid.URIUtils; @ToString @@ -54,29 +64,54 @@ public class WebDavResource { CTAG, ETAG, CONTENT_TYPE } + public enum MultigetType { + ADDRESS_BOOK, + CALENDAR + } public enum PutMode { ADD_DONT_OVERWRITE, UPDATE_DONT_OVERWRITE } + // location of this resource @Getter protected URI location; - protected Set capabilities = new HashSet(), methods = new HashSet(); + + // DAV capabilities (DAV: header) and allowed DAV methods (set for OPTIONS request) + protected Set capabilities = new HashSet(), + methods = new HashSet(); + + // DAV properties protected HashMap properties = new HashMap(); + + // list of members (only for collections) + @Getter protected List members; + + // content (available after GET) @Getter protected InputStream content; protected DefaultHttpClient client; - public WebDavResource(URI baseURL, String username, String password, boolean preemptive, boolean isCollection) throws URISyntaxException { + public WebDavResource(URI baseURL, boolean trailingSlash) throws URISyntaxException { location = baseURL.normalize(); - if (isCollection && !location.getPath().endsWith("/")) + if (trailingSlash && !location.getPath().endsWith("/")) location = new URI(location.getScheme(), location.getSchemeSpecificPart() + "/", null); // create new HTTP client client = new DefaultHttpClient(); client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "DAVdroid/" + Constants.APP_VERSION); + // allow gzip compression + GzipDecompressingEntity.enable(client); + + // redirections + client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false); + } + + public WebDavResource(URI baseURL, String username, String password, boolean preemptive, boolean trailingSlash) throws URISyntaxException { + this(baseURL, trailingSlash); + // authenticate client.getCredentialsProvider().setCredentials(new AuthScope(location.getHost(), location.getPort()), new UsernamePasswordCredentials(username, password)); @@ -85,29 +120,24 @@ public class WebDavResource { Log.i(TAG, "Using preemptive Basic Authentication"); client.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0); } - - // allow gzip compression - GzipDecompressingEntity.enable(client); - - // redirections - client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false); } - protected WebDavResource(WebDavCollection parent, URI uri) { + protected WebDavResource(WebDavResource parent, URI uri) { location = uri; client = parent.client; } - public WebDavResource(WebDavCollection parent, String member) { - location = Utils.resolveURI(parent.location, member); + public WebDavResource(WebDavResource parent, String member) { + location = URIUtils.resolve(parent.location, member); client = parent.client; } - public WebDavResource(WebDavCollection parent, String member, String ETag) { + public WebDavResource(WebDavResource parent, String member, String ETag) { this(parent, member); properties.put(Property.ETAG, ETag); } + protected void checkResponse(HttpResponse response) throws HttpException { checkResponse(response.getStatusLine()); } @@ -140,14 +170,12 @@ public class WebDavResource { checkResponse(response); Header[] allowHeaders = response.getHeaders("Allow"); - if (allowHeaders != null) - for (Header allowHeader : allowHeaders) - methods.addAll(Arrays.asList(allowHeader.getValue().split(", ?"))); + for (Header allowHeader : allowHeaders) + methods.addAll(Arrays.asList(allowHeader.getValue().split(", ?"))); Header[] capHeaders = response.getHeaders("DAV"); - if (capHeaders != null) - for (Header capHeader : capHeaders) - capabilities.addAll(Arrays.asList(capHeader.getValue().split(", ?"))); + for (Header capHeader : capHeaders) + capabilities.addAll(Arrays.asList(capHeader.getValue().split(", ?"))); } public boolean supportsDAV(String capability) { @@ -192,6 +220,9 @@ public class WebDavResource { public String getCTag() { return properties.get(Property.CTAG); } + public void invalidateCTag() { + properties.remove(Property.CTAG); + } public String getETag() { return properties.get(Property.ETAG); @@ -214,6 +245,81 @@ public class WebDavResource { } + /* collection operations */ + + public boolean propfind(HttpPropfind.Mode mode) throws IOException, InvalidDavResponseException, HttpException { + HttpPropfind propfind = new HttpPropfind(location, mode); + HttpResponse response = client.execute(propfind); + checkResponse(response); + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) { + DavMultistatus multistatus; + try { + Serializer serializer = new Persister(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = new TeeInputStream(response.getEntity().getContent(), baos); + multistatus = serializer.read(DavMultistatus.class, is, false); + + Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8")); + } catch (Exception ex) { + Log.w(TAG, "Invalid PROPFIND XML response", ex); + throw new InvalidDavResponseException(); + } + processMultiStatus(multistatus); + return true; + + } else + return false; + } + + public boolean multiGet(String[] names, MultigetType type) throws IOException, InvalidDavResponseException, HttpException { + DavMultiget multiget = (type == MultigetType.ADDRESS_BOOK) ? new DavAddressbookMultiget() : new DavCalendarMultiget(); + + multiget.prop = new DavProp(); + multiget.prop.getetag = new DavProp.DavPropGetETag(); + + if (type == MultigetType.ADDRESS_BOOK) + multiget.prop.addressData = new DavProp.DavPropAddressData(); + else if (type == MultigetType.CALENDAR) + multiget.prop.calendarData = new DavProp.DavPropCalendarData(); + + multiget.hrefs = new ArrayList(names.length); + for (String name : names) + multiget.hrefs.add(new DavHref(URIUtils.resolve(location, name).getPath())); + + Serializer serializer = new Persister(); + StringWriter writer = new StringWriter(); + try { + serializer.write(multiget, writer); + } catch (Exception e) { + Log.e(TAG, e.getLocalizedMessage()); + return false; + } + + HttpReport report = new HttpReport(location, writer.toString()); + HttpResponse response = client.execute(report); + checkResponse(response); + + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_MULTI_STATUS) { + DavMultistatus multistatus; + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = new TeeInputStream(response.getEntity().getContent(), baos); + multistatus = serializer.read(DavMultistatus.class, is, false); + + Log.d(TAG, "Received multistatus response: " + baos.toString("UTF-8")); + } catch (Exception e) { + Log.e(TAG, e.getLocalizedMessage()); + return false; + } + processMultiStatus(multistatus); + + } else + throw new InvalidDavResponseException(); + return true; + } + + /* resource operations */ public void get() throws IOException, HttpException { @@ -233,13 +339,13 @@ public class WebDavResource { put.addHeader("If-None-Match", "*"); break; case UPDATE_DONT_OVERWRITE: - put.addHeader("If-Match", getETag()); + put.addHeader("If-Match", (getETag() != null) ? getETag() : "*"); break; } if (getContentType() != null) put.addHeader("Content-Type", getContentType()); - + checkResponse(client.execute(put)); } @@ -252,4 +358,87 @@ public class WebDavResource { checkResponse(client.execute(delete)); } + + /* helpers */ + + protected void processMultiStatus(DavMultistatus multistatus) throws HttpException { + if (multistatus.response == null) // empty response + return; + + // member list will be built from response + List members = new LinkedList(); + + for (DavResponse singleResponse : multistatus.response) { + URI href; + try { + href = URIUtils.resolve(location, singleResponse.getHref().href); + } catch(IllegalArgumentException ex) { + Log.w(TAG, "Ignoring illegal member URI in multi-status response", ex); + continue; + } + + // about which resource is this response? + WebDavResource referenced = null; + if (URIUtils.isSame(location, href)) { // -> ourselves + referenced = this; + + } else { // -> about a member + referenced = new WebDavResource(this, href); + members.add(referenced); + } + + 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; + + if (prop.currentUserPrincipal != null && prop.currentUserPrincipal.getHref() != null) + referenced.properties.put(Property.CURRENT_USER_PRINCIPAL, prop.currentUserPrincipal.getHref().href); + + if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null) + referenced.properties.put(Property.ADDRESSBOOK_HOMESET, prop.addressbookHomeSet.getHref().href); + + if (singlePropstat.prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null) + referenced.properties.put(Property.CALENDAR_HOMESET, prop.calendarHomeSet.getHref().href); + + if (prop.displayname != null) + referenced.properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName()); + + if (prop.resourcetype != null) { + if (prop.resourcetype.getAddressbook() != null) { + referenced.properties.put(Property.IS_ADDRESSBOOK, "1"); + + if (prop.addressbookDescription != null) + referenced.properties.put(Property.DESCRIPTION, prop.addressbookDescription.getDescription()); + } else + referenced.properties.remove(Property.IS_ADDRESSBOOK); + + if (prop.resourcetype.getCalendar() != null) { + referenced.properties.put(Property.IS_CALENDAR, "1"); + + if (prop.calendarDescription != null) + referenced.properties.put(Property.DESCRIPTION, prop.calendarDescription.getDescription()); + } else + referenced.properties.remove(Property.IS_CALENDAR); + } + + if (prop.getctag != null) + referenced.properties.put(Property.CTAG, prop.getctag.getCTag()); + + if (prop.getetag != null) + referenced.properties.put(Property.ETAG, prop.getetag.getETag()); + + if (prop.calendarData != null) + referenced.content = new ByteArrayInputStream(prop.calendarData.ical.getBytes()); + else if (prop.addressData != null) + referenced.content = new ByteArrayInputStream(prop.addressData.vcard.getBytes()); + } + } + + this.members = members; + } } diff --git a/test/assets/test.random b/test/assets/test.random new file mode 100644 index 00000000..eb3e5b02 Binary files /dev/null and b/test/assets/test.random differ diff --git a/test/robohydra/.gitignore b/test/robohydra/.gitignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/test/robohydra/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/test/robohydra/davdroid.conf b/test/robohydra/davdroid.conf new file mode 100644 index 00000000..441ec99a --- /dev/null +++ b/test/robohydra/davdroid.conf @@ -0,0 +1,5 @@ +{"plugins":[ + "assets", + "redirect", + "dav-default" +]} diff --git a/test/robohydra/plugins/assets/index.js b/test/robohydra/plugins/assets/index.js new file mode 100644 index 00000000..2a254b9a --- /dev/null +++ b/test/robohydra/plugins/assets/index.js @@ -0,0 +1,12 @@ +var RoboHydraHeadFilesystem = require("robohydra").heads.RoboHydraHeadFilesystem; + +exports.getBodyParts = function(conf) { + return { + heads: [ + new RoboHydraHeadFilesystem({ + mountPath: '/assets/', + documentRoot: '../assets' + }) + ] + }; +}; diff --git a/test/robohydra/plugins/dav-default/index.js b/test/robohydra/plugins/dav-default/index.js new file mode 100644 index 00000000..043db786 --- /dev/null +++ b/test/robohydra/plugins/dav-default/index.js @@ -0,0 +1,156 @@ +var roboHydraHeadDAV = require("../headdav"); + +exports.getBodyParts = function(conf) { + return { + heads: [ + /* base URL */ + new RoboHydraHeadDAV({ + path: "/dav/", + handler: function(req,res,next) { } + }), + + /* principal URL */ + new RoboHydraHeadDAV({ + path: "/dav/principals/users/test", + handler: function(req,res,next) { + if (req.method == "PROPFIND" && req.rawBody.toString().match(/home-set/)) { + res.statusCode = 207; + res.write('\\ + \ + \ + ' + req.url + ' \ + \ + \ + \ + /dav/addressbooks/test/\ + \ + \ + /dav/calendars/test/\ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + '); + } + } + }), + + /* address-book home set */ + new RoboHydraHeadDAV({ + path: "/dav/addressbooks/test", + handler: function(req,res,next) { + if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) { + res.statusCode = 207; + res.write('\\ + \ + \ + /dav/addressbooks/test/useless-member\ + \ + \ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + /dav/addressbooks/test/default.vcf/\ + \ + \ + \ + \ + \ + \ + \ + Default Address Book\ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + '); + } + } + }), + + /* calendar home set */ + new RoboHydraHeadDAV({ + path: "/dav/calendars/test", + handler: function(req,res,next) { + if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) { + res.statusCode = 207; + res.write('\\ + \ + \ + /dav/calendars/test/shared.forbidden\ + \ + \ + \ + \ + HTTP/1.1 403 Forbidden\ + \ + \ + \ + /dav/calendars/test/private.ics\ + \ + \ + \ + \ + \ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + /dav/calendars/test/work.ics\ + \ + \ + \ + \ + \ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + '); + } + } + }), + + /* non-existing file */ + new RoboHydraHeadDAV({ + path: "/dav/collection/new.file", + handler: function(req,res,next) { + if (req.method == "PUT") { + if (req.headers['if-match']) /* can't overwrite new file */ + res.statusCode = 412; + else + res.statusCode = 201; + + } else if (req.method == "DELETE") + res.statusCode = 404; + } + }), + + /* existing file */ + new RoboHydraHeadDAV({ + path: "/dav/collection/existing.file", + handler: function(req,res,next) { + if (req.method == "PUT") { + if (req.headers['if-none-match']) /* requested "don't overwrite", but this file exists */ + res.statusCode = 412; + else + res.statusCode = 204; + + } else if (req.method == "DELETE") + res.statusCode = 204; + } + }) + ] + }; +}; diff --git a/test/robohydra/plugins/dav-invalid/index.js b/test/robohydra/plugins/dav-invalid/index.js new file mode 100644 index 00000000..b1c41a47 --- /dev/null +++ b/test/robohydra/plugins/dav-invalid/index.js @@ -0,0 +1,36 @@ +var roboHydraHeadDAV = require("../headdav"); + +exports.getBodyParts = function(conf) { + return { + heads: [ + /* address-book home set */ + new RoboHydraHeadDAV({ + path: "/dav-invalid/addressbooks/user%40domain/", + handler: function(req,res,next) { + if (req.method == "PROPFIND" && req.rawBody.toString().match(/addressbook-description/)) { + res.statusCode = 207; + res.write('\\ + \ + \ + /dav/addressbooks/user@domain/My Contacts.vcf/\ + \ + \ + \ + \ + \ + \ + \ + Address Book with @ and space in URL\ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + '); + } + } + }) + ] + }; +}; diff --git a/test/robohydra/plugins/headdav.js b/test/robohydra/plugins/headdav.js new file mode 100644 index 00000000..669868f9 --- /dev/null +++ b/test/robohydra/plugins/headdav.js @@ -0,0 +1,50 @@ +var roboHydra = require("robohydra"), + roboHydraHeads = roboHydra.heads, + roboHydraHead = roboHydraHeads.RoboHydraHead; + +RoboHydraHeadDAV = roboHydraHeads.roboHydraHeadType({ + name: 'WebDAV Server', + mandatoryProperties: [ 'path', 'handler' ], + + parentPropBuilder: function() { + var myHandler = this.handler; + return { + path: this.path, + handler: function(req,res,next) { + // default DAV behavior + res.headers['DAV'] = 'addressbook, calendar-access'; + res.statusCode = 500; + + // DAV operations that work on all URLs + if (req.method == "OPTIONS") { + res.statusCode = 204; + res.headers['Allow'] = 'OPTIONS, PROPFIND, GET, PUT, DELETE'; + + } else if (req.method == "PROPFIND" && req.rawBody.toString().match(/current-user-principal/)) { + res.statusCode = 207; + res.write('\\ + \ + \ + ' + req.url + ' \ + \ + \ + \ + /dav/principals/users/test\ + \ + \ + HTTP/1.1 200 OK\ + \ + \ + \ + '); + + } else + myHandler(req,res,next); + + res.end(); + } + } + } +}); + +module.exports = RoboHydraHeadDAV; diff --git a/test/robohydra/plugins/redirect/index.js b/test/robohydra/plugins/redirect/index.js new file mode 100644 index 00000000..b9c86634 --- /dev/null +++ b/test/robohydra/plugins/redirect/index.js @@ -0,0 +1,18 @@ +var RoboHydraHead = require("robohydra").heads.RoboHydraHead; + +exports.getBodyParts = function(conf) { + return { + heads: [ + new RoboHydraHead({ + path: "/redirect", + handler: function(req,res,next) { + res.statusCode = 302; + res.headers = { + location: 'http://www.example.com' + } + res.end(); + } + }) + ] + }; +}; diff --git a/test/robohydra/run.sh b/test/robohydra/run.sh new file mode 100755 index 00000000..f53c7967 --- /dev/null +++ b/test/robohydra/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +node_modules/robohydra/bin/robohydra.js davdroid.conf -I plugins diff --git a/test/src/at/bitfire/davdroid/test/URIUtilsTest.java b/test/src/at/bitfire/davdroid/test/URIUtilsTest.java new file mode 100644 index 00000000..a77a0e89 --- /dev/null +++ b/test/src/at/bitfire/davdroid/test/URIUtilsTest.java @@ -0,0 +1,39 @@ +package at.bitfire.davdroid.test; + +import java.net.URI; +import java.net.URISyntaxException; + +import android.test.InstrumentationTestCase; +import at.bitfire.davdroid.URIUtils; + +public class URIUtilsTest extends InstrumentationTestCase { + final static String + ROOT_URI = "http://server/", + BASE_URI = ROOT_URI + "dir/"; + URI baseURI; + + @Override + protected void setUp() throws Exception { + baseURI = new URI(BASE_URI); + } + + + public void testIsSame() throws URISyntaxException { + assertTrue(URIUtils.isSame(new URI(ROOT_URI + "my@email/"), new URI(ROOT_URI + "my%40email/"))); + } + + public void testResolve() { + // resolve absolute URL + assertEquals(ROOT_URI + "file", URIUtils.resolve(baseURI, "/file").toString()); + + // resolve relative URL (default case) + assertEquals(BASE_URI + "file", URIUtils.resolve(baseURI, "file").toString()); + + // resolve relative URL with special characters + assertEquals(BASE_URI + "fi:le", URIUtils.resolve(baseURI, "fi:le").toString()); + assertEquals(BASE_URI + "fi@le", URIUtils.resolve(baseURI, "fi@le").toString()); + + // resolve URL with other schema + assertEquals("https://server", URIUtils.resolve(baseURI, "https://server").toString()); + } +} diff --git a/test/src/at/bitfire/davdroid/webdav/test/WebDavCollectionTest.java b/test/src/at/bitfire/davdroid/webdav/test/WebDavCollectionTest.java deleted file mode 100644 index 08c2f77d..00000000 --- a/test/src/at/bitfire/davdroid/webdav/test/WebDavCollectionTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package at.bitfire.davdroid.webdav.test; - -import java.io.IOException; -import java.net.URI; - -import org.apache.http.HttpException; - -import android.test.InstrumentationTestCase; -import at.bitfire.davdroid.webdav.HttpPropfind; -import at.bitfire.davdroid.webdav.InvalidDavResponseException; -import at.bitfire.davdroid.webdav.WebDavCollection; -import at.bitfire.davdroid.webdav.WebDavResource; - -public class WebDavCollectionTest extends InstrumentationTestCase { - WebDavCollection dav; - - protected void setUp() throws Exception { - dav = new WebDavCollection(new URI("https://wurd.dev001.net/radicale/test/"), "test", "test", true); - } - - - public void testAutoDetection() throws InvalidDavResponseException, IOException, HttpException { - WebDavCollection myDav = dav; - - myDav.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL); - String currentUserPrincipal = myDav.getCurrentUserPrincipal(); - assertEquals("/radicale/test/", currentUserPrincipal); - - myDav = new WebDavCollection(dav, currentUserPrincipal); - myDav.propfind(HttpPropfind.Mode.HOME_SETS); - String homeSet = myDav.getAddressbookHomeSet(); - assertEquals("/radicale/test/", homeSet); - assertEquals("/radicale/test/", myDav.getCalendarHomeSet()); - - myDav = new WebDavCollection(dav, homeSet); - myDav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS); - assertEquals(3, myDav.getMembers().size()); - for (WebDavResource member : myDav.getMembers()) - assertTrue(member.isAddressBook() || member.isCalendar()); - } - -} diff --git a/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java b/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java index 3a6919ed..7653e100 100644 --- a/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java +++ b/test/src/at/bitfire/davdroid/webdav/test/WebDavResourceTest.java @@ -1,66 +1,178 @@ package at.bitfire.davdroid.webdav.test; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import org.apache.commons.io.IOUtils; import org.apache.http.HttpException; +import android.content.res.AssetManager; import android.test.InstrumentationTestCase; +import at.bitfire.davdroid.webdav.HttpPropfind; +import at.bitfire.davdroid.webdav.NotFoundException; +import at.bitfire.davdroid.webdav.PreconditionFailedException; import at.bitfire.davdroid.webdav.WebDavResource; +import at.bitfire.davdroid.webdav.WebDavResource.PutMode; + +// tests require running robohydra! public class WebDavResourceTest extends InstrumentationTestCase { - URI davBaseURI, uriWithoutDAV, uriWithRedirection; - final static String davUsername = "test", davPassword = "test"; + static final String ROBOHYDRA_BASE = "http://10.0.0.119:3000/"; + static byte[] SAMPLE_CONTENT = new byte[] { 1, 2, 3, 4, 5 }; + + AssetManager assetMgr; + WebDavResource simpleFile, + davCollection, davNonExistingFile, davExistingFile; + @Override protected void setUp() throws Exception { - davBaseURI = new URI("https://wurd.dev001.net/radicale/test/"); - uriWithoutDAV = new URI("http://www.apache.org"); - uriWithRedirection = new URI("http://wurd.dev001.net/public/"); + assetMgr = getInstrumentation().getContext().getResources().getAssets(); + + simpleFile = new WebDavResource(new URI(ROBOHYDRA_BASE + "assets/test.random"), false); + + davCollection = new WebDavResource(new URI(ROBOHYDRA_BASE + "dav"), true); + davNonExistingFile = new WebDavResource(davCollection, "collection/new.file"); + davExistingFile = new WebDavResource(davCollection, "collection/existing.file"); } + + /* test resource name handling */ - public void testGet() throws URISyntaxException, IOException, HttpException { - WebDavResource dav = new WebDavResource(uriWithoutDAV, "", "", false, true); - dav.get(); - InputStream is = dav.getContent(); - assertNotNull(is); + public void testGetName() { + // collection names should have a trailing slash + assertEquals("dav", davCollection.getName()); + // but non-collection names shouldn't + assertEquals("test.random", simpleFile.getName()); } public void testTrailingSlash() throws URISyntaxException { - WebDavResource dav = new WebDavResource(new URI("http://server/path"), "", "", false, true); - assertEquals("/path/", dav.getLocation().getPath()); - } + // collections should have a trailing slash + assertEquals("/dav/", davCollection.getLocation().getPath()); + // but non-collection members shouldn't + assertEquals("/assets/test.random", simpleFile.getLocation().getPath()); + } + + + /* test feature detection */ public void testOptions() throws URISyntaxException, IOException, HttpException { - String[] davMethods = new String[] { "PROPFIND", "PUT", "DELETE" }, + String[] davMethods = new String[] { "PROPFIND", "GET", "PUT", "DELETE" }, davCapabilities = new String[] { "addressbook", "calendar-access" }; // server without DAV - WebDavResource dav = new WebDavResource(uriWithoutDAV, "", "", false, true); - dav.options(); + simpleFile.options(); for (String method : davMethods) - assertFalse(dav.supportsMethod(method)); + assertFalse(simpleFile.supportsMethod(method)); for (String capability : davCapabilities) - assertFalse(dav.supportsDAV(capability)); + assertFalse(simpleFile.supportsDAV(capability)); // server with DAV - dav = new WebDavResource(davBaseURI, davUsername, davPassword, false, true); - dav.options(); + davCollection.options(); for (String davMethod : davMethods) - assert(dav.supportsMethod(davMethod)); + assert(davCollection.supportsMethod(davMethod)); for (String capability : davCapabilities) - assert(dav.supportsDAV(capability)); + assert(davCollection.supportsDAV(capability)); } - public void testRedirections() throws URISyntaxException, IOException { - WebDavResource dav = new WebDavResource(uriWithRedirection, "", "", false, true); + public void testPropfindCurrentUserPrincipal() throws IOException, HttpException { + assertTrue(davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL)); + assertEquals("/dav/principals/users/test", davCollection.getCurrentUserPrincipal()); + + assertFalse(simpleFile.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL)); + assertNull(simpleFile.getCurrentUserPrincipal()); + } + + public void testPropfindHomeSets() throws IOException, HttpException { + WebDavResource dav = new WebDavResource(davCollection, "principals/users/test"); + dav.propfind(HttpPropfind.Mode.HOME_SETS); + assertEquals("/dav/addressbooks/test/", dav.getAddressbookHomeSet()); + assertEquals("/dav/calendars/test/", dav.getCalendarHomeSet()); + } + + public void testPropfindAddressBooks() throws IOException, HttpException { + WebDavResource dav = new WebDavResource(davCollection, "addressbooks/test"); + dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS); + assertEquals(2, dav.getMembers().size()); + for (WebDavResource member : dav.getMembers()) { + if (member.getName().equals("default.vcf")) + assertTrue(member.isAddressBook()); + else + assertFalse(member.isAddressBook()); + assertFalse(member.isCalendar()); + } + } + + public void testPropfindCalendars() throws IOException, HttpException { + WebDavResource dav = new WebDavResource(davCollection, "calendars/test"); + dav.propfind(HttpPropfind.Mode.MEMBERS_COLLECTIONS); + assertEquals(3, dav.getMembers().size()); + for (WebDavResource member : dav.getMembers()) { + if (member.getName().contains(".ics")) + assertTrue(member.isCalendar()); + else + assertFalse(member.isCalendar()); + assertFalse(member.isAddressBook()); + } + } + + + /* test normal HTTP */ + + public void testDontFollowRedirections() throws URISyntaxException, IOException { + WebDavResource redirection = new WebDavResource(new URI(ROBOHYDRA_BASE + "redirect"), false); try { - dav.options(); + redirection.get(); } catch (HttpException e) { return; } fail(); } + + public void testGet() throws URISyntaxException, IOException, HttpException { + simpleFile.get(); + assertTrue(IOUtils.contentEquals( + assetMgr.open("test.random", AssetManager.ACCESS_STREAMING), + simpleFile.getContent() + )); + } + + public void testPutAddDontOverwrite() throws IOException, HttpException { + // should succeed on a non-existing file + davNonExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE); + + // should fail on an existing file + try { + davExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE); + } catch(PreconditionFailedException ex) { + return; + } + fail(); + } + + public void testPutUpdateDontOverwrite() throws IOException, HttpException { + // should succeed on an existing file + davExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); + + // should fail on a non-existing file + try { + davNonExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE); + } catch(PreconditionFailedException ex) { + return; + } + fail(); + } + + public void testDelete() throws IOException, HttpException { + // should succeed on an existing file + davExistingFile.delete(); + + // should fail on a non-existing file + try { + davNonExistingFile.delete(); + } catch (NotFoundException e) { + return; + } + fail(); + } }