? changes.txt Index: src/java/org/apache/commons/httpclient/HostConfiguration.java =================================================================== RCS file: /usr/local/tigris/data/helm/cvs/repository/httpclient-118/src/java/org/apache/commons/httpclient/HostConfiguration.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- src/java/org/apache/commons/httpclient/HostConfiguration.java 2003/10/30 20:32:58 1.1 +++ src/java/org/apache/commons/httpclient/HostConfiguration.java 2004/04/15 21:33:04 1.2 @@ -108,6 +108,16 @@ private InetAddress localAddress; /** + * The version of SOCKS Proxy we want to support. -1 indicates no support, + * the only valid values are 4 and 5 + */ + private int socksVersion; + + private String userName; + + private String userPass; + + /** * Constructor for HostConfiguration. */ public HostConfiguration() { @@ -122,6 +132,9 @@ this.proxyPort = -1; this.proxySet = false; this.localAddress = null; + this.socksVersion = -1; + this.userName = null; + this.userPass = null; } /** @@ -144,6 +157,9 @@ this.proxyPort = hostConfiguration.getProxyPort(); this.proxySet = hostConfiguration.isProxySet(); this.localAddress = hostConfiguration.getLocalAddress(); + this.socksVersion = hostConfiguration.getSOCKSVersion(); + this.userName = hostConfiguration.getSOCKSUsername(); + this.userPass = hostConfiguration.getSOCKSPassword(); } } @@ -295,7 +311,7 @@ * @param protocol the protocol */ public synchronized void setHost(String host, String virtualHost, int port, - Protocol protocol) { + Protocol protocol) { if (host == null) { throw new IllegalArgumentException("host must not be null"); @@ -441,6 +457,29 @@ } /** + * To ensure proper working of the proxy code, this method must not be + * called unless setProxy has been called + *

+ * @param user null if the proxy server does not need authentication + * @param pass null if the proxy server does not need authentication + */ + public synchronized void setSOCKSProxySettings(int version, + String proxyHost, int proxyPort, String user, String pass) { + if(version < 4 || version > 5) + throw new IllegalArgumentException("unsupported version of SOCKS"); + if(proxyHost == null || "".equals(proxyHost)) + throw new IllegalArgumentException("Proxy host is null"); + if((proxyPort & 0xFFFF0000) != 0 || (port ==0) ) + throw new IllegalArgumentException("Invalid Proxy Port"); + + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + this.socksVersion = version; + this.userName = user; + this.userPass = pass; + } + + /** * Returns the proxyHost. * * @return the proxy host, or null if not set @@ -461,6 +500,23 @@ public synchronized int getProxyPort() { return proxyPort; } + + public synchronized int getSOCKSVersion() { + return socksVersion; + } + + public synchronized String getSOCKSUsername() { + return userName; + } + + public synchronized String getSOCKSPassword() { + return userPass; + } + + public synchronized boolean isSOCKSSet() { + return (socksVersion==4 || socksVersion == 5); + } + /** * Set the local address to be used when creating connections. Index: src/java/org/apache/commons/httpclient/HttpClient.java =================================================================== RCS file: /usr/local/tigris/data/helm/cvs/repository/httpclient-118/src/java/org/apache/commons/httpclient/HttpClient.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- src/java/org/apache/commons/httpclient/HttpClient.java 2003/10/30 20:32:58 1.1 +++ src/java/org/apache/commons/httpclient/HttpClient.java 2004/04/15 21:33:04 1.2 @@ -90,7 +90,7 @@ * @author Laura Werner * @author Oleg Kalnichevski * - * @version $Revision: 1.1 $ $Date: 2003/10/30 20:32:58 $ + * @version $Revision: 1.2 $ $Date: 2004/04/15 21:33:04 $ */ public class HttpClient { @@ -565,18 +565,19 @@ * {@link HostConfiguration host configuration} with the given custom * {@link HttpState HTTP state}. * - * @param hostConfiguration The {@link HostConfiguration host configuration} to use. + * @param hostConfiguration The {@link HostConfiguration host configuration} + * to use. * @param method the {@link HttpMethod HTTP method} to execute. - * @param state the {@link HttpState HTTP state} to use when executing the method. - * If null, the state returned by {@link #getState} will be used instead. + * @param state the {@link HttpState HTTP state} to use when executing the + * method. If null, the state returned by {@link #getState} + * will be used instead. * * @return the method's response code * - * @throws IOException If an I/O (transport) error occurs. Some transport exceptions - * can be recovered from. - * @throws HttpException If a protocol exception occurs. Usually protocol exceptions - * cannot be recovered from. - * @since 2.0 + * @throws IOException If an I/O (transport) error occurs. Some transport + * exceptions can be recovered from. @throws HttpException If a protocol + * exception occurs. Usually protocol exceptions cannot be recovered from. + * @since 2.0 */ public int executeMethod(HostConfiguration hostConfiguration, HttpMethod method, HttpState state) @@ -630,6 +631,18 @@ defaultHostConfiguration.getProxyPort() ); } + + if(!methodConfiguration.isSOCKSSet() && + defaultHostConfiguration.isSOCKSSet()) { + int ver = defaultHostConfiguration.getSOCKSVersion(); + String host = defaultHostConfiguration.getProxyHost(); + int port = defaultHostConfiguration.getProxyPort(); + String user = defaultHostConfiguration.getSOCKSUsername(); + String pass = defaultHostConfiguration.getSOCKSPassword(); + methodConfiguration.setSOCKSProxySettings(ver, host, port, user, + pass); + } + if (methodConfiguration.getLocalAddress() == null && defaultHostConfiguration.getLocalAddress() != null) { Index: src/java/org/apache/commons/httpclient/HttpConnection.java =================================================================== RCS file: /usr/local/tigris/data/helm/cvs/repository/httpclient-118/src/java/org/apache/commons/httpclient/HttpConnection.java,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- src/java/org/apache/commons/httpclient/HttpConnection.java 2004/01/26 19:01:38 1.4 +++ src/java/org/apache/commons/httpclient/HttpConnection.java 2004/04/15 21:33:04 1.5 @@ -78,6 +78,7 @@ import org.apache.commons.httpclient.protocol.Protocol; import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; +import org.apache.commons.httpclient.protocol.SOCKSSocketFactory; import org.apache.commons.httpclient.util.TimeoutController; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -112,7 +113,7 @@ * @author Eric E Johnson * @author Laura Werner * - * @version $Revision: 1.4 $ $Date: 2004/01/26 19:01:38 $ + * @version $Revision: 1.5 $ $Date: 2004/04/15 21:33:04 $ */ public class HttpConnection { @@ -223,6 +224,7 @@ hostConfiguration.getPort(), hostConfiguration.getProtocol()); this.localAddress = hostConfiguration.getLocalAddress(); + this.hostConfiguration = hostConfiguration; } /** @@ -551,6 +553,13 @@ return (!(null == proxyHostName || 0 >= proxyPortNumber)); } + + public boolean isSOCKSRequired() { + if (hostConfiguration == null) + return false; + return hostConfiguration.isSOCKSSet(); + } + /** * Set the state to keep track of the last response for the last request. * @@ -654,10 +663,24 @@ // use the protocol's socket factory unless this is a secure // proxied connection - final ProtocolSocketFactory socketFactory = - (isSecure() && isProxied() - ? new DefaultProtocolSocketFactory() - : protocolInUse.getSocketFactory()); + ProtocolSocketFactory sFactory = null; + if(isSecure() && isProxied()) + sFactory = new DefaultProtocolSocketFactory(); + else if(isProxied() && isSOCKSRequired()) { + if(hostConfiguration==null) + throw new IOException("No config for SOCKS proxy"); + int ver = hostConfiguration.getSOCKSVersion(); + String user = hostConfiguration.getSOCKSUsername(); + String pass = hostConfiguration.getSOCKSPassword(); + String proxyHost = hostConfiguration.getProxyHost(); + int proxyPort = hostConfiguration.getProxyPort(); + sFactory = new SOCKSSocketFactory(ver,proxyHost, + proxyPort, user, pass); + } + else + sFactory = protocolInUse.getSocketFactory(); + + final ProtocolSocketFactory socketFactory = sFactory; if (connectTimeout == 0) { if (localAddress != null) { @@ -1527,4 +1551,6 @@ /** The time this connection was released. */ private long timeReleased = -1; + + private HostConfiguration hostConfiguration = null; } Index: src/java/org/apache/commons/httpclient/protocol/SOCKSSocketFactory.java =================================================================== RCS file: SOCKSSocketFactory.java diff -N SOCKSSocketFactory.java --- /dev/null Tue May 5 16:32:27 1998 +++ /tmp/cvsbDLJ3J Thu Apr 15 18:26:47 2004 @@ -0,0 +1,304 @@ +/* + * $Header: /usr/local/tigris/data/helm/cvs/repository/httpclient-118/src/java/org/apache/commons/httpclient/protocol/SOCKSSocketFactory.java,v 1.1 2004/04/15 21:33:04 sthadani Exp $ + * $Revision: 1.1 $ + * $Date: 2004/04/15 21:33:04 $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2002-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Commons", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + * [Additional notices, if required by prior licensing conditions] + * + * @author Sumeet Thadani + */ + +package org.apache.commons.httpclient.protocol; + +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +public class SOCKSSocketFactory implements ProtocolSocketFactory { + + private int _version; + private String _host; + private int _port; + private String _user; + private String _pass; + + private String _destHost; + private int _destPort; + + + private SOCKSSocketFactory() {} //so nobody uses this by accident + + /** + * @param user null if the sever does not need authentication + * @param pass null if the server does not need authentication + */ + public SOCKSSocketFactory(int version ,String proxyHost, int proxyPort, + String user, String pass) { + if(version < 4 || version > 5) + throw new IllegalArgumentException("unsupported version of SOCKS"); + if(proxyHost == null || "".equals(proxyHost)) + throw new IllegalArgumentException("Proxy host is null"); + if((proxyPort & 0xFFFF0000) != 0 || (proxyPort ==0) ) + throw new IllegalArgumentException("Invalid Proxy Port"); + + _version = version; + _host = proxyHost; + _port = proxyPort; + _user = user; + _pass = pass; + } + + public Socket createSocket(String host, int port, InetAddress clientHost, + int clientPort) throws IOException, UnknownHostException { + this._destHost = host; + this._destPort = port; + //make a connection with the proxy server + Socket sock = new Socket(_host, _port, clientHost, clientPort); + initializeConnection(sock); + return sock; + } + + public Socket createSocket(String host, int port) throws IOException, + UnknownHostException { + this._destHost = host; + this._destPort = port; + Socket sock = new Socket(_host, _port); + initializeConnection(sock); + return sock; + } + + private void initializeConnection(Socket socket) throws IOException, + UnknownHostException { + if(_version == 4) + doSOCKSv4Handshake(socket); + else if(_version == 5) + doSOCKSv5Handshake(socket); + else + throw new IllegalArgumentException("bad SOCKS version"); + } + + private void doSOCKSv4Handshake(Socket socket) throws IOException, + UnknownHostException { + byte[] hostBytes; + hostBytes = InetAddress.getByName(_destHost).getAddress(); + + byte[] portBytes = new byte[2]; + portBytes[0] = ((byte) (_destPort >> 8)); + portBytes[1] = ((byte) _destPort); + + OutputStream os = null; + InputStream in = null; + + //TODO: we need to add a TIMEOUT to these sockets + //proxySocket.setSoTimeout(timeout); + os = socket.getOutputStream(); + in = socket.getInputStream(); + + os.write(0x04); //version + os.write(0x01); //connect command + os.write(portBytes); //port to connect to + os.write(hostBytes); //host to connect to + //write user name if necessary + if(_user!=null) + os.write(_user.getBytes()); + os.write(0x00); //terminating 0 + os.flush(); + + // read response + // version should be 0 but some socks proxys answer 4 + int version = in.read(); + if (version != 0x00 && version != 0x04) { + socket.close(); + throw new IOException("Invalid version from socks proxy: "+ + version+ " expected 0 or 4"); + } + + // read the status, 0x5A is success + int status = in.read(); + if (status != 0x5A) { + socket.close(); + throw new IOException("Request rejected with status: " + status); + } + + // the socks proxy will now send the connected port and host + // we don't really check if it's the right one. + byte[] connectedHostPort = new byte[2]; + byte[] connectedHostAddress = new byte[4]; + if (in.read(connectedHostPort) == -1 || + in.read(connectedHostAddress) == -1) { + socket.close(); + throw new IOException("Connection failed"); + } + socket.setSoTimeout(0); + + } + + private void doSOCKSv5Handshake(Socket socket) throws IOException, + UnknownHostException { + byte[] hostBytes; + hostBytes = InetAddress.getByName(_destHost).getAddress(); + + byte[] portBytes = new byte[2]; + portBytes[0] = ((byte) (_destPort >> 8)); + portBytes[1] = ((byte) _destPort); + + OutputStream os = null; + InputStream in = null; + //TODO: We should have a timout setting here. + //socket.setSoTimeout(timeout); + os = socket.getOutputStream(); + in = socket.getInputStream(); + + os.write(0x05); //version + if (_user != null) { + os.write(0x02); //the number of authentication methods we support + os.write(0x00); //authentication method: no authentication + os.write(0x02); //authentication method: username/password + } else { + os.write(0x01); //the number of authentication methods we support + os.write(0x00); //authentication method: no authentication + } + os.flush(); + + int version = in.read(); + if (version != 0x05) { + socket.close(); + throw new IOException( + "Invalid version from socks proxy: " + version + " expected 5"); + } + + int auth_method = in.read(); + if (auth_method == 0x00) { + // no authentication + } else if (auth_method == 0x02) { + // username/password + + os.write(0x01); // version of authentication protocol + os.write((byte) _user.length()); // length of username field + os.write(_user.getBytes()); // username + os.write((byte) _pass.length()); // length of password field + os.write(_pass.getBytes()); // password + os.flush(); + + // read version for auth protocol from proxy, expects 1 + version = in.read(); + if (version != 0x01) { + socket.close(); + throw new IOException("Invalid version for authentication: "+ + version + " expected 1"); + } + + // read status, 0 is success + int status = in.read(); + if (status != 0x00) { + socket.close(); + throw new IOException( + "Authentication failed with status: " + status); + } + } + + // request connection + os.write(0x05); // version again + os.write(0x01); // connect command, + // 0x02 would be bind, 0x03 UDP associate + os.write(0x00); // reserved field, must be 0x00 + os.write(0x01); // address type: 0x01 is IPv4, 0x04 would be IPv6 + os.write(hostBytes); //host to connect to + os.write(portBytes); //port to connect to + os.flush(); + + // read response + // version should be 0x05 + version = in.read(); + if (version != 0x05) { + socket.close(); + throw new IOException( + "Invalid version from socks proxy: " + version + " expected 5"); + } + // read the status, 0x00 is success + int status = in.read(); + if (status != 0x00) { + socket.close(); + throw new IOException("Request rejected with status: " + status); + } + + // skip reserved byte; + in.read(); + + // read the address type in the reply and skip it. + int addrType = in.read(); + int bytesToSkip = 0; + if (addrType == 1) { // IPv4 + bytesToSkip = 4 + 2; + } else if (addrType == 3) { // domain name + bytesToSkip = in.read() + 2; + } else if (addrType == 4) { // IPv6 + bytesToSkip = 16 + 2; + } + + for (int i = 0; i < bytesToSkip; i++) { + if (in.read() == -1) { + throw new IOException("Connection failed"); + } + } + + socket.setSoTimeout(0); + } + + +}