diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 57063424..1e306681 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="31" + android:versionName="0.5.10" android:installLocation="internalOnly"> 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(); - } - -} diff --git a/src/at/bitfire/davdroid/resource/CalDavCalendar.java b/src/at/bitfire/davdroid/resource/CalDavCalendar.java index 1368bb79..ae901aac 100644 --- a/src/at/bitfire/davdroid/resource/CalDavCalendar.java +++ b/src/at/bitfire/davdroid/resource/CalDavCalendar.java @@ -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 { //private final static String TAG = "davdroid.CalDavCalendar"; @@ -33,7 +34,7 @@ public class CalDavCalendar extends RemoteCollection { } - 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); } } diff --git a/src/at/bitfire/davdroid/resource/CardDavAddressBook.java b/src/at/bitfire/davdroid/resource/CardDavAddressBook.java index 52d7ec3d..b60e7a52 100644 --- a/src/at/bitfire/davdroid/resource/CardDavAddressBook.java +++ b/src/at/bitfire/davdroid/resource/CardDavAddressBook.java @@ -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 { @@ -33,7 +34,7 @@ public class CardDavAddressBook extends RemoteCollection { } - 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); } } diff --git a/src/at/bitfire/davdroid/resource/RemoteCollection.java b/src/at/bitfire/davdroid/resource/RemoteCollection.java index 900fab11..0fb777a1 100644 --- a/src/at/bitfire/davdroid/resource/RemoteCollection.java +++ b/src/at/bitfire/davdroid/resource/RemoteCollection.java @@ -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 { 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); } diff --git a/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java b/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java index c20dc143..1da0546f 100644 --- a/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java +++ b/src/at/bitfire/davdroid/syncadapter/CalendarsSyncAdapterService.java @@ -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, 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, RemoteCollection> map = new HashMap, 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; diff --git a/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java b/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java index 81b9a9c9..6adee221 100644 --- a/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java +++ b/src/at/bitfire/davdroid/syncadapter/ContactsSyncAdapterService.java @@ -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, 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, RemoteCollection> map = new HashMap, RemoteCollection>(); map.put(database, dav); diff --git a/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java b/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java index 75e43b8d..0d89907d 100644 --- a/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java +++ b/src/at/bitfire/davdroid/syncadapter/DavSyncAdapter.java @@ -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() { + @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, RemoteCollection> getSyncPairs(Account account, ContentProviderClient provider); diff --git a/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java b/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java index c79622ae..3f2c1e7a 100644 --- a/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java +++ b/src/at/bitfire/davdroid/syncadapter/QueryServerDialogFragment.java @@ -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 { 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(); diff --git a/src/at/bitfire/davdroid/syncadapter/ServerInfo.java b/src/at/bitfire/davdroid/syncadapter/ServerInfo.java index 29162535..e380eb97 100644 --- a/src/at/bitfire/davdroid/syncadapter/ServerInfo.java +++ b/src/at/bitfire/davdroid/syncadapter/ServerInfo.java @@ -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; diff --git a/src/at/bitfire/davdroid/webdav/DavHttpClient.java b/src/at/bitfire/davdroid/webdav/DavHttpClient.java index d925a9f3..068a9fd4 100644 --- a/src/at/bitfire/davdroid/webdav/DavHttpClient.java +++ b/src/at/bitfire/davdroid/webdav/DavHttpClient.java @@ -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(); } - + } diff --git a/src/at/bitfire/davdroid/webdav/GzipDecompressingEntity.java b/src/at/bitfire/davdroid/webdav/GzipDecompressingEntity.java deleted file mode 100644 index 61df027f..00000000 --- a/src/at/bitfire/davdroid/webdav/GzipDecompressingEntity.java +++ /dev/null @@ -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; - } - } - } - } - } - }); - } -} diff --git a/src/at/bitfire/davdroid/webdav/HttpException.java b/src/at/bitfire/davdroid/webdav/HttpException.java index f41471b1..912da07d 100644 --- a/src/at/bitfire/davdroid/webdav/HttpException.java +++ b/src/at/bitfire/davdroid/webdav/HttpException.java @@ -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; diff --git a/src/at/bitfire/davdroid/webdav/HttpPropfind.java b/src/at/bitfire/davdroid/webdav/HttpPropfind.java index c7e4782d..08028ddf 100644 --- a/src/at/bitfire/davdroid/webdav/HttpPropfind.java +++ b/src/at/bitfire/davdroid/webdav/HttpPropfind.java @@ -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; } } diff --git a/src/at/bitfire/davdroid/webdav/HttpReport.java b/src/at/bitfire/davdroid/webdav/HttpReport.java index 45fd242a..8234c58d 100644 --- a/src/at/bitfire/davdroid/webdav/HttpReport.java +++ b/src/at/bitfire/davdroid/webdav/HttpReport.java @@ -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; } } diff --git a/src/at/bitfire/davdroid/webdav/PreemptiveAuthInterceptor.java b/src/at/bitfire/davdroid/webdav/PreemptiveAuthInterceptor.java deleted file mode 100644 index 28af72e3..00000000 --- a/src/at/bitfire/davdroid/webdav/PreemptiveAuthInterceptor.java +++ /dev/null @@ -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); - } - } - } -} diff --git a/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java b/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java index 821e3e60..9d31b4f3 100644 --- a/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java +++ b/src/at/bitfire/davdroid/webdav/TlsSniSocketFactory.java @@ -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); diff --git a/src/at/bitfire/davdroid/webdav/WebDavResource.java b/src/at/bitfire/davdroid/webdav/WebDavResource.java index 33182e27..727eb760 100644 --- a/src/at/bitfire/davdroid/webdav/WebDavResource.java +++ b/src/at/bitfire/davdroid/webdav/WebDavResource.java @@ -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;