/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.util;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import org.tmatesoft.svn.core.ISVNCanceller;
import org.tmatesoft.svn.core.SVNCancelException;
import org.tmatesoft.svn.core.SVNErrorCode;
import org.tmatesoft.svn.core.SVNErrorMessage;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.internal.wc.SVNClassLoader;
import org.tmatesoft.svn.core.internal.wc.SVNFileUtil;
import org.tmatesoft.svn.util.SVNDebugLog;
import org.tmatesoft.svn.util.SVNLogType;
/**
* SVNSocketFactory
is a utility class that represents a custom
* socket factory which provides creating either a plain socket or a secure one
* to encrypt data transmitted over network.
*
*
* The created socket then used by the inner engine of SVNKit
* library to communicate with a Subversion repository.
*
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNSocketFactory {
public interface SocketConfigurator {
void configureSocket(Socket socket);
}
private static final String EMPTY_JAVA7_TRUST_MANAGER_CLASSNAME = "org.tmatesoft.svn.core.internal.util.Java7EmptyTrustManager";
private static boolean ourIsSocketStaleCheck = false;
private static int ourSocketReceiveBufferSize = 0; // default
private static ISVNThreadPool ourThreadPool = SVNClassLoader.getThreadPool();
private static String ourSSLProtocols = System.getProperty("svnkit.http.sslProtocols");
private static volatile SocketConfigurator configurator;
public static Socket createPlainSocket(String host, int port, int connectTimeout, int readTimeout, ISVNCanceller cancel) throws IOException, SVNException {
InetAddress address = createAddress(host);
Socket socket = new Socket();
final int bufferSize = getSocketReceiveBufferSize();
if (bufferSize > 0) {
socket.setReceiveBufferSize(bufferSize);
}
final InetSocketAddress socketAddress = new InetSocketAddress(address, port);
socket.setReuseAddress(true);
socket.setTcpNoDelay(true);
socket.setKeepAlive(true);
socket.setSoLinger(true, 0);
socket.setSoTimeout(readTimeout);
configureSocket(socket);
connect(socket, socketAddress, connectTimeout, cancel);
return socket;
}
private static void configureSocket(Socket socket) throws SVNException {
final SocketConfigurator configurator = SVNSocketFactory.configurator;
if (configurator != null) {
try {
configurator.configureSocket(socket);
} catch (Throwable th) {
SVNDebugLog.getDefaultLog().logError(SVNLogType.NETWORK, th);
throw new SVNException(SVNErrorMessage.create(SVNErrorCode.RA_SVN_IO_ERROR, th), th);
}
}
}
public static void setConfigurator(SVNSocketFactory.SocketConfigurator configurator) {
SVNSocketFactory.configurator = configurator;
}
public static SVNSocketFactory.SocketConfigurator getConfigurator() {
return SVNSocketFactory.configurator;
}
public static synchronized void setSSLProtocols(String sslProtocols) {
ourSSLProtocols = sslProtocols;
}
public static synchronized String getSSLProtocols() {
return ourSSLProtocols;
}
public static Socket createSSLSocket(KeyManager[] keyManagers, TrustManager trustManager, String host, int port, int connectTimeout, int readTimeout, ISVNCanceller cancel) throws IOException, SVNException {
try {
final SSLSocket socket = (SSLSocket) _createSSLSocket(keyManagers, trustManager, host, port, connectTimeout, readTimeout, cancel, true);
// To verify that handshake works with regard to SNI
socket.startHandshake();
return socket;
} catch (javax.net.ssl.SSLProtocolException e) {
if (e.getMessage() != null && e.getMessage().contains("handshake alert: unrecognized_name")) {
return _createSSLSocket(keyManagers, trustManager, host, port, connectTimeout, readTimeout, cancel, false);
}
throw e;
}
}
private static Socket _createSSLSocket(KeyManager[] keyManagers, TrustManager trustManager, String host, int port, int connectTimeout, int readTimeout, ISVNCanceller cancel, boolean withSNIsupport) throws IOException, SVNException {
InetAddress address = createAddress(host);
Socket sslSocket = createSSLContext(keyManagers, trustManager).getSocketFactory().createSocket();
int bufferSize = getSocketReceiveBufferSize();
if (bufferSize > 0) {
sslSocket.setReceiveBufferSize(bufferSize);
}
if (withSNIsupport && sslSocket instanceof SSLSocket && SVNFileUtil.getJavaVersion() < 8) {
sslSocket = setSSLSocketHost(sslSocket, host);
}
InetSocketAddress socketAddress = new InetSocketAddress(address, port);
sslSocket.setReuseAddress(true);
sslSocket.setTcpNoDelay(true);
sslSocket.setKeepAlive(true);
sslSocket.setSoLinger(true, 0);
sslSocket.setSoTimeout(readTimeout);
sslSocket = configureSSLSocket(sslSocket);
connect(sslSocket, socketAddress, connectTimeout, cancel);
return sslSocket;
}
public static Socket createSSLSocket(KeyManager[] keyManagers, TrustManager trustManager, String host, int port, Socket socket, int readTimeout) throws IOException, SVNException {
Socket sslSocket = createSSLContext(keyManagers, trustManager).getSocketFactory().createSocket(socket, host, port, true);
sslSocket = setSSLSocketHost(sslSocket, host);
sslSocket.setReuseAddress(true);
sslSocket.setTcpNoDelay(true);
sslSocket.setKeepAlive(true);
sslSocket.setSoLinger(true, 0);
sslSocket.setSoTimeout(readTimeout);
sslSocket = configureSSLSocket(sslSocket);
return sslSocket;
}
private static Socket setSSLSocketHost(Socket sslSocket, String host) {
try {
Method m = sslSocket.getClass().getMethod("setHost", String.class);
if (m != null) {
m.invoke(sslSocket, host);
SVNDebugLog.getDefaultLog().logFinest(SVNLogType.NETWORK, "Host set on an SSL socket");
}
} catch (SecurityException e) {
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return sslSocket;
}
public static ISVNThreadPool getThreadPool() {
return ourThreadPool;
}
public static void connect(Socket socket, InetSocketAddress address, int timeout, ISVNCanceller cancel) throws IOException, SVNException {
if (cancel == null || cancel == ISVNCanceller.NULL) {
socket.connect(address, timeout);
return;
}
SVNSocketConnection socketConnection = new SVNSocketConnection(socket, address, timeout);
ISVNTask task = ourThreadPool.run(socketConnection, true);
while (!socketConnection.isSocketConnected()) {
try {
cancel.checkCancelled();
} catch (SVNCancelException e) {
task.cancel(true);
throw e;
}
}
if (socketConnection.getError() != null) {
throw socketConnection.getError();
}
}
private static InetAddress createAddress(String hostName) throws UnknownHostException {
byte[] bytes = new byte[4];
int index = 0;
for (StringTokenizer tokens = new StringTokenizer(hostName, "."); tokens.hasMoreTokens();) {
String token = tokens.nextToken();
try {
byte b = (byte) Integer.parseInt(token);
if (index < bytes.length) {
bytes[index] = b;
index++;
} else {
bytes = null;
break;
}
} catch (NumberFormatException e) {
bytes = null;
break;
}
}
if (bytes != null && index == 4) {
return InetAddress.getByAddress(hostName, bytes);
}
return InetAddress.getByName(hostName);
}
public static synchronized void setSocketReceiveBufferSize(int size) {
ourSocketReceiveBufferSize = size;
}
public static synchronized int getSocketReceiveBufferSize() {
return ourSocketReceiveBufferSize;
}
public static void setSocketStaleCheckEnabled(boolean enabled) {
ourIsSocketStaleCheck = enabled;
}
public static boolean isSocketStaleCheckEnabled() {
return ourIsSocketStaleCheck;
}
public static boolean isSocketStale(Socket socket) throws IOException {
if (!isSocketStaleCheckEnabled()) {
return socket == null || socket.isClosed() || !socket.isConnected();
}
boolean isStale = true;
if (socket != null) {
isStale = false;
try {
if (socket.getInputStream().available() == 0) {
int timeout = socket.getSoTimeout();
try {
socket.setSoTimeout(1);
socket.getInputStream().mark(1);
int byteRead = socket.getInputStream().read();
if (byteRead == -1) {
isStale = true;
} else {
socket.getInputStream().reset();
}
} finally {
socket.setSoTimeout(timeout);
}
}
} catch (InterruptedIOException e) {
if (!SocketTimeoutException.class.isInstance(e)) {
throw e;
}
} catch (IOException e) {
isStale = true;
}
}
return isStale;
}
private static X509TrustManager EMPTY_TRUST_MANAGER = new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
};
private static KeyManager[] EMPTY_KEY_MANAGERS = new KeyManager[0];
private static X509TrustManager getEmptyTrustManager() {
final String jreVersion = System.getProperty("java.runtime.version", "1.6.0");
final String[] jreVersionComponents = jreVersion.split("\\.");
if (jreVersionComponents.length > 1) {
try {
final int version = Integer.parseInt(jreVersionComponents[1]);
if (version >= 7) {
final Class clazz;
try {
clazz = SVNSocketFactory.class.getClassLoader().loadClass(EMPTY_JAVA7_TRUST_MANAGER_CLASSNAME);
final Object obj = clazz.newInstance();
if (obj instanceof X509TrustManager) {
return (X509TrustManager) obj;
}
} catch (ClassNotFoundException e) {
//
} catch (IllegalAccessException e) {
//
} catch (InstantiationException e) {
//
}
}
} catch (NumberFormatException nfe) {
//
}
}
return EMPTY_TRUST_MANAGER;
}
public static SSLContext createSSLContext(KeyManager[] keyManagers, TrustManager trustManager) throws IOException {
final TrustManager[] trustManagers = new TrustManager[] {trustManager != null ? trustManager : getEmptyTrustManager()};
keyManagers = keyManagers != null ? keyManagers : EMPTY_KEY_MANAGERS;
try {
return createSSLContext(keyManagers, trustManagers, getEnabledSSLProtocols(true));
} catch (NoSuchAlgorithmException e) {
try {
return createSSLContext(keyManagers, trustManagers, getEnabledSSLProtocols(false));
} catch (NoSuchAlgorithmException e1) {
throw new IOException(e1.getMessage());
}
}
}
private static SSLContext createSSLContext(KeyManager[] keyManagers, final TrustManager[] trustManagers, final List