1
0
mirror of https://github.com/etesync/android synced 2025-01-23 14:10:54 +00:00

Upgrade to HttpComponents 4.3.2, version bump to 0.5.10

This commit is contained in:
rfc2822 2014-03-23 01:11:53 +01:00
parent e0b21abc35
commit e2f154c963
20 changed files with 279 additions and 416 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="at.bitfire.davdroid"
android:versionCode="30"
android:versionName="0.5.9" android:installLocation="internalOnly">
android:versionCode="31"
android:versionName="0.5.10" android:installLocation="internalOnly">
<uses-sdk
android:minSdkVersion="14"

Binary file not shown.

View File

@ -12,7 +12,7 @@ package at.bitfire.davdroid;
public class Constants {
public static final String
APP_VERSION = "0.5.9-alpha",
APP_VERSION = "0.5.10-alpha",
ACCOUNT_TYPE = "bitfire.at.davdroid",

View File

@ -1,74 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Richard Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Richard Hirner (bitfire web engineering) - initial API and implementation
******************************************************************************/
package at.bitfire.davdroid;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;
public class LoggingInputStream extends FilterInputStream {
private static final int MAX_LENGTH = 1000; // don't log more than this amount of data
String tag;
ByteArrayOutputStream log = new ByteArrayOutputStream(MAX_LENGTH);
int logSize = 0;
boolean overflow = false;
public LoggingInputStream(String tag, InputStream proxy) {
super(proxy);
this.tag = tag;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int read() throws IOException {
int b = super.read();
if (logSize < MAX_LENGTH) {
log.write(b);
logSize++;
} else
overflow = true;
return b;
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount)
throws IOException {
int read = super.read(buffer, byteOffset, byteCount);
int bytesToLog = read;
if (bytesToLog + logSize > MAX_LENGTH) {
bytesToLog = MAX_LENGTH - logSize;
overflow = true;
}
if (bytesToLog > 0) {
log.write(buffer, byteOffset, bytesToLog);
logSize += bytesToLog;
}
return read;
}
@Override
public void close() throws IOException {
Log.d(tag, log.toString() + (overflow ? "" : ""));
super.close();
}
}

View File

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

View File

@ -12,6 +12,7 @@ package at.bitfire.davdroid.resource;
import java.net.URISyntaxException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import at.bitfire.davdroid.webdav.DavMultiget;
public class CardDavAddressBook extends RemoteCollection<Contact> {
@ -33,7 +34,7 @@ public class CardDavAddressBook extends RemoteCollection<Contact> {
}
public CardDavAddressBook(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(baseURL, user, password, preemptiveAuth);
public CardDavAddressBook(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
super(httpClient, baseURL, user, password, preemptiveAuth);
}
}

View File

@ -30,18 +30,22 @@ import at.bitfire.davdroid.webdav.HttpException;
import at.bitfire.davdroid.webdav.HttpPropfind;
import at.bitfire.davdroid.webdav.WebDavResource;
import at.bitfire.davdroid.webdav.WebDavResource.PutMode;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
public abstract class RemoteCollection<T extends Resource> {
private static final String TAG = "davdroid.RemoteCollection";
CloseableHttpClient httpClient;
@Getter WebDavResource collection;
abstract protected String memberContentType();
abstract protected DavMultiget.Type multiGetType();
abstract protected T newResourceSkeleton(String name, String ETag);
public RemoteCollection(String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
collection = new WebDavResource(new URI(baseURL), user, password, preemptiveAuth, true);
public RemoteCollection(CloseableHttpClient httpClient, String baseURL, String user, String password, boolean preemptiveAuth) throws URISyntaxException {
this.httpClient = httpClient;
collection = new WebDavResource(httpClient, new URI(baseURL), user, password, preemptiveAuth, true);
}

View File

@ -31,36 +31,45 @@ import at.bitfire.davdroid.resource.RemoteCollection;
public class CalendarsSyncAdapterService extends Service {
private static SyncAdapter syncAdapter;
@Override @Synchronized
public void onCreate() {
if (syncAdapter == null)
syncAdapter = new SyncAdapter(getApplicationContext());
}
@Override @Synchronized
public void onDestroy() {
syncAdapter.close();
syncAdapter = null;
}
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
private static class SyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.CalendarsSyncAdapter";
public SyncAdapter(Context context) {
private SyncAdapter(Context context) {
super(context);
}
@Override
protected Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider) {
AccountSettings settings = new AccountSettings(context, account);
AccountSettings settings = new AccountSettings(getContext(), account);
String userName = settings.getUserName(),
password = settings.getPassword();
boolean preemptive = settings.getPreemptiveAuth();
try {
Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>();
for (LocalCalendar calendar : LocalCalendar.findAll(account, provider)) {
RemoteCollection<?> dav = new CalDavCalendar(calendar.getUrl(), userName, password, preemptive);
RemoteCollection<?> dav = new CalDavCalendar(httpClient, calendar.getUrl(), userName, password, preemptive);
map.put(calendar, dav);
}
return map;

View File

@ -29,6 +29,7 @@ import at.bitfire.davdroid.resource.RemoteCollection;
public class ContactsSyncAdapterService extends Service {
private static ContactsSyncAdapter syncAdapter;
@Override @Synchronized
public void onCreate() {
@ -36,21 +37,30 @@ public class ContactsSyncAdapterService extends Service {
syncAdapter = new ContactsSyncAdapter(getApplicationContext());
}
@Override
public void onDestroy() {
syncAdapter.close();
syncAdapter = null;
}
@Override
public IBinder onBind(Intent intent) {
return syncAdapter.getSyncAdapterBinder();
}
private static class ContactsSyncAdapter extends DavSyncAdapter {
private final static String TAG = "davdroid.ContactsSyncAdapter";
public ContactsSyncAdapter(Context context) {
private ContactsSyncAdapter(Context context) {
super(context);
Log.i(TAG, "httpClient = " + httpClient);
}
@Override
protected Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider) {
AccountSettings settings = new AccountSettings(context, account);
AccountSettings settings = new AccountSettings(getContext(), account);
String userName = settings.getUserName(),
password = settings.getPassword();
boolean preemptive = settings.getPreemptiveAuth();
@ -61,7 +71,8 @@ public class ContactsSyncAdapterService extends Service {
try {
LocalCollection<?> database = new LocalAddressBook(account, provider, settings);
RemoteCollection<?> dav = new CardDavAddressBook(addressBookURL, userName, password, preemptive);
Log.i(TAG, "httpClient 2 = " + httpClient);
RemoteCollection<?> dav = new CardDavAddressBook(httpClient, addressBookURL, userName, password, preemptive);
Map<LocalCollection<?>, RemoteCollection<?>> map = new HashMap<LocalCollection<?>, RemoteCollection<?>>();
map.put(database, dav);

View File

@ -10,11 +10,13 @@
******************************************************************************/
package at.bitfire.davdroid.syncadapter;
import java.io.Closeable;
import java.io.IOException;
import java.util.Map;
import org.apache.http.HttpStatus;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import lombok.Getter;
import android.accounts.Account;
import android.accounts.AccountManager;
@ -23,6 +25,7 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.util.Log;
@ -30,15 +33,16 @@ import at.bitfire.davdroid.resource.LocalCollection;
import at.bitfire.davdroid.resource.LocalStorageException;
import at.bitfire.davdroid.resource.RemoteCollection;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.HttpException;
public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter implements Closeable {
private final static String TAG = "davdroid.DavSyncAdapter";
protected Context context;
protected AccountManager accountManager;
@Getter private static String androidID;
protected AccountManager accountManager;
protected CloseableHttpClient httpClient;
public DavSyncAdapter(Context context) {
@ -48,11 +52,28 @@ public abstract class DavSyncAdapter extends AbstractThreadedSyncAdapter {
if (androidID == null)
androidID = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);
}
this.context = context;
accountManager = AccountManager.get(context);
httpClient = DavHttpClient.create();
}
@Override public void close() {
// apparently may be called from a GUI thread
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
try {
httpClient.close();
httpClient = null;
} catch (IOException e) {
Log.w(TAG, "Couldn't close HTTP client", e);
}
return null;
}
}.execute();
}
protected abstract Map<LocalCollection<?>, RemoteCollection<?>> getSyncPairs(Account account, ContentProviderClient provider);

View File

@ -16,8 +16,6 @@ import java.net.URISyntaxException;
import java.util.LinkedList;
import java.util.List;
import org.apache.http.HttpException;
import android.app.DialogFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.AsyncTaskLoader;
@ -32,9 +30,12 @@ import android.widget.ProgressBar;
import android.widget.Toast;
import at.bitfire.davdroid.R;
import at.bitfire.davdroid.webdav.DavException;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
import at.bitfire.davdroid.webdav.DavHttpClient;
import at.bitfire.davdroid.webdav.DavIncapableException;
import at.bitfire.davdroid.webdav.HttpPropfind.Mode;
import at.bitfire.davdroid.webdav.WebDavResource;
import ch.boye.httpclientandroidlib.HttpException;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
public class QueryServerDialogFragment extends DialogFragment implements LoaderCallbacks<ServerInfo> {
private static final String TAG = "davdroid.QueryServerDialogFragment";
@ -111,9 +112,10 @@ public class QueryServerDialogFragment extends DialogFragment implements LoaderC
args.getBoolean(EXTRA_AUTH_PREEMPTIVE)
);
CloseableHttpClient httpClient = DavHttpClient.create();
try {
// (1/5) detect capabilities
WebDavResource base = new WebDavResource(new URI(serverInfo.getBaseURL()), serverInfo.getUserName(),
WebDavResource base = new WebDavResource(httpClient, new URI(serverInfo.getProvidedURL()), serverInfo.getUserName(),
serverInfo.getPassword(), serverInfo.isAuthPreemptive(), true);
base.options();

View File

@ -22,7 +22,7 @@ import lombok.RequiredArgsConstructor;
public class ServerInfo implements Serializable {
private static final long serialVersionUID = 6744847358282980437L;
final private String baseURL;
final private String providedURL;
final private String userName, password;
final boolean authPreemptive;

View File

@ -10,48 +10,45 @@
******************************************************************************/
package at.bitfire.davdroid.webdav;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import at.bitfire.davdroid.Constants;
import ch.boye.httpclientandroidlib.client.config.RequestConfig;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ch.boye.httpclientandroidlib.impl.client.HttpClients;
import ch.boye.httpclientandroidlib.impl.conn.ManagedHttpClientConnectionFactory;
import ch.boye.httpclientandroidlib.impl.conn.PoolingHttpClientConnectionManager;
// see AndroidHttpClient
public class DavHttpClient extends DefaultHttpClient {
private DavHttpClient(HttpParams params) {
super(params);
public class DavHttpClient {
protected final static RequestConfig defaultRqConfig;
static {
// use request defaults from AndroidHttpClient
defaultRqConfig = RequestConfig.copy(RequestConfig.DEFAULT)
.setConnectTimeout(20*1000)
.setSocketTimeout(20*1000)
.setStaleConnectionCheckEnabled(false)
.build();
// enable logging
ManagedHttpClientConnectionFactory.INSTANCE.wirelog.enableDebug(true);
ManagedHttpClientConnectionFactory.INSTANCE.log.enableDebug(true);
}
public static DefaultHttpClient getDefault() {
HttpParams params = new BasicHttpParams();
params.setParameter(CoreProtocolPNames.USER_AGENT, "DAVdroid/" + Constants.APP_VERSION);
// use defaults of AndroidHttpClient
HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
HttpConnectionParams.setSoTimeout(params, 20 * 1000);
HttpConnectionParams.setSocketBufferSize(params, 8192);
HttpConnectionParams.setStaleCheckingEnabled(params, false);
// don't allow redirections
HttpClientParams.setRedirecting(params, false);
DavHttpClient httpClient = new DavHttpClient(params);
// use our own, SNI-capable LayeredSocketFactory for https://
SchemeRegistry schemeRegistry = httpClient.getConnectionManager().getSchemeRegistry();
schemeRegistry.register(new Scheme("https", new TlsSniSocketFactory(), 443));
// allow gzip compression
GzipDecompressingEntity.enable(httpClient);
return httpClient;
public static CloseableHttpClient create() {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
// limits per DavHttpClient (= per DavSyncAdapter extends AbstractThreadedSyncAdapter)
connectionManager.setMaxTotal(2); // max. 2 connections in total
connectionManager.setDefaultMaxPerRoute(2); // max. 2 connections per host
return HttpClients.custom()
.useSystemProperties()
.setSSLSocketFactory(TlsSniSocketFactory.INSTANCE)
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(defaultRqConfig)
.setUserAgent("DAVdroid/" + Constants.APP_VERSION)
.disableCookieManagement()
.build();
}
}

View File

@ -1,78 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Richard Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Richard Hirner (bitfire web engineering) - initial API and implementation
******************************************************************************/
package at.bitfire.davdroid.webdav;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.entity.HttpEntityWrapper;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HttpContext;
class GzipDecompressingEntity extends HttpEntityWrapper {
public GzipDecompressingEntity(final HttpEntity entity) {
super(entity);
}
@Override
public InputStream getContent()
throws IOException, IllegalStateException {
// the wrapped entity's getContent() decides about repeatability
InputStream wrappedin = wrappedEntity.getContent();
return new GZIPInputStream(wrappedin);
}
@Override
public long getContentLength() {
// length of ungzipped content is not known
return -1;
}
public static void enable(DefaultHttpClient client) {
client.addRequestInterceptor(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
if (!request.containsHeader("Accept-Encoding"))
request.addHeader("Accept-Encoding", "gzip");
}
});
client.addResponseInterceptor(new HttpResponseInterceptor() {
@Override
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
Header ceheader = entity.getContentEncoding();
if (ceheader != null) {
HeaderElement[] codecs = ceheader.getElements();
for (int i = 0; i < codecs.length; i++) {
if (codecs[i].getName().equalsIgnoreCase("gzip")) {
response.setEntity(new GzipDecompressingEntity(response.getEntity()));
return;
}
}
}
}
}
});
}
}

View File

@ -2,7 +2,7 @@ package at.bitfire.davdroid.webdav;
import lombok.Getter;
public class HttpException extends org.apache.http.HttpException {
public class HttpException extends ch.boye.httpclientandroidlib.HttpException {
private static final long serialVersionUID = -4805778240079377401L;
@Getter private int code;

View File

@ -14,16 +14,18 @@ import java.io.StringWriter;
import java.net.URI;
import java.util.LinkedList;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.entity.StringEntity;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import android.util.Log;
import ch.boye.httpclientandroidlib.client.methods.HttpEntityEnclosingRequestBase;
import ch.boye.httpclientandroidlib.entity.StringEntity;
public class HttpPropfind extends HttpEntityEnclosingRequestBase {
private static final String TAG = "davdroid.HttpPropfind";
public final static String METHOD_NAME = "PROPFIND";
public enum Mode {
CURRENT_USER_PRINCIPAL,
HOME_SETS,
@ -32,10 +34,15 @@ public class HttpPropfind extends HttpEntityEnclosingRequestBase {
MEMBERS_ETAG
}
HttpPropfind(URI uri, Mode mode) {
HttpPropfind(URI uri) {
setURI(uri);
}
HttpPropfind(URI uri, Mode mode) {
this(uri);
setHeader("Content-Type", "text/xml; charset=\"utf-8\"");
setHeader("Content-Type", "text/xml; charset=UTF-8");
DavPropfind propfind = new DavPropfind();
propfind.prop = new DavProp();
@ -77,8 +84,6 @@ public class HttpPropfind extends HttpEntityEnclosingRequestBase {
setHeader("Depth", String.valueOf(depth));
setEntity(new StringEntity(writer.toString(), "UTF-8"));
Log.d(TAG, "Prepared PROPFIND request for " + uri + ": " + writer.toString());
} catch(Exception ex) {
Log.e(TAG, "Couldn't prepare PROPFIND request for " + uri, ex);
abort();
@ -87,6 +92,6 @@ public class HttpPropfind extends HttpEntityEnclosingRequestBase {
@Override
public String getMethod() {
return "PROPFIND";
return METHOD_NAME;
}
}

View File

@ -10,35 +10,31 @@
******************************************************************************/
package at.bitfire.davdroid.webdav;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.entity.StringEntity;
import android.util.Log;
import ch.boye.httpclientandroidlib.client.methods.HttpEntityEnclosingRequestBase;
import ch.boye.httpclientandroidlib.entity.StringEntity;
public class HttpReport extends HttpEntityEnclosingRequestBase {
private static final String TAG = "DavHttpReport";
public final static String METHOD_NAME = "REPORT";
HttpReport(URI uri) {
setURI(uri);
}
HttpReport(URI uri, String entity) {
setURI(uri);
this(uri);
setHeader("Content-Type", "text/xml; charset=UTF-8");
setHeader("Depth", "0");
try {
setEntity(new StringEntity(entity, "UTF-8"));
Log.d(TAG, "Prepared REPORT request for " + uri + ": " + entity);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, e.getMessage());
}
setEntity(new StringEntity(entity, "UTF-8"));
}
@Override
public String getMethod() {
return "REPORT";
return METHOD_NAME;
}
}

View File

@ -1,44 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 Richard Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Richard Hirner (bitfire web engineering) - initial API and implementation
******************************************************************************/
package at.bitfire.davdroid.webdav;
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
public class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
if (authState.getAuthScheme() == null) {
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
if (creds != null) {
authState.setAuthScheme(new BasicScheme());
authState.setCredentials(creds);
}
}
}
}

View File

@ -12,6 +12,7 @@ package at.bitfire.davdroid.webdav;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
@ -20,50 +21,43 @@ import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.params.HttpParams;
import ch.boye.httpclientandroidlib.HttpHost;
import ch.boye.httpclientandroidlib.conn.socket.LayeredConnectionSocketFactory;
import ch.boye.httpclientandroidlib.conn.ssl.BrowserCompatHostnameVerifier;
import ch.boye.httpclientandroidlib.protocol.HttpContext;
import android.annotation.TargetApi;
import android.net.SSLCertificateSocketFactory;
import android.os.Build;
import android.util.Log;
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class TlsSniSocketFactory implements LayeredSocketFactory {
public class TlsSniSocketFactory implements LayeredConnectionSocketFactory {
private static final String TAG = "davdroid.SNISocketFactory";
final static HostnameVerifier hostnameVerifier = SSLSocketFactory.STRICT_HOSTNAME_VERIFIER;
final static HostnameVerifier hostnameVerifier = new BrowserCompatHostnameVerifier();
final static TlsSniSocketFactory INSTANCE = new TlsSniSocketFactory();
// Plain TCP/IP (layer below TLS)
@Override
public Socket connectSocket(Socket s, String host, int port, InetAddress localAddress, int localPort, HttpParams params) throws IOException {
public Socket createSocket(HttpContext context) throws IOException {
return null;
}
@Override
public Socket createSocket() throws IOException {
public Socket connectSocket(int timeout, Socket socket, HttpHost host, InetSocketAddress remoteAddr, InetSocketAddress localAddr, HttpContext context)
throws IOException {
return null;
}
@Override
public boolean isSecure(Socket s) throws IllegalArgumentException {
if (s instanceof SSLSocket)
return ((SSLSocket)s).isConnected();
return false;
}
// TLS layer
@Override
public Socket createSocket(Socket plainSocket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
if (autoClose) {
// we don't need the plainSocket
plainSocket.close();
}
public Socket createLayeredSocket(Socket plainSocket, String host, int port, HttpContext context) throws IOException, UnknownHostException {
plainSocket.close();
// create and connect SSL socket, but don't do hostname/certificate verification yet
SSLCertificateSocketFactory sslSocketFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(0);

View File

@ -26,31 +26,36 @@ import lombok.Cleanup;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicLineParser;
import org.apache.http.protocol.HTTP;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.core.Persister;
import android.util.Log;
import at.bitfire.davdroid.LoggingInputStream;
import at.bitfire.davdroid.URIUtils;
import at.bitfire.davdroid.resource.Event;
import at.bitfire.davdroid.webdav.DavProp.DavPropComp;
import ch.boye.httpclientandroidlib.Header;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpHost;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.HttpStatus;
import ch.boye.httpclientandroidlib.StatusLine;
import ch.boye.httpclientandroidlib.auth.AuthScope;
import ch.boye.httpclientandroidlib.auth.UsernamePasswordCredentials;
import ch.boye.httpclientandroidlib.client.AuthCache;
import ch.boye.httpclientandroidlib.client.methods.CloseableHttpResponse;
import ch.boye.httpclientandroidlib.client.methods.HttpDelete;
import ch.boye.httpclientandroidlib.client.methods.HttpGet;
import ch.boye.httpclientandroidlib.client.methods.HttpOptions;
import ch.boye.httpclientandroidlib.client.methods.HttpPut;
import ch.boye.httpclientandroidlib.client.protocol.HttpClientContext;
import ch.boye.httpclientandroidlib.entity.ByteArrayEntity;
import ch.boye.httpclientandroidlib.impl.auth.BasicScheme;
import ch.boye.httpclientandroidlib.impl.client.BasicAuthCache;
import ch.boye.httpclientandroidlib.impl.client.BasicCredentialsProvider;
import ch.boye.httpclientandroidlib.impl.client.CloseableHttpClient;
import ch.boye.httpclientandroidlib.message.BasicLineParser;
import ch.boye.httpclientandroidlib.util.EntityUtils;
@ToString
@ -89,39 +94,50 @@ public class WebDavResource {
// content (available after GET)
@Getter protected byte[] content;
protected DefaultHttpClient client;
protected CloseableHttpClient httpClient;
protected HttpClientContext context;
public WebDavResource(URI baseURL, boolean trailingSlash) throws URISyntaxException {
public WebDavResource(CloseableHttpClient httpClient, URI baseURL, boolean trailingSlash) throws URISyntaxException {
this.httpClient = httpClient;
location = baseURL.normalize();
if (trailingSlash && !location.getRawPath().endsWith("/"))
location = new URI(location.getScheme(), location.getSchemeSpecificPart() + "/", null);
client = DavHttpClient.getDefault();
context = HttpClientContext.create();
context.setCredentialsProvider(new BasicCredentialsProvider());
}
public WebDavResource(URI baseURL, String username, String password, boolean preemptive, boolean trailingSlash) throws URISyntaxException {
this(baseURL, trailingSlash);
public WebDavResource(CloseableHttpClient httpClient, URI baseURL, String username, String password, boolean preemptive, boolean trailingSlash) throws URISyntaxException {
this(httpClient, baseURL, trailingSlash);
HttpHost host = new HttpHost(baseURL.getHost(), baseURL.getPort(), baseURL.getScheme());
context.getCredentialsProvider().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
// authenticate
client.getCredentialsProvider().setCredentials(
new AuthScope(location.getHost(), location.getPort()),
new UsernamePasswordCredentials(username, password)
);
if (preemptive) {
Log.i(TAG, "Using preemptive authentication (not compatible with Digest auth)");
client.addRequestInterceptor(new PreemptiveAuthInterceptor(), 0);
Log.d(TAG, "Using preemptive authentication (not compatible with Digest auth)");
AuthCache authCache = context.getAuthCache();
if (authCache == null)
authCache = new BasicAuthCache();
authCache.put(host, new BasicScheme());
context.setAuthCache(authCache);
}
}
private WebDavResource(WebDavResource parent) { // based on existing WebDavResource, reuse settings
httpClient = parent.httpClient;
context = parent.context;
}
protected WebDavResource(WebDavResource parent, URI uri) {
this(parent);
location = uri;
client = parent.client;
}
public WebDavResource(WebDavResource parent, String member) {
this(parent, parent.location.resolve(URIUtils.sanitize(member)));
this(parent);
location = parent.location.resolve(URIUtils.sanitize(member));
}
public WebDavResource(WebDavResource parent, String member, boolean trailingSlash) {
@ -132,25 +148,27 @@ public class WebDavResource {
this(parent, member);
properties.put(Property.ETAG, ETag);
}
/* feature detection */
public void options() throws IOException, HttpException {
HttpOptions options = new HttpOptions(location);
HttpResponse response = client.execute(options);
checkResponse(response);
if (response.getEntity() != null)
response.getEntity().consumeContent();
Header[] allowHeaders = response.getHeaders("Allow");
for (Header allowHeader : allowHeaders)
methods.addAll(Arrays.asList(allowHeader.getValue().split(", ?")));
Header[] capHeaders = response.getHeaders("DAV");
for (Header capHeader : capHeaders)
capabilities.addAll(Arrays.asList(capHeader.getValue().split(", ?")));
CloseableHttpResponse response = httpClient.execute(options, context);
try {
checkResponse(response);
Header[] allowHeaders = response.getHeaders("Allow");
for (Header allowHeader : allowHeaders)
methods.addAll(Arrays.asList(allowHeader.getValue().split(", ?")));
Header[] capHeaders = response.getHeaders("DAV");
for (Header capHeader : capHeaders)
capabilities.addAll(Arrays.asList(capHeader.getValue().split(", ?")));
} finally {
response.close();
}
}
public boolean supportsDAV(String capability) {
@ -236,29 +254,29 @@ public class WebDavResource {
public void propfind(HttpPropfind.Mode mode) throws IOException, DavException, HttpException {
HttpPropfind propfind = new HttpPropfind(location, mode);
HttpResponse response = client.execute(propfind);
checkResponse(response);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS)
throw new DavNoMultiStatusException();
HttpEntity entity = response.getEntity();
if (entity == null)
throw new DavNoContentException();
@Cleanup InputStream rawContent = entity.getContent();
if (rawContent == null)
throw new DavNoContentException();
@Cleanup LoggingInputStream content = new LoggingInputStream(TAG, rawContent);
DavMultistatus multistatus;
CloseableHttpResponse response = httpClient.execute(propfind, context);
try {
Serializer serializer = new Persister();
multistatus = serializer.read(DavMultistatus.class, content, false);
} catch (Exception ex) {
throw new DavException("Couldn't parse Multi-Status response on PROPFIND", ex);
checkResponse(response);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS)
throw new DavNoMultiStatusException();
HttpEntity entity = response.getEntity();
if (entity == null)
throw new DavNoContentException();
@Cleanup InputStream content = entity.getContent();
DavMultistatus multistatus;
try {
Serializer serializer = new Persister();
multistatus = serializer.read(DavMultistatus.class, content, false);
} catch (Exception ex) {
throw new DavException("Couldn't parse Multi-Status response on PROPFIND", ex);
}
processMultiStatus(multistatus);
} finally {
response.close();
}
processMultiStatus(multistatus);
}
public void multiGet(DavMultiget.Type type, String[] names) throws IOException, DavException, HttpException {
@ -277,28 +295,28 @@ public class WebDavResource {
}
HttpReport report = new HttpReport(location, writer.toString());
HttpResponse response = client.execute(report);
checkResponse(response);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS)
throw new DavNoMultiStatusException();
HttpEntity entity = response.getEntity();
if (entity == null)
throw new DavNoContentException();
@Cleanup InputStream rawContent = entity.getContent();
if (rawContent == null)
throw new DavNoContentException();
@Cleanup LoggingInputStream content = new LoggingInputStream(TAG, rawContent);
DavMultistatus multiStatus;
CloseableHttpResponse response = httpClient.execute(report, context);
try {
multiStatus = serializer.read(DavMultistatus.class, content, false);
} catch (Exception ex) {
throw new DavException("Couldn't parse Multi-Status response on REPORT multi-get", ex);
checkResponse(response);
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_MULTI_STATUS)
throw new DavNoMultiStatusException();
HttpEntity entity = response.getEntity();
if (entity == null)
throw new DavNoContentException();
@Cleanup InputStream content = entity.getContent();
DavMultistatus multiStatus;
try {
multiStatus = serializer.read(DavMultistatus.class, content, false);
} catch (Exception ex) {
throw new DavException("Couldn't parse Multi-Status response on REPORT multi-get", ex);
}
processMultiStatus(multiStatus);
} finally {
response.close();
}
processMultiStatus(multiStatus);
}
@ -306,25 +324,21 @@ public class WebDavResource {
public void get() throws IOException, HttpException, DavException {
HttpGet get = new HttpGet(location);
HttpResponse response = client.execute(get);
checkResponse(response);
HttpEntity entity = response.getEntity();
if (entity == null)
throw new DavNoContentException();
@Cleanup InputStream rawContent = entity.getContent();
if (rawContent == null)
throw new DavNoContentException();
@Cleanup LoggingInputStream content = new LoggingInputStream(TAG, rawContent);
this.content = IOUtils.toByteArray(content);
CloseableHttpResponse response = httpClient.execute(get, context);
try {
checkResponse(response);
HttpEntity entity = response.getEntity();
if (entity == null)
throw new DavNoContentException();
content = EntityUtils.toByteArray(entity);
} finally {
response.close();
}
}
public void put(byte[] data, PutMode mode) throws IOException, HttpException {
Log.d(TAG, "Sending PUT request:");
Log.d(TAG, IOUtils.toString(data, HTTP.UTF_8));
HttpPut put = new HttpPut(location);
put.setEntity(new ByteArrayEntity(data));
@ -340,9 +354,12 @@ public class WebDavResource {
if (getContentType() != null)
put.addHeader("Content-Type", getContentType());
HttpResponse response = client.execute(put);
@Cleanup("consumeContent") HttpEntity entity = response.getEntity();
checkResponse(response);
CloseableHttpResponse response = httpClient.execute(put, context);
try {
checkResponse(response);
} finally {
response.close();
}
}
public void delete() throws IOException, HttpException {
@ -351,9 +368,12 @@ public class WebDavResource {
if (getETag() != null)
delete.addHeader("If-Match", getETag());
HttpResponse response = client.execute(delete);
@Cleanup("consumeContent") HttpEntity entity = response.getEntity();
checkResponse(response);
CloseableHttpResponse response = httpClient.execute(delete, context);
try {
checkResponse(response);
} finally {
response.close();
}
}
@ -366,8 +386,6 @@ public class WebDavResource {
protected static void checkResponse(StatusLine statusLine) throws HttpException {
int code = statusLine.getStatusCode();
Log.d(TAG, "Received " + statusLine.getProtocolVersion() + " " + code + " " + statusLine.getReasonPhrase());
if (code/100 == 1 || code/100 == 2) // everything OK
return;