package org.appwork.utils.net.httpconnection; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.net.URL; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.appwork.utils.net.CountingOutputStream; public class Socks5HTTPConnectionImpl extends HTTPConnectionImpl { protected Socket socks5socket = null; protected InputStream socks5inputstream = null; protected OutputStream socks5outputstream = null; private int httpPort; private String httpHost; public Socks5HTTPConnectionImpl(final URL url, final HTTPProxy proxy) { super(url, proxy); } protected void authenticateProxy() throws IOException { try { final String user = this.proxy.getUser() == null ? "" : this.proxy.getUser(); final String pass = this.proxy.getPass() == null ? "" : this.proxy.getPass(); final byte[] username = user.getBytes("UTF-8"); final byte[] password = pass.getBytes("UTF-8"); /* must be 1 */ this.socks5outputstream.write((byte) 1); /* send username */ this.socks5outputstream.write((byte) username.length); this.socks5outputstream.write(username); /* send password */ this.socks5outputstream.write((byte) password.length); this.socks5outputstream.write(password); /* read response, 2 bytes */ final byte[] resp = this.readResponse(2); if (resp[0] != 1) { throw new IOException("Socks5HTTPConnection: invalid Socks5 response"); } if (resp[1] != 0) { this.proxy.setStatus(HTTPProxy.STATUS.INVALIDAUTH); throw new ProxyAuthException(); } } catch (final IOException e) { try { this.socks5socket.close(); } catch (final Throwable e2) { } throw e; } } @Override public void connect() throws IOException { if (this.isConnected()) { return;/* oder fehler */ } if (this.proxy == null || !this.proxy.getType().equals(HTTPProxy.TYPE.SOCKS5)) { throw new IOException("Socks5HTTPConnection: invalid Socks5 Proxy!"); } /* create and connect to socks5 proxy */ this.socks5socket = new Socket(); this.socks5socket.setSoTimeout(this.readTimeout); final long startTime = System.currentTimeMillis(); try { this.socks5socket.connect(new InetSocketAddress(this.proxy.getHost(), this.proxy.getPort()), this.connectTimeout); } catch (final IOException e) { this.proxy.setStatus(HTTPProxy.STATUS.OFFLINE); throw new ProxyConnectException(e.getMessage()); } this.socks5inputstream = this.socks5socket.getInputStream(); this.socks5outputstream = this.socks5socket.getOutputStream(); /* establish connection to socks5 */ final int method = this.sayHello(); if (method == 2) { /* username/password authentication */ this.authenticateProxy(); } /* establish to destination through socks5 */ this.httpPort = this.httpURL.getPort(); this.httpHost = this.httpURL.getHost(); if (this.httpPort == -1) { this.httpPort = this.httpURL.getDefaultPort(); } final Socket establishedConnection = this.establishConnection(); if (this.httpURL.getProtocol().startsWith("https")) { /* we need to lay ssl over normal socks5 connection */ SSLSocket sslSocket = null; try { final SSLSocketFactory socketFactory = TrustALLSSLFactory.getSSLFactoryTrustALL(); sslSocket = (SSLSocket) socketFactory.createSocket(establishedConnection, this.httpHost, this.httpPort, true); sslSocket.startHandshake(); } catch (final SSLHandshakeException e) { try { this.socks5socket.close(); } catch (final Throwable e2) { } throw new IOException("Socks5HTTPConnection: " + e); } this.httpSocket = sslSocket; } else { /* we can continue to use the socks5 connection */ this.httpSocket = establishedConnection; } this.httpResponseCode = -1; this.requestTime = System.currentTimeMillis() - startTime; this.httpPath = new org.appwork.utils.Regex(this.httpURL.toString(), "https?://.*?(/.+)").getMatch(0); if (this.httpPath == null) { this.httpPath = "/"; } /* now send Request */ final StringBuilder sb = new StringBuilder(); sb.append(this.httpMethod.name()).append(' ').append(this.httpPath).append(" HTTP/1.1\r\n"); for (final String key : this.requestProperties.keySet()) { if (this.requestProperties.get(key) == null) { continue; } sb.append(key).append(": ").append(this.requestProperties.get(key)).append("\r\n"); } sb.append("\r\n"); this.httpSocket.getOutputStream().write(sb.toString().getBytes("UTF-8")); this.httpSocket.getOutputStream().flush(); if (this.httpMethod != RequestMethod.POST) { this.outputStream = this.httpSocket.getOutputStream(); this.outputClosed = true; this.connectInputStream(); } else { this.outputStream = new CountingOutputStream(this.httpSocket.getOutputStream()); } } @Override public void disconnect() { if (this.isConnected()) { try { this.httpSocket.close(); } catch (final Throwable e) { e.printStackTrace(); } } try { this.socks5socket.close(); } catch (final Throwable e) { e.printStackTrace(); } } protected Socket establishConnection() throws IOException { try { /* socks5 */ this.socks5outputstream.write((byte) 5); /* tcp/ip connection */ this.socks5outputstream.write((byte) 1); /* reserved */ this.socks5outputstream.write((byte) 0); /* we use domain names */ this.socks5outputstream.write((byte) 3); /* send somain name */ final byte[] domain = this.httpHost.getBytes("UTF-8"); this.socks5outputstream.write((byte) domain.length); this.socks5outputstream.write(domain); /* send port */ /* network byte order */ this.socks5outputstream.write(this.httpPort >> 8 & 0xff); this.socks5outputstream.write(this.httpPort & 0xff); this.socks5outputstream.flush(); /* read response, 4 bytes and then read rest of response */ final byte[] resp = this.readResponse(4); if (resp[0] != 5) { throw new IOException("Socks5HTTPConnection: invalid Socks5 response"); } switch (resp[1]) { case 0: break; case 3: throw new SocketException("Network is unreachable"); case 4: throw new SocketException("Host is unreachable"); case 5: throw new ConnectException("Connection refused"); case 1: case 2: case 6: case 7: case 8: throw new ConnectException("Socks5HTTPConnection: could not establish connection, status=" + resp[1]); } if (resp[3] == 1) { /* ip4v response */ this.readResponse(4 + 2); } else if (resp[3] == 3) { /* domain name response */ this.readResponse(1 + domain.length + 2); } else { throw new IOException("Socks5HTTPConnection: unsupported address Type " + resp[3]); } return this.socks5socket; } catch (final IOException e) { try { this.socks5socket.close(); } catch (final Throwable e2) { } throw e; } } /* reads response with expLength bytes */ protected byte[] readResponse(final int expLength) throws IOException { final byte[] response = new byte[expLength]; int index = 0; int read = 0; while (index < expLength && (read = this.socks5inputstream.read()) != -1) { response[index] = (byte) read; index++; } if (index < expLength) { throw new IOException("Socks5HTTPConnection: not enough data read"); } return response; } protected int sayHello() throws IOException { try { /* socks5 */ this.socks5outputstream.write((byte) 5); /* only none ans password/username auth method */ this.socks5outputstream.write((byte) 2); /* none */ this.socks5outputstream.write((byte) 2); /* username/password */ this.socks5outputstream.write((byte) 0); this.socks5outputstream.flush(); /* read response, 2 bytes */ final byte[] resp = this.readResponse(2); if (resp[0] != 5) { throw new IOException("Socks5HTTPConnection: invalid Socks5 response"); } if (resp[1] == 255) { throw new IOException("Socks5HTTPConnection: no acceptable authentication method found"); } return resp[1]; } catch (final IOException e) { try { this.socks5socket.close(); } catch (final Throwable e2) { } throw e; } } }