snichol 2002/10/04 12:50:20 Modified: java/docs changes.html intro.html java/src/org/apache/soap/transport/http SOAPHTTPConnection.java java/src/org/apache/soap/util/net HTTPUtils.java SSLUtils.java Added: java/src/org/apache/soap/util MutableBoolean.java Log: Read HTTP[S] proxy information from system properties if not explicitly specified. Revision Changes Path 1.47 +5 -1 xml-soap/java/docs/changes.html Index: changes.html =================================================================== RCS file: /home/cvs/xml-soap/java/docs/changes.html,v retrieving revision 1.46 retrieving revision 1.47 diff -u -r1.46 -r1.47 --- changes.html 16 Sep 2002 15:26:25 -0000 1.46 +++ changes.html 4 Oct 2002 19:50:19 -0000 1.47 @@ -72,7 +72,9 @@ <li>Add a document/literal serialization option. This is intended for use by SOAP clients that need to communicate with services that use document/literal encoding. Parameters will be serialized using document/literal - style. Return values must be mapped by parameter name (the existing + style. (No xsi:type is emitted; therefore, this does not work with + schemas specifying xsd:anyType for a parameter.) + Return values must be mapped by parameter name (the existing interop hack).</li> <li>Support gzip encoding for HTTP. This is enabled through SOAPContext for clients and the deployment descriptor for services.</li> @@ -86,6 +88,8 @@ for each compound type being deserialized. Conversely, it can write a Map similary, providing an alternative to the default Apache SOAP serialization of Maps.</li> + <li>Read HTTP[S] proxy information from system properties if not explicitly + specified.</li> </ul> </li> </ul> 1.13 +1 -0 xml-soap/java/docs/intro.html Index: intro.html =================================================================== RCS file: /home/cvs/xml-soap/java/docs/intro.html,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- intro.html 13 Sep 2002 14:11:41 -0000 1.12 +++ intro.html 4 Oct 2002 19:50:19 -0000 1.13 @@ -108,6 +108,7 @@ <li>multi-dimensional arrays (5.4.2), arrays of arrays (5.4.2), partially transmitted arrays (5.4.2.1), sparse arrays (5.4.2.2)</li> <li>root attribute (5.6)</li> + <li>input/output and output parameters</li> </ul> <p>The following limitations on 1.27 +2 -15 xml-soap/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java Index: SOAPHTTPConnection.java =================================================================== RCS file: /home/cvs/xml-soap/java/src/org/apache/soap/transport/http/SOAPHTTPConnection.java,v retrieving revision 1.26 retrieving revision 1.27 diff -u -r1.26 -r1.27 --- SOAPHTTPConnection.java 5 Sep 2002 16:50:52 -0000 1.26 +++ SOAPHTTPConnection.java 4 Oct 2002 19:50:19 -0000 1.27 @@ -201,19 +201,6 @@ return responseCopy; } - private static String encodeAuth(String userName, String password) - throws SOAPException - { - try - { - return Base64.encode((userName + ":" + password).getBytes("8859_1")); - } - catch (UnsupportedEncodingException e) - { - throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e); - } - } - /** * Indicate whether to maintain HTTP sessions. */ @@ -373,13 +360,13 @@ if (userName != null) { // add the Authorization header for Basic authentication headers.put (Constants.HEADER_AUTHORIZATION, - "Basic " + encodeAuth(userName, password)); + "Basic " + HTTPUtils.encodeAuth(userName, password)); } if (proxyUserName != null) { // add the Proxy-Authorization header for proxy authentication headers.put (Constants.HEADER_PROXY_AUTHORIZATION, - "Basic " + encodeAuth(proxyUserName, proxyPassword)); + "Basic " + HTTPUtils.encodeAuth(proxyUserName, proxyPassword)); } TransportMessage response; 1.1 xml-soap/java/src/org/apache/soap/util/MutableBoolean.java Index: MutableBoolean.java =================================================================== /* * The Apache Software License, Version 1.1 * * * Copyright (c) 2000 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 acknowledgment: * "This product includes software developed by the * Apache Software Foundation (http://www.apache.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "SOAP" 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 [EMAIL PROTECTED] * * 5. Products derived from this software may not be called "Apache", * nor may "Apache" appear in their name, without prior written * permission of the Apache Software Foundation. * * 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 and was * originally based on software copyright (c) 2000, International * Business Machines, Inc., http://www.apache.org. For more * information on the Apache Software Foundation, please see * <http://www.apache.org/>. */ package org.apache.soap.util; /** * A boolean object that can have its value changed. * * @author Scott Nichol ([EMAIL PROTECTED]) */ public class MutableBoolean { private boolean value; public MutableBoolean(boolean value) { this.value = value; } public boolean getValue() { return value; } public void setValue(boolean value) { this.value = value; } public boolean equals(MutableBoolean mb) { return this.value == mb.value; } public String toString() { return value ? "true" : "false"; } } 1.32 +324 -48 xml-soap/java/src/org/apache/soap/util/net/HTTPUtils.java Index: HTTPUtils.java =================================================================== RCS file: /home/cvs/xml-soap/java/src/org/apache/soap/util/net/HTTPUtils.java,v retrieving revision 1.31 retrieving revision 1.32 diff -u -r1.31 -r1.32 --- HTTPUtils.java 6 Sep 2002 17:02:59 -0000 1.31 +++ HTTPUtils.java 4 Oct 2002 19:50:19 -0000 1.32 @@ -57,18 +57,21 @@ package org.apache.soap.util.net; -import java.net.*; import java.io.*; +import java.lang.reflect.*; +import java.net.*; import java.util.*; +import javax.mail.*; +import javax.mail.internet.*; +import javax.activation.*; + import org.apache.soap.*; +import org.apache.soap.encoding.soapenc.Base64; import org.apache.soap.rpc.*; import org.apache.soap.transport.*; +import org.apache.soap.util.MutableBoolean; import org.apache.soap.util.mime.*; -import javax.mail.*; -import javax.mail.internet.*; -import javax.activation.*; -import java.lang.reflect.*; /** * A bunch of utility stuff for doing HTTP things. @@ -80,6 +83,8 @@ * @author Wouter Cloetens ([EMAIL PROTECTED]) * @author Scott Nichol ([EMAIL PROTECTED]) * @author Arek Wnukowski ([EMAIL PROTECTED]) + * @author Doug Davis ([EMAIL PROTECTED]) (code lifted from Axis) + * @author Davanum Srinivas ([EMAIL PROTECTED]) (code lifted from Axis) */ public class HTTPUtils { private static final String HTTP_VERSION = "1.0"; @@ -89,47 +94,121 @@ public static final int DEFAULT_OUTPUT_BUFFER_SIZE = 512; /** - * This method either creates a socket or calls SSLUtils to + * Encodes an HTTP Basic authentication username and password. + */ + public static String encodeAuth(String userName, String password) + throws SOAPException { + try { + return Base64.encode((userName + ":" + password).getBytes("8859_1")); + } catch (UnsupportedEncodingException e) { + throw new SOAPException (Constants.FAULT_CODE_CLIENT, e.getMessage(), e); + } + } + + /** + * This method calls SSLUtils to * create an SSLSocket. It uses reflection to avoid a compile time * dependency on SSL. * * @author Chris Nelson */ - private static Socket buildSocket(URL url, int targetPort, - String httpProxyHost, int httpProxyPort, - Boolean tcpNoDelay) + private static Socket getSecureSocket(String host, int port, + String httpProxyHost, int httpProxyPort, + Hashtable headers, Boolean tcpNoDelay) throws Exception { - Socket s = null; - String host = null; - int port = targetPort; - host = url.getHost(); - - if (url.getProtocol().equalsIgnoreCase("HTTPS")) { - // Using reflection to avoid compile time dependencies - Class SSLUtilsClass = - Class.forName("org.apache.soap.util.net.SSLUtils"); - Class[] paramTypes = new Class[] {String.class, int.class, String.class, int.class}; - Method buildSSLSocket = SSLUtilsClass.getMethod( - "buildSSLSocket", paramTypes); - Object[] params = new Object[] {host, new Integer(port), - httpProxyHost, new Integer(httpProxyPort)}; - s = (Socket)buildSSLSocket.invoke(null, params); - } else { - if (httpProxyHost != null) { - host = httpProxyHost; - port = httpProxyPort; - } - s = new Socket(host, port); + + if (httpProxyHost == null) { + String proxyHost = System.getProperty("https.proxyHost"); + String nonProxyHosts = System.getProperty("https.nonProxyHosts"); + boolean hostInNonProxyList = isHostInNonProxyList(host, nonProxyHosts); + if (proxyHost != null && !hostInNonProxyList) { + // use proxy from system values + httpProxyHost = proxyHost; + httpProxyPort = Integer.getInteger("https.proxyPort", HTTPS_DEFAULT_PORT).intValue(); + // TODO: build authentication string here instead of SSLUtils, + // but into headers, and pass headers to SSLUtils. } - - if (tcpNoDelay != null) - { - if (s != null) - s.setTcpNoDelay(tcpNoDelay.booleanValue()); + } + + // Using reflection to avoid compile time dependencies + Class SSLUtilsClass = + Class.forName("org.apache.soap.util.net.SSLUtils"); + Class[] paramTypes = new Class[] {String.class, int.class, + String.class, int.class, + Boolean.class}; + Method buildSSLSocket = SSLUtilsClass.getMethod("buildSSLSocket", paramTypes); + Object[] params = new Object[] {host, new Integer(port), + httpProxyHost, new Integer(httpProxyPort), + tcpNoDelay}; + + try { + return (Socket) buildSSLSocket.invoke(null, params); + } catch (Exception e) { + StringBuffer msg = new StringBuffer(512); + msg.append("Error SSL connecting to ").append(host).append(':').append(port); + if (httpProxyHost != null) + msg.append(" via ").append(httpProxyHost).append(':').append(httpProxyPort); + msg.append(": ").append(e.toString()); + throw new SOAPException(Constants.FAULT_CODE_CLIENT, msg.toString(), e); + } + } + + /** + * This method either creates a socket. + * + * @author Chris Nelson + */ + private static Socket getSocket(String host, int port, + String httpProxyHost, int httpProxyPort, + Hashtable headers, Boolean tcpNoDelay, + MutableBoolean proxyUsed) + throws Exception { + Socket s = null; + + if (httpProxyHost != null) { + // user-specified values have precedence + proxyUsed.setValue(true); + } else { + httpProxyHost = System.getProperty("http.proxyHost"); + if (httpProxyHost != null) { + String nonProxyHosts = System.getProperty("http.nonProxyHosts"); + if (!isHostInNonProxyList(host, nonProxyHosts)) { + // use proxy from system values + httpProxyPort = Integer.getInteger("http.proxyPort", HTTP_DEFAULT_PORT).intValue(); + String proxyUserName = System.getProperty("http.proxyUser"); + if (proxyUserName != null) { + String proxyPassword = System.getProperty("http.proxyPassword"); + headers.put(Constants.HEADER_PROXY_AUTHORIZATION, + "Basic " + encodeAuth(proxyUserName, proxyPassword)); + } + proxyUsed.setValue(true); + } else { + proxyUsed.setValue(false); + } + } else { + proxyUsed.setValue(false); } + } - return s; - } + try { + if (proxyUsed.getValue()) + s = new Socket(httpProxyHost, httpProxyPort); + else + s = new Socket(host, port); + } catch (Exception e) { + StringBuffer msg = new StringBuffer(512); + msg.append("Error connecting to ").append(host).append(':').append(port); + if (proxyUsed.getValue()) + msg.append(" via ").append(httpProxyHost).append(':').append(httpProxyPort); + msg.append(": ").append(e.toString()); + throw new SOAPException(Constants.FAULT_CODE_CLIENT, msg.toString(), e); + } + + if (s != null && tcpNoDelay != null) + s.setTcpNoDelay(tcpNoDelay.booleanValue()); + + return s; + } /** * Obtain a header value from the table using a case insensitive search. @@ -276,19 +355,27 @@ StringBuffer requestCopy, StringBuffer responseCopy) throws IOException, SOAPException { - /* Open the connection */ + OutputStream outStream = null; InputStream inStream = null; BufferedReader in = null; - int port; - Socket s; - try { - port = getPort(url); + String host = url.getHost(); + int port = getPort(url); + String protocol = url.getProtocol(); + Socket s = null; + boolean proxyUsed = false; - s = buildSocket(url, port, httpProxyHost, httpProxyPort, tcpNoDelay); - if (url.getProtocol().equalsIgnoreCase("HTTPS")) { - // Ignore proxy from now on. Buildsocket takes handles it - httpProxyHost = null; + /* Open the connection */ + try { + if (protocol.equalsIgnoreCase("HTTPS")) { + s = getSecureSocket(host, port, httpProxyHost, httpProxyPort, + request.getHeaders(), tcpNoDelay); + } else { + MutableBoolean isProxyUsed = new MutableBoolean(proxyUsed); + s = getSocket(host, port, httpProxyHost, httpProxyPort, + request.getHeaders(), tcpNoDelay, + isProxyUsed); + proxyUsed = isProxyUsed.getValue(); } if (timeout > 0) // Should be redundant but not every JVM likes this @@ -298,6 +385,9 @@ inStream = s.getInputStream (); } catch (Exception e) { + if (e instanceof SOAPException) + throw (SOAPException) e; + Throwable t = e; if (t instanceof InvocationTargetException) { @@ -309,14 +399,14 @@ } /* Compute the Request URI */ - String URI = (httpProxyHost == null ? url.getFile() : url.toString()); + String URI = (!proxyUsed ? url.getFile() : url.toString()); if (URI.length() == 0) URI = "/"; /* Construct the HTTP header. */ StringBuffer headerbuf = new StringBuffer(512); headerbuf.append(Constants.HEADER_POST).append(' ').append(URI) .append(" HTTP/").append(HTTP_VERSION).append("\r\n") - .append(Constants.HEADER_HOST).append(": ").append(url.getHost()) + .append(Constants.HEADER_HOST).append(": ").append(host) .append(':').append(port) .append("\r\n") .append(Constants.HEADER_CONTENT_TYPE).append(": ") @@ -478,4 +568,190 @@ s.close(); return response; } + + /** + * Check if the specified host is in the list of non proxy hosts. + * + * @param host host name + * @param nonProxyHosts string containing the list of non proxy hosts + * + * @return true/false + */ + private static boolean isHostInNonProxyList(String host, String nonProxyHosts) { + if ((nonProxyHosts == null) || (host == null)) { + return false; + } + StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|"); + + while (tokenizer.hasMoreTokens()) { + String pattern = tokenizer.nextToken(); + + if (match(pattern, host, false)) { + return true; + } + } + return false; + } + + /** + * Matches a string against a pattern. The pattern contains two special + * characters: + * '*' which means zero or more characters, + * + * @param pattern the (non-null) pattern to match against + * @param str the (non-null) string that must be matched against the + * pattern + * @param isCaseSensitive + * + * @return <code>true</code> when the string matches against the pattern, + * <code>false</code> otherwise. + */ + private static boolean match(String pattern, String str, + boolean isCaseSensitive) { + char[] patArr = pattern.toCharArray(); + char[] strArr = str.toCharArray(); + int patIdxStart = 0; + int patIdxEnd = patArr.length - 1; + int strIdxStart = 0; + int strIdxEnd = strArr.length - 1; + char ch; + boolean containsStar = false; + + for (int i = 0; i < patArr.length; i++) { + if (patArr[i] == '*') { + containsStar = true; + break; + } + } + if (!containsStar) { + + // No '*'s, so we make a shortcut + if (patIdxEnd != strIdxEnd) { + return false; // Pattern and string do not have the same size + } + for (int i = 0; i <= patIdxEnd; i++) { + ch = patArr[i]; + if (isCaseSensitive && (ch != strArr[i])) { + return false; // Character mismatch + } + if (!isCaseSensitive + && (Character.toUpperCase(ch) + != Character.toUpperCase(strArr[i]))) { + return false; // Character mismatch + } + } + return true; // String matches against pattern + } + if (patIdxEnd == 0) { + return true; // Pattern contains only '*', which matches anything + } + + // Process characters before first star + while ((ch = patArr[patIdxStart]) != '*' + && (strIdxStart <= strIdxEnd)) { + if (isCaseSensitive && (ch != strArr[strIdxStart])) { + return false; // Character mismatch + } + if (!isCaseSensitive + && (Character.toUpperCase(ch) + != Character.toUpperCase(strArr[strIdxStart]))) { + return false; // Character mismatch + } + patIdxStart++; + strIdxStart++; + } + if (strIdxStart > strIdxEnd) { + + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (patArr[i] != '*') { + return false; + } + } + return true; + } + + // Process characters after last star + while ((ch = patArr[patIdxEnd]) != '*' && (strIdxStart <= strIdxEnd)) { + if (isCaseSensitive && (ch != strArr[strIdxEnd])) { + return false; // Character mismatch + } + if (!isCaseSensitive + && (Character.toUpperCase(ch) + != Character.toUpperCase(strArr[strIdxEnd]))) { + return false; // Character mismatch + } + patIdxEnd--; + strIdxEnd--; + } + if (strIdxStart > strIdxEnd) { + + // All characters in the string are used. Check if only '*'s are + // left in the pattern. If so, we succeeded. Otherwise failure. + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (patArr[i] != '*') { + return false; + } + } + return true; + } + + // process pattern between stars. padIdxStart and patIdxEnd point + // always to a '*'. + while ((patIdxStart != patIdxEnd) && (strIdxStart <= strIdxEnd)) { + int patIdxTmp = -1; + + for (int i = patIdxStart + 1; i <= patIdxEnd; i++) { + if (patArr[i] == '*') { + patIdxTmp = i; + break; + } + } + if (patIdxTmp == patIdxStart + 1) { + + // Two stars next to each other, skip the first one. + patIdxStart++; + continue; + } + + // Find the pattern between padIdxStart & padIdxTmp in str between + // strIdxStart & strIdxEnd + int patLength = (patIdxTmp - patIdxStart - 1); + int strLength = (strIdxEnd - strIdxStart + 1); + int foundIdx = -1; + + strLoop: + for (int i = 0; i <= strLength - patLength; i++) { + for (int j = 0; j < patLength; j++) { + ch = patArr[patIdxStart + j + 1]; + if (isCaseSensitive + && (ch != strArr[strIdxStart + i + j])) { + continue strLoop; + } + if (!isCaseSensitive && (Character + .toUpperCase(ch) != Character + .toUpperCase(strArr[strIdxStart + i + j]))) { + continue strLoop; + } + } + foundIdx = strIdxStart + i; + break; + } + if (foundIdx == -1) { + return false; + } + patIdxStart = patIdxTmp; + strIdxStart = foundIdx + patLength; + } + + // All characters in the string are used. Check if only '*'s are left + // in the pattern. If so, we succeeded. Otherwise failure. + for (int i = patIdxStart; i <= patIdxEnd; i++) { + if (patArr[i] != '*') { + return false; + } + } + return true; + } } 1.7 +184 -164 xml-soap/java/src/org/apache/soap/util/net/SSLUtils.java Index: SSLUtils.java =================================================================== RCS file: /home/cvs/xml-soap/java/src/org/apache/soap/util/net/SSLUtils.java,v retrieving revision 1.6 retrieving revision 1.7 diff -u -r1.6 -r1.7 --- SSLUtils.java 1 Aug 2002 18:16:48 -0000 1.6 +++ SSLUtils.java 4 Oct 2002 19:50:19 -0000 1.7 @@ -64,173 +64,193 @@ import java.security.*; /** - * A bunch of utility stuff for doing SSL things. + * A bunch of utility stuff for doing SSL things. It is separate from + * HTTPUtils to avoid HTTPUtils from depending on SSL libraries at + * compile time (although everything in the class could be written to use + * reflection and accomplish the same thing.) * * @author Chris Nelson ([EMAIL PROTECTED]) * @author Phil Bohnenkamp ([EMAIL PROTECTED]) + * @author Scott Nichol ([EMAIL PROTECTED]) */ public class SSLUtils { - static String tunnelHost; - static int tunnelPort; - - /** This method builds an SSL socket, after auto-starting SSL */ - public static Socket buildSSLSocket(String host, int port, String httpProxyHost, - int httpProxyPort) - throws IOException, UnknownHostException - { - SSLSocket sslSocket = null; - SSLSocketFactory factory = - (SSLSocketFactory)SSLSocketFactory.getDefault(); - - // Determine if a proxy should be used. Use system properties if set - // Otherwise use http proxy. If neither is set, dont use a proxy - tunnelHost = System.getProperty("https.proxyHost"); - tunnelPort = Integer.getInteger("https.proxyPort", 80).intValue(); - - if (tunnelHost==null) { - // Try to use http proxy instead - tunnelHost = httpProxyHost; - tunnelPort = httpProxyPort; - } - - /* - System.out.println("https proxyHost=" + tunnelHost + - " proxyPort=" + tunnelPort + - " host=" + host + - " port=" + port); - */ - - /* - * If a proxy has been set... - * Set up a socket to do tunneling through the proxy. - * Start it off as a regular socket, then layer SSL - * over the top of it. - */ - if (tunnelHost==null) { - sslSocket = (SSLSocket)factory.createSocket(host, port); - } else { - Socket tunnel = new Socket(tunnelHost, tunnelPort); - doTunnelHandshake(tunnel, host, port); - - // Overlay tunnel socket with SSL - sslSocket = (SSLSocket)factory.createSocket(tunnel, host, port, true); - } - - /* - * Handshaking is started manually in this example because - * PrintWriter catches all IOExceptions (including - * SSLExceptions), sets an internal error flag, and then - * returns without rethrowing the exception. - * - * Unfortunately, this means any error messages are lost, - * which caused lots of confusion for others using this - * code. The only way to tell there was an error is to call - * PrintWriter.checkError(). - */ - sslSocket.startHandshake(); - - return sslSocket; - - } - - static private void doTunnelHandshake(Socket tunnel, String host, int port) - throws IOException - { - /* - * The proxy may need an authorization string. Check - * standard https property. - */ - String proxyAuth = System.getProperty("https.proxyAuth"); - - String msg; - OutputStream out = tunnel.getOutputStream(); - - if (proxyAuth == null) - { - // Autherization not required - - msg = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n" - + "User-Agent: " - + sun.net.www.protocol.http.HttpURLConnection.userAgent - + "\r\n\r\n"; - } - else - { - // need to specify an authorization string in http header - msg = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n" - + "Proxy-Authorization: " + proxyAuth + "\r\n" - + "User-Agent: " - + sun.net.www.protocol.http.HttpURLConnection.userAgent - + "\r\n\r\n"; - } - - byte b[]; - try { - /* - * We really do want ASCII7 -- the http protocol doesn't change - * with locale. - */ - b = msg.getBytes("ASCII7"); - } catch (UnsupportedEncodingException ignored) { - /* - * If ASCII7 isn't there, something serious is wrong, but - * Paranoia Is Good (tm) - */ - b = msg.getBytes(); - } - out.write(b); - out.flush(); - - /* - * We need to store the reply so we can create a detailed - * error message to the user. - */ - byte reply[] = new byte[200]; - int replyLen = 0; - int newlinesSeen = 0; - boolean headerDone = false; /* Done on first newline */ - - InputStream in = tunnel.getInputStream(); - boolean error = false; - - while (newlinesSeen < 2) { - int i = in.read(); - if (i < 0) { - throw new IOException("Unexpected EOF from proxy"); - } - if (i == '\n') { - headerDone = true; - ++newlinesSeen; - } else if (i != '\r') { - newlinesSeen = 0; - if (!headerDone && replyLen < reply.length) { - reply[replyLen++] = (byte) i; - } - } - } - - /* - * Converting the byte array to a string is slightly wasteful - * in the case where the connection was successful, but it's - * insignificant compared to the network overhead. - */ - String replyStr; - try { - replyStr = new String(reply, 0, replyLen, "ASCII7"); - } catch (UnsupportedEncodingException ignored) { - replyStr = new String(reply, 0, replyLen); - } - - // Parse response, check for status code - StringTokenizer st = new StringTokenizer(replyStr); - st.nextToken(); // ignore version part - if (!st.nextToken().startsWith("200")) { - throw new IOException("Unable to tunnel through " - + tunnelHost + ":" + tunnelPort - + ". Proxy returns \"" + replyStr + "\""); - } - - /* tunneling Handshake was successful! */ - } + /** + * Builds an SSL socket, after auto-starting SSL. + * + * @param host The host to which to connect. + * @param port The port to which to connect. + * @param tunnelHost The host to which to tunnel through. + * @param tunnelPort The port to which to tunnel through. + * + * @return The socket. + */ + public static Socket buildSSLSocket(String host, int port, + String tunnelHost, int tunnelPort) + throws IOException, UnknownHostException { + + return buildSSLSocket(host, port, + tunnelHost, tunnelPort, + null); + } + + /** + * Builds an SSL socket, after auto-starting SSL. + * + * @param host The host to which to connect. + * @param port The port to which to connect. + * @param tunnelHost The host to which to tunnel through. + * @param tunnelPort The port to which to tunnel through. + * @param tcpNoDelay Whether or not to disable Nagling. + * + * @return The socket. + */ + public static Socket buildSSLSocket(String host, int port, + String tunnelHost, int tunnelPort, + Boolean tcpNoDelay) + throws IOException, UnknownHostException { + + SSLSocket sslSocket = null; + SSLSocketFactory factory = + (SSLSocketFactory)SSLSocketFactory.getDefault(); + + if (tunnelHost == null) { + sslSocket = (SSLSocket) factory.createSocket(host, port); + if (sslSocket != null && tcpNoDelay != null) + sslSocket.setTcpNoDelay(tcpNoDelay.booleanValue()); + } else { + /* + * If a proxy has been set... + * Set up a socket to do tunneling through the proxy. + * Start it off as a regular socket, then layer SSL + * over the top of it. + */ + Socket tunnel = doTunnelHandshake(tunnelHost, tunnelPort, + host, port, tcpNoDelay); + + // Overlay tunnel socket with SSL + sslSocket = (SSLSocket) factory.createSocket(tunnel, host, port, true); + if (sslSocket != null && tcpNoDelay != null) + sslSocket.setTcpNoDelay(tcpNoDelay.booleanValue()); + } + + /* + * Handshaking is started manually in this example because + * PrintWriter catches all IOExceptions (including + * SSLExceptions), sets an internal error flag, and then + * returns without rethrowing the exception. + * + * Unfortunately, this means any error messages are lost, + * which caused lots of confusion for others using this + * code. The only way to tell there was an error is to call + * PrintWriter.checkError(). + */ + sslSocket.startHandshake(); + + return sslSocket; + } + + static private Socket doTunnelHandshake(String tunnelHost, int tunnelPort, + String host, int port, + Boolean tcpNoDelay) + throws IOException { + + Socket tunnel = new Socket(tunnelHost, tunnelPort); + if (tunnel != null && tcpNoDelay != null) + tunnel.setTcpNoDelay(tcpNoDelay.booleanValue()); + + /* + * The proxy may need an authorization string. Check + * standard https property. + * + * TODO: pass this as a parameter. + */ + String proxyAuth = System.getProperty("https.proxyAuth"); + + String msg; + OutputStream out = tunnel.getOutputStream(); + + if (proxyAuth == null) { + // Authorization not required + + msg = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n" + + "User-Agent: " + + sun.net.www.protocol.http.HttpURLConnection.userAgent + + "\r\n\r\n"; + } else { + // need to specify an authorization string in http header + msg = "CONNECT " + host + ":" + port + " HTTP/1.0\r\n" + + "Proxy-Authorization: " + proxyAuth + "\r\n" + + "User-Agent: " + + sun.net.www.protocol.http.HttpURLConnection.userAgent + + "\r\n\r\n"; + } + + byte b[]; + try { + /* + * We really do want ASCII7 -- the http protocol doesn't change + * with locale. + */ + b = msg.getBytes("ASCII7"); + } catch (UnsupportedEncodingException ignored) { + /* + * If ASCII7 isn't there, something serious is wrong, but + * Paranoia Is Good (tm) + */ + b = msg.getBytes(); + } + out.write(b); + out.flush(); + + /* + * We need to store the reply so we can create a detailed + * error message to the user. + */ + byte reply[] = new byte[200]; + int replyLen = 0; + int newlinesSeen = 0; + boolean headerDone = false; /* Done on first newline */ + InputStream in = tunnel.getInputStream(); + boolean error = false; + + while (newlinesSeen < 2) { + int i = in.read(); + if (i < 0) { + throw new IOException("Unexpected EOF from proxy"); + } + if (i == '\n') { + headerDone = true; + ++newlinesSeen; + } else if (i != '\r') { + newlinesSeen = 0; + if (!headerDone && replyLen < reply.length) { + reply[replyLen++] = (byte) i; + } + } + } + + /* + * Converting the byte array to a string is slightly wasteful + * in the case where the connection was successful, but it's + * insignificant compared to the network overhead. + */ + String replyStr; + try { + replyStr = new String(reply, 0, replyLen, "ASCII7"); + } catch (UnsupportedEncodingException ignored) { + replyStr = new String(reply, 0, replyLen); + } + + // Parse response, check for status code + StringTokenizer st = new StringTokenizer(replyStr); + st.nextToken(); // ignore version part + if (!st.nextToken().startsWith("200")) { + throw new IOException("Unable to tunnel through " + + tunnelHost + ":" + tunnelPort + + ". Proxy returns \"" + replyStr + "\""); + } + + /* tunneling Handshake was successful! */ + return tunnel; + } } -
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>