Browse Source

SSL connections update: notice about untrusted certificates and allow the user save them as reliable

David A. Velasco 12 years ago
parent
commit
48f13c8adc
26 changed files with 1560 additions and 177 deletions
  1. 2 2
      AndroidManifest.xml
  2. 116 0
      res/layout/ssl_validator_layout.xml
  3. 11 0
      res/values/strings.xml
  4. 1 1
      src/com/owncloud/android/Uploader.java
  5. 6 3
      src/com/owncloud/android/authenticator/AuthenticationRunnable.java
  6. 137 0
      src/com/owncloud/android/authenticator/ConnectionCheckOperation.java
  7. 2 2
      src/com/owncloud/android/authenticator/ConnectionCheckerRunnable.java
  8. 2 1
      src/com/owncloud/android/files/services/FileDownloader.java
  9. 1 1
      src/com/owncloud/android/files/services/FileOperation.java
  10. 2 1
      src/com/owncloud/android/files/services/FileUploader.java
  11. 1 1
      src/com/owncloud/android/files/services/InstantUploadService.java
  12. 168 0
      src/com/owncloud/android/network/AdvancedSslSocketFactory.java
  13. 117 0
      src/com/owncloud/android/network/AdvancedX509TrustManager.java
  14. 2 1
      src/com/owncloud/android/network/EasySSLSocketFactory.java
  15. 1 1
      src/com/owncloud/android/network/EasyX509TrustManager.java
  16. 283 0
      src/com/owncloud/android/network/OwnCloudClientUtils.java
  17. 82 0
      src/com/owncloud/android/network/SslAnalyzer.java
  18. 7 0
      src/com/owncloud/android/operations/OnRemoteOperationListener.java
  19. 135 0
      src/com/owncloud/android/operations/RemoteOperation.java
  20. 121 0
      src/com/owncloud/android/operations/RemoteOperationResult.java
  21. 1 1
      src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java
  22. 167 13
      src/com/owncloud/android/ui/activity/AuthenticatorActivity.java
  23. 1 1
      src/com/owncloud/android/ui/activity/FileDisplayActivity.java
  24. 193 0
      src/com/owncloud/android/ui/dialog/SslValidatorDialog.java
  25. 1 1
      src/com/owncloud/android/ui/fragment/FileDetailFragment.java
  26. 0 147
      src/com/owncloud/android/utils/OwnCloudClientUtils.java

+ 2 - 2
AndroidManifest.xml

@@ -17,8 +17,8 @@
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
  -->
 <manifest package="com.owncloud.android"
-    android:versionCode="103008"
-    android:versionName="1.3.8" xmlns:android="http://schemas.android.com/apk/res/android">
+    android:versionCode="103009"
+    android:versionName="1.3.9" xmlns:android="http://schemas.android.com/apk/res/android">
 
     <uses-permission android:name="android.permission.GET_ACCOUNTS" />
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />

+ 116 - 0
res/layout/ssl_validator_layout.xml

@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+    ownCloud Android client application
+
+    Copyright (C) 2012  Bartek Przybylski
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center"
+    android:orientation="vertical" >
+
+	<TextView
+		android:id="@+id/header"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:text="@string/ssl_validator_header"
+		android:padding="5dp"
+		android:textAppearance="?android:attr/textAppearanceMedium"
+		 />
+    
+	<TextView
+		android:id="@+id/reason_cert_not_trusted"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_gravity="left"
+		android:paddingLeft="20dp"
+		android:text="@string/ssl_validator_reason_cert_not_trusted"
+		android:textAppearance="?android:attr/textAppearanceSmall"
+		 />
+	
+	<TextView
+		android:id="@+id/reason_cert_expired"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_gravity="left"
+		android:paddingLeft="20dp"
+		android:text="@string/ssl_validator_reason_cert_expired"
+		android:textAppearance="?android:attr/textAppearanceSmall"
+		 />
+	
+	<TextView
+		android:id="@+id/reason_cert_not_yet_valid"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_gravity="left"
+		android:paddingLeft="20dp"
+		android:text="@string/ssl_validator_reason_cert_not_yet_valid"
+		android:textAppearance="?android:attr/textAppearanceSmall"
+		 />
+		
+	<TextView
+		android:id="@+id/reason_hostname_not_vertified"
+		android:layout_width="wrap_content"
+		android:layout_height="wrap_content"
+		android:layout_gravity="left"
+		android:paddingLeft="20dp"
+		android:text="@string/ssl_validator_reason_hostname_not_vertified"
+		android:textAppearance="?android:attr/textAppearanceSmall"
+		 />
+	
+    
+	<TextView
+        android:id="@+id/issuer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+		android:padding="5dp"
+        android:text="@string/text_placeholder"
+        android:textAppearance="?android:attr/textAppearanceSmall"
+        />
+
+	<TextView
+        android:id="@+id/question"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+		android:padding="5dp"
+        android:text="@string/ssl_validator_question"
+        android:textAppearance="?android:attr/textAppearanceMedium"
+        >
+    </TextView>
+
+	<LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center_horizontal" >
+
+        <Button
+            android:id="@+id/cancel"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/common_cancel" />
+
+        <Button
+            android:id="@+id/ok"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/common_ok" />
+
+    </LinearLayout>
+
+</LinearLayout>

+ 11 - 0
res/values/strings.xml

@@ -183,4 +183,15 @@
 	
     <string name="filedisplay_unexpected_bad_get_content">"Unexpected problem ; please, try other app to select the file"</string>
     <string name="filedisplay_no_file_selected">No file was selected</string>
+    
+    <string name="ssl_validator_title">Warning</string>
+    <string name="ssl_validator_header">The identity of the site could not be verified</string>
+    <string name="ssl_validator_reason_cert_not_trusted">- The server certificate is not trusted</string>
+    <string name="ssl_validator_reason_cert_expired">- The server certificate expired</string>
+    <string name="ssl_validator_reason_cert_not_yet_valid">- The server certificate is too young</string>
+    <string name="ssl_validator_reason_hostname_not_vertified">- The URL does not match the host name in the certificate</string>
+    <string name="ssl_validator_question">Do you want to trust this certificate anyway?</string>
+    <string name="ssl_validator_not_saved">The certificate could not be saved</string>
+    
+    <string name="text_placeholder">This is a placeholder</string>
 </resources>

+ 1 - 1
src/com/owncloud/android/Uploader.java

@@ -30,7 +30,7 @@ import com.owncloud.android.datamodel.DataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileUploader;
-import com.owncloud.android.utils.OwnCloudClientUtils;
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;

+ 6 - 3
src/com/owncloud/android/authenticator/AuthenticationRunnable.java

@@ -22,10 +22,11 @@ import java.net.URL;
 
 import org.apache.commons.httpclient.HttpStatus;
 
-import com.owncloud.android.utils.OwnCloudClientUtils;
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import eu.alefzero.webdav.WebdavClient;
 
+import android.content.Context;
 import android.net.Uri;
 import android.os.Handler;
 
@@ -36,12 +37,14 @@ public class AuthenticationRunnable implements Runnable {
     private URL mUrl;
     private String mUsername;
     private String mPassword;
+    private Context mContext;
 
-    public AuthenticationRunnable(URL url, String username, String password) {
+    public AuthenticationRunnable(URL url, String username, String password, Context context) {
         mListener = null;
         mUrl = url;
         mUsername = username;
         mPassword = password;
+        mContext = context;
     }
 
     public void setOnAuthenticationResultListener(
@@ -54,7 +57,7 @@ public class AuthenticationRunnable implements Runnable {
     public void run() {
         Uri uri;
         uri = Uri.parse(mUrl.toString());
-        WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(uri, mUsername, mPassword);
+        WebdavClient wdc = OwnCloudClientUtils.createOwnCloudClient(uri, mUsername, mPassword, mContext);
         int login_result = wdc.tryToLogin();
         switch (login_result) {
         case HttpStatus.SC_OK:

+ 137 - 0
src/com/owncloud/android/authenticator/ConnectionCheckOperation.java

@@ -0,0 +1,137 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012 Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.authenticator;
+
+import org.apache.commons.httpclient.HttpStatus;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.network.SslAnalyzer;
+import com.owncloud.android.operations.RemoteOperation;
+import com.owncloud.android.operations.RemoteOperationResult;
+import com.owncloud.android.utils.OwnCloudVersion;
+
+import eu.alefzero.webdav.WebdavClient;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.util.Log;
+
+public class ConnectionCheckOperation extends RemoteOperation {
+    
+    /** Maximum time to wait for a response from the server when the connection is being tested, in MILLISECONDs.  */
+    public static final int TRY_CONNECTION_TIMEOUT = 5000;
+    
+    private static final String TAG = ConnectionCheckerRunnable.class.getCanonicalName();
+    
+    private String mUrl;
+    private RemoteOperationResult mLatestResult;
+    private Context mContext;
+    private OwnCloudVersion mOCVersion;
+
+    public ConnectionCheckOperation(String url, Context context) {
+        mUrl = url;
+        mContext = context;
+        mOCVersion = null;
+    }
+    
+    public OwnCloudVersion getDiscoveredVersion() {
+        return mOCVersion;
+    }
+
+    private boolean tryConnection(WebdavClient wc, String urlSt) {
+        boolean retval = false;
+        GetMethod get = null;
+        try {
+            get = new GetMethod(urlSt);
+            int status = wc.executeMethod(get, TRY_CONNECTION_TIMEOUT, TRY_CONNECTION_TIMEOUT);
+            String response = get.getResponseBodyAsString();
+            if (status == HttpStatus.SC_OK) {
+                JSONObject json = new JSONObject(response);
+                if (!json.getBoolean("installed")) {
+                    mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED);
+                } else {
+                    mOCVersion = new OwnCloudVersion(json.getString("version"));
+                    if (!mOCVersion.isVersionValid()) {
+                        mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.BAD_OC_VERSION);
+                        
+                    } else {
+                        retval = true;
+                    }
+                }
+                
+            } else {
+                mLatestResult = new RemoteOperationResult(false, status);
+            }
+
+        } catch (JSONException e) {
+            mLatestResult = new RemoteOperationResult(RemoteOperationResult.ResultCode.INSTANCE_NOT_CONFIGURED);
+            //Log.e(TAG, "JSON exception while trying connection (instance not configured) ", e);
+            
+        } catch (Exception e) {
+            mLatestResult = new RemoteOperationResult(e);
+            //Log.e(TAG, "Unexpected exception while trying connection", e);
+            
+        } finally {
+            if (get != null)
+                get.releaseConnection();
+        }
+
+        return retval;
+    }
+
+    private boolean isOnline() {
+        ConnectivityManager cm = (ConnectivityManager) mContext
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        return cm != null && cm.getActiveNetworkInfo() != null
+                && cm.getActiveNetworkInfo().isConnectedOrConnecting();
+    }
+
+	@Override
+	protected RemoteOperationResult run(WebdavClient client) {
+        if (!isOnline()) {
+        	return new RemoteOperationResult(RemoteOperationResult.ResultCode.NO_NETWORK_CONNECTION);
+        }
+        if (mUrl.startsWith("http://") || mUrl.startsWith("https://")) {
+            mLatestResult = new RemoteOperationResult(
+                                mUrl.startsWith("https://") ? RemoteOperationResult.ResultCode.OK_SSL : RemoteOperationResult.ResultCode.OK_NO_SSL
+                            );
+            tryConnection(client, mUrl + AccountUtils.STATUS_PATH);
+            return mLatestResult;
+            
+        } else {
+            client.setBaseUri(Uri.parse("https://" + mUrl + AccountUtils.STATUS_PATH));
+            if (tryConnection(client, "https://" + mUrl + AccountUtils.STATUS_PATH)) {
+                return new RemoteOperationResult(RemoteOperationResult.ResultCode.OK_SSL);
+        			
+            } else if (!SslAnalyzer.isRecoverable(mLatestResult)) {
+                
+                Log.d(TAG, "establishing secure connection failed, trying non secure connection");
+                client.setBaseUri(Uri.parse("http://" + mUrl + AccountUtils.STATUS_PATH));
+                if (tryConnection(client, "http://" + mUrl + AccountUtils.STATUS_PATH)) {
+                    return new RemoteOperationResult(RemoteOperationResult.ResultCode.OK_NO_SSL);
+                }
+            }
+            return mLatestResult;
+        }
+	}
+
+}

+ 2 - 2
src/com/owncloud/android/authenticator/ConnectionCheckerRunnable.java

@@ -36,7 +36,7 @@ import org.json.JSONObject;
 
 import com.owncloud.android.AccountUtils;
 import com.owncloud.android.authenticator.OnConnectCheckListener.ResultType;
-import com.owncloud.android.utils.OwnCloudClientUtils;
+import com.owncloud.android.network.OwnCloudClientUtils;
 import com.owncloud.android.utils.OwnCloudVersion;
 
 import eu.alefzero.webdav.WebdavClient;
@@ -107,7 +107,7 @@ public class ConnectionCheckerRunnable implements Runnable {
         boolean retval = false;
         GetMethod get = null;
         try {
-            WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(urlSt));
+            WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(urlSt), mContext);
             get = new GetMethod(urlSt);
             int status = wc.executeMethod(get, TRY_CONNECTION_TIMEOUT, TRY_CONNECTION_TIMEOUT);
             String response = get.getResponseBodyAsString();

+ 2 - 1
src/com/owncloud/android/files/services/FileDownloader.java

@@ -8,7 +8,8 @@ import java.util.Map;
 import com.owncloud.android.authenticator.AccountAuthenticator;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import eu.alefzero.webdav.OnDatatransferProgressListener;
-import com.owncloud.android.utils.OwnCloudClientUtils;
+
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;

+ 1 - 1
src/com/owncloud/android/files/services/FileOperation.java

@@ -21,7 +21,7 @@ import java.io.File;
 
 import com.owncloud.android.AccountUtils;
 import com.owncloud.android.datamodel.OCFile;
-import com.owncloud.android.utils.OwnCloudClientUtils;
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import android.accounts.Account;
 import android.content.Context;

+ 2 - 1
src/com/owncloud/android/files/services/FileUploader.java

@@ -10,7 +10,8 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.PhotoTakenBroadcastReceiver;
 
 import eu.alefzero.webdav.OnDatatransferProgressListener;
-import com.owncloud.android.utils.OwnCloudClientUtils;
+
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import android.accounts.Account;
 import android.app.Notification;

+ 1 - 1
src/com/owncloud/android/files/services/InstantUploadService.java

@@ -22,7 +22,7 @@ import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
 
-import com.owncloud.android.utils.OwnCloudClientUtils;
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import eu.alefzero.webdav.WebdavClient;
 

+ 168 - 0
src/com/owncloud/android/network/AdvancedSslSocketFactory.java

@@ -0,0 +1,168 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012 Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.network;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+
+import org.apache.commons.httpclient.ConnectTimeoutException;
+import org.apache.commons.httpclient.params.HttpConnectionParams;
+import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
+import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
+import org.apache.http.conn.ssl.X509HostnameVerifier;
+
+import android.util.Log;
+
+/**
+ * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with 
+ * a custom SSLContext and an optional Hostname Verifier.
+ * 
+ * @author David A. Velasco
+ */
+
+public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
+
+    private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName();
+    
+    private SSLContext mSslContext = null;
+    private X509HostnameVerifier mHostnameVerifier;
+
+    /**
+     * Constructor for AdvancedSSLProtocolSocketFactory.
+     */
+    public AdvancedSslSocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
+        if (sslContext == null)
+            throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext");
+        mSslContext = sslContext;
+        mHostnameVerifier = hostnameVerifier;
+    }
+
+    /**
+     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
+     */
+    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
+        Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort);
+        verifyHostname(host, socket);
+        return socket;
+    }
+
+    
+    /**
+     * Attempts to get a new socket connection to the given host within the
+     * given time limit.
+     * 
+     * @param host the host name/IP
+     * @param port the port on the host
+     * @param clientHost the local host name/IP to bind the socket to
+     * @param clientPort the port on the local machine
+     * @param params {@link HttpConnectionParams Http connection parameters}
+     * 
+     * @return Socket a new socket
+     * 
+     * @throws IOException if an I/O error occurs while creating the socket
+     * @throws UnknownHostException if the IP address of the host cannot be
+     *             determined
+     */
+    public Socket createSocket(final String host, final int port,
+            final InetAddress localAddress, final int localPort,
+            final HttpConnectionParams params) throws IOException,
+            UnknownHostException, ConnectTimeoutException {
+        Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params);
+        if (params == null) {
+            throw new IllegalArgumentException("Parameters may not be null");
+        }
+        int timeout = params.getConnectionTimeout();
+        SocketFactory socketfactory = mSslContext.getSocketFactory();
+        Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
+        Socket socket = socketfactory.createSocket();
+        SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
+        SocketAddress remoteaddr = new InetSocketAddress(host, port);
+        socket.setSoTimeout(params.getSoTimeout());
+        socket.bind(localaddr);
+        socket.connect(remoteaddr, timeout);
+        verifyHostname(host, socket);
+        return socket;
+    }
+
+    /**
+     * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
+     */
+    public Socket createSocket(String host, int port) throws IOException,
+            UnknownHostException {
+        Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
+        Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
+        verifyHostname(host, socket);
+        return socket; 
+    }
+
+    /**
+     * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
+     */
+    /*public Socket createSocket(Socket socket, String host, int port,
+            boolean autoClose) throws IOException, UnknownHostException {
+        Log.d(TAG, "Creating SSL Socket from other shocket " + socket + " to remote " + host + ":" + port);
+        return getSSLContext().getSocketFactory().createSocket(socket, host,
+                port, autoClose);
+    }*/
+
+    public boolean equals(Object obj) {
+        return ((obj != null) && obj.getClass().equals(
+                AdvancedSslSocketFactory.class));
+    }
+
+    public int hashCode() {
+        return AdvancedSslSocketFactory.class.hashCode();
+    }
+
+
+    public X509HostnameVerifier getHostNameVerifier() {
+        return mHostnameVerifier;
+    }
+    
+    
+    public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) {
+        mHostnameVerifier = hostnameVerifier;
+    }
+    
+    /**
+     * Verifies the host name with the content of the server certificate using the current host name verifier, if some
+     * @param socket
+     */
+    private void verifyHostname(String host, Socket socket) throws IOException {
+        if (mHostnameVerifier != null) {
+            try {
+                mHostnameVerifier.verify(host, (SSLSocket) socket);
+            } catch (IOException iox) {
+                try {
+                    socket.close();
+                } catch (Exception x) {}
+                throw iox;
+            }
+        }
+    }
+    
+}

+ 117 - 0
src/com/owncloud/android/network/AdvancedX509TrustManager.java

@@ -0,0 +1,117 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012 Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.network;
+
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertStoreException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import android.util.Log;
+
+/**
+ * @author David A. Velasco
+ */
+public class AdvancedX509TrustManager implements X509TrustManager {
+    
+    private static final String TAG = AdvancedX509TrustManager.class.getSimpleName();
+
+    private X509TrustManager mStandardTrustManager = null;
+    private KeyStore mKnownServersKeyStore;
+
+    /**
+     * Constructor for AdvancedX509TrustManager
+     * 
+     * @param  knownServersCertStore    Local certificates store with server certificates explicitly trusted by the user.
+     * @throws CertStoreException       When no default X509TrustManager instance was found in the system.
+     */
+    public AdvancedX509TrustManager(KeyStore knownServersKeyStore)
+            throws NoSuchAlgorithmException, KeyStoreException, CertStoreException {
+        super();
+        TrustManagerFactory factory = TrustManagerFactory
+                .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        factory.init((KeyStore)null);
+        mStandardTrustManager = findX509TrustManager(factory);
+
+        mKnownServersKeyStore = knownServersKeyStore;
+    }
+    
+    
+    /**
+     * Locates the first X509TrustManager provided by a given TrustManagerFactory
+     * @param factory               TrustManagerFactory to inspect in the search for a X509TrustManager
+     * @return                      The first X509TrustManager found in factory.
+     * @throws CertStoreException   When no X509TrustManager instance was found in factory
+     */
+    private X509TrustManager findX509TrustManager(TrustManagerFactory factory) throws CertStoreException {
+        TrustManager tms[] = factory.getTrustManagers();
+        for (int i = 0; i < tms.length; i++) {
+            if (tms[i] instanceof X509TrustManager) {
+                return (X509TrustManager) tms[i];
+            }
+        }
+        return null;
+    }
+    
+
+    /**
+     * @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[],
+     *      String authType)
+     */
+    public void checkClientTrusted(X509Certificate[] certificates, String authType) throws CertificateException {
+        mStandardTrustManager.checkClientTrusted(certificates, authType);
+    }
+
+    
+    /**
+     * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[],
+     *      String authType)
+     */
+    public void checkServerTrusted(X509Certificate[] certificates, String authType) throws CertificateException {
+        if (!isKnownServer(certificates[0])) {
+            Log.d(TAG, "checkClientTrusted() with standard trust manager...");
+            mStandardTrustManager.checkClientTrusted(certificates, authType);
+        }
+    }
+    
+    
+    /**
+     * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
+     */
+    public X509Certificate[] getAcceptedIssuers() {
+        return mStandardTrustManager.getAcceptedIssuers();
+    }
+
+    
+    private boolean isKnownServer(X509Certificate cert) {
+        try {
+            return (mKnownServersKeyStore.getCertificateAlias(cert) != null);
+        } catch (KeyStoreException e) {
+            Log.d(TAG, "Fail while checking certificate in the known-servers store");
+            return false;
+        }
+    }
+    
+}

+ 2 - 1
src/com/owncloud/android/authenticator/EasySSLSocketFactory.java → src/com/owncloud/android/network/EasySSLSocketFactory.java

@@ -28,7 +28,7 @@
  *
  */
 
-package com.owncloud.android.authenticator;
+package com.owncloud.android.network;
 
 import java.io.IOException;
 import java.net.InetAddress;
@@ -47,6 +47,7 @@ import org.apache.commons.httpclient.params.HttpConnectionParams;
 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
 
+
 import android.util.Log;
 
 /**

+ 1 - 1
src/com/owncloud/android/authenticator/EasyX509TrustManager.java → src/com/owncloud/android/network/EasyX509TrustManager.java

@@ -1,4 +1,4 @@
-package com.owncloud.android.authenticator;
+package com.owncloud.android.network;
 
 /*
  * Licensed to the Apache Software Foundation (ASF) under one

+ 283 - 0
src/com/owncloud/android/network/OwnCloudClientUtils.java

@@ -0,0 +1,283 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.network;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.protocol.Protocol;
+
+import com.owncloud.android.AccountUtils;
+import com.owncloud.android.authenticator.AccountAuthenticator;
+import com.owncloud.android.utils.OwnCloudVersion;
+
+import eu.alefzero.webdav.WebdavClient;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+public class OwnCloudClientUtils {
+    
+    final private static String TAG = "OwnCloudClientFactory";
+    
+    /** Default timeout for waiting data from the server */
+    public static final int DEFAULT_DATA_TIMEOUT = 60000;
+    
+    /** Default timeout for establishing a connection */
+    public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
+
+    /** Connection manager for all the WebdavClients */
+    private static MultiThreadedHttpConnectionManager mConnManager = null;
+    
+    private static Protocol mDefaultHttpsProtocol = null;
+
+    private static AdvancedSslSocketFactory mAdvancedSslSocketFactory = null;
+    
+    
+    /**
+     * Creates a WebdavClient setup for an ownCloud account
+     * 
+     * @param account   The ownCloud account
+     * @param context   The application context
+     * @return          A WebdavClient object ready to be used
+     */
+    public static WebdavClient createOwnCloudClient (Account account, Context context) {
+        Log.d(TAG, "Creating WebdavClient associated to " + account.name);
+       
+        String baseUrl = AccountManager.get(context).getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL);
+        OwnCloudVersion ownCloudVersion = new OwnCloudVersion(AccountManager.get(context).getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
+        String webDavPath = AccountUtils.getWebdavPath(ownCloudVersion);
+        
+        WebdavClient client = createOwnCloudClient(Uri.parse(baseUrl + webDavPath), context);
+        
+        String username = account.name.substring(0, account.name.lastIndexOf('@'));
+        String password = AccountManager.get(context).getPassword(account);
+        //String password = am.blockingGetAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, true);
+        
+        client.setCredentials(username, password);
+        
+        return client;
+    }
+    
+    
+    /**
+     * Creates a WebdavClient to try a new account before saving it
+     * 
+     * @param uri       URL to the ownCloud server
+     * @param username  User name
+     * @param password  User password
+     * @param context   Android context where the WebdavClient is being created.
+     * @return          A WebdavClient object ready to be used
+     */
+    public static WebdavClient createOwnCloudClient(Uri uri, String username, String password, Context context) {
+        Log.d(TAG, "Creating WebdavClient for " + username + "@" + uri);
+        
+        WebdavClient client = createOwnCloudClient(uri, context);
+        
+        client.setCredentials(username, password);
+        
+        return client;
+    }
+    
+    
+    /**
+     * Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections.
+     * 
+     * @param uri       URL to the ownCloud server
+     * @param context   Android context where the WebdavClient is being created.
+     * @return          A WebdavClient object ready to be used
+     */
+    public static WebdavClient createOwnCloudClient(Uri uri, Context context) {
+        Log.d(TAG, "Creating WebdavClient for " + uri);
+        
+        //allowSelfsignedCertificates(true);
+        try {
+            registerAdvancedSslContext(true, context);
+        }  catch (GeneralSecurityException e) {
+            Log.e(TAG, "Advanced SSL Context could not be loaded. Default SSL management in the system will be used for HTTPS connections", e);
+            
+        } catch (IOException e) {
+            Log.e(TAG, "The local server truststore could not be read. Default SSL management in the system will be used for HTTPS connections", e);
+        }
+        
+        WebdavClient client = new WebdavClient(getMultiThreadedConnManager());
+        
+        client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT);
+        client.setBaseUri(uri);
+        
+        return client;
+    }
+    
+    
+    /**
+     * Allows or disallows self-signed certificates in ownCloud servers to reach
+     * 
+     * @param allow     'True' to allow, 'false' to disallow
+     */
+    public static void allowSelfsignedCertificates(boolean allow) {
+        Protocol pr = null;
+        try {
+            pr = Protocol.getProtocol("https");
+            if (pr != null && mDefaultHttpsProtocol == null) {
+            	mDefaultHttpsProtocol = pr;
+            }
+        } catch (IllegalStateException e) {
+            // nothing to do here; really
+        }
+        boolean isAllowed = (pr != null && pr.getSocketFactory() instanceof EasySSLSocketFactory);
+        if (allow && !isAllowed) {
+            Protocol.registerProtocol("https", new Protocol("https", new EasySSLSocketFactory(), 443));
+        } else if (!allow && isAllowed) {
+        	if (mDefaultHttpsProtocol != null) {
+        		Protocol.registerProtocol("https", mDefaultHttpsProtocol);
+        	}
+        }
+    }
+
+    
+    /**
+     * Registers or unregisters the proper components for advanced SSL handling.
+     * @throws IOException 
+     */
+    private static void registerAdvancedSslContext(boolean register, Context context) throws GeneralSecurityException, IOException {
+        Protocol pr = null;
+        try {
+            pr = Protocol.getProtocol("https");
+            if (pr != null && mDefaultHttpsProtocol == null) {
+                mDefaultHttpsProtocol = pr;
+            }
+        } catch (IllegalStateException e) {
+            // nothing to do here; really
+        }
+        boolean isRegistered = (pr != null && pr.getSocketFactory() instanceof AdvancedSslSocketFactory);
+        if (register && !isRegistered) {
+            Protocol.registerProtocol("https", new Protocol("https", getAdvancedSslSocketFactory(context), 443));
+            
+        } else if (!register && isRegistered) {
+            if (mDefaultHttpsProtocol != null) {
+                Protocol.registerProtocol("https", mDefaultHttpsProtocol);
+            }
+        }
+    }
+    
+    private static AdvancedSslSocketFactory getAdvancedSslSocketFactory(Context context) throws GeneralSecurityException, IOException {
+        if (mAdvancedSslSocketFactory  == null) {
+            KeyStore trustStore = getKnownServersStore(context);
+            AdvancedX509TrustManager trustMgr = new AdvancedX509TrustManager(trustStore);
+            TrustManager[] tms = new TrustManager[] { trustMgr };
+                
+            SSLContext sslContext = SSLContext.getInstance("TLS");
+            sslContext.init(null, tms, null);
+                    
+            /*} catch (KeyStoreException e) {
+                e.printStackTrace();
+                    
+            } catch (NoSuchAlgorithmException e) {
+                e.printStackTrace();
+                    
+            } catch (KeyManagementException e) {
+                e.printStackTrace();
+                    
+            }*/
+            mAdvancedSslSocketFactory = new AdvancedSslSocketFactory(sslContext, null);    // TODO HOST NAME VERIFIER
+        }
+        return mAdvancedSslSocketFactory;
+    }
+
+
+    private static String LOCAL_TRUSTSTORE_FILENAME = "knownServers.bks";
+    
+    private static String LOCAL_TRUSTSTORE_PASSWORD = "password";
+
+    private static KeyStore mKnownServersStore = null;
+    
+    /**
+     * Returns the local store of reliable server certificates, explicitly accepted by the user.
+     * 
+     * Returns a KeyStore instance with empty content if the local store was never created.
+     * 
+     * Loads the store from the storage environment if needed.
+     * 
+     * @param context                       Android context where the operation is being performed.
+     * @return                              KeyStore instance with explicitly-accepted server certificates. 
+     * @throws KeyStoreException            When the KeyStore instance could not be created.
+     * @throws IOException                  When an existing local trust store could not be loaded.
+     * @throws NoSuchAlgorithmException     When the existing local trust store was saved with an unsupported algorithm.
+     * @throws CertificateException         When an exception occurred while loading the certificates from the local trust store.
+     */
+    private static KeyStore getKnownServersStore(Context context) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
+        if (mKnownServersStore == null) {
+            //mKnownServersStore = KeyStore.getInstance("BKS");
+            mKnownServersStore = KeyStore.getInstance(KeyStore.getDefaultType());
+            File localTrustStoreFile = new File(context.getFilesDir(), LOCAL_TRUSTSTORE_FILENAME);
+            Log.d(TAG, "Searching known-servers store at " + localTrustStoreFile.getAbsolutePath());
+            if (localTrustStoreFile.exists()) {
+                InputStream in = new FileInputStream(localTrustStoreFile);
+                try {
+                    mKnownServersStore.load(in, LOCAL_TRUSTSTORE_PASSWORD.toCharArray());
+                } finally {
+                    in.close();
+                }
+            } else {
+                mKnownServersStore.load(null, LOCAL_TRUSTSTORE_PASSWORD.toCharArray()); // necessary to initialize an empty KeyStore instance
+            }
+        }
+        return mKnownServersStore;
+    }
+    
+    
+    public static void addCertToKnownServersStore(Certificate cert, Context context) throws  KeyStoreException, NoSuchAlgorithmException, 
+                                                                                            CertificateException, IOException {
+        KeyStore knownServers = getKnownServersStore(context);
+        knownServers.setCertificateEntry(Integer.toString(cert.hashCode()), cert);
+        FileOutputStream fos = null;
+        try {
+            fos = context.openFileOutput(LOCAL_TRUSTSTORE_FILENAME, Context.MODE_PRIVATE);
+            knownServers.store(fos, LOCAL_TRUSTSTORE_PASSWORD.toCharArray());
+        } finally {
+            fos.close();
+        }
+    }
+    
+    
+    static private MultiThreadedHttpConnectionManager getMultiThreadedConnManager() {
+        if (mConnManager == null) {
+            mConnManager = new MultiThreadedHttpConnectionManager();
+            mConnManager.getParams().setDefaultMaxConnectionsPerHost(5);
+            mConnManager.getParams().setMaxTotalConnections(5);
+        }
+        return mConnManager;
+    }
+
+}

+ 82 - 0
src/com/owncloud/android/network/SslAnalyzer.java

@@ -0,0 +1,82 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+package com.owncloud.android.network;
+
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+import com.owncloud.android.operations.RemoteOperationResult;
+
+/**
+ * Helper class to check if a SSL error is related to a condition that could be avoided with assistance from the user.
+ * 
+ * @author David A. Velasco
+ */
+public class SslAnalyzer {
+    
+    /**
+     * Search for a SSL-related exception in a remote operation result that can be recoverable
+     * by allowing the user to state the reliability of the certificate from the server. 
+     * 
+     * @param result        Result of a remote operation.
+     * @return              An exception instance that caused the failure of the remote operation and that can be avoided if the user
+     *                      states the certificate from the server as reliable; or NULL if the result is that's not possible
+     */
+    public static Exception getRecoverableException(RemoteOperationResult result) {
+        Exception ret = null;
+        SSLException e = (SSLException)result.getException();
+        Throwable cause = null;
+        if (e != null) {
+            if (e instanceof SSLPeerUnverifiedException) {
+                ret = e;
+                
+            } else { 
+                cause = e.getCause();
+                Throwable previousCause = null;
+                boolean recoverableCertException = false;
+                while (cause != null && cause != previousCause && !recoverableCertException) {     // getCause() is not funny
+                    recoverableCertException = (  cause instanceof CertPathValidatorException ||
+                                                cause instanceof CertificateExpiredException ||
+                                                cause instanceof CertificateNotYetValidException );
+                    if (recoverableCertException)
+                        ret = (Exception)cause;
+                    previousCause = cause;
+                    cause = cause.getCause();
+                }
+            }
+        }
+        return ret;
+    }
+    
+    
+    /**
+     * Checks if a remote operation result can be recoverable
+     * by allowing the user to state the reliability of the certificate from the server. 
+     * 
+     * @param result        Result of a remote operation.
+     * @return              An exception instance that caused the failure of the remote operation and that can be avoided if the user
+     *                      states the certificate from the server as reliable; or NULL if the result is that's not possible
+     */
+    public static boolean isRecoverable(RemoteOperationResult result) {
+        return (getRecoverableException(result) != null);
+    }
+
+}

+ 7 - 0
src/com/owncloud/android/operations/OnRemoteOperationListener.java

@@ -0,0 +1,7 @@
+package com.owncloud.android.operations;
+
+public interface OnRemoteOperationListener {
+
+	void onRemoteOperationFinish(RemoteOperation caller, RemoteOperationResult result);
+
+}

+ 135 - 0
src/com/owncloud/android/operations/RemoteOperation.java

@@ -0,0 +1,135 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.operations;
+
+import android.os.Handler;
+
+import eu.alefzero.webdav.WebdavClient;
+
+/**
+ * Operation which execution involves one or several interactions with an ownCloud server.
+ * 
+ * Provides methods to execute the operation both synchronously or asynchronously.
+ * 
+ * @author David A. Velasco 
+ */
+public abstract class RemoteOperation implements Runnable {
+	
+	/** Object to interact with the ownCloud server */
+	private WebdavClient mClient = null;
+	
+	/** Callback object to notify about the execution of the remote operation */
+	private OnRemoteOperationListener mListener = null;
+	
+	/** Handler to the thread where mListener methods will be called */
+	private Handler mListenerHandler = null;
+
+	
+	/**
+	 *  Abstract method to implement the operation in derived classes.
+	 */
+	protected abstract RemoteOperationResult run(WebdavClient client); 
+	
+	
+	/**
+	 * Synchronously executes the remote operation
+	 * 
+	 * @param client	Client object to reach an ownCloud server during the execution of the operation.
+	 * @return			Result of the operation.
+	 */
+	public final RemoteOperationResult execute(WebdavClient client) {
+		if (client == null)
+			throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
+		mClient = client;
+		return run(client);
+	}
+
+	
+	/**
+	 * Asynchronously executes the remote operation
+	 * 
+	 * @param client			Client object to reach an ownCloud server during the execution of the operation.
+	 * @param listener			Listener to be notified about the execution of the operation.
+	 * @param listenerHandler	Handler associated to the thread where the methods of the listener objects must be called.
+	 * @return					Thread were the remote operation is executed.
+	 */
+	public final Thread execute(WebdavClient client, OnRemoteOperationListener listener, Handler listenerHandler) {
+		if (client == null) {
+			throw new IllegalArgumentException("Trying to execute a remote operation with a NULL WebdavClient");
+		}
+		mClient = client;
+		
+		if (listener == null) {
+			throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result");
+		}
+		mListener = listener;
+		
+		if (listenerHandler == null) {
+			throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread");
+		}
+		mListenerHandler = listenerHandler;
+		
+		Thread runnerThread = new Thread(this);
+		runnerThread.start();
+		return runnerThread;
+	}
+	
+    /**
+     * Synchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient)}
+     * 
+     * @param listener          Listener to be notified about the execution of the operation.
+     * @param listenerHandler   Handler associated to the thread where the methods of the listener objects must be called.
+     * @return                  Thread were the remote operation is executed.
+     */
+    public final RemoteOperationResult retry() {
+        return execute(mClient);
+    }
+    
+    /**
+     * Asynchronously retries the remote operation using the same WebdavClient in the last call to {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}
+     * 
+     * @param listener          Listener to be notified about the execution of the operation.
+     * @param listenerHandler   Handler associated to the thread where the methods of the listener objects must be called.
+     * @return                  Thread were the remote operation is executed.
+     */
+    public final Thread retry(OnRemoteOperationListener listener, Handler listenerHandler) {
+        return execute(mClient, listener, listenerHandler);
+    }
+	
+	
+	/**
+	 * Asynchronous execution of the operation 
+	 * started by {@link RemoteOperation#execute(WebdavClient, OnRemoteOperationListener, Handler)}, 
+	 * and result posting.
+	 */
+    @Override
+    public final void run() {
+    	final RemoteOperationResult result = execute(mClient);
+    	
+        if (mListenerHandler != null && mListener != null) {
+        	mListenerHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onRemoteOperationFinish(RemoteOperation.this, result);
+                }
+            });
+        }
+    }
+	
+	
+}

+ 121 - 0
src/com/owncloud/android/operations/RemoteOperationResult.java

@@ -0,0 +1,121 @@
+package com.owncloud.android.operations;
+
+import java.net.MalformedURLException;
+import java.net.SocketException;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+
+import javax.net.ssl.SSLException;
+
+import org.apache.commons.httpclient.ConnectTimeoutException;
+import org.apache.commons.httpclient.HttpStatus;
+
+
+public class RemoteOperationResult {
+    
+    public enum ResultCode {    // TODO leave alone our own errors
+        OK,
+        OK_SSL,
+        OK_NO_SSL,
+        UNHANDLED_HTTP_CODE,
+        FILE_NOT_FOUND, 
+        INSTANCE_NOT_CONFIGURED, 
+        UNKNOWN_ERROR, 
+        WRONG_CONNECTION,  
+        TIMEOUT, 
+        INCORRECT_ADDRESS, 
+        HOST_NOT_AVAILABLE, 
+        NO_NETWORK_CONNECTION, 
+        SSL_ERROR,
+        BAD_OC_VERSION, 
+    }
+    
+    private boolean mSuccess = false;
+    private int mHttpCode = -1;
+    private Exception mException = null;
+    private ResultCode mCode = ResultCode.UNKNOWN_ERROR;
+    
+    public RemoteOperationResult(ResultCode code) {
+        mCode = code;
+        mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL);
+    }
+    
+    public RemoteOperationResult(boolean success, int httpCode) {
+        mSuccess = success; 
+        mHttpCode = httpCode;
+
+        if (success) {
+            mCode = ResultCode.OK;
+            
+        } else if (httpCode > 0) {
+            switch (httpCode) {
+                case HttpStatus.SC_NOT_FOUND:
+                    mCode = ResultCode.FILE_NOT_FOUND;
+                    break;
+                case HttpStatus.SC_INTERNAL_SERVER_ERROR:
+                    mCode = ResultCode.INSTANCE_NOT_CONFIGURED;
+                    break;
+                default:
+                    mCode = ResultCode.UNHANDLED_HTTP_CODE;
+            }
+        }
+    }
+    
+    public RemoteOperationResult(Exception e) {
+        mException = e; 
+        
+        if (e instanceof SocketException) {  
+            mCode = ResultCode.WRONG_CONNECTION;
+            //Log.e(TAG, "Socket exception while trying connection", e);
+        
+        } else if (e instanceof SocketTimeoutException) {
+            mCode = ResultCode.TIMEOUT;
+            //Log.e(TAG, "Socket timeout exception while trying connection", e);
+        
+        } else if (e instanceof ConnectTimeoutException) {
+            mCode = ResultCode.TIMEOUT;
+            //Log.e(TAG, "Socket timeout exception while trying connection", e);
+            
+        } else if (e instanceof MalformedURLException) {
+            mCode = ResultCode.INCORRECT_ADDRESS;
+            //Log.e(TAG, "Connect exception while trying connection", e);
+        
+        } else if (e instanceof UnknownHostException) {
+            mCode = ResultCode.HOST_NOT_AVAILABLE;
+            //Log.e(TAG, "Unknown host exception while trying connection", e);
+        
+        } else if (e instanceof SSLException) {
+            mCode = ResultCode.SSL_ERROR;
+            //Log.e(TAG, "SSL exception while trying connection", e);
+            
+        } else {
+            mCode = ResultCode.UNKNOWN_ERROR;
+        }
+            
+        /*  }   catch (HttpException e) { // other specific exceptions from org.apache.commons.httpclient
+                Log.e(TAG, "HTTP exception while trying connection", e);
+            }   catch (IOException e) {   // UnkownsServiceException, and any other transport exceptions that could occur
+                Log.e(TAG, "I/O exception while trying connection", e);
+            }   catch (Exception e) {
+                Log.e(TAG, "Unexpected exception while trying connection", e);
+        */
+    }
+    
+    
+    public boolean isSuccess() {
+        return mSuccess;
+    }
+    
+    public int getHttpCode() {
+        return mHttpCode;
+    }
+    
+    public ResultCode getCode() {
+        return mCode;
+    }
+    
+    public Exception getException() {
+        return mException;
+    }
+
+}

+ 1 - 1
src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java

@@ -30,7 +30,7 @@ import org.apache.http.protocol.HttpContext;
 
 import com.owncloud.android.authenticator.AccountAuthenticator;
 import com.owncloud.android.datamodel.DataStorageManager;
-import com.owncloud.android.utils.OwnCloudClientUtils;
+import com.owncloud.android.network.OwnCloudClientUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;

+ 167 - 13
src/com/owncloud/android/ui/activity/AuthenticatorActivity.java

@@ -20,21 +20,24 @@ package com.owncloud.android.ui.activity;
 
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.net.URLEncoder;
 
 import com.owncloud.android.AccountUtils;
 import com.owncloud.android.authenticator.AccountAuthenticator;
 import com.owncloud.android.authenticator.AuthenticationRunnable;
-import com.owncloud.android.authenticator.ConnectionCheckerRunnable;
+import com.owncloud.android.authenticator.ConnectionCheckOperation;
 import com.owncloud.android.authenticator.OnAuthenticationResultListener;
 import com.owncloud.android.authenticator.OnConnectCheckListener;
-import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
-import com.owncloud.android.extensions.ExtensionsAvailableActivity;
-import com.owncloud.android.utils.OwnCloudVersion;
+import com.owncloud.android.ui.dialog.SslValidatorDialog;
+import com.owncloud.android.ui.dialog.SslValidatorDialog.OnSslValidatorListener;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.operations.OnRemoteOperationListener;
+import com.owncloud.android.operations.RemoteOperation;
+import com.owncloud.android.operations.RemoteOperationResult;
 
 import android.accounts.Account;
 import android.accounts.AccountAuthenticatorActivity;
 import android.accounts.AccountManager;
+import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
 import android.content.ContentResolver;
@@ -55,6 +58,8 @@ import android.widget.ImageView;
 import android.widget.TextView;
 import com.owncloud.android.R;
 
+import eu.alefzero.webdav.WebdavClient;
+
 /**
  * This Activity is used to add an ownCloud account to the App
  * 
@@ -62,24 +67,29 @@ import com.owncloud.android.R;
  * 
  */
 public class AuthenticatorActivity extends AccountAuthenticatorActivity
-        implements OnAuthenticationResultListener, OnConnectCheckListener,
+        implements OnAuthenticationResultListener, OnConnectCheckListener, OnRemoteOperationListener, OnSslValidatorListener, 
         OnFocusChangeListener, OnClickListener {
+
     private static final int DIALOG_LOGIN_PROGRESS = 0;
+    private static final int DIALOG_SSL_VALIDATOR = 1;
+    private static final int DIALOG_CERT_NOT_SAVED = 2;
 
     private static final String TAG = "AuthActivity";
 
     private Thread mAuthThread;
     private AuthenticationRunnable mAuthRunnable;
-    private ConnectionCheckerRunnable mConnChkRunnable;
+    //private ConnectionCheckerRunnable mConnChkRunnable = null;
+    private ConnectionCheckOperation mConnChkRunnable;
     private final Handler mHandler = new Handler();
     private String mBaseUrl;
-
+    
     private static final String STATUS_TEXT = "STATUS_TEXT";
     private static final String STATUS_ICON = "STATUS_ICON";
     private static final String STATUS_CORRECT = "STATUS_CORRECT";
     private static final String IS_SSL_CONN = "IS_SSL_CONN";
     private int mStatusText, mStatusIcon;
     private boolean mStatusCorrect, mIsSslConn;
+    private RemoteOperationResult mLastSslFailedResult;
 
     public static final String PARAM_USERNAME = "param_Username";
     public static final String PARAM_HOSTNAME = "param_Hostname";
@@ -149,12 +159,47 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             dialog = working_dialog;
             break;
         }
+        case DIALOG_SSL_VALIDATOR: {
+            SslValidatorDialog sslValidator = SslValidatorDialog.newInstance(this, mLastSslFailedResult, this);
+            if (sslValidator != null)
+                dialog = sslValidator;
+            // else, mLastSslFailedResult is not an SSL fail recoverable by accepting the server certificate as reliable; dialog will still be null
+            break;
+        }
+        case DIALOG_CERT_NOT_SAVED: {
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+            builder.setMessage(getResources().getString(R.string.ssl_validator_not_saved));
+            builder.setCancelable(false);
+            builder.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        dialog.dismiss();
+                    };
+                });
+            dialog = builder.create();
+            break;
+        }
         default:
             Log.e(TAG, "Incorrect dialog called with id = " + id);
         }
         return dialog;
     }
 
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+        switch (id) {
+        case DIALOG_LOGIN_PROGRESS:
+        case DIALOG_CERT_NOT_SAVED:
+            break;
+        case DIALOG_SSL_VALIDATOR: {
+            ((SslValidatorDialog)dialog).updateResult(mLastSslFailedResult);
+            break;
+        }
+        default:
+            Log.e(TAG, "Incorrect dialog called with id = " + id);
+        }
+    }
+
     public void onAuthenticationResult(boolean success, String message) {
         if (success) {
             TextView username_text = (TextView) findViewById(R.id.account_username), password_text = (TextView) findViewById(R.id.account_password);
@@ -203,6 +248,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             accManager.setUserData(account,
                     AccountAuthenticator.KEY_OC_VERSION, mConnChkRunnable
                             .getDiscoveredVersion().toString());
+            
             accManager.setUserData(account,
                     AccountAuthenticator.KEY_OC_BASE_URL, mBaseUrl);
 
@@ -272,6 +318,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         URL uri = null;
         String webdav_path = AccountUtils.getWebdavPath(mConnChkRunnable
                 .getDiscoveredVersion());
+        
         if (webdav_path == null) {
             onAuthenticationResult(false, getString(R.string.auth_bad_oc_version_title));
             return;
@@ -288,7 +335,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         }
 
         showDialog(DIALOG_LOGIN_PROGRESS);
-        mAuthRunnable = new AuthenticationRunnable(uri, username, password);
+        mAuthRunnable = new AuthenticationRunnable(uri, username, password, this);
         mAuthRunnable.setOnAuthenticationResultListener(this, mHandler);
         mAuthThread = new Thread(mAuthRunnable);
         mAuthThread.start();
@@ -383,10 +430,13 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
                 if (uri.length() != 0) {
                     setResultIconAndText(R.drawable.progress_small,
                             R.string.auth_testing_connection);
-                    mConnChkRunnable = new ConnectionCheckerRunnable(uri, this);
-                    mConnChkRunnable.setListener(this, mHandler);
-                    mAuthThread = new Thread(mConnChkRunnable);
-                    mAuthThread.start();
+                    //mConnChkRunnable = new ConnectionCheckerRunnable(uri, this);
+                    mConnChkRunnable = new ConnectionCheckOperation(uri, this);
+                    //mConnChkRunnable.setListener(this, mHandler);
+                    //mAuthThread = new Thread(mConnChkRunnable);
+                    //mAuthThread.start();
+            		WebdavClient client = OwnCloudClientUtils.createOwnCloudClient(Uri.parse(uri), this);
+                    mAuthThread = mConnChkRunnable.execute(client, this, mHandler);
                 } else {
                     findViewById(R.id.refreshButton).setVisibility(
                             View.INVISIBLE);
@@ -436,4 +486,108 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             view.setInputType(input_type);
         }
     }
+
+	@Override
+	public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
+		if (operation.equals(mConnChkRunnable)) {
+		    
+	        mStatusText = mStatusIcon = 0;
+	        mStatusCorrect = false;
+	        String t_url = ((TextView) findViewById(R.id.host_URL)).getText()
+	                .toString().trim().toLowerCase();
+	        
+	        switch (result.getCode()) {
+	        case OK_SSL:
+	            mIsSslConn = true;
+	            mStatusIcon = android.R.drawable.ic_secure;
+	            mStatusText = R.string.auth_secure_connection;
+	            mStatusCorrect = true;
+	            break;
+	            
+	        case OK_NO_SSL:
+	        case OK:
+	            mIsSslConn = false;
+	            mStatusCorrect = true;
+	            if (t_url.startsWith("http://") ) {
+	                mStatusText = R.string.auth_connection_established;
+	                mStatusIcon = R.drawable.ic_ok;
+	            } else {
+	                mStatusText = R.string.auth_nossl_plain_ok_title;
+	                mStatusIcon = android.R.drawable.ic_partial_secure;
+	            }
+	            break;
+	        
+	            
+	        case BAD_OC_VERSION:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_bad_oc_version_title;
+	            break;
+	        case WRONG_CONNECTION:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_wrong_connection_title;
+	            break;
+	        case TIMEOUT:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_timeout_title;
+	            break;
+	        case INCORRECT_ADDRESS:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_incorrect_address_title;
+	            break;
+	            
+	        case SSL_ERROR:
+                mStatusIcon = R.drawable.common_error;
+                mStatusText = R.string.auth_ssl_general_error_title;
+                //mStatusText = R.string.auth_ssl_unverified_server_title;
+                mLastSslFailedResult = result;
+                showDialog(DIALOG_SSL_VALIDATOR);   // see onCreateDialog(); it does not always show the dialog
+	            /*if (InteractiveSslValidatorActivity.isRecoverable(result)) {
+	                Intent intent = new Intent(this, InteractiveSslValidatorActivity.class);
+	                startActivityForResult(intent, REQUEST_FOR_SSL_CERT);
+	            }*/
+	            break;
+	            
+	        case HOST_NOT_AVAILABLE:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_unknown_host_title;
+	            break;
+	        case NO_NETWORK_CONNECTION:
+	            mStatusIcon = R.drawable.no_network;
+	            mStatusText = R.string.auth_no_net_conn_title;
+	            break;
+	        case INSTANCE_NOT_CONFIGURED:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_not_configured_title;
+	            break;
+	        case FILE_NOT_FOUND:
+	            mStatusIcon = R.drawable.common_error;
+	            mStatusText = R.string.auth_incorrect_path_title;
+	            break;
+            case UNHANDLED_HTTP_CODE:
+            case UNKNOWN_ERROR:
+                mStatusIcon = R.drawable.common_error;
+                mStatusText = R.string.auth_unknown_error_title;
+                break;
+	        default:
+	            Log.e(TAG, "Incorrect connection checker result type: " + operation);
+	        }
+	        setResultIconAndText(mStatusIcon, mStatusText);
+	        if (!mStatusCorrect)
+	            findViewById(R.id.refreshButton).setVisibility(View.VISIBLE);
+	        else
+	            findViewById(R.id.refreshButton).setVisibility(View.INVISIBLE);
+	        findViewById(R.id.buttonOK).setEnabled(mStatusCorrect);
+		}
+	}
+
+	
+    public void onSavedCertificate() {
+        mAuthThread = mConnChkRunnable.retry(this, mHandler);                
+    }
+
+    @Override
+    public void onFailedSavingCertificate() {
+        showDialog(DIALOG_CERT_NOT_SAVED);
+    }
+    
 }

+ 1 - 1
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -71,10 +71,10 @@ import com.owncloud.android.files.OwnCloudFileObserver;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileObserverService;
 import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.network.OwnCloudClientUtils;
 import com.owncloud.android.syncadapter.FileSyncService;
 import com.owncloud.android.ui.fragment.FileDetailFragment;
 import com.owncloud.android.ui.fragment.OCFileListFragment;
-import com.owncloud.android.utils.OwnCloudClientUtils;
 
 import com.owncloud.android.R;
 import eu.alefzero.webdav.WebdavClient;

+ 193 - 0
src/com/owncloud/android/ui/dialog/SslValidatorDialog.java

@@ -0,0 +1,193 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2011  Bartek Przybylski
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package com.owncloud.android.ui.dialog;
+
+import java.io.IOException;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertPath;
+import java.security.cert.CertPathValidatorException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.net.ssl.SSLPeerUnverifiedException;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.Window;
+import android.widget.TextView;
+
+import com.owncloud.android.R;
+import com.owncloud.android.network.OwnCloudClientUtils;
+import com.owncloud.android.network.SslAnalyzer;
+import com.owncloud.android.operations.RemoteOperationResult;
+
+/**
+ * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
+ * 
+ * @author David A. Velasco
+ */
+public class SslValidatorDialog extends Dialog {
+
+    private final static String TAG = SslValidatorDialog.class.getSimpleName();
+
+    private OnSslValidatorListener mListener;
+    private Exception mException = null;
+    private View mView;
+    
+    
+    /**
+     * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
+     * be trusted.
+     * 
+     * @param context       Android context where the dialog will live.
+     * @param result        Result of a failed remote operation.
+     * @param listener      Object to notice when the server certificate was added to the local certificates store.
+     * @return              A new SslValidatorDialog instance, or NULL if the operation can not be recovered
+     *                      by setting the certificate as reliable.
+     */
+    public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) {
+        Exception e = SslAnalyzer.getRecoverableException(result);
+        if (e != null) {
+            SslValidatorDialog dialog = new SslValidatorDialog(context, listener);
+            return dialog;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Private constructor. 
+     * 
+     * Instances have to be created through static {@link SslValidatorDialog#newInstance}.
+     * 
+     * @param context       Android context where the dialog will live
+     * @param e             Exception causing the need of prompt the user about the server certificate.
+     * @param listener      Object to notice when the server certificate was added to the local certificates store.
+     */
+    private SslValidatorDialog(Context context, OnSslValidatorListener listener) {
+        super(context);
+        mListener = listener;
+    }
+    
+    
+    /**
+     * {@inheritDoc}
+     */
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        mView = getLayoutInflater().inflate(R.layout.ssl_validator_layout, null);
+        setContentView(mView); 
+        //setTitle(R.string.ssl_validator_title);
+        
+        mView.findViewById(R.id.ok).setOnClickListener( 
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        try {
+                            saveServerCert();
+                            dismiss();
+                            if (mListener != null)
+                                mListener.onSavedCertificate();
+                            else
+                                Log.d(TAG, "Nobody there to notify the certificate was saved");
+                            
+                        } catch (Exception e) {
+                            dismiss();
+                            if (mListener != null)
+                                mListener.onFailedSavingCertificate();
+                            Log.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
+                        }
+                    }
+                });
+        
+        mView.findViewById(R.id.cancel).setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        cancel();
+                    }
+                });
+    }
+    
+    
+    public void updateResult(RemoteOperationResult result) {
+        mException = SslAnalyzer.getRecoverableException(result);
+        if (mException instanceof CertPathValidatorException ) {
+            showCertificateData(((CertPathValidatorException)mException).getCertPath());
+            ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE);
+            ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_hostname_not_vertified)).setVisibility(View.GONE);
+            
+        } else if (mException instanceof CertificateExpiredException ) {
+            ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE);
+            ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_hostname_not_vertified)).setVisibility(View.GONE);
+            
+        } else if (mException instanceof CertificateNotYetValidException ) {
+            ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE);
+            ((TextView)mView.findViewById(R.id.reason_hostname_not_vertified)).setVisibility(View.GONE);
+            
+        } else if (mException instanceof SSLPeerUnverifiedException ) {
+            ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.GONE);
+            ((TextView)mView.findViewById(R.id.reason_hostname_not_vertified)).setVisibility(View.VISIBLE);
+        }
+        
+    }
+    
+    private void showCertificateData(CertPath certPath) {
+        final List<? extends Certificate> certs = certPath.getCertificates();
+        /*X509Certificate badCert = null;
+        if (e.getIndex() >= 0 && e.getIndex() < certs.size()) 
+            badCert = (X509Certificate) certs.get(e.getIndex());*/
+        if (certs.size() > 0) {
+            X509Certificate serverCert = (X509Certificate) certs.get(0);
+            String text = serverCert.getSubjectDN().getName();
+            text = text.substring(text.indexOf(",") + 1);
+            ((TextView)mView.findViewById(R.id.issuer)).setText(text);
+        }
+    }
+
+    private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
+        // TODO be able to add certificate for any recoverable exception
+        if (mException instanceof CertPathValidatorException) {
+            OwnCloudClientUtils.addCertToKnownServersStore(((CertPathValidatorException) mException).getCertPath().getCertificates().get(0), getContext());
+        }
+    }
+
+    
+    public interface OnSslValidatorListener {
+        public void onSavedCertificate();
+        public void onFailedSavingCertificate();
+    }
+}
+

+ 1 - 1
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -81,9 +81,9 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileDownloader;
 import com.owncloud.android.files.services.FileObserverService;
 import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.network.OwnCloudClientUtils;
 import com.owncloud.android.ui.activity.FileDetailActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
-import com.owncloud.android.utils.OwnCloudClientUtils;
 import com.owncloud.android.utils.OwnCloudVersion;
 
 import com.owncloud.android.R;

+ 0 - 147
src/com/owncloud/android/utils/OwnCloudClientUtils.java

@@ -1,147 +0,0 @@
-/* ownCloud Android client application
- *   Copyright (C) 2011  Bartek Przybylski
- *
- *   This program is free software: you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation, either version 3 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
- *
- */
-package com.owncloud.android.utils;
-
-import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
-import org.apache.commons.httpclient.protocol.Protocol;
-
-import com.owncloud.android.AccountUtils;
-import com.owncloud.android.authenticator.AccountAuthenticator;
-import com.owncloud.android.authenticator.EasySSLSocketFactory;
-import com.owncloud.android.utils.OwnCloudVersion;
-
-import eu.alefzero.webdav.WebdavClient;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.Context;
-import android.net.Uri;
-import android.util.Log;
-
-public class OwnCloudClientUtils {
-    
-    final private static String TAG = "OwnCloudClientFactory";
-    
-    /** Default timeout for waiting data from the server */
-    public static final int DEFAULT_DATA_TIMEOUT = 60000;
-    
-    /** Default timeout for establishing a connection */
-    public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
-
-    /** Connection manager for all the WebdavClients */
-    static private MultiThreadedHttpConnectionManager mConnManager = null;
-    
-    
-    /**
-     * Creates a WebdavClient setup for an ownCloud account
-     * 
-     * @param account   The ownCloud account
-     * @param context   The application context
-     * @return          A WebdavClient object ready to be used
-     */
-    public static WebdavClient createOwnCloudClient (Account account, Context context) {
-        Log.d(TAG, "Creating WebdavClient associated to " + account.name);
-       
-        String baseUrl = AccountManager.get(context).getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL);
-        OwnCloudVersion ownCloudVersion = new OwnCloudVersion(AccountManager.get(context).getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
-        String webDavPath = AccountUtils.getWebdavPath(ownCloudVersion);
-        
-        WebdavClient client = createOwnCloudClient(Uri.parse(baseUrl + webDavPath));
-        
-        String username = account.name.substring(0, account.name.lastIndexOf('@'));
-        String password = AccountManager.get(context).getPassword(account);
-        //String password = am.blockingGetAuthToken(mAccount, AccountAuthenticator.AUTH_TOKEN_TYPE, true);
-        
-        client.setCredentials(username, password);
-        
-        return client;
-    }
-    
-    
-    /**
-     * Creates a WebdavClient to try a new account before saving it
-     * 
-     * @param uri       URL to the ownCloud server
-     * @param username  User name
-     * @param password  User password
-     * @return          A WebdavClient object ready to be used
-     */
-    public static WebdavClient createOwnCloudClient(Uri uri, String username, String password) {
-        Log.d(TAG, "Creating WebdavClient for " + username + "@" + uri);
-        
-        WebdavClient client = createOwnCloudClient(uri);
-        
-        client.setCredentials(username, password);
-        
-        return client;
-    }
-    
-    
-    /**
-     * Creates a WebdavClient to access a URL and sets the desired parameters for ownCloud client connections.
-     * 
-     * @param uri       URL to the ownCloud server
-     * @return          A WebdavClient object ready to be used
-     */
-    public static WebdavClient createOwnCloudClient(Uri uri) {
-        Log.d(TAG, "Creating WebdavClient for " + uri);
-        
-        allowSelfsignedCertificates(true);
-        
-        WebdavClient client = new WebdavClient(getMultiThreadedConnManager());
-        
-        allowSelfsignedCertificates(true);
-        client.setDefaultTimeouts(DEFAULT_DATA_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT);
-        client.setBaseUri(uri);
-        
-        return client;
-    }
-    
-    
-    /**
-     * Allows or disallows self-signed certificates in ownCloud servers to reach
-     * 
-     * @param allow     'True' to allow, 'false' to disallow
-     */
-    public static void allowSelfsignedCertificates(boolean allow) {
-        Protocol pr = null;
-        try {
-            pr = Protocol.getProtocol("https");
-        } catch (IllegalStateException e) {
-            // nothing to do here; really
-        }
-        boolean isAllowed = (pr != null && pr.getSocketFactory() instanceof EasySSLSocketFactory);
-        if (allow && !isAllowed) {
-            Protocol.registerProtocol("https", new Protocol("https", new EasySSLSocketFactory(), 443));
-        } else if (!allow && isAllowed) {
-            // TODO - a more strict SocketFactory object should be provided here
-        }
-    }
-
-    
-    
-    static private MultiThreadedHttpConnectionManager getMultiThreadedConnManager() {
-        if (mConnManager == null) {
-            mConnManager = new MultiThreadedHttpConnectionManager();
-            mConnManager.getParams().setDefaultMaxConnectionsPerHost(5);
-            mConnManager.getParams().setMaxTotalConnections(5);
-        }
-        return mConnManager;
-    }
-
-}