Преглед на файлове

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

David A. Velasco преди 12 години
родител
ревизия
48f13c8adc
променени са 26 файла, в които са добавени 1560 реда и са изтрити 177 реда
  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;
-    }
-
-}