mirror of
https://github.com/etesync/android
synced 2024-11-26 01:48:34 +00:00
refactoring, RoboHydra tests
This commit is contained in:
parent
0626f1ecdc
commit
362f0036be
@ -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",
|
||||
|
||||
|
@ -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);
|
@ -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<Event> {
|
||||
//private final static String TAG = "davdroid.CalDavCalendar";
|
||||
|
@ -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<Contact> {
|
||||
//private final static String TAG = "davdroid.CardDavAddressBook";
|
||||
|
@ -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<ResourceType extends Resource> {
|
||||
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<ResourceType extends Resource> {
|
||||
collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG);
|
||||
|
||||
List<ResourceType> resources = new LinkedList<ResourceType>();
|
||||
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<ResourceType extends Resource> {
|
||||
collection.multiGet(names.toArray(new String[0]), multiGetType());
|
||||
|
||||
LinkedList<ResourceType> foundResources = new LinkedList<ResourceType>();
|
||||
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<ResourceType extends Resource> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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<ServerInfo> {
|
||||
@ -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<ServerInfo.ResourceInfo> addressBooks = new LinkedList<ServerInfo.ResourceInfo>();
|
||||
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<ServerInfo.ResourceInfo> calendars = new LinkedList<ServerInfo.ResourceInfo>();
|
||||
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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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<WebDavResource> members = new LinkedList<WebDavResource>();
|
||||
|
||||
|
||||
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<DavHref>(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String> capabilities = new HashSet<String>(), methods = new HashSet<String>();
|
||||
|
||||
// DAV capabilities (DAV: header) and allowed DAV methods (set for OPTIONS request)
|
||||
protected Set<String> capabilities = new HashSet<String>(),
|
||||
methods = new HashSet<String>();
|
||||
|
||||
// DAV properties
|
||||
protected HashMap<Property, String> properties = new HashMap<Property, String>();
|
||||
|
||||
// list of members (only for collections)
|
||||
@Getter protected List<WebDavResource> 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<DavHref>(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<WebDavResource> members = new LinkedList<WebDavResource>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
BIN
test/assets/test.random
Normal file
BIN
test/assets/test.random
Normal file
Binary file not shown.
1
test/robohydra/.gitignore
vendored
Normal file
1
test/robohydra/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
5
test/robohydra/davdroid.conf
Normal file
5
test/robohydra/davdroid.conf
Normal file
@ -0,0 +1,5 @@
|
||||
{"plugins":[
|
||||
"assets",
|
||||
"redirect",
|
||||
"dav-default"
|
||||
]}
|
12
test/robohydra/plugins/assets/index.js
Normal file
12
test/robohydra/plugins/assets/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
var RoboHydraHeadFilesystem = require("robohydra").heads.RoboHydraHeadFilesystem;
|
||||
|
||||
exports.getBodyParts = function(conf) {
|
||||
return {
|
||||
heads: [
|
||||
new RoboHydraHeadFilesystem({
|
||||
mountPath: '/assets/',
|
||||
documentRoot: '../assets'
|
||||
})
|
||||
]
|
||||
};
|
||||
};
|
156
test/robohydra/plugins/dav-default/index.js
Normal file
156
test/robohydra/plugins/dav-default/index.js
Normal file
@ -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('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||
<multistatus xmlns="DAV:">\
|
||||
<response>\
|
||||
<href>' + req.url + '</href> \
|
||||
<propstat>\
|
||||
<prop>\
|
||||
<CARD:addressbook-home-set xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||
<href>/dav/addressbooks/test/</href>\
|
||||
</CARD:addressbook-home-set>\
|
||||
<CAL:calendar-home-set xmlns:CAL="urn:ietf:params:xml:ns:caldav">\
|
||||
<href>/dav/calendars/test/</href>\
|
||||
</CAL:calendar-home-set>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
</multistatus>\
|
||||
');
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
/* 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('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||
<multistatus xmlns="DAV:">\
|
||||
<response>\
|
||||
<href>/dav/addressbooks/test/useless-member</href>\
|
||||
<propstat>\
|
||||
<prop>\
|
||||
<resourcetype/>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
<response>\
|
||||
<href>/dav/addressbooks/test/default.vcf/</href>\
|
||||
<propstat>\
|
||||
<prop xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||
<resourcetype>\
|
||||
<collection/>\
|
||||
<CARD:addressbook/>\
|
||||
</resourcetype>\
|
||||
<CARD:addressbook-description>\
|
||||
Default Address Book\
|
||||
</CARD:addressbook-description>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
</multistatus>\
|
||||
');
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
/* 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('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||
<multistatus xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav">\
|
||||
<response>\
|
||||
<href>/dav/calendars/test/shared.forbidden</href>\
|
||||
<propstat>\
|
||||
<prop>\
|
||||
<resourcetype/>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 403 Forbidden</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
<response>\
|
||||
<href>/dav/calendars/test/private.ics</href>\
|
||||
<propstat>\
|
||||
<prop>\
|
||||
<resourcetype>\
|
||||
<collection/>\
|
||||
<CAL:calendar/>\
|
||||
</resourcetype>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
<response>\
|
||||
<href>/dav/calendars/test/work.ics</href>\
|
||||
<propstat>\
|
||||
<prop>\
|
||||
<resourcetype>\
|
||||
<collection/>\
|
||||
<CAL:calendar/>\
|
||||
</resourcetype>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
</multistatus>\
|
||||
');
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
/* 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;
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
};
|
36
test/robohydra/plugins/dav-invalid/index.js
Normal file
36
test/robohydra/plugins/dav-invalid/index.js
Normal file
@ -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('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||
<multistatus xmlns="DAV:">\
|
||||
<response>\
|
||||
<href>/dav/addressbooks/user@domain/My Contacts.vcf/</href>\
|
||||
<propstat>\
|
||||
<prop xmlns:CARD="urn:ietf:params:xml:ns:carddav">\
|
||||
<resourcetype>\
|
||||
<collection/>\
|
||||
<CARD:addressbook/>\
|
||||
</resourcetype>\
|
||||
<CARD:addressbook-description>\
|
||||
Address Book with @ and space in URL\
|
||||
</CARD:addressbook-description>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
</multistatus>\
|
||||
');
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
};
|
50
test/robohydra/plugins/headdav.js
Normal file
50
test/robohydra/plugins/headdav.js
Normal file
@ -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('\<?xml version="1.0" encoding="utf-8" ?>\
|
||||
<multistatus xmlns="DAV:">\
|
||||
<response>\
|
||||
<href>' + req.url + '</href> \
|
||||
<propstat>\
|
||||
<prop>\
|
||||
<current-user-principal>\
|
||||
<href>/dav/principals/users/test</href>\
|
||||
</current-user-principal>\
|
||||
</prop>\
|
||||
<status>HTTP/1.1 200 OK</status>\
|
||||
</propstat>\
|
||||
</response>\
|
||||
</multistatus>\
|
||||
');
|
||||
|
||||
} else
|
||||
myHandler(req,res,next);
|
||||
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = RoboHydraHeadDAV;
|
18
test/robohydra/plugins/redirect/index.js
Normal file
18
test/robohydra/plugins/redirect/index.js
Normal file
@ -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();
|
||||
}
|
||||
})
|
||||
]
|
||||
};
|
||||
};
|
2
test/robohydra/run.sh
Executable file
2
test/robohydra/run.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
node_modules/robohydra/bin/robohydra.js davdroid.conf -I plugins
|
39
test/src/at/bitfire/davdroid/test/URIUtilsTest.java
Normal file
39
test/src/at/bitfire/davdroid/test/URIUtilsTest.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user