mirror of
https://github.com/etesync/android
synced 2025-01-11 00:01:12 +00:00
Initial support for SRV/TXT service discovery
This commit is contained in:
parent
e3a7c7092e
commit
8d4c353d8c
BIN
libs/org.xbill.dns_2.1.6.jar
Normal file
BIN
libs/org.xbill.dns_2.1.6.jar
Normal file
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]/"));
|
||||
}
|
||||
}
|
48
test/src/at/bitfire/davdroid/test/URLUtilsTest.java
Normal file
48
test/src/at/bitfire/davdroid/test/URLUtilsTest.java
Normal file
@ -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…
Reference in New Issue
Block a user