
package info.guardianproject.onionkit.proxy;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import ch.boye.httpclientandroidlib.HttpHost;
import ch.boye.httpclientandroidlib.conn.OperatedClientConnection;
import ch.boye.httpclientandroidlib.conn.scheme.Scheme;
import ch.boye.httpclientandroidlib.conn.scheme.SchemeRegistry;
import ch.boye.httpclientandroidlib.conn.ssl.SSLSocketFactory;
import ch.boye.httpclientandroidlib.impl.conn.DefaultClientConnectionOperator;
import ch.boye.httpclientandroidlib.params.HttpParams;
import ch.boye.httpclientandroidlib.protocol.HttpContext;

public class SocksProxyClientConnOperator extends DefaultClientConnectionOperator {

	private static final int CONNECT_TIMEOUT_MILLISECONDS = 60000;
	private static final int READ_TIMEOUT_MILLISECONDS = 60000;

	private String mProxyHost;
	private int mProxyPort;
	    
	public SocksProxyClientConnOperator(SchemeRegistry registry, String proxyHost, int proxyPort) {
        super(registry);
        
        mProxyHost = proxyHost;
        mProxyPort = proxyPort;
    }

    // Derived from the original DefaultClientConnectionOperator.java in Apache HttpClient 4.2
    @Override
    public void openConnection(
            final OperatedClientConnection conn,
            final HttpHost target,
            final InetAddress local,
            final HttpContext context,
            final HttpParams params) throws IOException {
        Socket socket = null;
        Socket sslSocket = null;
        try {
            if (conn == null || target == null || params == null) {
                throw new IllegalArgumentException("Required argument may not be null");
            }
            if (conn.isOpen()) {
                throw new IllegalStateException("Connection must not be open");
            }

            Scheme scheme = schemeRegistry.getScheme(target.getSchemeName());
            SSLSocketFactory sslSocketFactory = (SSLSocketFactory)scheme.getSchemeSocketFactory();

            int port = scheme.resolvePort(target.getPort());
            String host = target.getHostName();

            // Perform explicit SOCKS4a connection request. SOCKS4a supports remote host name resolution
            // (i.e., Tor resolves the hostname, which may be an onion address).
            // The Android (Apache Harmony) Socket class appears to support only SOCKS4 and throws an
            // exception on an address created using INetAddress.createUnresolved() -- so the typical
            // technique for using Java SOCKS4a/5 doesn't appear to work on Android:
            // https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/net/PlainSocketImpl.java
            // See also: http://www.mit.edu/~foley/TinFoil/src/tinfoil/TorLib.java, for a similar implementation

            // From http://en.wikipedia.org/wiki/SOCKS#SOCKS4a:
            //
            // field 1: SOCKS version number, 1 byte, must be 0x04 for this version
            // field 2: command code, 1 byte:
            //     0x01 = establish a TCP/IP stream connection
            //     0x02 = establish a TCP/IP port binding
            // field 3: network byte order port number, 2 bytes
            // field 4: deliberate invalid IP address, 4 bytes, first three must be 0x00 and the last one must not be 0x00
            // field 5: the user ID string, variable length, terminated with a null (0x00)
            // field 6: the domain name of the host we want to contact, variable length, terminated with a null (0x00)


            socket = new Socket();
            conn.opening(socket, target);
            socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
            socket.connect(new InetSocketAddress(mProxyHost, mProxyPort), CONNECT_TIMEOUT_MILLISECONDS);

            DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
            outputStream.write((byte)0x04);
            outputStream.write((byte)0x01);
            outputStream.writeShort((short)port);
            outputStream.writeInt(0x01);
            outputStream.write((byte)0x00);
            outputStream.write(host.getBytes());
            outputStream.write((byte)0x00);

            DataInputStream inputStream = new DataInputStream(socket.getInputStream());
            if (inputStream.readByte() != (byte)0x00 || inputStream.readByte() != (byte)0x5a) {
                throw new IOException("SOCKS4a connect failed");
            }
            inputStream.readShort();
            inputStream.readInt();

            sslSocket = sslSocketFactory.createLayeredSocket(socket, host, port, params);
            conn.opening(sslSocket, target);
            sslSocket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);
            prepareSocket(sslSocket, context, params);
            conn.openCompleted(sslSocketFactory.isSecure(sslSocket), params);
            // TODO: clarify which connection throws java.net.SocketTimeoutException?
        } catch (IOException e) {
            try {
                if (sslSocket != null) {
                    sslSocket.close();
                }
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException ioe) {}
            throw e;
        }
    }

    @Override
    public void updateSecureConnection(
            final OperatedClientConnection conn,
            final HttpHost target,
            final HttpContext context,
            final HttpParams params) throws IOException {
        throw new RuntimeException("operation not supported");
    }

    @Override
    protected InetAddress[] resolveHostname(final String host) throws UnknownHostException {
        throw new RuntimeException("operation not supported");
    }
}

@@ -489,24 +489,156 @@
 meRegistry;%0A
+import ch.boye.httpclientandroidlib.conn.scheme.SchemeSocketFactory;%0Aimport ch.boye.httpclientandroidlib.conn.scheme.SocketFactory;%0A
 import ch.bo
@@ -2179,18 +2179,21 @@
        S
-SL
+cheme
 SocketFa
@@ -2199,18 +2199,21 @@
 actory s
-sl
+cheme
 SocketFa
@@ -2224,26 +2224,8 @@
 y = 
-(SSLSocketFactory)
 sche
@@ -4809,23 +4809,125 @@
     
-sslSocket = ssl
+if (schemeSocketFactory instanceof SSLSocketFactory)%0A            %7B%0A%09            sslSocket = ((SSLSocketFactory)scheme
 Sock
@@ -4935,16 +4935,17 @@
 tFactory
+)
 .createL
@@ -4978,32 +4978,33 @@
  port, params);%0A
+%09
             conn
@@ -5024,32 +5024,33 @@
 ocket, target);%0A
+%09
             sslS
@@ -5088,32 +5088,33 @@
 _MILLISECONDS);%0A
+%09
             prep
@@ -5144,32 +5144,33 @@
 ntext, params);%0A
+%09
             conn
@@ -5185,18 +5185,21 @@
 pleted(s
-sl
+cheme
 SocketFa
@@ -5234,16 +5234,312 @@
 arams);%0A
+            %7D%0A            else%0A            %7B%0A            %09conn.opening(socket,  target);%0A            %09socket.setSoTimeout(READ_TIMEOUT_MILLISECONDS);%0A            %09prepareSocket(socket, context, params);%0A            %09conn.openCompleted(schemeSocketFactory.isSecure(socket), params);%0A            %7D%0A
         

fix SOCKS proxying to work with HTTP connections