Move resource detection to separate class + tests

pull/2/head
rfc2822 10 years ago
parent 75e5a59948
commit 2c79ae20e5

3
.gitignore vendored

@ -31,6 +31,9 @@ proguard/
*.iws
.idea/
# private files
.private
### ECLIPSE

@ -21,9 +21,6 @@
<string name="exception_uri_syntax">URI invàlida: %s</string>
<string name="exception_incapable_resource">Missing capabilities: %s</string>
<string name="neither_caldav_nor_carddav">Ni CalDAV ni CardDAV estan disponibles</string>
<string name="error_principal_path">"No s\'ha pogut determinar la ruta principal (RFC 5397)"</string>
<string name="error_home_set_address_books">"No s\'ha pogut determinar l\'arrel de les llibretes de contactes"</string>
<string name="error_home_set_calendars">"No s\'ha pogut determinar l\'arrel dels calendaris"</string>
<string name="add_account">Afegir compte</string>
<string name="querying_server">Querying server. Please wait…</string>
<string name="exception_http">Error HTTP: %s</string>

@ -12,10 +12,7 @@
<string name="exception_io">E/A-Fehler: %s</string>
<string name="exception_uri_syntax">Ungültiger URI: %s</string>
<string name="exception_incapable_resource">Fehlende Server-Unterstützung: %s</string>
<string name="neither_caldav_nor_carddav">Weder CalDAV noch CardDAV ist verfügbar.</string>
<string name="error_principal_path">Benutzerpfad konnte nicht festgestellt werden (RFC 5397)</string>
<string name="error_home_set_address_books">Adressbuch-Ordner konnte nicht festgestellt werden</string>
<string name="error_home_set_calendars">Kalender-Ordner konnte nicht festgestellt werden</string>
<string name="neither_caldav_nor_carddav">An dieser Adresse konnte kein CalDAV- oder CardDAV-Dienst gefunden werden.</string>
<string name="add_account">Konto hinzufügen</string>
<string name="querying_server">Daten werden vom Server abgefragt. Bitte warten…</string>
<string name="exception_http">HTTP-Fehler: %s</string>

@ -21,9 +21,6 @@
<string name="exception_uri_syntax">URI no válida: %s</string>
<string name="exception_incapable_resource">Se han perdido capacidades: %s</string>
<string name="neither_caldav_nor_carddav">Ni CalDAV ni CardDAV están disponibles</string>
<string name="error_principal_path">"No se ha podido determinar la ruta principal (RFC 5397)"</string>
<string name="error_home_set_address_books">"No se ha podido determinar la configuración de inicio de la agenda"</string>
<string name="error_home_set_calendars">"No se ha podido determinar la configuración de inicio del calendario"</string>
<string name="add_account">Añadir cuenta</string>
<string name="querying_server">Consultando el servidor. Espera, por favor…</string>
<string name="exception_http">error HTTP: %s</string>

@ -21,9 +21,6 @@
<string name="exception_uri_syntax">URI incorrecte: %s</string>
<string name="exception_incapable_resource">Capacités manquantes: %s</string>
<string name="neither_caldav_nor_carddav">Aucun CalDAV ou CardDAV disponible</string>
<string name="error_principal_path">Impossible de déterminer le chemin principal(RFC 5397)</string>
<string name="error_home_set_address_books">Impossible de déterminer le type de carnets d\'adresses</string>
<string name="error_home_set_calendars">Impossible de déterminer le type d\'agendas"</string>
<string name="add_account">Ajouter un compte</string>
<string name="querying_server">Interroge le serveur. Patientez svp.</string>
<string name="exception_http">Erreur HTTP: %s</string>

@ -20,10 +20,7 @@
<string name="exception_io">I/O error: %s</string>
<string name="exception_uri_syntax">Invalid URI: %s</string>
<string name="exception_incapable_resource">Missing capabilities: %s</string>
<string name="neither_caldav_nor_carddav">Neither CalDAV nor CardDAV available</string>
<string name="error_principal_path">"Couldn't determine principal path (RFC 5397)"</string>
<string name="error_home_set_address_books">"Couldn't determine address book home set"</string>
<string name="error_home_set_calendars">"Couldn't determine calendar home set"</string>
<string name="neither_caldav_nor_carddav">No CalDAV-/CardDAV service is available at this location.</string>
<string name="add_account">Add account</string>
<string name="querying_server">Querying server. Please wait…</string>
<string name="exception_http">HTTP error: %s</string>

@ -0,0 +1,160 @@
package at.bitfire.davdroid.syncadapter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import ch.boye.httpclientandroidlib.HttpException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import android.content.Context;
import android.util.Log;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.DavIncapableException;
import at.bitfire.davdroid.webdav.WebDavResource;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
public class DavResourceFinder {
private final static String TAG = "davdroid.DavResourceFinder";
public static void findResources(Context context, ServerInfo serverInfo) throws URISyntaxException, DavException, HttpException, IOException {
// disable compression and enable network logging for debugging purposes
CloseableHttpClient httpClient = DavHttpClient.create(true, true);
WebDavResource base = new WebDavResource(httpClient, new URI(serverInfo.getProvidedURL()), serverInfo.getUserName(),
serverInfo.getPassword(), serverInfo.isAuthPreemptive());
// CardDAV
WebDavResource principal = getCurrentUserPrincipal(base, "carddav");
if (principal != null) {
serverInfo.setCardDAV(true);
principal.propfind(Mode.HOME_SETS);
String pathAddressBooks = principal.getAddressbookHomeSet();
if (pathAddressBooks != null) {
Log.i(TAG, "Found address book home set: " + pathAddressBooks);
WebDavResource homeSetAddressBooks = new WebDavResource(principal, pathAddressBooks);
if (checkCapabilities(homeSetAddressBooks, "addressbook")) {
homeSetAddressBooks.propfind(Mode.MEMBERS_COLLECTIONS);
List<ServerInfo.ResourceInfo> addressBooks = new LinkedList<ServerInfo.ResourceInfo>();
if (homeSetAddressBooks.getMembers() != null)
for (WebDavResource resource : homeSetAddressBooks.getMembers())
if (resource.isAddressBook()) {
Log.i(TAG, "Found address book: " + resource.getLocation().getRawPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
resource.isReadOnly(),
resource.getLocation().toASCIIString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
addressBooks.add(info);
}
serverInfo.setAddressBooks(addressBooks);
} else
Log.w(TAG, "Found address-book home set, but it doesn't advertise CardDAV support");
}
}
// CalDAV
principal = getCurrentUserPrincipal(base, "caldav");
if (principal != null) {
serverInfo.setCalDAV(true);
principal.propfind(Mode.HOME_SETS);
String pathCalendars = principal.getCalendarHomeSet();
if (pathCalendars != null) {
Log.i(TAG, "Found calendar home set: " + pathCalendars);
WebDavResource homeSetCalendars = new WebDavResource(principal, pathCalendars);
if (checkCapabilities(homeSetCalendars, "calendar-access")) {
homeSetCalendars.propfind(Mode.MEMBERS_COLLECTIONS);
List<ServerInfo.ResourceInfo> calendars = new LinkedList<ServerInfo.ResourceInfo>();
if (homeSetCalendars.getMembers() != null)
for (WebDavResource resource : homeSetCalendars.getMembers())
if (resource.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getRawPath());
if (resource.getSupportedComponents() != null) {
// CALDAV:supported-calendar-component-set available
boolean supportsEvents = false;
for (String supportedComponent : resource.getSupportedComponents())
if (supportedComponent.equalsIgnoreCase("VEVENT"))
supportsEvents = true;
if (!supportsEvents) // ignore collections without VEVENT support
continue;
}
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
resource.isReadOnly(),
resource.getLocation().toASCIIString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
info.setTimezone(resource.getTimezone());
calendars.add(info);
}
serverInfo.setCalendars(calendars);
} else
Log.w(TAG, "Found calendar home set, but it doesn't advertise CalDAV support");
}
}
if (!serverInfo.isCalDAV() && !serverInfo.isCardDAV())
throw new DavIncapableException(context.getString(R.string.neither_caldav_nor_carddav));
}
/**
* Detects the current-user-principal for a given WebDavResource. At first, /.well-known/ is tried. Only
* if no current-user-principal can be detected for the .well-known location, the given location of the resource
* is tried.
* @param resource Location that will be queried
* @param serviceName Well-known service name ("carddav", "caldav")
* @return WebDavResource of current-user-principal for the given service, or null if it can't be found
*/
private static WebDavResource getCurrentUserPrincipal(WebDavResource resource, String serviceName) throws IOException, HttpException, DavException {
// look for well-known service (RFC 5785)
try {
WebDavResource wellKnown = new WebDavResource(resource, "/.well-known/" + serviceName);
wellKnown.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (wellKnown.getCurrentUserPrincipal() != null)
return new WebDavResource(wellKnown, wellKnown.getCurrentUserPrincipal());
} catch (HttpException e) {
Log.d(TAG, "Well-known service detection failed with HTTP error", e);
} catch (DavException e) {
Log.d(TAG, "Well-known service detection failed at DAV level", e);
}
// fall back to user-given initial context path
resource.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (resource.getCurrentUserPrincipal() != null)
return new WebDavResource(resource, resource.getCurrentUserPrincipal());
return null;
}
private static boolean checkCapabilities(WebDavResource resource, String davCapability) throws IOException {
// check for necessary capabilities
try {
resource.options();
if (resource.supportsDAV(davCapability) &&
resource.supportsMethod("PROPFIND") &&
resource.supportsMethod("GET") &&
resource.supportsMethod("REPORT") &&
resource.supportsMethod("PUT") &&
resource.supportsMethod("DELETE"))
return true;
} catch(HttpException e) {
// for instance, 405 Method not allowed
}
return false;
}
}

@ -8,10 +8,7 @@
package at.bitfire.davdroid.syncadapter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import android.app.DialogFragment;
import android.app.LoaderManager.LoaderCallbacks;
@ -27,12 +24,7 @@ import android.widget.ProgressBar;
import android.widget.Toast;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.DavIncapableException;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
import at.bitfire.davdroid.webdav.WebDavResource;
import ch.boye.httpclientandroidlib.HttpException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks<ServerInfo> {
private static final String TAG = "davdroid.QueryServerDialogFragment";
@ -111,94 +103,8 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
args.getBoolean(EXTRA_AUTH_PREEMPTIVE)
);
// disable compression and enable network logging for debugging purposes
CloseableHttpClient httpClient = DavHttpClient.create(true, true);
try {
WebDavResource base = new WebDavResource(httpClient, new URI(serverInfo.getProvidedURL()), serverInfo.getUserName(),
serverInfo.getPassword(), serverInfo.isAuthPreemptive());
// CardDAV
WebDavResource principal = getCurrentUserPrincipal(base, "carddav");
if (principal != null) {
serverInfo.setCardDAV(true);
principal.propfind(Mode.HOME_SETS);
String pathAddressBooks = principal.getAddressbookHomeSet();
if (pathAddressBooks != null) {
Log.i(TAG, "Found address book home set: " + pathAddressBooks);
WebDavResource homeSetAddressBooks = new WebDavResource(principal, pathAddressBooks);
if (checkCapabilities(homeSetAddressBooks, "addressbook")) {
homeSetAddressBooks.propfind(Mode.MEMBERS_COLLECTIONS);
List<ServerInfo.ResourceInfo> addressBooks = new LinkedList<ServerInfo.ResourceInfo>();
if (homeSetAddressBooks.getMembers() != null)
for (WebDavResource resource : homeSetAddressBooks.getMembers())
if (resource.isAddressBook()) {
Log.i(TAG, "Found address book: " + resource.getLocation().getRawPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
resource.isReadOnly(),
resource.getLocation().toASCIIString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
addressBooks.add(info);
}
serverInfo.setAddressBooks(addressBooks);
} else
Log.w(TAG, "Found address-book home set, but it doesn't advertise CardDAV support");
}
}
// CalDAV
principal = getCurrentUserPrincipal(base, "caldav");
if (principal != null) {
serverInfo.setCalDAV(true);
principal.propfind(Mode.HOME_SETS);
String pathCalendars = principal.getCalendarHomeSet();
if (pathCalendars != null) {
Log.i(TAG, "Found calendar home set: " + pathCalendars);
WebDavResource homeSetCalendars = new WebDavResource(principal, pathCalendars);
if (checkCapabilities(homeSetCalendars, "calendar-access")) {
homeSetCalendars.propfind(Mode.MEMBERS_COLLECTIONS);
List<ServerInfo.ResourceInfo> calendars = new LinkedList<ServerInfo.ResourceInfo>();
if (homeSetCalendars.getMembers() != null)
for (WebDavResource resource : homeSetCalendars.getMembers())
if (resource.isCalendar()) {
Log.i(TAG, "Found calendar: " + resource.getLocation().getRawPath());
if (resource.getSupportedComponents() != null) {
// CALDAV:supported-calendar-component-set available
boolean supportsEvents = false;
for (String supportedComponent : resource.getSupportedComponents())
if (supportedComponent.equalsIgnoreCase("VEVENT"))
supportsEvents = true;
if (!supportsEvents) // ignore collections without VEVENT support
continue;
}
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
resource.isReadOnly(),
resource.getLocation().toASCIIString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
info.setTimezone(resource.getTimezone());
calendars.add(info);
}
serverInfo.setCalendars(calendars);
} else
Log.w(TAG, "Found calendar home set, but it doesn't advertise CalDAV support");
}
}
if (!serverInfo.isCalDAV() && !serverInfo.isCardDAV())
throw new DavIncapableException(getContext().getString(R.string.neither_caldav_nor_carddav));
DavResourceFinder.findResources(context, serverInfo);
} catch (URISyntaxException e) {
serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage()));
} catch (IOException e) {
@ -214,49 +120,5 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
return serverInfo;
}
/**
* Detects the current-user-principal for a given WebDavResource. At first, /.well-known/ is tried. Only
* if no current-user-principal can be detected for the .well-known location, the given location of the resource
* is tried.
* @param resource Location that will be queried
* @param serviceName Well-known service name ("carddav", "caldav")
* @return WebDavResource of current-user-principal for the given service, or null if it can't be found
*/
private WebDavResource getCurrentUserPrincipal(WebDavResource resource, String serviceName) throws IOException, HttpException, DavException {
// look for well-known service (RFC 5785)
try {
WebDavResource wellKnown = new WebDavResource(resource, "/.well-known/" + serviceName);
wellKnown.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (wellKnown.getCurrentUserPrincipal() != null)
return new WebDavResource(wellKnown, wellKnown.getCurrentUserPrincipal());
} catch (HttpException e) {
Log.d(TAG, "Well-known service detection failed with HTTP error", e);
} catch (DavException e) {
Log.d(TAG, "Well-known service detection failed at DAV level", e);
}
// fall back to user-given initial context path
resource.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (resource.getCurrentUserPrincipal() != null)
return new WebDavResource(resource, resource.getCurrentUserPrincipal());
return null;
}
private boolean checkCapabilities(WebDavResource resource, String davCapability) throws IOException {
// check for necessary capabilities
try {
resource.options();
if (resource.supportsDAV(davCapability) &&
resource.supportsMethod("PROPFIND") &&
resource.supportsMethod("GET") &&
resource.supportsMethod("REPORT") &&
resource.supportsMethod("PUT") &&
resource.supportsMethod("DELETE"))
return true;
} catch(HttpException e) {
// for instance, 405 Method not allowed
}
return false;
}
}
}

@ -3,11 +3,8 @@ var roboHydraHeadDAV = require("../headdav");
exports.getBodyParts = function(conf) {
return {
heads: [
/* base URL */
new RoboHydraHeadDAV({
path: "/dav/",
handler: function(req,res,next) { }
}),
/* base URL, provide default DAV here */
new RoboHydraHeadDAV({ path: "/dav/" }),
/* principal URL */
new RoboHydraHeadDAV({
@ -66,9 +63,7 @@ exports.getBodyParts = function(conf) {
<collection/>\
<CARD:addressbook/>\
</resourcetype>\
<CARD:addressbook-description>\
Default Address Book\
</CARD:addressbook-description>\
<CARD:addressbook-description>Default Address Book</CARD:addressbook-description>\
</prop>\
<status>HTTP/1.1 200 OK</status>\
</propstat>\
@ -104,6 +99,8 @@ exports.getBodyParts = function(conf) {
<collection/>\
<CAL:calendar/>\
</resourcetype>\
<displayname>Private Calendar</displayname>\
<CAL:calendar-description>This is my private calendar.</CAL:calendar-description>\
</prop>\
<status>HTTP/1.1 200 OK</status>\
</propstat>\
@ -116,6 +113,10 @@ exports.getBodyParts = function(conf) {
<collection/>\
<CAL:calendar/>\
</resourcetype>\
<current-user-privilege-set>\
<privilege><read/></privilege>\
</current-user-privilege-set>\
<displayname>Work Calendar</displayname>\
<A:calendar-color xmlns:A="http://apple.com/ns/ical/">0xFF00FF</A:calendar-color>\
</prop>\
<status>HTTP/1.1 200 OK</status>\

@ -4,7 +4,8 @@ var roboHydra = require("robohydra"),
RoboHydraHeadDAV = roboHydraHeads.roboHydraHeadType({
name: 'WebDAV Server',
mandatoryProperties: [ 'path', 'handler' ],
mandatoryProperties: [ 'path' ],
optionalProperties: [ 'handler' ],
parentPropBuilder: function() {
var myHandler = this.handler;
@ -18,7 +19,7 @@ RoboHydraHeadDAV = roboHydraHeads.roboHydraHeadType({
// DAV operations that work on all URLs
if (req.method == "OPTIONS") {
res.statusCode = 204;
res.headers['Allow'] = 'OPTIONS, PROPFIND, GET, PUT, DELETE';
res.headers['Allow'] = 'OPTIONS, PROPFIND, GET, PUT, DELETE, REPORT';
} else if (req.method == "PROPFIND" && req.rawBody.toString().match(/current-user-principal/)) {
res.statusCode = 207;
@ -38,7 +39,7 @@ RoboHydraHeadDAV = roboHydraHeads.roboHydraHeadType({
</multistatus>\
');
} else
} else if (typeof myHandler != 'undefined')
myHandler(req,res,next);
res.end();

@ -12,7 +12,7 @@ exports.getBodyParts = function(conf) {
headers: { Location: '/dav/' }
}),
new SimpleResponseHead({
path: '/.well-known/caldav',
path: '/.well-known/carddav',
status: 302,
headers: { Location: '/dav/' }
}),

@ -0,0 +1,37 @@
package at.bitfire.davdroid.syncadapter;
import java.util.List;
import android.test.InstrumentationTestCase;
import at.bitfire.davdroid.syncadapter.ServerInfo.ResourceInfo;
import at.bitfire.davdroid.test.Constants;
public class DavResourceFinderTest extends InstrumentationTestCase {
public void testFindResources() throws Exception {
ServerInfo info = new ServerInfo(Constants.ROBOHYDRA_BASE, "test", "test", true);
DavResourceFinder.findResources(getInstrumentation().getContext(), info);
// CardDAV
assertTrue(info.isCardDAV());
List<ResourceInfo> collections = info.getAddressBooks();
assertEquals(1, collections.size());
assertEquals("Default Address Book", collections.get(0).getDescription());
// CalDAV
assertTrue(info.isCalDAV());
collections = info.getCalendars();
assertEquals(2, collections.size());
ResourceInfo resource = collections.get(0);
assertEquals("Private Calendar", resource.getTitle());
assertEquals("This is my private calendar.", resource.getDescription());
assertFalse(resource.isReadOnly());
resource = collections.get(1);
assertEquals("Work Calendar", resource.getTitle());
assertTrue(resource.isReadOnly());
}
}

@ -2,7 +2,7 @@ package at.bitfire.davdroid.webdav;
import java.io.IOException;
import android.test.InstrumentationTestCase;
import junit.framework.TestCase;
import at.bitfire.davdroid.test.Constants;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.client.methods.HttpOptions;
@ -12,7 +12,7 @@ import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ch.boye.httpclientandroidlib.impl.client.HttpClientBuilder;
import ch.boye.httpclientandroidlib.protocol.HttpContext;
public class DavRedirectStrategyTest extends InstrumentationTestCase {
public class DavRedirectStrategyTest extends TestCase {
CloseableHttpClient httpClient;
DavRedirectStrategy strategy = DavRedirectStrategy.INSTANCE;

Loading…
Cancel
Save