mirror of
				https://github.com/owncloud/android-library.git
				synced 2025-11-03 20:08:00 +00:00 
			
		
		
		
	Merge pull request #159 from owncloud/retry_transfers_after_lost_network
Improve support for unexpected loss of network connection.
This commit is contained in:
		
						commit
						0dce40b160
					
				
							
								
								
									
										8
									
								
								ant.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								ant.properties
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
# This file contains custom properties used by the Ant build system.
 | 
			
		||||
#
 | 
			
		||||
# This file must be checked in Version Control Systems.
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
# Java version options
 | 
			
		||||
java.source=1.7
 | 
			
		||||
java.target=1.7
 | 
			
		||||
@ -50,18 +50,17 @@ import org.apache.http.conn.ssl.X509HostnameVerifier;
 | 
			
		||||
import com.owncloud.android.lib.common.utils.Log_OC;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with 
 | 
			
		||||
 * 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 SecureProtocolSocketFactory {
 | 
			
		||||
 | 
			
		||||
    private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName();
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    private SSLContext mSslContext = null;
 | 
			
		||||
    private AdvancedX509TrustManager mTrustManager = null;
 | 
			
		||||
    private X509HostnameVerifier mHostnameVerifier = null;
 | 
			
		||||
@ -69,33 +68,33 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
    public SSLContext getSslContext() {
 | 
			
		||||
        return mSslContext;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor for AdvancedSSLProtocolSocketFactory.
 | 
			
		||||
     */
 | 
			
		||||
    public AdvancedSslSocketFactory(
 | 
			
		||||
    		SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier
 | 
			
		||||
		) {
 | 
			
		||||
    	
 | 
			
		||||
        SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier
 | 
			
		||||
    ) {
 | 
			
		||||
 | 
			
		||||
        if (sslContext == null)
 | 
			
		||||
            throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext");
 | 
			
		||||
        if (trustManager == null && mHostnameVerifier != null)
 | 
			
		||||
            throw new IllegalArgumentException(
 | 
			
		||||
            		"AdvancedSslSocketFactory can not be created with a null Trust Manager and a " +
 | 
			
		||||
            		"not null Hostname Verifier"
 | 
			
		||||
    		);
 | 
			
		||||
                "AdvancedSslSocketFactory can not be created with a null Trust Manager and a " +
 | 
			
		||||
                    "not null Hostname Verifier"
 | 
			
		||||
            );
 | 
			
		||||
        mSslContext = sslContext;
 | 
			
		||||
        mTrustManager = trustManager;
 | 
			
		||||
        mHostnameVerifier = hostnameVerifier;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
 | 
			
		||||
     * @see ProtocolSocketFactory#createSocket(java.lang.String, int, java.net.InetAddress, int)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) 
 | 
			
		||||
    		throws IOException, UnknownHostException {
 | 
			
		||||
    	
 | 
			
		||||
    public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort)
 | 
			
		||||
        throws IOException, UnknownHostException {
 | 
			
		||||
 | 
			
		||||
        Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort);
 | 
			
		||||
        enableSecureProtocols(socket);
 | 
			
		||||
        verifyPeerIdentity(host, port, socket);
 | 
			
		||||
@ -142,36 +141,34 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
	}
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
    /**
 | 
			
		||||
     * 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}
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * @param host         the host name/IP
 | 
			
		||||
     * @param port         the port on the host
 | 
			
		||||
     * @param localAddress the local host name/IP to bind the socket to
 | 
			
		||||
     * @param localPort    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 IOException          if an I/O error occurs while creating the socket
 | 
			
		||||
     * @throws UnknownHostException if the IP address of the host cannot be
 | 
			
		||||
     *             determined
 | 
			
		||||
     *                              determined
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Socket createSocket(final String host, final int port,
 | 
			
		||||
            final InetAddress localAddress, final int localPort,
 | 
			
		||||
            final HttpConnectionParams params) throws IOException,
 | 
			
		||||
            UnknownHostException, ConnectTimeoutException {
 | 
			
		||||
        Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + 
 | 
			
		||||
                               final InetAddress localAddress, final int localPort,
 | 
			
		||||
                               final HttpConnectionParams params) throws IOException,
 | 
			
		||||
        UnknownHostException, ConnectTimeoutException {
 | 
			
		||||
        Log_OC.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();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        //logSslInfo();
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        SocketFactory socketfactory = mSslContext.getSocketFactory();
 | 
			
		||||
        Log_OC.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
 | 
			
		||||
        Socket socket = socketfactory.createSocket();
 | 
			
		||||
@ -179,40 +176,41 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
        SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
 | 
			
		||||
        SocketAddress remoteaddr = new InetSocketAddress(host, port);
 | 
			
		||||
        socket.setSoTimeout(params.getSoTimeout());
 | 
			
		||||
        WriteTimeoutEnforcer.setSoWriteTimeout(params.getSoTimeout(), socket);
 | 
			
		||||
        socket.bind(localaddr);
 | 
			
		||||
        ServerNameIndicator.setServerNameIndication(host, (SSLSocket)socket);
 | 
			
		||||
        ServerNameIndicator.setServerNameIndication(host, (SSLSocket) socket);
 | 
			
		||||
        socket.connect(remoteaddr, timeout);
 | 
			
		||||
        verifyPeerIdentity(host, port, socket);
 | 
			
		||||
        return socket;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
     * @see ProtocolSocketFactory#createSocket(java.lang.String,int)
 | 
			
		||||
    /**
 | 
			
		||||
     * @see ProtocolSocketFactory#createSocket(java.lang.String, int)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Socket createSocket(String host, int port) throws IOException,
 | 
			
		||||
            UnknownHostException {
 | 
			
		||||
    	Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
 | 
			
		||||
        UnknownHostException {
 | 
			
		||||
        Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
 | 
			
		||||
        Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
 | 
			
		||||
        enableSecureProtocols(socket);
 | 
			
		||||
        verifyPeerIdentity(host, port, socket);
 | 
			
		||||
        return socket; 
 | 
			
		||||
        return socket;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,
 | 
			
		||||
        UnknownHostException {
 | 
			
		||||
        Socket sslSocket = mSslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
 | 
			
		||||
        enableSecureProtocols(sslSocket);
 | 
			
		||||
        verifyPeerIdentity(host, port, sslSocket);
 | 
			
		||||
        return sslSocket;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
	@Override
 | 
			
		||||
    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException,
 | 
			
		||||
    		UnknownHostException {
 | 
			
		||||
	    Socket sslSocket = mSslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
 | 
			
		||||
	    enableSecureProtocols(sslSocket);
 | 
			
		||||
	    verifyPeerIdentity(host, port, sslSocket);
 | 
			
		||||
	    return sslSocket;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        return ((obj != null) && obj.getClass().equals(
 | 
			
		||||
                AdvancedSslSocketFactory.class));
 | 
			
		||||
            AdvancedSslSocketFactory.class));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
@ -223,19 +221,20 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
    public X509HostnameVerifier getHostNameVerifier() {
 | 
			
		||||
        return mHostnameVerifier;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) {
 | 
			
		||||
        mHostnameVerifier = hostnameVerifier;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Verifies the identity of the server. 
 | 
			
		||||
     * 
 | 
			
		||||
     * Verifies the identity of the server.
 | 
			
		||||
     *
 | 
			
		||||
     * The server certificate is verified first.
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     * Then, the host name is compared with the content of the server certificate using the current host name verifier,
 | 
			
		||||
     *  if any.
 | 
			
		||||
     * if any.
 | 
			
		||||
     *
 | 
			
		||||
     * @param socket
 | 
			
		||||
     */
 | 
			
		||||
    private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException {
 | 
			
		||||
@ -246,31 +245,31 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
            try {
 | 
			
		||||
                SSLSocket sock = (SSLSocket) socket;    // a new SSLSession instance is created as a "side effect" 
 | 
			
		||||
                sock.startHandshake();
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
            } catch (RuntimeException e) {
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                if (e instanceof CertificateCombinedException) {
 | 
			
		||||
                    failInHandshake = (CertificateCombinedException) e;
 | 
			
		||||
                } else {
 | 
			
		||||
                    Throwable cause = e.getCause();
 | 
			
		||||
                    Throwable previousCause = null;
 | 
			
		||||
                    while (	cause != null && 
 | 
			
		||||
                    		cause != previousCause && 
 | 
			
		||||
                    		!(cause instanceof CertificateCombinedException)) {
 | 
			
		||||
                    while (cause != null &&
 | 
			
		||||
                        cause != previousCause &&
 | 
			
		||||
                        !(cause instanceof CertificateCombinedException)) {
 | 
			
		||||
                        previousCause = cause;
 | 
			
		||||
                        cause = cause.getCause();
 | 
			
		||||
                    }
 | 
			
		||||
                    if (cause != null && cause instanceof CertificateCombinedException) {
 | 
			
		||||
                        failInHandshake = (CertificateCombinedException)cause;
 | 
			
		||||
                        failInHandshake = (CertificateCombinedException) cause;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if (failInHandshake == null) {
 | 
			
		||||
                    throw e;
 | 
			
		||||
                }
 | 
			
		||||
                failInHandshake.setHostInUrl(host);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            /// 2. VERIFY HOSTNAME
 | 
			
		||||
            SSLSession newSession = null;
 | 
			
		||||
            boolean verifiedHostname = true;
 | 
			
		||||
@ -283,12 +282,12 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
                    } catch (SSLException e) {
 | 
			
		||||
                        verifiedHostname = false;
 | 
			
		||||
                    }
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                } else {
 | 
			
		||||
                    /// 2.2 : a new SSLSession instance was created in the handshake
 | 
			
		||||
                    newSession = ((SSLSocket)socket).getSession();
 | 
			
		||||
                    if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) {
 | 
			
		||||
                        verifiedHostname = mHostnameVerifier.verify(host, newSession); 
 | 
			
		||||
                    newSession = ((SSLSocket) socket).getSession();
 | 
			
		||||
                    if (!mTrustManager.isKnownServer((X509Certificate) (newSession.getPeerCertificates()[0]))) {
 | 
			
		||||
                        verifiedHostname = mHostnameVerifier.verify(host, newSession);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@ -296,25 +295,25 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
            /// 3. Combine the exceptions to throw, if any
 | 
			
		||||
            if (!verifiedHostname) {
 | 
			
		||||
                SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException(
 | 
			
		||||
                		"Names in the server certificate do not match to " + host + " in the URL"
 | 
			
		||||
            		);
 | 
			
		||||
                    "Names in the server certificate do not match to " + host + " in the URL"
 | 
			
		||||
                );
 | 
			
		||||
                if (failInHandshake == null) {
 | 
			
		||||
                    failInHandshake = new CertificateCombinedException(
 | 
			
		||||
                    		(X509Certificate) newSession.getPeerCertificates()[0]
 | 
			
		||||
    				);
 | 
			
		||||
                        (X509Certificate) newSession.getPeerCertificates()[0]
 | 
			
		||||
                    );
 | 
			
		||||
                    failInHandshake.setHostInUrl(host);
 | 
			
		||||
                }
 | 
			
		||||
                failInHandshake.setSslPeerUnverifiedException(pue);
 | 
			
		||||
                pue.initCause(failInHandshake);
 | 
			
		||||
                throw pue;
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
            } else if (failInHandshake != null) {
 | 
			
		||||
                SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified");
 | 
			
		||||
                hse.initCause(failInHandshake);
 | 
			
		||||
                throw hse;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
        } catch (IOException io) {        
 | 
			
		||||
 | 
			
		||||
        } catch (IOException io) {
 | 
			
		||||
            try {
 | 
			
		||||
                socket.close();
 | 
			
		||||
            } catch (Exception x) {
 | 
			
		||||
@ -324,22 +323,22 @@ public class AdvancedSslSocketFactory implements SecureProtocolSocketFactory {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Grants that all protocols supported by the Security Provider in mSslContext are enabled in socket.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * Grants also that no unsupported protocol is tried to be enabled. That would trigger an exception, breaking
 | 
			
		||||
	 * the connection process although some protocols are supported.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * This is not cosmetic: not all the supported protocols are enabled by default. Too see an overview of 
 | 
			
		||||
	 * supported and enabled protocols in the stock Security Provider in Android see the tables in
 | 
			
		||||
	 * http://developer.android.com/reference/javax/net/ssl/SSLSocket.html.
 | 
			
		||||
	 *  
 | 
			
		||||
	 * @param socket
 | 
			
		||||
	 */
 | 
			
		||||
    /**
 | 
			
		||||
     * Grants that all protocols supported by the Security Provider in mSslContext are enabled in socket.
 | 
			
		||||
     *
 | 
			
		||||
     * Grants also that no unsupported protocol is tried to be enabled. That would trigger an exception, breaking
 | 
			
		||||
     * the connection process although some protocols are supported.
 | 
			
		||||
     *
 | 
			
		||||
     * This is not cosmetic: not all the supported protocols are enabled by default. Too see an overview of
 | 
			
		||||
     * supported and enabled protocols in the stock Security Provider in Android see the tables in
 | 
			
		||||
     * http://developer.android.com/reference/javax/net/ssl/SSLSocket.html.
 | 
			
		||||
     *
 | 
			
		||||
     * @param socket
 | 
			
		||||
     */
 | 
			
		||||
    private void enableSecureProtocols(Socket socket) {
 | 
			
		||||
    	SSLParameters params = mSslContext.getSupportedSSLParameters();
 | 
			
		||||
    	String [] supportedProtocols = params.getProtocols();
 | 
			
		||||
    	((SSLSocket) socket).setEnabledProtocols(supportedProtocols);
 | 
			
		||||
        SSLParameters params = mSslContext.getSupportedSSLParameters();
 | 
			
		||||
        String[] supportedProtocols = params.getProtocols();
 | 
			
		||||
        ((SSLSocket) socket).setEnabledProtocols(supportedProtocols);
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,173 @@
 | 
			
		||||
/* ownCloud Android Library is available under MIT license
 | 
			
		||||
 *   Copyright (C) 2017 ownCloud GmbH.
 | 
			
		||||
 *   
 | 
			
		||||
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
 *   of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 *   in the Software without restriction, including without limitation the rights
 | 
			
		||||
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
			
		||||
 *   copies of the Software, and to permit persons to whom the Software is
 | 
			
		||||
 *   furnished to do so, subject to the following conditions:
 | 
			
		||||
 *   
 | 
			
		||||
 *   The above copyright notice and this permission notice shall be included in
 | 
			
		||||
 *   all copies or substantial portions of the Software.
 | 
			
		||||
 *   
 | 
			
		||||
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 | 
			
		||||
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | 
			
		||||
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 | 
			
		||||
 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
 | 
			
		||||
 *   BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
 | 
			
		||||
 *   ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 | 
			
		||||
 *   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
			
		||||
 *   THE SOFTWARE.
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.owncloud.android.lib.common.network;
 | 
			
		||||
 | 
			
		||||
import com.owncloud.android.lib.common.utils.Log_OC;
 | 
			
		||||
 | 
			
		||||
import java.lang.ref.WeakReference;
 | 
			
		||||
import java.lang.reflect.InvocationTargetException;
 | 
			
		||||
import java.lang.reflect.Method;
 | 
			
		||||
import java.net.Socket;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicReference;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Enforces, if possible, a write timeout for a socket.
 | 
			
		||||
 *
 | 
			
		||||
 * Built as a singleton.
 | 
			
		||||
 *
 | 
			
		||||
 * Tries to hit something like this:
 | 
			
		||||
 * https://android.googlesource.com/platform/external/conscrypt/+/lollipop-release/src/main/java/org/conscrypt/OpenSSLSocketImpl.java#1005
 | 
			
		||||
 *
 | 
			
		||||
 * Minimizes the chances of getting stalled in PUT/POST request if the network interface is lost while
 | 
			
		||||
 * writing the entity into the outwards sockect.
 | 
			
		||||
 *
 | 
			
		||||
 * It happens. See https://github.com/owncloud/android/issues/1684#issuecomment-295306015
 | 
			
		||||
 *
 | 
			
		||||
 * @author David A. Velasco
 | 
			
		||||
 */
 | 
			
		||||
public class WriteTimeoutEnforcer {
 | 
			
		||||
 | 
			
		||||
    private static final String TAG = WriteTimeoutEnforcer.class.getSimpleName();
 | 
			
		||||
 | 
			
		||||
    private static final AtomicReference<WriteTimeoutEnforcer> mSingleInstance = new AtomicReference<>();
 | 
			
		||||
 | 
			
		||||
    private static final String METHOD_NAME = "setSoWriteTimeout";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final WeakReference<Class<?>> mSocketClassRef;
 | 
			
		||||
    private final WeakReference<Method> mSetSoWriteTimeoutMethodRef;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Private constructor, class is a singleton.
 | 
			
		||||
     *
 | 
			
		||||
     * @param socketClass               Underlying implementation class of {@link Socket} used to connect
 | 
			
		||||
     *                                  with the server.
 | 
			
		||||
     * @param setSoWriteTimeoutMethod   Name of the method to call to set a write timeout in the socket.
 | 
			
		||||
     */
 | 
			
		||||
    private WriteTimeoutEnforcer(Class<?> socketClass, Method setSoWriteTimeoutMethod) {
 | 
			
		||||
        mSocketClassRef = new WeakReference<Class<?>>(socketClass);
 | 
			
		||||
        mSetSoWriteTimeoutMethodRef =
 | 
			
		||||
            (setSoWriteTimeoutMethod == null) ?
 | 
			
		||||
                null :
 | 
			
		||||
                new WeakReference<>(setSoWriteTimeoutMethod)
 | 
			
		||||
        ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calls the {@code #setSoWrite(int)} method of the underlying implementation
 | 
			
		||||
     * of {@link Socket} if exists.
 | 
			
		||||
 | 
			
		||||
     * Creates and initializes the single instance of the class when needed
 | 
			
		||||
     *
 | 
			
		||||
     * @param writeTimeoutMilliseconds  Write timeout to set, in milliseconds.
 | 
			
		||||
     * @param socket                    Client socket to connect with the server.
 | 
			
		||||
     */
 | 
			
		||||
    public static void setSoWriteTimeout(int writeTimeoutMilliseconds, Socket socket) {
 | 
			
		||||
        final Method setSoWriteTimeoutMethod = getMethod(socket);
 | 
			
		||||
        if (setSoWriteTimeoutMethod != null) {
 | 
			
		||||
            try {
 | 
			
		||||
                setSoWriteTimeoutMethod.invoke(socket, writeTimeoutMilliseconds);
 | 
			
		||||
                Log_OC.i(
 | 
			
		||||
                    TAG,
 | 
			
		||||
                    "Write timeout set in socket, writeTimeoutMilliseconds: "
 | 
			
		||||
                        + writeTimeoutMilliseconds
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
            } catch (IllegalArgumentException e) {
 | 
			
		||||
                Log_OC.e(TAG, "Call to (SocketImpl)#setSoWriteTimeout(int) failed ", e);
 | 
			
		||||
 | 
			
		||||
            } catch (IllegalAccessException e) {
 | 
			
		||||
                Log_OC.e(TAG, "Call to (SocketImpl)#setSoWriteTimeout(int) failed ", e);
 | 
			
		||||
 | 
			
		||||
            } catch (InvocationTargetException e) {
 | 
			
		||||
                Log_OC.e(TAG, "Call to (SocketImpl)#setSoWriteTimeout(int) failed ", e);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Log_OC.i(TAG, "Write timeout for socket not supported");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the method to invoke trying to minimize the cost of reflection reusing objects cached
 | 
			
		||||
     * in static members.
 | 
			
		||||
     *
 | 
			
		||||
     * @param socket    Instance of the socket to use in connection with server.
 | 
			
		||||
     * @return          Method to call to set a write timeout in the socket.
 | 
			
		||||
     */
 | 
			
		||||
    private static Method getMethod(Socket socket) {
 | 
			
		||||
        final Class<?> socketClass = socket.getClass();
 | 
			
		||||
        final WriteTimeoutEnforcer instance = mSingleInstance.get();
 | 
			
		||||
        if (instance == null) {
 | 
			
		||||
            return initFrom(socketClass);
 | 
			
		||||
 | 
			
		||||
        } else if (instance.mSocketClassRef.get() != socketClass) {
 | 
			
		||||
            // the underlying class changed
 | 
			
		||||
            return initFrom(socketClass);
 | 
			
		||||
 | 
			
		||||
        } else if (instance.mSetSoWriteTimeoutMethodRef == null) {
 | 
			
		||||
            // method not supported
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            final Method cachedSetSoWriteTimeoutMethod = instance.mSetSoWriteTimeoutMethodRef.get();
 | 
			
		||||
            return (cachedSetSoWriteTimeoutMethod == null) ?
 | 
			
		||||
                initFrom(socketClass) :
 | 
			
		||||
                cachedSetSoWriteTimeoutMethod
 | 
			
		||||
            ;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Singleton initializer.
 | 
			
		||||
     *
 | 
			
		||||
     * Uses reflection to extract and 'cache' the method to invoke to set a write timouet in a socket.
 | 
			
		||||
     *
 | 
			
		||||
     * @param socketClass   Underlying class providing the implementation of {@link Socket}.
 | 
			
		||||
     * @return              Method to call to set a write timeout in the socket.
 | 
			
		||||
     */
 | 
			
		||||
    private static Method initFrom(Class<?> socketClass) {
 | 
			
		||||
        Log_OC.i(TAG, "Socket implementation: " + socketClass.getCanonicalName());
 | 
			
		||||
        Method setSoWriteTimeoutMethod = null;
 | 
			
		||||
        try {
 | 
			
		||||
            setSoWriteTimeoutMethod = socketClass.getMethod(METHOD_NAME, int.class);
 | 
			
		||||
        } catch (SecurityException e) {
 | 
			
		||||
            Log_OC.e(TAG, "Could not access to (SocketImpl)#setSoWriteTimeout(int) method ", e);
 | 
			
		||||
 | 
			
		||||
        } catch (NoSuchMethodException e) {
 | 
			
		||||
            Log_OC.i(
 | 
			
		||||
                TAG,
 | 
			
		||||
                "Could not find (SocketImpl)#setSoWriteTimeout(int) method - write timeout not supported"
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        mSingleInstance.set(new WriteTimeoutEnforcer(socketClass, setSoWriteTimeoutMethod));
 | 
			
		||||
        return setSoWriteTimeoutMethod;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -35,7 +35,6 @@ import java.util.Set;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
			
		||||
 | 
			
		||||
import org.apache.commons.httpclient.Header;
 | 
			
		||||
import org.apache.commons.httpclient.HttpException;
 | 
			
		||||
import org.apache.commons.httpclient.HttpStatus;
 | 
			
		||||
import org.apache.commons.httpclient.methods.GetMethod;
 | 
			
		||||
 | 
			
		||||
@ -60,7 +59,7 @@ public class DownloadRemoteFileOperation extends RemoteOperation {
 | 
			
		||||
    private static final int FORBIDDEN_ERROR = 403;
 | 
			
		||||
    private static final int SERVICE_UNAVAILABLE_ERROR = 503;
 | 
			
		||||
 | 
			
		||||
    private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
 | 
			
		||||
    private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<>();
 | 
			
		||||
    private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
 | 
			
		||||
    private long mModificationTimestamp = 0;
 | 
			
		||||
    private String mEtag = "";
 | 
			
		||||
@ -76,7 +75,7 @@ public class DownloadRemoteFileOperation extends RemoteOperation {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    protected RemoteOperationResult run(OwnCloudClient client) {
 | 
			
		||||
        RemoteOperationResult result = null;
 | 
			
		||||
        RemoteOperationResult result;
 | 
			
		||||
 | 
			
		||||
        /// download will be performed to a temporal file, then moved to the final location
 | 
			
		||||
        File tmpFile = new File(getTmpPath());
 | 
			
		||||
@ -102,17 +101,18 @@ public class DownloadRemoteFileOperation extends RemoteOperation {
 | 
			
		||||
        IOException, OperationCancelledException {
 | 
			
		||||
 | 
			
		||||
        RemoteOperationResult result;
 | 
			
		||||
        int status = -1;
 | 
			
		||||
        int status;
 | 
			
		||||
        boolean savedFile = false;
 | 
			
		||||
        mGet = new GetMethod(client.getWebdavUri() + WebdavUtils.encodePath(mRemotePath));
 | 
			
		||||
        Iterator<OnDatatransferProgressListener> it = null;
 | 
			
		||||
        Iterator<OnDatatransferProgressListener> it;
 | 
			
		||||
 | 
			
		||||
        FileOutputStream fos = null;
 | 
			
		||||
        BufferedInputStream bis = null;
 | 
			
		||||
        try {
 | 
			
		||||
            status = client.executeMethod(mGet);
 | 
			
		||||
            if (isSuccess(status)) {
 | 
			
		||||
                targetFile.createNewFile();
 | 
			
		||||
                BufferedInputStream bis = new BufferedInputStream(mGet.getResponseBodyAsStream());
 | 
			
		||||
                bis = new BufferedInputStream(mGet.getResponseBodyAsStream());
 | 
			
		||||
                fos = new FileOutputStream(targetFile);
 | 
			
		||||
                long transferred = 0;
 | 
			
		||||
 | 
			
		||||
@ -122,7 +122,7 @@ public class DownloadRemoteFileOperation extends RemoteOperation {
 | 
			
		||||
                    Long.parseLong(contentLength.getValue()) : 0;
 | 
			
		||||
 | 
			
		||||
                byte[] bytes = new byte[4096];
 | 
			
		||||
                int readResult = 0;
 | 
			
		||||
                int readResult;
 | 
			
		||||
                while ((readResult = bis.read(bytes)) != -1) {
 | 
			
		||||
                    synchronized (mCancellationRequested) {
 | 
			
		||||
                        if (mCancellationRequested.get()) {
 | 
			
		||||
@ -147,7 +147,7 @@ public class DownloadRemoteFileOperation extends RemoteOperation {
 | 
			
		||||
                        modificationTime = mGet.getResponseHeader("last-modified");
 | 
			
		||||
                    }
 | 
			
		||||
                    if (modificationTime != null) {
 | 
			
		||||
                        Date d = WebdavUtils.parseResponseDate((String) modificationTime.getValue());
 | 
			
		||||
                        Date d = WebdavUtils.parseResponseDate(modificationTime.getValue());
 | 
			
		||||
                        mModificationTimestamp = (d != null) ? d.getTime() : 0;
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Log_OC.e(TAG, "Could not read modification time from response downloading " + mRemotePath);
 | 
			
		||||
@ -163,15 +163,16 @@ public class DownloadRemoteFileOperation extends RemoteOperation {
 | 
			
		||||
                    // TODO some kind of error control!
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            } else if (status != FORBIDDEN_ERROR && status != SERVICE_UNAVAILABLE_ERROR){
 | 
			
		||||
            } else if (status != FORBIDDEN_ERROR && status != SERVICE_UNAVAILABLE_ERROR) {
 | 
			
		||||
                client.exhaustResponse(mGet.getResponseBodyAsStream());
 | 
			
		||||
 | 
			
		||||
            } // else, body read by RemoteOeprationResult constructor
 | 
			
		||||
            } // else, body read by RemoteOperationResult constructor
 | 
			
		||||
 | 
			
		||||
            result = new RemoteOperationResult(isSuccess(status), mGet);
 | 
			
		||||
 | 
			
		||||
        } finally {
 | 
			
		||||
            if (fos != null) fos.close();
 | 
			
		||||
            if (bis != null) bis.close();
 | 
			
		||||
            if (!savedFile && targetFile.exists()) {
 | 
			
		||||
                targetFile.delete();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user