Initial support for SRV/TXT service discovery

pull/2/head
rfc2822 10 years ago
parent e3a7c7092e
commit 8d4c353d8c

Binary file not shown.

@ -18,15 +18,14 @@
android:text="@string/root_url" />
<Spinner
android:id="@+id/select_protocol"
android:id="@+id/login_scheme"
android:layout_width="wrap_content"
android:layout_gravity="left"
android:entries="@array/http_protocols" />
android:entries="@array/uri_scheme" />
<EditText
android:id="@+id/baseURL"
android:id="@+id/login_authority_path"
android:layout_gravity="fill_horizontal"
android:hint="my.server.com"
android:imeOptions="flagForceAscii|actionNext"
android:inputType="textUri"
android:layout_width="0dp"

@ -4,9 +4,10 @@
<string name="app_name">DAVdroid</string>
<string name="menu_settings">Settings</string>
<string-array name="http_protocols">
<string-array name="uri_scheme">
<item>http://</item>
<item>https://</item>
<item>mailto:</item>
</string-array>
<string name="http_warning">"If you don't use encryption (HTTPS), other people may easily intercept your login details, contacts and events."</string>

@ -7,8 +7,8 @@
******************************************************************************/
package at.bitfire.davdroid;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -16,7 +16,7 @@ import android.annotation.SuppressLint;
import android.util.Log;
@SuppressLint("DefaultLocale")
public class URIUtils {
public class URLUtils {
private static final String TAG = "davdroid.URIUtils";
@ -28,7 +28,23 @@ public class URIUtils {
return href;
}
public static URI ensureTrailingSlash(URI href) {
public static URL ensureTrailingSlash(URL href) {
if (!href.getPath().endsWith("/"))
try {
URL newURL = new URL(href, href.getPath() + "/");
// "@" is the only character that is not encoded
//newURL = new URI(newURI.toString().replaceAll("@", "%40"));
Log.d(TAG, "Implicitly appending trailing slash to collection " + href + " -> " + newURL);
return newURL;
} catch (MalformedURLException e) {
Log.e(TAG, "Couldn't append trailing slash to collection URI", e);
}
return href;
}
/*public static URI ensureTrailingSlash(URI href) {
if (!href.getPath().endsWith("/"))
try {
URI newURI = new URI(href.getScheme(), href.getAuthority(), href.getPath() + "/", href.getQuery(), null);
@ -42,7 +58,7 @@ public class URIUtils {
Log.e(TAG, "Couldn't append trailing slash to collection URI", e);
}
return href;
}
}*/
/** handles invalid URLs/paths as good as possible **/

@ -7,7 +7,7 @@
******************************************************************************/
package at.bitfire.davdroid.resource;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import at.bitfire.davdroid.webdav.DavMultiget;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
@ -31,7 +31,7 @@ public class CalDavCalendar extends RemoteCollection<Event> {
}
public CalDavCalendar(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
public CalDavCalendar(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws MalformedURLException {
super(httpClient, baseURL, user, password, preemptiveAuth);
}
}

@ -7,10 +7,10 @@
******************************************************************************/
package at.bitfire.davdroid.resource;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import at.bitfire.davdroid.webdav.DavMultiget;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
public class CardDavAddressBook extends RemoteCollection<Contact> {
//private final static String TAG = "davdroid.CardDavAddressBook";
@ -31,7 +31,7 @@ public class CardDavAddressBook extends RemoteCollection<Contact> {
}
public CardDavAddressBook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
public CardDavAddressBook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws MalformedURLException {
super(httpClient, baseURL, user, password, preemptiveAuth);
}
}

@ -1,39 +1,57 @@
package at.bitfire.davdroid.resource;
import java.io.Closeable;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import ch.boye.httpclientandroidlib.HttpException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ezvcard.VCardVersion;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.SRVRecord;
import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import android.content.Context;
import android.util.Log;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.URIUtils;
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.NotAuthorizedException;
import at.bitfire.davdroid.webdav.WebDavResource;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
import ch.boye.httpclientandroidlib.HttpException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ezvcard.VCardVersion;
public class DavResourceFinder {
public class DavResourceFinder implements Closeable {
private final static String TAG = "davdroid.DavResourceFinder";
protected Context context;
protected CloseableHttpClient httpClient;
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);
public DavResourceFinder(Context context) {
this.context = context;
WebDavResource base = new WebDavResource(httpClient,
new URI(URIUtils.ensureTrailingSlash(serverInfo.getProvidedURL())),
serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.isAuthPreemptive());
// disable compression and enable network logging for debugging purposes
httpClient = DavHttpClient.create(true, true);
}
@Override
public void close() throws IOException {
httpClient.close();
}
public void findResources(ServerInfo serverInfo) throws URISyntaxException, DavException, HttpException, IOException {
// CardDAV
WebDavResource principal = getCurrentUserPrincipal(base, "carddav");
WebDavResource principal = getCurrentUserPrincipal(serverInfo, "carddav");
if (principal != null) {
serverInfo.setCardDAV(true);
@ -50,11 +68,11 @@ public class DavResourceFinder {
if (homeSetAddressBooks.getMembers() != null)
for (WebDavResource resource : homeSetAddressBooks.getMembers())
if (resource.isAddressBook()) {
Log.i(TAG, "Found address book: " + resource.getLocation().getRawPath());
Log.i(TAG, "Found address book: " + resource.getLocation().getPath());
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.ADDRESS_BOOK,
resource.isReadOnly(),
resource.getLocation().toASCIIString(),
resource.getLocation().toString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
@ -73,7 +91,7 @@ public class DavResourceFinder {
}
// CalDAV
principal = getCurrentUserPrincipal(base, "caldav");
principal = getCurrentUserPrincipal(serverInfo, "caldav");
if (principal != null) {
serverInfo.setCalDAV(true);
@ -105,7 +123,7 @@ public class DavResourceFinder {
ServerInfo.ResourceInfo info = new ServerInfo.ResourceInfo(
ServerInfo.ResourceInfo.Type.CALENDAR,
resource.isReadOnly(),
resource.getLocation().toASCIIString(),
resource.getLocation().toString(),
resource.getDisplayName(),
resource.getDescription(), resource.getColor()
);
@ -124,6 +142,75 @@ public class DavResourceFinder {
}
/**
* Finds the initial service URL from a given base URI (HTTP[S] or mailto URI, user name, password)
* @param serverInfo User-given service information (including base URI, i.e. HTTP[S] URL+user name+password or mailto URI and password)
* @param serviceName Service name ("carddav" or "caldav")
* @return Initial service URL (HTTP/HTTPS), without user credentials
* @throws URISyntaxException when the user-given URI is invalid
* @throws MalformedURLException when the user-given URI is invalid
* @throws UnknownServiceURLException when no intial service URL could be determined
*/
URL getInitialURL(ServerInfo serverInfo, String serviceName) throws URISyntaxException, MalformedURLException {
String scheme = null,
domain = null;
int port = -1;
String path = "/";
URI baseURI = serverInfo.getBaseURI();
if ("mailto".equalsIgnoreCase(baseURI.getScheme())) {
// mailto URIs
String mailbox = serverInfo.getBaseURI().getSchemeSpecificPart();
// determine service FQDN
int pos = mailbox.lastIndexOf("@");
if (pos == -1)
throw new URISyntaxException(mailbox, "Email address doesn't contain @");
domain = mailbox.substring(pos + 1);
} else {
// HTTP(S) URLs
scheme = baseURI.getScheme();
domain = baseURI.getHost();
port = baseURI.getPort();
path = baseURI.getPath();
}
// try to determine FQDN and port number using SRV records
try {
String name = "_" + serviceName + "s._tcp." + domain;
Log.d(TAG, "Looking up SRV records for " + name);
Record[] records = new Lookup(name, Type.SRV).run();
if (records != null && records.length >= 1) {
SRVRecord srv = selectSRVRecord(records);
scheme = "https";
domain = srv.getTarget().toString(true);
port = srv.getPort();
Log.d(TAG, "Found " + serviceName + "s service for " + domain + " -> " + domain + ":" + port);
// SRV record found, look for TXT record too (for initial context path)
records = new Lookup(name, Type.TXT).run();
if (records != null && records.length >= 1) {
TXTRecord txt = (TXTRecord)records[0];
for (String segment : (String[])txt.getStrings().toArray())
if (segment.startsWith("path=")) {
path = segment.substring(5);
Log.d(TAG, "Found initial context path for " + serviceName + " at " + domain + " -> " + path);
break;
}
}
}
} catch (TextParseException e) {
throw new URISyntaxException(domain, "Invalid domain name");
}
if (port != -1)
return new URL(scheme, domain, port, path);
else
return new URL(scheme, domain, path);
}
/**
* 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
@ -132,10 +219,18 @@ public class DavResourceFinder {
* @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, NotAuthorizedException {
WebDavResource getCurrentUserPrincipal(ServerInfo serverInfo, String serviceName) throws URISyntaxException, IOException, NotAuthorizedException {
URL initialURL = getInitialURL(serverInfo, serviceName);
// determine base URL (host name and initial context path)
WebDavResource base = new WebDavResource(httpClient,
//new URI(URIUtils.ensureTrailingSlash(serverInfo.getBaseURI())),
initialURL,
serverInfo.getUserName(), serverInfo.getPassword(), serverInfo.isAuthPreemptive());
// look for well-known service (RFC 5785)
try {
WebDavResource wellKnown = new WebDavResource(resource, "/.well-known/" + serviceName);
WebDavResource wellKnown = new WebDavResource(base, "/.well-known/" + serviceName);
wellKnown.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (wellKnown.getCurrentUserPrincipal() != null)
return new WebDavResource(wellKnown, wellKnown.getCurrentUserPrincipal());
@ -150,9 +245,9 @@ public class DavResourceFinder {
// fall back to user-given initial context path
try {
resource.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (resource.getCurrentUserPrincipal() != null)
return new WebDavResource(resource, resource.getCurrentUserPrincipal());
base.propfind(Mode.CURRENT_USER_PRINCIPAL);
if (base.getCurrentUserPrincipal() != null)
return new WebDavResource(base, base.getCurrentUserPrincipal());
} catch (NotAuthorizedException e) {
Log.d(TAG, "Not authorized for querying principal for " + serviceName + " service", e);
throw e;
@ -165,7 +260,7 @@ public class DavResourceFinder {
return null;
}
private static boolean checkHomesetCapabilities(WebDavResource resource, String davCapability) throws IOException {
private static boolean checkHomesetCapabilities(WebDavResource resource, String davCapability) throws URISyntaxException, IOException {
// check for necessary capabilities
try {
resource.options();
@ -178,4 +273,11 @@ public class DavResourceFinder {
return false;
}
SRVRecord selectSRVRecord(Record[] records) {
if (records.length > 1)
Log.w(TAG, "Multiple SRV records not supported yet; using first one");
return (SRVRecord)records[0];
}
}

@ -11,8 +11,9 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
@ -46,16 +47,16 @@ public abstract class RemoteCollection<T extends Resource> {
abstract protected DavMultiget.Type multiGetType();
abstract protected T newResourceSkeleton(String name, String ETag);
public RemoteCollection(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
public RemoteCollection(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws MalformedURLException {
this.httpClient = httpClient;
collection = new WebDavResource(httpClient, new URI(baseURL), user, password, preemptiveAuth);
collection = new WebDavResource(httpClient, new URL(baseURL), user, password, preemptiveAuth);
}
/* collection operations */
public String getCTag() throws IOException, HttpException {
public String getCTag() throws URISyntaxException, IOException, HttpException {
try {
if (collection.getCTag() == null && collection.getMembers() == null) // not already fetched
collection.propfind(HttpPropfind.Mode.COLLECTION_CTAG);
@ -65,7 +66,7 @@ public abstract class RemoteCollection<T extends Resource> {
return collection.getCTag();
}
public Resource[] getMemberETags() throws IOException, DavException, HttpException {
public Resource[] getMemberETags() throws URISyntaxException, IOException, DavException, HttpException {
collection.propfind(HttpPropfind.Mode.MEMBERS_ETAG);
List<T> resources = new LinkedList<T>();
@ -77,7 +78,7 @@ public abstract class RemoteCollection<T extends Resource> {
}
@SuppressWarnings("unchecked")
public Resource[] multiGet(Resource[] resources) throws IOException, DavException, HttpException {
public Resource[] multiGet(Resource[] resources) throws URISyntaxException, IOException, DavException, HttpException {
try {
if (resources.length == 1)
return (T[]) new Resource[] { get(resources[0]) };
@ -118,7 +119,7 @@ public abstract class RemoteCollection<T extends Resource> {
/* internal member operations */
public Resource get(Resource resource) throws IOException, HttpException, DavException, InvalidResourceException {
public Resource get(Resource resource) throws URISyntaxException, IOException, HttpException, DavException, InvalidResourceException {
WebDavResource member = new WebDavResource(collection, resource.getName());
if (resource instanceof Contact)
@ -144,7 +145,7 @@ public abstract class RemoteCollection<T extends Resource> {
}
// returns ETag of the created resource, if returned by server
public String add(Resource res) throws IOException, HttpException, ValidationException {
public String add(Resource res) throws URISyntaxException, IOException, HttpException, ValidationException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.setContentType(memberContentType());
@ -157,7 +158,7 @@ public abstract class RemoteCollection<T extends Resource> {
return eTag;
}
public void delete(Resource res) throws IOException, HttpException {
public void delete(Resource res) throws URISyntaxException, IOException, HttpException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.delete();
@ -165,7 +166,7 @@ public abstract class RemoteCollection<T extends Resource> {
}
// returns ETag of the updated resource, if returned by server
public String update(Resource res) throws IOException, HttpException, ValidationException {
public String update(Resource res) throws URISyntaxException, IOException, HttpException, ValidationException {
WebDavResource member = new WebDavResource(collection, res.getName(), res.getETag());
member.setContentType(memberContentType());

@ -8,6 +8,7 @@
package at.bitfire.davdroid.resource;
import java.io.Serializable;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
@ -20,7 +21,11 @@ import lombok.RequiredArgsConstructor;
public class ServerInfo implements Serializable {
private static final long serialVersionUID = 6744847358282980437L;
final private String providedURL;
enum Scheme {
HTTP, HTTPS, MAILTO
}
final private URI baseURI;
final private String userName, password;
final boolean authPreemptive;

@ -7,7 +7,7 @@
******************************************************************************/
package at.bitfire.davdroid.syncadapter;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
@ -71,7 +71,7 @@ public class CalendarsSyncAdapterService extends Service {
return map;
} catch (RemoteException ex) {
Log.e(TAG, "Couldn't find local calendars", ex);
} catch (URISyntaxException ex) {
} catch (MalformedURLException ex) {
Log.e(TAG, "Couldn't build calendar URI", ex);
}

@ -7,7 +7,7 @@
******************************************************************************/
package at.bitfire.davdroid.syncadapter;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
@ -72,7 +72,7 @@ public class ContactsSyncAdapterService extends Service {
map.put(database, dav);
return map;
} catch (URISyntaxException ex) {
} catch (MalformedURLException ex) {
Log.e(TAG, "Couldn't build address book URI", ex);
}

@ -9,6 +9,7 @@ package at.bitfire.davdroid.syncadapter;
import java.io.Closeable;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@ -131,11 +132,9 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
try {
for (Map.Entry<LocalCollection<?>, RemoteCollection<?>> entry : syncCollections.entrySet())
new SyncManager(entry.getKey(), entry.getValue()).synchronize(extras.containsKey(ContentResolver.SYNC_EXTRAS_MANUAL), syncResult);
} catch (DavException ex) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "Invalid DAV response", ex);
} catch (HttpException ex) {
if (ex.getCode() == HttpStatus.SC_UNAUTHORIZED) {
Log.e(TAG, "HTTP Unauthorized " + ex.getCode(), ex);
@ -147,13 +146,14 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter impleme
Log.w(TAG, "Soft HTTP error " + ex.getCode() + " (Android will try again later)", ex);
syncResult.stats.numIoExceptions++;
}
} catch (LocalStorageException ex) {
syncResult.databaseError = true;
Log.e(TAG, "Local storage (content provider) exception", ex);
} catch (IOException ex) {
syncResult.stats.numIoExceptions++;
Log.e(TAG, "I/O error (Android will try again later)", ex);
} catch (URISyntaxException ex) {
Log.e(TAG, "Invalid URI (file name) syntax", ex);
}
} finally {
// allow httpClient shutdown

@ -7,11 +7,6 @@
******************************************************************************/
package at.bitfire.davdroid.syncadapter;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.lang.StringUtils;
import android.app.DialogFragment;
import android.app.Fragment;
import android.app.FragmentTransaction;
@ -32,13 +27,12 @@ import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.URIUtils;
public class EnterCredentialsFragment extends Fragment implements TextWatcher {
String protocol;
String scheme;
TextView textHttpWarning;
EditText editBaseURL, editUserName, editPassword;
EditText editBaseURI, editUserName, editPassword;
CheckBox checkboxPreemptive;
Button btnNext;
@ -50,24 +44,24 @@ public class EnterCredentialsFragment extends Fragment implements TextWatcher {
// protocol selection spinner
textHttpWarning = (TextView) v.findViewById(R.id.http_warning);
Spinner spnrProtocol = (Spinner) v.findViewById(R.id.select_protocol);
spnrProtocol.setOnItemSelectedListener(new OnItemSelectedListener() {
Spinner spnrScheme = (Spinner) v.findViewById(R.id.login_scheme);
spnrScheme.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
protocol = parent.getAdapter().getItem(position).toString();
textHttpWarning.setVisibility(protocol.equals("https://") ? View.GONE : View.VISIBLE);
scheme = parent.getAdapter().getItem(position).toString();
textHttpWarning.setVisibility(scheme.equals("https://") ? View.GONE : View.VISIBLE);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
protocol = null;
scheme = null;
}
});
spnrProtocol.setSelection(1); // HTTPS
spnrScheme.setSelection(1); // HTTPS
// other input fields
editBaseURL = (EditText) v.findViewById(R.id.baseURL);
editBaseURL.addTextChangedListener(this);
editBaseURI = (EditText) v.findViewById(R.id.login_authority_path);
editBaseURI.addTextChangedListener(this);
editUserName = (EditText) v.findViewById(R.id.userName);
editUserName.addTextChangedListener(this);
@ -105,8 +99,9 @@ public class EnterCredentialsFragment extends Fragment implements TextWatcher {
Bundle args = new Bundle();
String host_path = editBaseURL.getText().toString();
args.putString(QueryServerDialogFragment.EXTRA_BASE_URL, URIUtils.sanitize(protocol + host_path));
String authority_path = editBaseURI.getText().toString();
//args.putString(QueryServerDialogFragment.EXTRA_BASE_URI, URIUtils.sanitize(scheme + host_path));
args.putString(QueryServerDialogFragment.EXTRA_BASE_URI, scheme + authority_path);
args.putString(QueryServerDialogFragment.EXTRA_USER_NAME, editUserName.getText().toString());
args.putString(QueryServerDialogFragment.EXTRA_PASSWORD, editPassword.getText().toString());
args.putBoolean(QueryServerDialogFragment.EXTRA_AUTH_PREEMPTIVE, checkboxPreemptive.isChecked());
@ -125,15 +120,15 @@ public class EnterCredentialsFragment extends Fragment implements TextWatcher {
editUserName.getText().length() > 0 &&
editPassword.getText().length() > 0;
if (ok)
/*if (ok)
// check host name
try {
URI uri = new URI(URIUtils.sanitize(protocol + editBaseURL.getText().toString()));
URI uri = new URI(URIUtils.sanitize(scheme + editBaseURI.getText().toString()));
if (StringUtils.isBlank(uri.getHost()))
ok = false;
} catch (URISyntaxException e) {
ok = false;
}
}*/
MenuItem item = menu.findItem(R.id.next);
item.setEnabled(ok);

@ -8,8 +8,10 @@
package at.bitfire.davdroid.syncadapter;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import lombok.Cleanup;
import android.app.DialogFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
@ -31,7 +33,7 @@ import ch.boye.httpclientandroidlib.HttpException;
public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks<ServerInfo> {
private static final String TAG = "davdroid.QueryServerDialogFragment";
public static final String
EXTRA_BASE_URL = "base_uri",
EXTRA_BASE_URI = "base_uri",
EXTRA_USER_NAME = "user_name",
EXTRA_PASSWORD = "password",
EXTRA_AUTH_PREEMPTIVE = "auth_preemptive";
@ -99,14 +101,15 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
@Override
public ServerInfo loadInBackground() {
ServerInfo serverInfo = new ServerInfo(
args.getString(EXTRA_BASE_URL),
URI.create(args.getString(EXTRA_BASE_URI)),
args.getString(EXTRA_USER_NAME),
args.getString(EXTRA_PASSWORD),
args.getBoolean(EXTRA_AUTH_PREEMPTIVE)
);
try {
DavResourceFinder.findResources(context, serverInfo);
@Cleanup DavResourceFinder finder = new DavResourceFinder(context);
finder.findResources(serverInfo);
} catch (URISyntaxException e) {
serverInfo.setErrorMessage(getContext().getString(R.string.exception_uri_syntax, e.getMessage()));
} catch (IOException e) {

@ -8,6 +8,7 @@
package at.bitfire.davdroid.syncadapter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
@ -40,7 +41,7 @@ public class SyncManager {
}
public void synchronize(boolean manualSync, SyncResult syncResult) throws LocalStorageException, IOException, HttpException, DavException {
public void synchronize(boolean manualSync, SyncResult syncResult) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException {
// PHASE 1: push local changes to server
int deletedRemotely = pushDeleted(),
addedRemotely = pushNew(),
@ -98,7 +99,7 @@ public class SyncManager {
}
private int pushDeleted() throws LocalStorageException, IOException, HttpException {
private int pushDeleted() throws URISyntaxException, LocalStorageException, IOException, HttpException {
int count = 0;
long[] deletedIDs = local.findDeleted();
@ -129,7 +130,7 @@ public class SyncManager {
return count;
}
private int pushNew() throws LocalStorageException, IOException, HttpException {
private int pushNew() throws URISyntaxException, LocalStorageException, IOException, HttpException {
int count = 0;
long[] newIDs = local.findNew();
Log.i(TAG, "Uploading " + newIDs.length + " new resource(s) (if not existing)");
@ -155,7 +156,7 @@ public class SyncManager {
return count;
}
private int pushDirty() throws LocalStorageException, IOException, HttpException {
private int pushDirty() throws URISyntaxException, LocalStorageException, IOException, HttpException {
int count = 0;
long[] dirtyIDs = local.findUpdated();
Log.i(TAG, "Uploading " + dirtyIDs.length + " modified resource(s) (if not changed)");
@ -182,7 +183,7 @@ public class SyncManager {
return count;
}
private int pullNew(Resource[] resourcesToAdd) throws LocalStorageException, IOException, HttpException, DavException {
private int pullNew(Resource[] resourcesToAdd) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException {
int count = 0;
Log.i(TAG, "Fetching " + resourcesToAdd.length + " new remote resource(s)");
@ -196,7 +197,7 @@ public class SyncManager {
return count;
}
private int pullChanged(Resource[] resourcesToUpdate) throws LocalStorageException, IOException, HttpException, DavException {
private int pullChanged(Resource[] resourcesToUpdate) throws URISyntaxException, LocalStorageException, IOException, HttpException, DavException {
int count = 0;
Log.i(TAG, "Fetching " + resourcesToUpdate.length + " updated remote resource(s)");

@ -1,7 +1,9 @@
package at.bitfire.davdroid.webdav;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import android.util.Log;
import ch.boye.httpclientandroidlib.Header;
@ -66,7 +68,7 @@ public class DavRedirectStrategy implements RedirectStrategy {
* Gets the destination of a redirection
* @return absolute URL of new location; null if not available
*/
static URI getLocation(HttpRequest request, HttpResponse response, HttpContext context) {
static URL getLocation(HttpRequest request, HttpResponse response, HttpContext context) {
Header locationHdr = response.getFirstHeader("Location");
if (locationHdr == null) {
Log.e(TAG, "Received redirection without Location header, ignoring");
@ -86,10 +88,12 @@ public class DavRedirectStrategy implements RedirectStrategy {
else
return null;
}
location = originalURI.resolve(location);
return new URL(originalURI.toURL(), location.toString());
}
return location;
return location.toURL();
} catch (URISyntaxException e) {
Log.e(TAG, "Received redirection from/to invalid URI, ignoring", e);
} catch (MalformedURLException e) {
Log.e(TAG, "Received redirection from/to invalid URL, ignoring", e);
}
return null;

@ -120,7 +120,7 @@ public class TlsSniSocketFactory implements LayeredConnectionSocketFactory {
if (!hostnameVerifier.verify(host, session))
throw new SSLPeerUnverifiedException("Cannot verify hostname: " + host);
Log.i(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
Log.d(TAG, "Established " + session.getProtocol() + " connection with " + session.getPeerHost() +
" using " + session.getCipherSuite());
}

@ -10,8 +10,9 @@ package at.bitfire.davdroid.webdav;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.net.URI;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@ -28,7 +29,7 @@ import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import android.util.Log;
import at.bitfire.davdroid.URIUtils;
import at.bitfire.davdroid.URLUtils;
import at.bitfire.davdroid.resource.Event;
import at.bitfire.davdroid.webdav.DavProp.Comp;
import ch.boye.httpclientandroidlib.Header;
@ -79,7 +80,7 @@ public class WebDavResource {
}
// location of this resource
@Getter protected URI location;
@Getter protected URL location;
// DAV capabilities (DAV: header) and allowed DAV methods (set for OPTIONS request)
protected Set<String> capabilities = new HashSet<String>(),
@ -99,18 +100,18 @@ public class WebDavResource {
protected HttpClientContext context;
public WebDavResource(CloseableHttpClient httpClient, URI baseURL) throws URISyntaxException {
public WebDavResource(CloseableHttpClient httpClient, URL baseURL) {
this.httpClient = httpClient;
location = baseURL.normalize();
location = baseURL;
context = HttpClientContext.create();
context.setCredentialsProvider(new BasicCredentialsProvider());
}
public WebDavResource(CloseableHttpClient httpClient, URI baseURL, String username, String password, boolean preemptive) throws URISyntaxException {
public WebDavResource(CloseableHttpClient httpClient, URL baseURL, String username, String password, boolean preemptive) {
this(httpClient, baseURL);
HttpHost host = new HttpHost(baseURL.getHost(), baseURL.getPort(), baseURL.getScheme());
HttpHost host = new HttpHost(baseURL.getHost(), baseURL.getPort(), baseURL.getProtocol());
context.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
if (preemptive) {
@ -129,17 +130,17 @@ public class WebDavResource {
location = parent.location;
}
protected WebDavResource(WebDavResource parent, URI uri) {
protected WebDavResource(WebDavResource parent, URL url) {
this(parent);
location = uri;
location = url;
}
public WebDavResource(WebDavResource parent, String member) {
public WebDavResource(WebDavResource parent, String member) throws MalformedURLException {
this(parent);
location = parent.location.resolve(URIUtils.sanitize(member));
location = new URL(parent.location, URLUtils.sanitize(member));
}
public WebDavResource(WebDavResource parent, String member, String ETag) {
public WebDavResource(WebDavResource parent, String member, String ETag) throws MalformedURLException {
this(parent, member);
properties.put(Property.ETAG, ETag);
}
@ -148,8 +149,8 @@ public class WebDavResource {
/* feature detection */
public void options() throws IOException, HttpException {
HttpOptions options = new HttpOptions(location);
public void options() throws URISyntaxException, IOException, HttpException {
HttpOptions options = new HttpOptions(location.toURI());
CloseableHttpResponse response = httpClient.execute(options, context);
try {
checkResponse(response);
@ -178,7 +179,7 @@ public class WebDavResource {
/* file hierarchy methods */
public String getName() {
String[] names = StringUtils.split(location.getRawPath(), "/");
String[] names = StringUtils.split(location.getPath(), "/");
return names[names.length - 1];
}
@ -252,13 +253,13 @@ public class WebDavResource {
/* collection operations */
public void propfind(HttpPropfind.Mode mode) throws IOException, DavException, HttpException {
public void propfind(HttpPropfind.Mode mode) throws URISyntaxException, IOException, DavException, HttpException {
CloseableHttpResponse response = null;
// processMultiStatus() requires knowledge of the actual content location,
// so we have to handle redirections manually and create a new request for the new location
for (int i = context.getRequestConfig().getMaxRedirects(); i > 0; i--) {
HttpPropfind propfind = new HttpPropfind(location, mode);
HttpPropfind propfind = new HttpPropfind(location.toURI(), mode);
response = httpClient.execute(propfind, context);
if (response.getStatusLine().getStatusCode()/100 == 3) {
@ -281,7 +282,7 @@ public class WebDavResource {
}
}
public void multiGet(DavMultiget.Type type, String[] names) throws IOException, DavException, HttpException {
public void multiGet(DavMultiget.Type type, String[] names) throws URISyntaxException, IOException, DavException, HttpException {
CloseableHttpResponse response = null;
// processMultiStatus() requires knowledge of the actual content location,
@ -290,7 +291,7 @@ public class WebDavResource {
// build multi-get XML request
List<String> hrefs = new LinkedList<String>();
for (String name : names)
hrefs.add(location.resolve(name).getRawPath());
hrefs.add(new URL(location, name).getPath());
DavMultiget multiget = DavMultiget.newRequest(type, hrefs.toArray(new String[0]));
StringWriter writer = new StringWriter();
@ -303,7 +304,7 @@ public class WebDavResource {
}
// submit REPORT request
HttpReport report = new HttpReport(location, writer.toString());
HttpReport report = new HttpReport(location.toURI(), writer.toString());
response = httpClient.execute(report, context);
if (response.getStatusLine().getStatusCode()/100 == 3) {
@ -330,8 +331,8 @@ public class WebDavResource {
/* resource operations */
public void get(String acceptedType) throws IOException, HttpException, DavException {
HttpGet get = new HttpGet(location);
public void get(String acceptedType) throws URISyntaxException, IOException, HttpException, DavException {
HttpGet get = new HttpGet(location.toURI());
get.addHeader("Accept", acceptedType);
CloseableHttpResponse response = httpClient.execute(get, context);
@ -349,8 +350,8 @@ public class WebDavResource {
}
// returns the ETag of the created/updated resource, if available (null otherwise)
public String put(byte[] data, PutMode mode) throws IOException, HttpException {
HttpPut put = new HttpPut(location);
public String put(byte[] data, PutMode mode) throws URISyntaxException, IOException, HttpException {
HttpPut put = new HttpPut(location.toURI());
put.setEntity(new ByteArrayEntity(data));
switch (mode) {
@ -379,8 +380,8 @@ public class WebDavResource {
return null;
}
public void delete() throws IOException, HttpException {
HttpDelete delete = new HttpDelete(location);
public void delete() throws URISyntaxException, IOException, HttpException {
HttpDelete delete = new HttpDelete(location.toURI());
if (getETag() != null)
delete.addHeader("If-Match", getETag());
@ -404,9 +405,9 @@ public class WebDavResource {
if (contentLocationHdr != null)
try {
// Content-Location was set, update location correspondingly
location = location.resolve(new URI(contentLocationHdr.getValue()));
location = new URL(location, contentLocationHdr.getValue());
Log.d(TAG, "Set Content-Location to " + location);
} catch (URISyntaxException e) {
} catch (MalformedURLException e) {
Log.w(TAG, "Ignoring invalid Content-Location", e);
}
}
@ -455,9 +456,9 @@ public class WebDavResource {
// iterate through all resources (either ourselves or member)
for (DavResponse singleResponse : multiStatus.response) {
URI href;
URL href;
try {
href = location.resolve(URIUtils.sanitize(singleResponse.getHref().href));
href = new URL(location, URLUtils.sanitize(singleResponse.getHref().href));
} catch(IllegalArgumentException ex) {
Log.w(TAG, "Ignoring illegal member URI in multi-status response", ex);
continue;
@ -499,10 +500,10 @@ public class WebDavResource {
}
if (prop.addressbookHomeSet != null && prop.addressbookHomeSet.getHref() != null)
properties.put(Property.ADDRESSBOOK_HOMESET, URIUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href));
properties.put(Property.ADDRESSBOOK_HOMESET, URLUtils.ensureTrailingSlash(prop.addressbookHomeSet.getHref().href));
if (prop.calendarHomeSet != null && prop.calendarHomeSet.getHref() != null)
properties.put(Property.CALENDAR_HOMESET, URIUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href));
properties.put(Property.CALENDAR_HOMESET, URLUtils.ensureTrailingSlash(prop.calendarHomeSet.getHref().href));
if (prop.displayname != null)
properties.put(Property.DISPLAY_NAME, prop.displayname.getDisplayName());
@ -511,7 +512,7 @@ public class WebDavResource {
if (prop.resourcetype.getCollection() != null) {
properties.put(Property.IS_COLLECTION, "1");
// is a collection, ensure trailing slash
href = URIUtils.ensureTrailingSlash(href);
href = URLUtils.ensureTrailingSlash(href);
}
if (prop.resourcetype.getAddressbook() != null) { // CardDAV collection properties
properties.put(Property.IS_ADDRESSBOOK, "1");
@ -558,7 +559,7 @@ public class WebDavResource {
}
// about which resource is this response?
if (location.equals(href) || URIUtils.ensureTrailingSlash(location).equals(href)) { // about ourselves
if (location.equals(href) || URLUtils.ensureTrailingSlash(location).equals(href)) { // about ourselves
this.properties.putAll(properties);
if (supportedComponents != null)
this.supportedComponents = supportedComponents;

@ -1,19 +1,34 @@
package at.bitfire.davdroid.syncadapter;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import ezvcard.VCardVersion;
import android.test.InstrumentationTestCase;
import at.bitfire.davdroid.resource.DavResourceFinder;
import at.bitfire.davdroid.resource.ServerInfo;
import at.bitfire.davdroid.resource.ServerInfo.ResourceInfo;
import at.bitfire.davdroid.test.Constants;
import ezvcard.VCardVersion;
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);
DavResourceFinder finder;
@Override
protected void setUp() {
finder = new DavResourceFinder(getInstrumentation().getContext());
}
@Override
protected void tearDown() throws IOException {
finder.close();
}
public void testFindResourcesRobohydra() throws Exception {
ServerInfo info = new ServerInfo(new URI(Constants.ROBOHYDRA_BASE), "test", "test", true);
finder.findResources(info);
// CardDAV
assertTrue(info.isCardDAV());

@ -1,18 +1,18 @@
package at.bitfire.davdroid.test;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.MalformedURLException;
import java.net.URL;
import android.util.Log;
public class Constants {
public static final String ROBOHYDRA_BASE = "http://10.0.0.11:3000/";
public static URI roboHydra;
public static URL roboHydra;
static {
try {
roboHydra = new URI(ROBOHYDRA_BASE);
} catch(URISyntaxException e) {
roboHydra = new URL(ROBOHYDRA_BASE);
} catch(MalformedURLException e) {
Log.wtf("davdroid.test.Constants", "Invalid RoboHydra base URL");
}
}

@ -1,49 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Ricki 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.test;
import java.net.URI;
import java.net.URISyntaxException;
import junit.framework.TestCase;
import at.bitfire.davdroid.URIUtils;
public class URIUtilsTest extends TestCase {
public void testEnsureTrailingSlash() throws URISyntaxException {
assertEquals("/test/", URIUtils.ensureTrailingSlash("/test"));
assertEquals("/test/", URIUtils.ensureTrailingSlash("/test/"));
String withoutSlash = "http://www.test.at/dav/collection",
withSlash = withoutSlash + "/";
assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withoutSlash)));
assertEquals(new URI(withSlash), URIUtils.ensureTrailingSlash(new URI(withSlash)));
}
public void testSanitize() {
assertNull(URIUtils.sanitize(null));
// escape "@"
assertEquals("https://my%40server/my%40email.com/dir", URIUtils.sanitize("https://my@server/my@email.com/dir"));
assertEquals("http://my%40server/my%40email.com/dir", URIUtils.sanitize("http://my@server/my@email.com/dir"));
assertEquals("//my%40server/my%40email.com/dir", URIUtils.sanitize("//my@server/my@email.com/dir"));
assertEquals("/my%40email.com/dir", URIUtils.sanitize("/my@email.com/dir"));
assertEquals("my%40email.com/dir", URIUtils.sanitize("my@email.com/dir"));
// escape ":" in path but not as port separator
assertEquals("https://www.test.at:80/my%3afile.vcf", URIUtils.sanitize("https://www.test.at:80/my:file.vcf"));
assertEquals("http://www.test.at:80/my%3afile.vcf", URIUtils.sanitize("http://www.test.at:80/my:file.vcf"));
assertEquals("//www.test.at:80/my%3afile.vcf", URIUtils.sanitize("//www.test.at:80/my:file.vcf"));
assertEquals("/my%3afile.vcf", URIUtils.sanitize("/my:file.vcf"));
assertEquals("my%3afile.vcf", URIUtils.sanitize("my:file.vcf"));
// keep literal IPv6 addresses (only in host name)
assertEquals("https://[1:2::1]/", URIUtils.sanitize("https://[1:2::1]/"));
assertEquals("/%5b1%3a2%3a%3a1%5d/", URIUtils.sanitize("/[1:2::1]/"));
}
}

@ -0,0 +1,48 @@
/*******************************************************************************
* Copyright (c) 2014 Ricki 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.test;
import java.net.URL;
import junit.framework.TestCase;
import at.bitfire.davdroid.URLUtils;
public class URLUtilsTest extends TestCase {
public void testEnsureTrailingSlash() throws Exception {
assertEquals("/test/", URLUtils.ensureTrailingSlash("/test"));
assertEquals("/test/", URLUtils.ensureTrailingSlash("/test/"));
String withoutSlash = "http://www.test.at/dav/collection",
withSlash = withoutSlash + "/";
assertEquals(new URL(withSlash), URLUtils.ensureTrailingSlash(new URL(withoutSlash)));
assertEquals(new URL(withSlash), URLUtils.ensureTrailingSlash(new URL(withSlash)));
}
public void testSanitize() {
assertNull(URLUtils.sanitize(null));
// escape "@"
assertEquals("https://my%40server/my%40email.com/dir", URLUtils.sanitize("https://my@server/my@email.com/dir"));
assertEquals("http://my%40server/my%40email.com/dir", URLUtils.sanitize("http://my@server/my@email.com/dir"));
assertEquals("//my%40server/my%40email.com/dir", URLUtils.sanitize("//my@server/my@email.com/dir"));
assertEquals("/my%40email.com/dir", URLUtils.sanitize("/my@email.com/dir"));
assertEquals("my%40email.com/dir", URLUtils.sanitize("my@email.com/dir"));
// escape ":" in path but not as port separator
assertEquals("https://www.test.at:80/my%3afile.vcf", URLUtils.sanitize("https://www.test.at:80/my:file.vcf"));
assertEquals("http://www.test.at:80/my%3afile.vcf", URLUtils.sanitize("http://www.test.at:80/my:file.vcf"));
assertEquals("//www.test.at:80/my%3afile.vcf", URLUtils.sanitize("//www.test.at:80/my:file.vcf"));
assertEquals("/my%3afile.vcf", URLUtils.sanitize("/my:file.vcf"));
assertEquals("my%3afile.vcf", URLUtils.sanitize("my:file.vcf"));
// keep literal IPv6 addresses (only in host name)
assertEquals("https://[1:2::1]/", URLUtils.sanitize("https://[1:2::1]/"));
assertEquals("/%5b1%3a2%3a%3a1%5d/", URLUtils.sanitize("/[1:2::1]/"));
}
}

@ -1,6 +1,7 @@
package at.bitfire.davdroid.webdav;
import java.io.IOException;
import java.net.URL;
import junit.framework.TestCase;
import at.bitfire.davdroid.test.Constants;
@ -34,7 +35,7 @@ public class DavRedirectStrategyTest extends TestCase {
// happy cases
public void testNonRedirection() throws Exception {
HttpUriRequest request = new HttpOptions(Constants.roboHydra);
HttpUriRequest request = new HttpOptions(Constants.roboHydra.toURI());
HttpResponse response = httpClient.execute(request);
assertFalse(strategy.isRedirected(request, response, null));
}
@ -43,12 +44,12 @@ public class DavRedirectStrategyTest extends TestCase {
final String newLocation = "/new-location";
HttpContext context = HttpClientContext.create();
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/301?to=" + newLocation));
HttpUriRequest request = new HttpOptions(new URL(Constants.roboHydra, "redirect/301?to=" + newLocation).toURI());
HttpResponse response = httpClient.execute(request, context);
assertTrue(strategy.isRedirected(request, response, context));
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
assertEquals(Constants.roboHydra.resolve(newLocation), redirected.getURI());
assertEquals(new URL(Constants.roboHydra, newLocation).toURI(), redirected.getURI());
}
@ -56,18 +57,18 @@ public class DavRedirectStrategyTest extends TestCase {
public void testMissingLocation() throws Exception {
HttpContext context = HttpClientContext.create();
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/without-location"));
HttpUriRequest request = new HttpOptions(new URL(Constants.roboHydra, "redirect/without-location").toURI());
HttpResponse response = httpClient.execute(request, context);
assertFalse(strategy.isRedirected(request, response, context));
}
public void testRelativeLocation() throws Exception {
HttpContext context = HttpClientContext.create();
HttpUriRequest request = new HttpOptions(Constants.roboHydra.resolve("redirect/relative"));
HttpUriRequest request = new HttpOptions(new URL(Constants.roboHydra, "redirect/relative").toURI());
HttpResponse response = httpClient.execute(request, context);
assertTrue(strategy.isRedirected(request, response, context));
HttpUriRequest redirected = strategy.getRedirect(request, response, context);
assertEquals(Constants.roboHydra.resolve("/new/location"), redirected.getURI());
assertEquals(new URL(Constants.roboHydra, "/new/location").toURI(), redirected.getURI());
}
}

@ -7,10 +7,8 @@
******************************************************************************/
package at.bitfire.davdroid.webdav;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
@ -48,26 +46,26 @@ public class WebDavResourceTest extends InstrumentationTestCase {
assetMgr = getInstrumentation().getContext().getResources().getAssets();
baseDAV = new WebDavResource(httpClient, Constants.roboHydra.resolve("/dav/"));
baseDAV = new WebDavResource(httpClient, new URL(Constants.roboHydra, "/dav/"));
simpleFile = new WebDavResource(httpClient, new URI(Constants.ROBOHYDRA_BASE + "assets/test.random"));
simpleFile = new WebDavResource(httpClient, new URL(Constants.ROBOHYDRA_BASE + "assets/test.random"));
davCollection = new WebDavResource(httpClient, new URI(Constants.ROBOHYDRA_BASE + "dav/"));
davCollection = new WebDavResource(httpClient, new URL(Constants.ROBOHYDRA_BASE + "dav/"));
davNonExistingFile = new WebDavResource(davCollection, "collection/new.file");
davExistingFile = new WebDavResource(davCollection, "collection/existing.file");
davInvalid = new WebDavResource(httpClient, new URI(Constants.ROBOHYDRA_BASE + "dav-invalid/"));
davInvalid = new WebDavResource(httpClient, new URL(Constants.ROBOHYDRA_BASE + "dav-invalid/"));
}
@Override
protected void tearDown() throws IOException {
protected void tearDown() throws Exception {
httpClient.close();
}
/* test feature detection */
public void testOptions() throws URISyntaxException, IOException, HttpException {
public void testOptions() throws Exception {
String[] davMethods = new String[] { "PROPFIND", "GET", "PUT", "DELETE", "REPORT" },
davCapabilities = new String[] { "addressbook", "calendar-access" };
@ -79,7 +77,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
assert(capable.supportsDAV(capability));
}
public void testPropfindCurrentUserPrincipal() throws IOException, HttpException, DavException {
public void testPropfindCurrentUserPrincipal() throws Exception {
davCollection.propfind(HttpPropfind.Mode.CURRENT_USER_PRINCIPAL);
assertEquals("/dav/principals/users/test", davCollection.getCurrentUserPrincipal());
@ -92,14 +90,14 @@ public class WebDavResourceTest extends InstrumentationTestCase {
assertNull(simpleFile.getCurrentUserPrincipal());
}
public void testPropfindHomeSets() throws IOException, HttpException, DavException {
public void testPropfindHomeSets() throws Exception {
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, DavException {
public void testPropfindAddressBooks() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "addressbooks/test");
dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS);
assertEquals(2, dav.getMembers().size());
@ -112,7 +110,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
}
}
public void testPropfindCalendars() throws IOException, HttpException, DavException {
public void testPropfindCalendars() throws Exception {
WebDavResource dav = new WebDavResource(davCollection, "calendars/test");
dav.propfind(Mode.CALDAV_COLLECTIONS);
assertEquals(3, dav.getMembers().size());
@ -126,7 +124,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
}
}
public void testPropfindTrailingSlashes() throws IOException, HttpException, DavException {
public void testPropfindTrailingSlashes() throws Exception {
final String principalOK = "/principals/ok";
String requestPaths[] = {
@ -146,22 +144,22 @@ public class WebDavResourceTest extends InstrumentationTestCase {
/* test normal HTTP/WebDAV */
public void testPropfindRedirection() throws URISyntaxException, IOException, DavException, HttpException {
public void testPropfindRedirection() throws Exception {
// PROPFIND redirection
WebDavResource redirected = new WebDavResource(baseDAV, "/redirect/301?to=/dav/");
redirected.propfind(Mode.CURRENT_USER_PRINCIPAL);
assertEquals("/dav/", redirected.getLocation().getPath());
}
public void testGet() throws URISyntaxException, IOException, HttpException, DavException {
public void testGet() throws Exception {
simpleFile.get("*/*");
@Cleanup InputStream is = assetMgr.open("test.random", AssetManager.ACCESS_STREAMING);
byte[] expected = IOUtils.toByteArray(is);
assertTrue(Arrays.equals(expected, simpleFile.getContent()));
}
public void testGetHttpsWithSni() throws URISyntaxException, HttpException, IOException, DavException {
WebDavResource file = new WebDavResource(httpClient, new URI("https://sni.velox.ch"));
public void testGetHttpsWithSni() throws Exception {
WebDavResource file = new WebDavResource(httpClient, new URL("https://sni.velox.ch"));
boolean sniWorking = false;
try {
@ -173,7 +171,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
assertTrue(sniWorking);
}
public void testMultiGet() throws DavException, IOException, HttpException {
public void testMultiGet() throws Exception {
WebDavResource davAddressBook = new WebDavResource(davCollection, "addressbooks/default.vcf");
davAddressBook.multiGet(DavMultiget.Type.ADDRESS_BOOK, new String[] { "1.vcf", "2.vcf" });
assertEquals(2, davAddressBook.getMembers().size());
@ -182,7 +180,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
}
}
public void testPutAddDontOverwrite() throws IOException, HttpException {
public void testPutAddDontOverwrite() throws Exception {
// should succeed on a non-existing file
assertEquals("has-just-been-created", davNonExistingFile.put(SAMPLE_CONTENT, PutMode.ADD_DONT_OVERWRITE));
@ -194,7 +192,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
}
}
public void testPutUpdateDontOverwrite() throws IOException, HttpException {
public void testPutUpdateDontOverwrite() throws Exception {
// should succeed on an existing file
assertEquals("has-just-been-updated", davExistingFile.put(SAMPLE_CONTENT, PutMode.UPDATE_DONT_OVERWRITE));
@ -206,7 +204,7 @@ public class WebDavResourceTest extends InstrumentationTestCase {
}
}
public void testDelete() throws IOException, HttpException {
public void testDelete() throws Exception {
// should succeed on an existing file
davExistingFile.delete();
@ -224,13 +222,13 @@ public class WebDavResourceTest extends InstrumentationTestCase {
/* special test */
public void testInvalidURLs() throws IOException, HttpException, DavException {
public void testInvalidURLs() throws Exception {
WebDavResource dav = new WebDavResource(davInvalid, "addressbooks/user%40domain/");
dav.propfind(HttpPropfind.Mode.CARDDAV_COLLECTIONS);
List<WebDavResource> members = dav.getMembers();
assertEquals(2, members.size());
assertEquals(Constants.ROBOHYDRA_BASE + "dav/addressbooks/user%40domain/My%20Contacts%3a1.vcf/", members.get(0).getLocation().toString());
assertEquals("HTTPS://example.com/user%40domain/absolute-url.vcf/", members.get(1).getLocation().toString());
assertEquals("https://example.com/user%40domain/absolute-url.vcf/", members.get(1).getLocation().toString());
}
}

Loading…
Cancel
Save