You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@manifoldcf.apache.org by kw...@apache.org on 2010/03/05 11:33:58 UTC

svn commit: r919362 - in /incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient: HostConfiguration.java auth/NTLM.java auth/NTLMScheme.java params/HttpClientParams.java protocol/ProtocolFactory.java

Author: kwright
Date: Fri Mar  5 10:33:58 2010
New Revision: 919362

URL: http://svn.apache.org/viewvc?rev=919362&view=rev
Log:
Apply required LCF patches to httpclient 3x code stream.

Added:
    incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java   (with props)
Modified:
    incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java
    incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java
    incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java
    incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java

Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java?rev=919362&r1=919361&r2=919362&view=diff
==============================================================================
--- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java (original)
+++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/HostConfiguration.java Fri Mar  5 10:33:58 2010
@@ -31,7 +31,9 @@
 package org.apache.commons.httpclient;
 
 import org.apache.commons.httpclient.params.HostParams;
+import org.apache.commons.httpclient.params.HttpClientParams;
 import org.apache.commons.httpclient.protocol.Protocol;
+import org.apache.commons.httpclient.protocol.ProtocolFactory;
 import org.apache.commons.httpclient.util.LangUtils;
 
 import java.net.InetAddress;
@@ -250,7 +252,10 @@
      * @param protocol The protocol.
      */
     public synchronized void setHost(final String host, int port, final String protocol) {
-        this.host = new HttpHost(host, port, Protocol.getProtocol(protocol));
+        if (this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY) != null)
+            this.host = new HttpHost(host, port, ((ProtocolFactory)this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY)).getProtocol(protocol));
+        else
+            this.host = new HttpHost(host, port, Protocol.getProtocol(protocol));
     }
     
     /**
@@ -293,7 +298,7 @@
      * @param port The port
      */
     public synchronized void setHost(final String host, int port) {
-        setHost(host, port, Protocol.getProtocol("http"));
+        setHost(host, port, "http");
     }
     
     /**
@@ -302,7 +307,11 @@
      * @param host The host(IP or DNS name).
      */
     public synchronized void setHost(final String host) {
-        Protocol defaultProtocol = Protocol.getProtocol("http"); 
+        Protocol defaultProtocol;
+        if (this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY) != null)
+            defaultProtocol = ((ProtocolFactory)this.params.getParameter(HttpClientParams.PROTOCOL_FACTORY)).getProtocol("http");
+        else
+            defaultProtocol = Protocol.getProtocol("http"); 
         setHost(host, defaultProtocol.getDefaultPort(), defaultProtocol);
     }
     

Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java?rev=919362&r1=919361&r2=919362&view=diff
==============================================================================
--- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java (original)
+++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLM.java Fri Mar  5 10:33:58 2010
@@ -32,6 +32,9 @@
 
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.security.Key;
+import java.security.MessageDigest;
+
 import java.util.Locale;
 
 import javax.crypto.BadPaddingException;
@@ -52,9 +55,13 @@
  * exists for it.  This class is based upon the reverse engineering
  * efforts of a wide range of people.</p>
  *
+ * THIS IS A VERY HELPFUL REFERENCE: http://en.wikipedia.org/wiki/NTLM
+ *
  * <p>Please note that an implementation of JCE must be correctly installed and configured when
  * using NTLM support.</p>
  *
+ * NTLMv2 protocol description provided by Michael B Allen <jcifs at samba.org>
+ *
  * <p>This class should not be used externally to HttpClient as it's API is specifically
  * designed to work with HttpClient's use case, in particular it's connection management.</p>
  *
@@ -67,18 +74,52 @@
  */
 final class NTLM {
 
-    /** Character encoding */
-    public static final String DEFAULT_CHARSET = "ASCII";
+    // Flags we use
+    protected final static int FLAG_UNICODE_ENCODING = 0x00000001;
+    protected final static int FLAG_TARGET_DESIRED = 0x00000004;
+    protected final static int FLAG_NEGOTIATE_SIGN = 0x00000010;
+    protected final static int FLAG_NEGOTIATE_SEAL = 0x00000020;
+    protected final static int FLAG_NEGOTIATE_NTLM = 0x00000200;
+    protected final static int FLAG_NEGOTIATE_ALWAYS_SIGN = 0x00008000;
+    protected final static int FLAG_NEGOTIATE_NTLM2 = 0x00080000;
+    protected final static int FLAG_NEGOTIATE_128 = 0x20000000;
+    protected final static int FLAG_NEGOTIATE_KEY_EXCH = 0x40000000;
+
+    /** Secure random generator */
+    private static java.security.SecureRandom randomGenerator;
+    static
+    {
+	try
+	{
+		randomGenerator = java.security.SecureRandom.getInstance("SHA1PRNG");
+	}
+	catch (Exception e)
+	{
+		// If exception, nothing we can really do about it - can't even count on log being initialized
+		System.err.println("Couldn't initialize random generator: "+e.getMessage());
+		e.printStackTrace(System.err);
+	}
+    }
 
-    /** The current response */
-    private byte[] currentResponse;
+    /** Signature string for NTLM messages */
+    private static final String signatureString = "NTLMSSP";
 
-    /** The current position */
-    private int currentPosition = 0;
+    /** Character encoding */
+    public static final String DEFAULT_CHARSET = "ASCII";
 
     /** The character set to use for encoding the credentials */
     private String credentialCharset = DEFAULT_CHARSET;
     
+    /** The signature string as bytes in the default encoding */
+    private static byte[] signatureBytes;
+    static
+    {
+	byte[] bytesWithoutNull = EncodingUtil.getBytes(signatureString, "ASCII");
+	signatureBytes = new byte[bytesWithoutNull.length + 1];
+	System.arraycopy(bytesWithoutNull,0,signatureBytes,0,bytesWithoutNull.length);
+	signatureBytes[bytesWithoutNull.length] = (byte) 0x00;
+    }
+
     /**
      * Returns the response for the given message.
      *
@@ -98,469 +139,1268 @@
         if (message == null || message.trim().equals("")) {
             response = getType1Message(host, domain);
         } else {
-            response = getType3Message(username, password, host, domain,
-                    parseType2Message(message));
+	    Type2Message t2m = new Type2Message(message);
+	    response = getType3Message(username, password, host, domain,
+                t2m.getChallenge(), t2m.getFlags(), t2m.getTarget(), t2m.getTargetInfo());
         }
         return response;
     }
 
     /**
-     * Return the cipher for the specified key.
-     * @param key The key.
-     * @return Cipher The cipher.
-     * @throws AuthenticationException If the cipher cannot be retrieved.
-     */
-    private Cipher getCipher(byte[] key) throws AuthenticationException {
-        try {
-            final Cipher ecipher = Cipher.getInstance("DES/ECB/NoPadding");
-            key = setupKey(key);
-            ecipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "DES"));
-            return ecipher;
-        } catch (NoSuchAlgorithmException e) {
-            throw new AuthenticationException("DES encryption is not available.", e);
-        } catch (InvalidKeyException e) {
-            throw new AuthenticationException("Invalid key for DES encryption.", e);
-        } catch (NoSuchPaddingException e) {
-            throw new AuthenticationException(
-                "NoPadding option for DES is not available.", e);
-        }
+     * Creates the first message (type 1 message) in the NTLM authentication sequence.
+     * This message includes the user name, domain and host for the authentication session.
+     *
+     * @param host the computer name of the host requesting authentication.
+     * @param domain The domain to authenticate with.
+     * @return String the message to add to the HTTP request header.
+     */
+    public String getType1Message(String host, String domain)
+        throws AuthenticationException {
+        return new Type1Message(domain,host).getResponse();
     }
 
     /** 
-     * Adds parity bits to the key.
-     * @param key56 The key
-     * @return The modified key.
-     */
-    private byte[] setupKey(byte[] key56) {
-        byte[] key = new byte[8];
-        key[0] = (byte) ((key56[0] >> 1) & 0xff);
-        key[1] = (byte) ((((key56[0] & 0x01) << 6) 
-            | (((key56[1] & 0xff) >> 2) & 0xff)) & 0xff);
-        key[2] = (byte) ((((key56[1] & 0x03) << 5) 
-            | (((key56[2] & 0xff) >> 3) & 0xff)) & 0xff);
-        key[3] = (byte) ((((key56[2] & 0x07) << 4) 
-            | (((key56[3] & 0xff) >> 4) & 0xff)) & 0xff);
-        key[4] = (byte) ((((key56[3] & 0x0f) << 3) 
-            | (((key56[4] & 0xff) >> 5) & 0xff)) & 0xff);
-        key[5] = (byte) ((((key56[4] & 0x1f) << 2) 
-            | (((key56[5] & 0xff) >> 6) & 0xff)) & 0xff);
-        key[6] = (byte) ((((key56[5] & 0x3f) << 1) 
-            | (((key56[6] & 0xff) >> 7) & 0xff)) & 0xff);
-        key[7] = (byte) (key56[6] & 0x7f);
-        
-        for (int i = 0; i < key.length; i++) {
-            key[i] = (byte) (key[i] << 1);
-        }
-        return key;
+     * Creates the type 3 message using the given server nonce.  The type 3 message includes all the
+     * information for authentication, host, domain, username and the result of encrypting the
+     * nonce sent by the server using the user's password as the key.
+     *
+     * @param user The user name.  This should not include the domain name.
+     * @param password The password.
+     * @param host The host that is originating the authentication request.
+     * @param domain The domain to authenticate within.
+     * @param nonce the 8 byte array the server sent.
+     * @return The type 3 message.
+     * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
+     */
+    public String getType3Message(String user, String password,
+            String host, String domain, byte[] nonce, int type2Flags, String target, byte[] targetInformation)
+    throws AuthenticationException {
+        return new Type3Message(domain,host,user,password,nonce,type2Flags,target,targetInformation).getResponse();
     }
 
     /**
-     * Encrypt the data.
-     * @param key The key.
-     * @param bytes The data
-     * @return byte[] The encrypted data
-     * @throws HttpException If {@link Cipher.doFinal(byte[])} fails
+     * @return Returns the credentialCharset.
      */
-    private byte[] encrypt(byte[] key, byte[] bytes)
-        throws AuthenticationException {
-        Cipher ecipher = getCipher(key);
-        try {
-            byte[] enc = ecipher.doFinal(bytes);
-            return enc;
-        } catch (IllegalBlockSizeException e) {
-            throw new AuthenticationException("Invalid block size for DES encryption.", e);
-        } catch (BadPaddingException e) {
-            throw new AuthenticationException("Data not padded correctly for DES encryption.", e);
-        }
+    public String getCredentialCharset() {
+        return credentialCharset;
     }
 
     /** 
-     * Prepares the object to create a response of the given length.
-     * @param length the length of the response to prepare.
+     * @param credentialCharset The credentialCharset to set.
      */
-    private void prepareResponse(int length) {
-        currentResponse = new byte[length];
-        currentPosition = 0;
+    public void setCredentialCharset(String credentialCharset) {
+        this.credentialCharset = credentialCharset;
     }
 
-    /** 
-     * Adds the given byte to the response.
-     * @param b the byte to add.
-     */
-    private void addByte(byte b) {
-        currentResponse[currentPosition] = b;
-        currentPosition++;
+    /** Strip dot suffix from a name */
+    private static String stripDotSuffix(String value)
+    {
+	int index = value.indexOf(".");
+	if (index != -1)
+            return value.substring(0,index);
+	return value;
     }
 
-    /** 
-     * Adds the given bytes to the response.
-     * @param bytes the bytes to add.
-     */
-    private void addBytes(byte[] bytes) {
-        for (int i = 0; i < bytes.length; i++) {
-            currentResponse[currentPosition] = bytes[i];
-            currentPosition++;
-        }
+    /** Convert host to standard form */
+    private static String convertHost(String host)
+    {
+	return stripDotSuffix(host);
     }
 
-    /** 
-     * Returns the response that has been generated after shrinking the array if
-     * required and base64 encodes the response.
-     * @return The response as above.
-     */
-    private String getResponse() {
-        byte[] resp;
-        if (currentResponse.length > currentPosition) {
-            byte[] tmp = new byte[currentPosition];
-            for (int i = 0; i < currentPosition; i++) {
-                tmp[i] = currentResponse[i];
-            }
-            resp = tmp;
-        } else {
-            resp = currentResponse;
+    /** Convert domain to standard form */
+    private static String convertDomain(String domain)
+    {
+        return stripDotSuffix(domain);
+    }
+
+    private static int readULong(byte[] src, int index)
+        throws AuthenticationException {
+        if (src.length < index + 4)
+            throw new AuthenticationException("NTLM authentication - buffer too small for DWORD");
+        return (src[index] & 0xff) |
+                ((src[index + 1] & 0xff) << 8) |
+                ((src[index + 2] & 0xff) << 16) |
+                ((src[index + 3] & 0xff) << 24);
+    }
+
+    private static int readUShort(byte[] src, int index)
+        throws AuthenticationException {
+        if (src.length < index + 2)
+            throw new AuthenticationException("NTLM authentication - buffer too small for WORD");
+        return (src[index] & 0xff) | ((src[index + 1] & 0xff) << 8);
+    }
+
+    private static byte[] readSecurityBuffer(byte[] src, int index) 
+        throws AuthenticationException {
+        int length = readUShort(src, index);
+        int offset = readULong(src, index + 4);
+	if (src.length < offset + length)
+            throw new AuthenticationException("NTLM authentication - buffer too small for data item");
+        byte[] buffer = new byte[length];
+        System.arraycopy(src, offset, buffer, 0, length);
+        return buffer;
+    }
+
+    /** Calculate a challenge block */
+    private static byte[] makeRandomChallenge()
+    {
+        byte[] rval = new byte[8];
+        synchronized (randomGenerator)
+        {
+            randomGenerator.nextBytes(rval);
         }
-        return EncodingUtil.getAsciiString(Base64.encodeBase64(resp));
+        return rval;
+    }
+     
+    /** Calculate an NTLM2 challenge block */
+    private static byte[] makeNTLM2RandomChallenge()
+    {
+        byte[] rval = new byte[24];
+        synchronized (randomGenerator)
+        {
+            randomGenerator.nextBytes(rval);
+        }
+        // 8-byte challenge, padded with zeros to 24 bytes.
+        java.util.Arrays.fill(rval,8,24,(byte)0x00);
+        return rval;
     }
     
+    
     /**
-     * Creates the first message (type 1 message) in the NTLM authentication sequence.
-     * This message includes the user name, domain and host for the authentication session.
+     * Calculates the LM Response for the given challenge, using the specified
+     * password.
      *
-     * @param host the computer name of the host requesting authentication.
-     * @param domain The domain to authenticate with.
-     * @return String the message to add to the HTTP request header.
+     * @param password The user's password.
+     * @param challenge The Type 2 challenge from the server.
+     *
+     * @return The LM Response.
      */
-    public String getType1Message(String host, String domain) {
-        host = host.toUpperCase(Locale.ENGLISH);
-        domain = domain.toUpperCase(Locale.ENGLISH);
-        byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
-        byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
+    public static byte[] getLMResponse(String password, byte[] challenge)
+        throws AuthenticationException {
+        byte[] lmHash = lmHash(password);
+        return lmResponse(lmHash, challenge);
+    }
 
-        int finalLength = 32 + hostBytes.length + domainBytes.length;
-        prepareResponse(finalLength);
-        
-        // The initial id string.
-        byte[] protocol = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
-        addBytes(protocol);
-        addByte((byte) 0);
-
-        // Type
-        addByte((byte) 1);
-        addByte((byte) 0);
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // Flags
-        addByte((byte) 6);
-        addByte((byte) 82);
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // Domain length (first time).
-        int iDomLen = domainBytes.length;
-        byte[] domLen = convertShort(iDomLen);
-        addByte(domLen[0]);
-        addByte(domLen[1]);
-
-        // Domain length (second time).
-        addByte(domLen[0]);
-        addByte(domLen[1]);
-
-        // Domain offset.
-        byte[] domOff = convertShort(hostBytes.length + 32);
-        addByte(domOff[0]);
-        addByte(domOff[1]);
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // Host length (first time).
-        byte[] hostLen = convertShort(hostBytes.length);
-        addByte(hostLen[0]);
-        addByte(hostLen[1]);
-
-        // Host length (second time).
-        addByte(hostLen[0]);
-        addByte(hostLen[1]);
-
-        // Host offset (always 32).
-        byte[] hostOff = convertShort(32);
-        addByte(hostOff[0]);
-        addByte(hostOff[1]);
-        addByte((byte) 0);
-        addByte((byte) 0);
+    /**
+     * Calculates the NTLM Response for the given challenge, using the
+     * specified password.
+     *
+     * @param password The user's password.
+     * @param challenge The Type 2 challenge from the server.
+     *
+     * @return The NTLM Response.
+     */
+    public static byte[] getNTLMResponse(String password, byte[] challenge)
+        throws AuthenticationException {
+        byte[] ntlmHash = ntlmHash(password);
+        return lmResponse(ntlmHash, challenge);
+    }
 
-        // Host String.
-        addBytes(hostBytes);
+    /**
+     * Calculates the NTLMv2 Response for the given challenge, using the
+     * specified authentication target, username, password, target information
+     * block, and client challenge.
+     *
+     * @param target The authentication target (i.e., domain).
+     * @param user The username. 
+     * @param password The user's password.
+     * @param targetInformation The target information block from the Type 2
+     * message.
+     * @param challenge The Type 2 challenge from the server.
+     * @param clientChallenge The random 8-byte client challenge. 
+     *
+     * @return The NTLMv2 Response.
+     */
+    public static byte[] getNTLMv2Response(String target, String user,
+        String password, byte[] challenge,
+        byte[] clientChallenge, byte[] targetInformation)
+            throws AuthenticationException {
+        byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
+        byte[] blob = createBlob(clientChallenge, targetInformation);
+        return lmv2Response(ntlmv2Hash, challenge, blob);
+    }
 
-        // Domain String.
-        addBytes(domainBytes);
+    /**
+     * Calculates the LMv2 Response for the given challenge, using the
+     * specified authentication target, username, password, and client
+     * challenge.
+     *
+     * @param target The authentication target (i.e., domain).
+     * @param user The username.
+     * @param password The user's password.
+     * @param challenge The Type 2 challenge from the server.
+     * @param clientChallenge The random 8-byte client challenge.
+     *
+     * @return The LMv2 Response. 
+     */
+    public static byte[] getLMv2Response(String target, String user,
+        String password, byte[] challenge, byte[] clientChallenge)
+            throws AuthenticationException {
+        byte[] ntlmv2Hash = ntlmv2Hash(target, user, password);
+        return lmv2Response(ntlmv2Hash, challenge, clientChallenge);
+    }
 
-        return getResponse();
+    /**
+     * Calculates the NTLM2 Session Response for the given challenge, using the
+     * specified password and client challenge.
+     *
+     * @param password The user's password.
+     * @param challenge The Type 2 challenge from the server.
+     * @param clientChallenge The random 8-byte client challenge.
+     *
+     * @return The NTLM2 Session Response.  This is placed in the NTLM
+     * response field of the Type 3 message; the LM response field contains
+     * the client challenge, null-padded to 24 bytes.
+     */
+    public static byte[] getNTLM2SessionResponse(String password,
+        byte[] challenge, byte[] clientChallenge) throws AuthenticationException
+    {
+        try
+        {
+            byte[] ntlmHash = ntlmHash(password);
+
+            // Look up MD5 algorithm (was necessary on jdk 1.4.2)
+            // This used to be needed, but java 1.5.0_07 includes the MD5 algorithm (finally)
+            //Class x = Class.forName("gnu.crypto.hash.MD5");
+            //Method updateMethod = x.getMethod("update",new Class[]{byte[].class});
+            //Method digestMethod = x.getMethod("digest",new Class[0]);
+            //Object mdInstance = x.newInstance();
+            //updateMethod.invoke(mdInstance,new Object[]{challenge});
+            //updateMethod.invoke(mdInstance,new Object[]{clientChallenge});
+            //byte[] digest = (byte[])digestMethod.invoke(mdInstance,new Object[0]);
+	
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            md5.update(challenge);
+            md5.update(clientChallenge);
+            byte[] digest = md5.digest();
+	
+            byte[] sessionHash = new byte[8];
+            System.arraycopy(digest, 0, sessionHash, 0, 8);
+            return lmResponse(ntlmHash, sessionHash);
+        }
+        catch (Exception e)
+        {
+            if (e instanceof AuthenticationException)
+                throw (AuthenticationException)e;
+            throw new AuthenticationException(e.getMessage(),e);
+      `}
     }
 
-    /** 
-     * Extracts the server nonce out of the given message type 2.
-     * 
-     * @param message the String containing the base64 encoded message.
-     * @return an array of 8 bytes that the server sent to be used when
-     * hashing the password.
-     */
-    public byte[] parseType2Message(String message) {
-        // Decode the message first.
-        byte[] msg = Base64.decodeBase64(EncodingUtil.getBytes(message, DEFAULT_CHARSET));
-        byte[] nonce = new byte[8];
-        // The nonce is the 8 bytes starting from the byte in position 24.
-        for (int i = 0; i < 8; i++) {
-            nonce[i] = msg[i + 24];
+    /**
+     * Creates the LM Hash of the user's password.
+     *
+     * @param password The password.
+     *
+     * @return The LM Hash of the given password, used in the calculation
+     * of the LM Response.
+     */
+    private static byte[] lmHash(String password) throws AuthenticationException
+    {
+        try
+        {
+            byte[] oemPassword = password.toUpperCase().getBytes("US-ASCII");
+            int length = Math.min(oemPassword.length, 14);
+            byte[] keyBytes = new byte[14];
+            System.arraycopy(oemPassword, 0, keyBytes, 0, length);
+            Key lowKey = createDESKey(keyBytes, 0);
+            Key highKey = createDESKey(keyBytes, 7);
+            byte[] magicConstant = "KGS!@#$%".getBytes("US-ASCII");
+            Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
+            des.init(Cipher.ENCRYPT_MODE, lowKey);
+            byte[] lowHash = des.doFinal(magicConstant);
+            des.init(Cipher.ENCRYPT_MODE, highKey);
+            byte[] highHash = des.doFinal(magicConstant);
+            byte[] lmHash = new byte[16];
+            System.arraycopy(lowHash, 0, lmHash, 0, 8);
+            System.arraycopy(highHash, 0, lmHash, 8, 8);
+            return lmHash;
+        }
+        catch (Exception e)
+        {
+            throw new AuthenticationException(e.getMessage(),e);
         }
-        return nonce;
     }
 
-    /** 
-     * Creates the type 3 message using the given server nonce.  The type 3 message includes all the
-     * information for authentication, host, domain, username and the result of encrypting the
-     * nonce sent by the server using the user's password as the key.
+    /**
+     * Creates the NTLM Hash of the user's password.
      *
-     * @param user The user name.  This should not include the domain name.
      * @param password The password.
-     * @param host The host that is originating the authentication request.
-     * @param domain The domain to authenticate within.
-     * @param nonce the 8 byte array the server sent.
-     * @return The type 3 message.
-     * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails.
+     *
+     * @return The NTLM Hash of the given password, used in the calculation
+     * of the NTLM Response and the NTLMv2 and LMv2 Hashes.
      */
-    public String getType3Message(String user, String password,
-            String host, String domain, byte[] nonce)
-    throws AuthenticationException {
+    private static byte[] ntlmHash(String password) throws AuthenticationException
+    {
+        try
+        {
+            byte[] unicodePassword = password.getBytes("UnicodeLittleUnmarked");
+            MD4 md4 = new MD4();
+            md4.update(unicodePassword);
+            return md4.getOutput();
+        }
+        catch (java.io.UnsupportedEncodingException e)
+        {
+            throw new AuthenticationException("Unicode not supported: "+e.getMessage(),e);
+        }
+    }
 
-        int ntRespLen = 0;
-        int lmRespLen = 24;
-        domain = domain.toUpperCase(Locale.ENGLISH);
-        host = host.toUpperCase(Locale.ENGLISH);
-        user = user.toUpperCase(Locale.ENGLISH);
-        byte[] domainBytes = EncodingUtil.getBytes(domain, DEFAULT_CHARSET);
-        byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET);
-        byte[] userBytes = EncodingUtil.getBytes(user, credentialCharset);
-        int domainLen = domainBytes.length;
-        int hostLen = hostBytes.length;
-        int userLen = userBytes.length;
-        int finalLength = 64 + ntRespLen + lmRespLen + domainLen 
-            + userLen + hostLen;
-        prepareResponse(finalLength);
-        byte[] ntlmssp = EncodingUtil.getBytes("NTLMSSP", DEFAULT_CHARSET);
-        addBytes(ntlmssp);
-        addByte((byte) 0);
-        addByte((byte) 3);
-        addByte((byte) 0);
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // LM Resp Length (twice)
-        addBytes(convertShort(24));
-        addBytes(convertShort(24));
-
-        // LM Resp Offset
-        addBytes(convertShort(finalLength - 24));
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // NT Resp Length (twice)
-        addBytes(convertShort(0));
-        addBytes(convertShort(0));
-
-        // NT Resp Offset
-        addBytes(convertShort(finalLength));
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // Domain length (twice)
-        addBytes(convertShort(domainLen));
-        addBytes(convertShort(domainLen));
-        
-        // Domain offset.
-        addBytes(convertShort(64));
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // User Length (twice)
-        addBytes(convertShort(userLen));
-        addBytes(convertShort(userLen));
-
-        // User offset
-        addBytes(convertShort(64 + domainLen));
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // Host length (twice)
-        addBytes(convertShort(hostLen));
-        addBytes(convertShort(hostLen));
-
-        // Host offset
-        addBytes(convertShort(64 + domainLen + userLen));
-
-        for (int i = 0; i < 6; i++) {
-            addByte((byte) 0);
-        }
-
-        // Message length
-        addBytes(convertShort(finalLength));
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        // Flags
-        addByte((byte) 6);
-        addByte((byte) 82);
-        addByte((byte) 0);
-        addByte((byte) 0);
-
-        addBytes(domainBytes);
-        addBytes(userBytes);
-        addBytes(hostBytes);
-        addBytes(hashPassword(password, nonce));
-        return getResponse();
+    /**
+     * Creates the NTLMv2 Hash of the user's password.
+     *
+     * @param target The authentication target (i.e., domain).
+     * @param user The username.
+     * @param password The password.
+     *
+     * @return The NTLMv2 Hash, used in the calculation of the NTLMv2
+     * and LMv2 Responses. 
+     */
+    private static byte[] ntlmv2Hash(String target, String user,
+        String password) throws AuthenticationException
+    {
+        try
+        {
+            byte[] ntlmHash = ntlmHash(password);
+            HMACMD5 hmacMD5 = new HMACMD5(ntlmHash);
+            // Upper case username, mixed case target!!
+            hmacMD5.update(user.toUpperCase().getBytes("UnicodeLittleUnmarked"));
+            hmacMD5.update(target.getBytes("UnicodeLittleUnmarked"));
+            return hmacMD5.getOutput();
+        }
+        catch (java.io.UnsupportedEncodingException e)
+        {
+            throw new AuthenticationException("Unicode not supported! "+e.getMessage(),e);
+        }
+    }
+
+    /**
+     * Creates the LM Response from the given hash and Type 2 challenge.
+     *
+     * @param hash The LM or NTLM Hash.
+     * @param challenge The server challenge from the Type 2 message.
+     *
+     * @return The response (either LM or NTLM, depending on the provided
+     * hash).
+     */
+    private static byte[] lmResponse(byte[] hash, byte[] challenge)
+        throws AuthenticationException
+    {
+        try
+        {
+            byte[] keyBytes = new byte[21];
+            System.arraycopy(hash, 0, keyBytes, 0, 16);
+            Key lowKey = createDESKey(keyBytes, 0);
+            Key middleKey = createDESKey(keyBytes, 7);
+            Key highKey = createDESKey(keyBytes, 14);
+            Cipher des = Cipher.getInstance("DES/ECB/NoPadding");
+            des.init(Cipher.ENCRYPT_MODE, lowKey);
+            byte[] lowResponse = des.doFinal(challenge);
+            des.init(Cipher.ENCRYPT_MODE, middleKey);
+            byte[] middleResponse = des.doFinal(challenge);
+            des.init(Cipher.ENCRYPT_MODE, highKey);
+            byte[] highResponse = des.doFinal(challenge);
+            byte[] lmResponse = new byte[24];
+            System.arraycopy(lowResponse, 0, lmResponse, 0, 8);
+            System.arraycopy(middleResponse, 0, lmResponse, 8, 8);
+            System.arraycopy(highResponse, 0, lmResponse, 16, 8);
+            return lmResponse;
+        }
+        catch (Exception e)
+        {
+            throw new AuthenticationException(e.getMessage(),e);
+        }
+    }
+
+    /**
+     * Creates the LMv2 Response from the given hash, client data, and
+     * Type 2 challenge.
+     *
+     * @param hash The NTLMv2 Hash.
+     * @param clientData The client data (blob or client challenge).
+     * @param challenge The server challenge from the Type 2 message.
+     *
+     * @return The response (either NTLMv2 or LMv2, depending on the
+     * client data).
+     */
+    private static byte[] lmv2Response(byte[] hash, byte[] challenge,
+        byte[] clientData) throws AuthenticationException
+    {
+        HMACMD5 hmacMD5 = new HMACMD5(hash);
+        hmacMD5.update(challenge);
+        hmacMD5.update(clientData);
+        byte[] mac = hmacMD5.getOutput();
+        byte[] lmv2Response = new byte[mac.length + clientData.length];
+        System.arraycopy(mac, 0, lmv2Response, 0, mac.length);
+        System.arraycopy(clientData, 0, lmv2Response, mac.length,
+            clientData.length);
+        return lmv2Response;
+    }
+
+    /**
+     * Creates the NTLMv2 blob from the given target information block and
+     * client challenge.
+     *
+     * @param targetInformation The target information block from the Type 2
+     * message.
+     * @param clientChallenge The random 8-byte client challenge.
+     *
+     * @return The blob, used in the calculation of the NTLMv2 Response.
+     */
+    private static byte[] createBlob(byte[] clientChallenge, byte[] targetInformation) {
+        byte[] blobSignature = new byte[] {
+            (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00
+        };
+        byte[] reserved = new byte[] {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+        };
+        byte[] unknown1 = new byte[] {
+            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
+        };
+        long time = System.currentTimeMillis();
+        time += 11644473600000l; // milliseconds from January 1, 1601 -> epoch.
+        time *= 10000; // tenths of a microsecond.
+        // convert to little-endian byte array.
+        byte[] timestamp = new byte[8];
+        for (int i = 0; i < 8; i++) {
+            timestamp[i] = (byte) time;
+            time >>>= 8;
+        }
+        byte[] blob = new byte[blobSignature.length + reserved.length+ timestamp.length + 8 +
+            unknown1.length + targetInformation.length];
+        int offset = 0;
+        System.arraycopy(blobSignature, 0, blob, offset, blobSignature.length);
+        offset += blobSignature.length;
+        System.arraycopy(reserved, 0, blob, offset, reserved.length);
+        offset += reserved.length;
+        System.arraycopy(timestamp, 0, blob, offset, timestamp.length);
+        offset += timestamp.length;
+        System.arraycopy(clientChallenge, 0, blob, offset,8);
+        offset += 8;
+        System.arraycopy(unknown1, 0, blob, offset, unknown1.length);
+        offset += unknown1.length;
+        System.arraycopy(targetInformation, 0, blob, offset,
+            targetInformation.length);
+        return blob;
     }
 
     /** 
-     * Creates the LANManager and NT response for the given password using the
-     * given nonce.
-     * @param password the password to create a hash for.
-     * @param nonce the nonce sent by the server.
-     * @return The response.
-     * @throws HttpException If {@link #encrypt(byte[],byte[])} fails.
+     * Creates a DES encryption key from the given key material.
+     *
+     * @param bytes A byte array containing the DES key material.
+     * @param offset The offset in the given byte array at which
+     * the 7-byte key material starts.
+     *
+     * @return A DES encryption key created from the key material
+     * starting at the specified offset in the given byte array.
      */
-    private byte[] hashPassword(String password, byte[] nonce)
-        throws AuthenticationException {
-        byte[] passw = EncodingUtil.getBytes(password.toUpperCase(Locale.ENGLISH), credentialCharset);
-        byte[] lmPw1 = new byte[7];
-        byte[] lmPw2 = new byte[7];
+    private static Key createDESKey(byte[] bytes, int offset) {
+        byte[] keyBytes = new byte[7];
+        System.arraycopy(bytes, offset, keyBytes, 0, 7);
+        byte[] material = new byte[8];
+        material[0] = keyBytes[0];
+        material[1] = (byte) (keyBytes[0] << 7 | (keyBytes[1] & 0xff) >>> 1);
+        material[2] = (byte) (keyBytes[1] << 6 | (keyBytes[2] & 0xff) >>> 2);
+        material[3] = (byte) (keyBytes[2] << 5 | (keyBytes[3] & 0xff) >>> 3);
+        material[4] = (byte) (keyBytes[3] << 4 | (keyBytes[4] & 0xff) >>> 4);
+        material[5] = (byte) (keyBytes[4] << 3 | (keyBytes[5] & 0xff) >>> 5);
+        material[6] = (byte) (keyBytes[5] << 2 | (keyBytes[6] & 0xff) >>> 6);
+        material[7] = (byte) (keyBytes[6] << 1);
+        oddParity(material);
+        return new SecretKeySpec(material, "DES");
+    }
 
-        int len = passw.length;
-        if (len > 7) {
-            len = 7;
+    /**
+     * Applies odd parity to the given byte array.
+     *
+     * @param bytes The data whose parity bits are to be adjusted for
+     * odd parity.
+     */
+    private static void oddParity(byte[] bytes) {
+        for (int i = 0; i < bytes.length; i++) {
+            byte b = bytes[i];
+            boolean needsParity = (((b >>> 7) ^ (b >>> 6) ^ (b >>> 5) ^
+                                    (b >>> 4) ^ (b >>> 3) ^ (b >>> 2) ^
+                                    (b >>> 1)) & 0x01) == 0;
+            if (needsParity) {
+                bytes[i] |= (byte) 0x01;
+            } else {
+                bytes[i] &= (byte) 0xfe;
+            }
         }
+    }
+    
+    /** NTLM message generation, base class */
+    protected static class NTLMMessage
+    {
+        /** The current response */
+        private byte[] messageContents = null;
+
+        /** The current output position */
+        private int currentOutputPosition = 0;
+
+        /** Constructor to use when message contents are not yet known */
+        public NTLMMessage()
+        {
+        }
+	
+        /** Constructor to use when message contents are known */
+        public NTLMMessage(String messageBody, int expectedType)
+            throws AuthenticationException
+        {
+            messageContents = Base64.decodeBase64(EncodingUtil.getBytes(messageBody, DEFAULT_CHARSET));
+            // Look for NTLM message
+            if (messageContents.length < signatureBytes.length)
+                throw new AuthenticationException("NTLM message decoding error - packet too short");
+            int i = 0;
+            while (i < signatureBytes.length)
+            {
+                if (messageContents[i] != signatureBytes[i])
+                    throw new AuthenticationException("NTLM message expected - instead got unrecognized bytes");
+                i++;
+            }
 
-        int idx;
-        for (idx = 0; idx < len; idx++) {
-            lmPw1[idx] = passw[idx];
+            // Check to be sure there's a type 2 message indicator next
+            int type = readULong(signatureBytes.length);
+            if (type != expectedType)
+                throw new AuthenticationException("NTLM type "+Integer.toString(expectedType)+" message expected - instead got type "+Integer.toString(type));
+		
+            currentOutputPosition = messageContents.length;
+        }
+
+        /** Get the length of the signature and flags, so calculations can adjust offsets accordingly.
+        */
+        protected int getPreambleLength()
+        {
+            return signatureBytes.length + 4;
+        }
+	
+        /** Get the message length */
+        protected int getMessageLength()
+        {
+            return currentOutputPosition;
         }
-        for (; idx < 7; idx++) {
-            lmPw1[idx] = (byte) 0;
+        
+        /** Read a byte from a position within the message buffer */
+        protected byte readByte(int position) throws AuthenticationException
+        {
+            if (messageContents.length < position + 1)
+                throw new AuthenticationException("NTLM: Message too short");
+            return messageContents[position];
+        }
+
+        /** Read a bunch of bytes from a position in the message buffer */
+        protected void readBytes(byte[] buffer, int position)
+            throws AuthenticationException
+        {
+            if (messageContents.length < position + buffer.length)
+                throw new AuthenticationException("NTLM: Message too short");
+            System.arraycopy(messageContents,position,buffer,0,buffer.length);
+        }
+	
+        /** Read a ushort from a position within the message buffer */
+        protected int readUShort(int position) throws AuthenticationException
+        {
+            return NTLM.readUShort(messageContents,position);
+        }
+	
+        /** Read a ulong from a position within the message buffer */
+        protected int readULong(int position) throws AuthenticationException
+        {
+            return NTLM.readULong(messageContents,position);
+        }
+	
+        /** Read a security buffer from a position within the message buffer */
+        protected byte[] readSecurityBuffer(int position) throws AuthenticationException
+        {
+            return NTLM.readSecurityBuffer(messageContents,position);
+        }
+	
+        /** 
+        * Prepares the object to create a response of the given length.
+        * @param length the maximum length of the response to prepare, not including the type and the signature (which this method adds).
+        */
+        protected void prepareResponse(int maxlength, int messageType) {
+            messageContents = new byte[maxlength];
+            currentOutputPosition = 0;
+            addBytes(signatureBytes);
+            addULong(messageType);
+        }
+
+        /** 
+        * Adds the given byte to the response.
+        * @param b the byte to add.
+        */
+        protected void addByte(byte b) {
+            messageContents[currentOutputPosition] = b;
+            currentOutputPosition++;
+        }
+
+        /** 
+        * Adds the given bytes to the response.
+        * @param bytes the bytes to add.
+        */
+        protected void addBytes(byte[] bytes) {
+            for (int i = 0; i < bytes.length; i++) {
+                messageContents[currentOutputPosition] = bytes[i];
+                currentOutputPosition++;
+            }
         }
 
-        len = passw.length;
-        if (len > 14) {
-            len = 14;
-        }
-        for (idx = 7; idx < len; idx++) {
-            lmPw2[idx - 7] = passw[idx];
+        /** Adds a USHORT to the response */
+        protected void addUShort(int value) {
+            addByte((byte) (value & 0xff));
+            addByte((byte) (value >> 8 & 0xff));
+        }
+	
+        /** Adds a ULong to the response */
+        protected void addULong(int value) {
+            addByte((byte) (value & 0xff));
+            addByte((byte) (value >> 8 & 0xff));
+            addByte((byte) (value >> 16 & 0xff));
+            addByte((byte) (value >> 24 & 0xff));
+        }
+
+        /** 
+        * Returns the response that has been generated after shrinking the array if
+        * required and base64 encodes the response.
+        * @return The response as above.
+        */
+        public String getResponse() {
+            byte[] resp;
+            if (messageContents.length > currentOutputPosition) {
+                byte[] tmp = new byte[currentOutputPosition];
+                for (int i = 0; i < currentOutputPosition; i++) {
+                    tmp[i] = messageContents[i];
+                }
+                resp = tmp;
+            } else {
+                resp = messageContents;
+            }
+            return EncodingUtil.getAsciiString(Base64.encodeBase64(resp));
         }
-        for (; idx < 14; idx++) {
-            lmPw2[idx - 7] = (byte) 0;
+
+    }
+    
+    /** Type 1 message assembly class */
+    public static class Type1Message extends NTLMMessage
+    {
+	protected byte[] hostBytes;
+	protected byte[] domainBytes;
+
+        /** Constructor.  Include the arguments the message will need */
+        public Type1Message(String domain, String host)
+	    throws AuthenticationException
+        {
+            super();
+            try
+            {
+                // Strip off domain name from the host!
+                host = convertHost(host);
+                // Use only the base domain name!
+                domain = convertDomain(domain);
+	
+                hostBytes = host.getBytes("UnicodeLittleUnmarked");
+                domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked");
+            }
+            catch (java.io.UnsupportedEncodingException e)
+            {
+                throw new AuthenticationException("Unicode unsupported: "+e.getMessage(),e);
+            }
         }
+	
+        /** Getting the response involves building the message before returning it */
+        public String getResponse()
+        {
+            // Now, build the message.  Calculate its length first, including signature or type.
+            int finalLength = 32 + hostBytes.length + domainBytes.length;
+            
+            // Set up the response.  This will initialize the signature, message type, and flags.
+            prepareResponse(finalLength,1);
+		
+            // Flags.  These are the complete set of flags we support.
+            addULong(FLAG_NEGOTIATE_NTLM |
+                FLAG_NEGOTIATE_NTLM2 |
+                FLAG_NEGOTIATE_SIGN |
+                FLAG_NEGOTIATE_SEAL |
+                /* FLAG_NEGOTIATE_ALWAYS_SIGN |
+                    FLAG_NEGOTIATE_KEY_EXCH | */
+                FLAG_UNICODE_ENCODING |
+                FLAG_TARGET_DESIRED |
+                FLAG_NEGOTIATE_128);
+
+            // Domain length (two times).
+            addUShort(domainBytes.length);
+            addUShort(domainBytes.length);
+
+            // Domain offset.
+            addULong(hostBytes.length + 32);
+
+            // Host length (two times).
+            addUShort(hostBytes.length);
+            addUShort(hostBytes.length);
+
+            // Host offset (always 32).
+            addULong(32);
+		
+            // Host String.
+            addBytes(hostBytes);
 
-        // Create LanManager hashed Password
-        byte[] magic = {
-            (byte) 0x4B, (byte) 0x47, (byte) 0x53, (byte) 0x21, 
-            (byte) 0x40, (byte) 0x23, (byte) 0x24, (byte) 0x25
-        };
+            // Domain String.
+            addBytes(domainBytes);
 
-        byte[] lmHpw1;
-        lmHpw1 = encrypt(lmPw1, magic);
+            return super.getResponse();
+        }
 
-        byte[] lmHpw2 = encrypt(lmPw2, magic);
+    }
 
-        byte[] lmHpw = new byte[21];
-        for (int i = 0; i < lmHpw1.length; i++) {
-            lmHpw[i] = lmHpw1[i];
-        }
-        for (int i = 0; i < lmHpw2.length; i++) {
-            lmHpw[i + 8] = lmHpw2[i];
+    /** Type 2 message class */
+    public static class Type2Message extends NTLMMessage
+    {
+        protected byte[] challenge;
+        protected String target;
+        protected byte[] targetInfo;
+        protected int flags;
+
+        public Type2Message(String message)
+	    throws AuthenticationException
+        {
+            super(message,2);
+		
+            // Parse out the rest of the info we need from the message
+            // The nonce is the 8 bytes starting from the byte in position 24.
+            challenge = new byte[8];
+            readBytes(challenge,24);
+
+            flags = readULong(20);
+            if ((flags & FLAG_UNICODE_ENCODING) == 0)
+                throw new AuthenticationException("NTLM type 2 message has flags that make no sense: "+Integer.toString(flags));
+            // Do the target!
+            target = null;
+            // The TARGET_DESIRED flag is said to not have understood semantics in Type2 messages, so use the length of the packet to decide
+            // how to proceed instead
+            if (getMessageLength() >= 12 + 8)
+            {
+                byte[] bytes = readSecurityBuffer(12);
+                if (bytes.length != 0)
+                {
+                    try
+                    {
+                        target = new String(bytes,"UnicodeLittleUnmarked");
+                    }
+                    catch (java.io.UnsupportedEncodingException e)
+                    {
+                        throw new AuthenticationException(e.getMessage(),e);
+                    }
+                }
+            }
+
+            // Do the target info!
+            targetInfo = null;
+            // TARGET_DESIRED flag cannot be relied on, so use packet length
+            if (getMessageLength() >= 40 + 8)
+            {
+                byte[] bytes = readSecurityBuffer(40);
+                if (bytes.length != 0)
+                {
+                    targetInfo = bytes;
+                }
+            }
         }
-        for (int i = 0; i < 5; i++) {
-            lmHpw[i + 16] = (byte) 0;
+	
+        /** Retrieve the challenge */
+        public byte[] getChallenge()
+        {
+            return challenge;
+        }
+	
+        /** Retrieve the target */
+        public String getTarget()
+        {
+            return target;
+        }
+	
+        /** Retrieve the target info */
+        public byte[] getTargetInfo()
+        {
+            return targetInfo;
+        }
+	
+        /** Retrieve the response flags */
+        public int getFlags()
+        {
+            return flags;
         }
 
-        // Create the responses.
-        byte[] lmResp = new byte[24];
-        calcResp(lmHpw, nonce, lmResp);
-
-        return lmResp;
     }
 
-    /** 
-     * Takes a 21 byte array and treats it as 3 56-bit DES keys.  The 8 byte
-     * plaintext is encrypted with each key and the resulting 24 bytes are
-     * stored in the results array.
-     * 
-     * @param keys The keys.
-     * @param plaintext The plain text to encrypt.
-     * @param results Where the results are stored.
-     * @throws AuthenticationException If {@link #encrypt(byte[],byte[])} fails.
-     */
-    private void calcResp(byte[] keys, byte[] plaintext, byte[] results)
-        throws AuthenticationException {
-        byte[] keys1 = new byte[7];
-        byte[] keys2 = new byte[7];
-        byte[] keys3 = new byte[7];
-        for (int i = 0; i < 7; i++) {
-            keys1[i] = keys[i];
-        }
+    /** Type 3 message assembly class */
+    public static class Type3Message extends NTLMMessage
+    {
+        // Response flags from the type2 message
+        protected int type2Flags;
+
+        protected byte[] domainBytes;
+        protected byte[] hostBytes;
+        protected byte[] userBytes;
+	
+        protected byte[] lmResp;
+        protected byte[] ntResp;
+
+        /** Constructor.  Pass the arguments we will need */
+        public Type3Message(String domain, String host, String user, String password,
+            byte[] nonce, int type2Flags, String target, byte[] targetInformation)
+	    throws AuthenticationException
+        {
+            // Save the flags
+            this.type2Flags = type2Flags;
+		
+            // Strip off domain name from the host!
+            host = convertHost(host);
+            // Use only the base domain name!
+            domain = convertDomain(domain);
+
+            // Use the new code to calculate the responses, including v2 if that seems warranted.
+            try
+            {
+                if (targetInformation != null && target != null)
+                {
+                    byte[] clientChallenge = makeRandomChallenge();
+                    ntResp = getNTLMv2Response(target,user,password,nonce,clientChallenge,targetInformation);
+                    lmResp = getLMv2Response(target,user,password,nonce,clientChallenge);
+                }
+                else
+                {
+                    if ((type2Flags & FLAG_NEGOTIATE_NTLM2) != 0)
+                    {
+                        // NTLM2 session stuff is requested
+                        byte[] clientChallenge = makeNTLM2RandomChallenge();
+				
+                        ntResp = getNTLM2SessionResponse(password,nonce,clientChallenge);
+                        lmResp = clientChallenge;
+				
+                        // All the other flags we send (signing, sealing, key exchange) are supported, but they don't do anything at all in an
+                        // NTLM2 context!  So we're done at this point.
+                    }
+                    else
+                    {
+                        ntResp = getNTLMResponse(password,nonce);
+                        lmResp = getLMResponse(password,nonce);
+                    }
+                }
+            }
+            catch (AuthenticationException e)
+            {
+                // This likely means we couldn't find the MD4 hash algorithm - fail back to just using LM
+                ntResp = new byte[0];
+                lmResp = getLMResponse(password,nonce);
+            }
 
-        for (int i = 0; i < 7; i++) {
-            keys2[i] = keys[i + 7];
+            try
+            {
+                domainBytes = domain.toUpperCase().getBytes("UnicodeLittleUnmarked");
+                hostBytes = host.getBytes("UnicodeLittleUnmarked");
+                userBytes = user.getBytes("UnicodeLittleUnmarked");
+            }
+            catch (java.io.UnsupportedEncodingException e)
+            {
+                throw new AuthenticationException("Unicode not supported: "+e.getMessage(),e);
+            }
         }
+	
+        /** Assemble the response */
+        public String getResponse()
+        {
+            int ntRespLen = ntResp.length;
+            int lmRespLen = lmResp.length;
+
+            int domainLen = domainBytes.length;
+            int hostLen = hostBytes.length;
+            int userLen = userBytes.length;
+		    
+            // Calculate the layout within the packet
+            int lmRespOffset = 64;
+            int ntRespOffset = lmRespOffset + lmRespLen;
+            int domainOffset = ntRespOffset + ntRespLen;
+            int userOffset = domainOffset + domainLen;
+            int hostOffset = userOffset + userLen;
+            int sessionKeyOffset = hostOffset + hostLen;
+            int finalLength = sessionKeyOffset + 0;
+		
+            // Start the response.  Length includes signature and type
+            prepareResponse(finalLength,3);
+		
+            // LM Resp Length (twice)
+            addUShort(lmRespLen);
+            addUShort(lmRespLen);
+
+            // LM Resp Offset
+            addULong(lmRespOffset);
+
+            // NT Resp Length (twice)
+            addUShort(ntRespLen);
+            addUShort(ntRespLen);
+
+            // NT Resp Offset
+            addULong(ntRespOffset);
+
+            // Domain length (twice)
+            addUShort(domainLen);
+            addUShort(domainLen);
+		
+            // Domain offset.
+            addULong(domainOffset);
+
+            // User Length (twice)
+            addUShort(userLen);
+            addUShort(userLen);
+
+            // User offset
+            addULong(userOffset);
+
+            // Host length (twice)
+            addUShort(hostLen);
+            addUShort(hostLen);
+
+            // Host offset
+            addULong(hostOffset);
+		
+            // 4 bytes of zeros - not sure what this is
+            addULong(0);
+
+            // Message length
+            addULong(finalLength);
+		
+            // Flags.  Currently:  NEGOTIATE_NTLM + UNICODE_ENCODING + TARGET_DESIRED + NEGOTIATE_128
+            addULong(FLAG_NEGOTIATE_NTLM | FLAG_UNICODE_ENCODING | FLAG_TARGET_DESIRED | FLAG_NEGOTIATE_128 |
+                (type2Flags & FLAG_NEGOTIATE_NTLM2) |
+                (type2Flags & FLAG_NEGOTIATE_SIGN) |
+                (type2Flags & FLAG_NEGOTIATE_SEAL) |
+                (type2Flags & FLAG_NEGOTIATE_KEY_EXCH) |
+                (type2Flags & FLAG_NEGOTIATE_ALWAYS_SIGN));
+
+            // Add the actual data
+            addBytes(lmResp);
+            addBytes(ntResp);
+            addBytes(domainBytes);
+            addBytes(userBytes);
+            addBytes(hostBytes);
 
-        for (int i = 0; i < 7; i++) {
-            keys3[i] = keys[i + 14];
+            return super.getResponse();
         }
-        byte[] results1 = encrypt(keys1, plaintext);
+    }
 
-        byte[] results2 = encrypt(keys2, plaintext);
+    protected static void writeULong(byte[] buffer, int value, int offset)
+    {
+        buffer[offset] = (byte) (value & 0xff);
+        buffer[offset+1] = (byte) (value >> 8 & 0xff);
+        buffer[offset+2] = (byte) (value >> 16 & 0xff);
+        buffer[offset+3] = (byte) (value >> 24 & 0xff);
+    }
+    
+    protected static int F(int x, int y, int z) {
+        return((x & y) | (~x & z));
+    }
+    protected static int G(int x, int y, int z) {
+        return((x & y) | (x & z) | (y & z));
+    }
+    protected static int H(int x, int y, int z) {
+        return(x ^ y ^ z);
+    }
 
-        byte[] results3 = encrypt(keys3, plaintext);
+    protected static int rotintlft(int val, int numbits) {
+        return((val << numbits) | (val >>> (32 - numbits)));
+    }
 
-        for (int i = 0; i < 8; i++) {
-            results[i] = results1[i];
-        }
-        for (int i = 0; i < 8; i++) {
-            results[i + 8] = results2[i];
+    /** Cryptography support - MD4.
+    * The following class was based loosely on the RFC and on code found at http://www.cs.umd.edu/~harry/jotp/src/md.java.
+    * Code correctness was verified by looking at MD4.java from the jcifs library (http://jcifs.samba.org).
+    * It was massaged extensively to the final form found here by Karl Wright (kwright@metacarta.com).
+    */
+    protected static class MD4
+    {
+        protected int A = 0x67452301;
+        protected int B = 0xefcdab89;
+        protected int C = 0x98badcfe;
+        protected int D = 0x10325476;
+        protected long count = 0L;
+        protected byte[] dataBuffer = new byte[64];
+	
+        public MD4()
+        {
+        }
+	
+        public void update(byte[] input)
+        {
+            // We always deal with 512 bits at a time.  Correspondingly, there is a buffer 64 bytes long that we write data into until it gets full.
+            int curBufferPos = (int)(count & 63L);
+            int inputIndex = 0;
+            while (input.length - inputIndex + curBufferPos >= dataBuffer.length)
+            {
+                // We have enough data to do the next step.  Do a partial copy and a transform, updating inputIndex and curBufferPos accordingly
+                int transferAmt = dataBuffer.length - curBufferPos;
+                System.arraycopy(input,inputIndex,dataBuffer,curBufferPos,transferAmt);
+                count += transferAmt;
+                curBufferPos = 0;
+                inputIndex += transferAmt;
+                processBuffer();
+            }
+		
+            // If there's anything left, copy it into the buffer and leave it.  We know there's not enough left to process.
+            if (inputIndex < input.length)
+            {
+                int transferAmt = input.length - inputIndex;
+                System.arraycopy(input,inputIndex,dataBuffer,curBufferPos,transferAmt);
+                count += transferAmt;
+                curBufferPos += transferAmt;
+            }
         }
-        for (int i = 0; i < 8; i++) {
-            results[i + 16] = results3[i];
+	
+        public byte[] getOutput()
+        {
+            // Feed pad/length data into engine.  This must round out the input to a multiple of 512 bits.
+            int bufferIndex = (int)(count & 63L);
+            int padLen = (bufferIndex < 56) ? (56 - bufferIndex) : (120 - bufferIndex);
+            byte[] postBytes = new byte[padLen + 8];
+            // Leading 0x80, specified amount of zero padding, then length in bits.
+            postBytes[0] = (byte)0x80;
+            // Fill out the last 8 bytes with the length
+            for (int i = 0; i < 8; i++)
+            {
+                postBytes[padLen + i] = (byte)((count * 8) >>> (8 * i));
+            }
+		
+            // Update the engine
+            update(postBytes);
+
+            // Calculate final result
+            byte[] result = new byte[16];
+            writeULong(result,A,0);
+            writeULong(result,B,4);
+            writeULong(result,C,8);
+            writeULong(result,D,12);
+            return result;
+        }
+
+        protected void processBuffer()
+        {
+            // Convert current buffer to 16 ulongs
+            int[] d = new int[16];
+
+            for (int i = 0; i < 16; i++)
+            {
+                d[i] = (dataBuffer[i*4] & 0xff) + ((dataBuffer[i*4+1] & 0xff) << 8) + 
+                    ((dataBuffer[i*4+2] & 0xff) << 16) + ((dataBuffer[i*4+3] & 0xff) << 24);
+            }
+		
+            // Do a round of processing
+            int AA = A; int BB = B; int CC = C; int DD = D;
+            round1(d);
+            round2(d);
+            round3(d);
+            A += AA; B+= BB; C+= CC; D+= DD;
+
+        }
+
+        protected void round1(int[] d) {
+            A = rotintlft((A + F(B, C, D) + d[0]), 3);
+            D = rotintlft((D + F(A, B, C) + d[1]), 7);
+            C = rotintlft((C + F(D, A, B) + d[2]), 11);
+            B = rotintlft((B + F(C, D, A) + d[3]), 19);
+
+            A = rotintlft((A + F(B, C, D) + d[4]), 3);
+            D = rotintlft((D + F(A, B, C) + d[5]), 7);
+            C = rotintlft((C + F(D, A, B) + d[6]), 11);
+            B = rotintlft((B + F(C, D, A) + d[7]), 19);
+
+            A = rotintlft((A + F(B, C, D) + d[8]), 3);
+            D = rotintlft((D + F(A, B, C) + d[9]), 7);
+            C = rotintlft((C + F(D, A, B) + d[10]), 11);
+            B = rotintlft((B + F(C, D, A) + d[11]), 19);
+
+            A = rotintlft((A + F(B, C, D) + d[12]), 3);
+            D = rotintlft((D + F(A, B, C) + d[13]), 7);
+            C = rotintlft((C + F(D, A, B) + d[14]), 11);
+            B = rotintlft((B + F(C, D, A) + d[15]), 19);
+	}
+	    
+        protected void round2(int[] d) {
+            A = rotintlft((A + G(B, C, D) + d[0] + 0x5a827999), 3);
+            D = rotintlft((D + G(A, B, C) + d[4] + 0x5a827999), 5);
+            C = rotintlft((C + G(D, A, B) + d[8] + 0x5a827999), 9);
+            B = rotintlft((B + G(C, D, A) + d[12] + 0x5a827999), 13);
+
+            A = rotintlft((A + G(B, C, D) + d[1] + 0x5a827999), 3);
+            D = rotintlft((D + G(A, B, C) + d[5] + 0x5a827999), 5);
+            C = rotintlft((C + G(D, A, B) + d[9] + 0x5a827999), 9);
+            B = rotintlft((B + G(C, D, A) + d[13] + 0x5a827999), 13);
+
+            A = rotintlft((A + G(B, C, D) + d[2] + 0x5a827999), 3);
+            D = rotintlft((D + G(A, B, C) + d[6] + 0x5a827999), 5);
+            C = rotintlft((C + G(D, A, B) + d[10] + 0x5a827999), 9);
+            B = rotintlft((B + G(C, D, A) + d[14] + 0x5a827999), 13);
+
+            A = rotintlft((A + G(B, C, D) + d[3] + 0x5a827999), 3);
+            D = rotintlft((D + G(A, B, C) + d[7] + 0x5a827999), 5);
+            C = rotintlft((C + G(D, A, B) + d[11] + 0x5a827999), 9);
+            B = rotintlft((B + G(C, D, A) + d[15] + 0x5a827999), 13);
+
+        }
+
+        protected void round3(int[] d) {
+            A = rotintlft((A + H(B, C, D) + d[0] + 0x6ed9eba1), 3);
+            D = rotintlft((D + H(A, B, C) + d[8] + 0x6ed9eba1), 9);
+            C = rotintlft((C + H(D, A, B) + d[4] + 0x6ed9eba1), 11);
+            B = rotintlft((B + H(C, D, A) + d[12] + 0x6ed9eba1), 15);
+
+            A = rotintlft((A + H(B, C, D) + d[2] + 0x6ed9eba1), 3);
+            D = rotintlft((D + H(A, B, C) + d[10] + 0x6ed9eba1), 9);
+            C = rotintlft((C + H(D, A, B) + d[6] + 0x6ed9eba1), 11);
+            B = rotintlft((B + H(C, D, A) + d[14] + 0x6ed9eba1), 15);
+
+            A = rotintlft((A + H(B, C, D) + d[1] + 0x6ed9eba1), 3);
+            D = rotintlft((D + H(A, B, C) + d[9] + 0x6ed9eba1), 9);
+            C = rotintlft((C + H(D, A, B) + d[5] + 0x6ed9eba1), 11);
+            B = rotintlft((B + H(C, D, A) + d[13] + 0x6ed9eba1), 15);
+
+            A = rotintlft((A + H(B, C, D) + d[3] + 0x6ed9eba1), 3);
+            D = rotintlft((D + H(A, B, C) + d[11] + 0x6ed9eba1), 9);
+            C = rotintlft((C + H(D, A, B) + d[7] + 0x6ed9eba1), 11);
+            B = rotintlft((B + H(C, D, A) + d[15] + 0x6ed9eba1), 15);
+
         }
+
     }
 
-    /** 
-     * Converts a given number to a two byte array in little endian order.
-     * @param num the number to convert.
-     * @return The byte representation of <i>num</i> in little endian order.
-     */
-    private byte[] convertShort(int num) {
-        byte[] val = new byte[2];
-        String hex = Integer.toString(num, 16);
-        while (hex.length() < 4) {
-            hex = "0" + hex;
-        }
-        String low = hex.substring(2, 4);
-        String high = hex.substring(0, 2);
-
-        val[0] = (byte) Integer.parseInt(low, 16);
-        val[1] = (byte) Integer.parseInt(high, 16);
-        return val;
+    /** Cryptography support - HMACMD5 - algorithmically based on various web resources by Karl Wright */
+    protected static class HMACMD5
+    {
+        protected byte[] ipad;
+        protected byte[] opad;
+        protected MessageDigest md5;
+
+        public HMACMD5(byte[] key)
+            throws AuthenticationException
+        {
+            try
+            {
+                md5 = MessageDigest.getInstance("MD5");
+            }
+            catch (Exception ex)
+            {
+                // Umm, the algorithm doesn't exist - throw an AuthenticationException!
+                throw new AuthenticationException("Error getting md5 message digest implementation: "+ex.getMessage(),ex);
+            }
+
+            // Initialize the pad buffers with the key
+            ipad = new byte[64];
+            opad = new byte[64];
+
+            int keyLength = key.length;
+            if (keyLength > 64)
+            {
+                // Use MD5 of the key instead, as described in RFC 2104
+                md5.update(key);
+                key = md5.digest();
+                keyLength = key.length;
+            }
+            int i = 0;
+            while (i < keyLength)
+            {
+                ipad[i] = (byte) (key[i] ^ (byte)0x36);
+                opad[i] = (byte) (key[i] ^ (byte)0x5c);
+                i++;
+            }
+            while (i < 64)
+            {
+                ipad[i] = (byte)0x36;
+                opad[i] = (byte)0x5c;
+                i++;
+            }
+	    
+            // Very important: update the digest with the ipad buffer
+            md5.reset();
+            md5.update(ipad);
+
+        }
+	
+        /** Grab the current digest.  This is the "answer". */
+        public byte[] getOutput()
+        {
+            byte[] digest = md5.digest();
+            md5.update(opad);
+            return md5.digest(digest);
+        }
+	
+        /** Update by adding a complete array */
+        public void update(byte[] input)
+        {
+            md5.update(input);
+        }
+	
+        /** Update the algorithm */
+        public void update(byte[] input, int offset, int length)
+        {
+            md5.update(input,offset,length);
+        }
+	
     }
     
-    /**
-     * @return Returns the credentialCharset.
-     */
-    public String getCredentialCharset() {
-        return credentialCharset;
+    /* Run test suite */
+    public static void main(String[] args)
+        throws Exception
+    {
+        // MD4 test suite:
+        checkMD4("","31d6cfe0d16ae931b73c59d7e0c089c0");
+        checkMD4("a","bde52cb31de33e46245e05fbdbd6fb24");
+        checkMD4("abc","a448017aaf21d8525fc10ae87aa6729d");
+        checkMD4("message digest","d9130a8164549fe818874806e1c7014b");
+        checkMD4("abcdefghijklmnopqrstuvwxyz","d79e1c308aa5bbcdeea8ed63df412da9");
+        checkMD4("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
+            "043f8582f241db351ce627e153e7f0e4");
+        checkMD4("12345678901234567890123456789012345678901234567890123456789012345678901234567890",
+            "e33b4ddc9c38f2199c3e7b164fcc0536");
+	    
+        System.out.println("Tests pass");
     }
-
-    /**
-     * @param credentialCharset The credentialCharset to set.
-     */
-    public void setCredentialCharset(String credentialCharset) {
-        this.credentialCharset = credentialCharset;
+   
+    /* Test suite helper */
+    protected static byte checkToNibble(char c)
+    {
+        if (c >= 'a' && c <= 'f')
+            return (byte)(c - 'a' + 0x0a);
+        return (byte)(c - '0');
+    }
+    
+    /*Test suite helper */
+    protected static byte[] checkToBytes(String hex)
+    {
+        byte[] rval = new byte[hex.length()/2];
+        int i = 0;
+        while (i < rval.length)
+        {
+            rval[i] = (byte)((checkToNibble(hex.charAt(i*2)) << 4) | (checkToNibble(hex.charAt(i*2+1))));
+            i++;
+        }
+        return rval;
+    }
+    
+    /* Test suite MD4 helper */
+    protected static void checkMD4(String input, String hexOutput)
+        throws Exception
+    {
+        MD4 md4;
+        md4 = new MD4 ();
+        md4.update(input.getBytes("ASCII"));
+        byte[] answer = md4.getOutput();
+        byte[] correctAnswer = checkToBytes(hexOutput);
+        if (answer.length != correctAnswer.length)
+            throw new Exception("Answer length disagrees for MD4('"+input+"')");
+        int i = 0;
+        while (i < answer.length)
+        {
+            if (answer[i] != correctAnswer[i])
+                throw new Exception("Answer value for MD4('"+input+"') disagrees at position "+Integer.toString(i));
+            i++;
+        }
     }
 
 }

Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java?rev=919362&r1=919361&r2=919362&view=diff
==============================================================================
--- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java (original)
+++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java Fri Mar  5 10:33:58 2010
@@ -336,18 +336,23 @@
         NTLM ntlm = new NTLM();
         ntlm.setCredentialCharset(method.getParams().getCredentialCharset());
         String response = null;
+
         if (this.state == INITIATED || this.state == FAILED) {
             response = ntlm.getType1Message(
                 ntcredentials.getHost(), 
                 ntcredentials.getDomain());
             this.state = TYPE1_MSG_GENERATED;
         } else {
+	    NTLM.Type2Message t2m = new NTLM.Type2Message(this.ntlmchallenge);
             response = ntlm.getType3Message(
                 ntcredentials.getUserName(), 
                 ntcredentials.getPassword(),
                 ntcredentials.getHost(), 
                 ntcredentials.getDomain(),
-                ntlm.parseType2Message(this.ntlmchallenge));
+                t2m.getChallenge(),
+		t2m.getFlags(),
+		t2m.getTarget(),
+		t2m.getTargetInfo());
             this.state = TYPE3_MSG_GENERATED;
         }
         return "NTLM " + response;

Modified: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java?rev=919362&r1=919361&r2=919362&view=diff
==============================================================================
--- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java (original)
+++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/params/HttpClientParams.java Fri Mar  5 10:33:58 2010
@@ -81,6 +81,14 @@
      */
     public static final String REJECT_RELATIVE_REDIRECT = "http.protocol.reject-relative-redirect"; 
 
+    /**
+     * Supplies a ProtocolFactory object, for custom protocol support even across redirections.
+     * <p>
+     * This parameter expects a value of type {@link ProtocolFactory}.
+     * </p>
+     */
+    public static final String PROTOCOL_FACTORY = "http.protocol.factory"; 
+
     /** 
      * Defines the maximum number of redirects to be followed. 
      * The limit on number of redirects is intended to prevent infinite loops. 

Added: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java
URL: http://svn.apache.org/viewvc/incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java?rev=919362&view=auto
==============================================================================
--- incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java (added)
+++ incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java Fri Mar  5 10:33:58 2010
@@ -0,0 +1,150 @@
+/*
+ * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/protocol/Protocol.java,v 1.10 2004/04/18 23:51:38 jsdever Exp $
+ * $Revision: 157457 $
+ * $Date: 2005-03-14 15:23:16 -0500 (Mon, 14 Mar 2005) $
+ *
+ * ====================================================================
+ *
+ *  Copyright 2002-2004 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * 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
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.commons.httpclient.protocol;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.httpclient.util.LangUtils;
+
+/**
+ * A class to encapsulate the specifics of a protocol.  This class class also
+ * provides the ability to customize the set and characteristics of the
+ * protocols used.
+ * 
+ * <p>One use case for modifying the default set of protocols would be to set a
+ * custom SSL socket factory.  This would look something like the following:
+ * <pre> 
+ * Protocol myHTTPS = new Protocol( "https", new MySSLSocketFactory(), 443 );
+ * 
+ * Protocol.registerProtocol( "https", myHTTPS );
+ * </pre>
+ *
+ * @author Michael Becke 
+ * @author Jeff Dever
+ * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
+ *  
+ * @since 2.0 
+ */
+public class ProtocolFactory {
+
+    /** The available protocols */
+    private Map PROTOCOLS = Collections.synchronizedMap(new HashMap());
+
+    /**
+     * Registers a new protocol with the given identifier.  If a protocol with
+     * the given ID already exists it will be overridden.  This ID is the same
+     * one used to retrieve the protocol from getProtocol(String).
+     * 
+     * @param id the identifier for this protocol
+     * @param protocol the protocol to register
+     * 
+     * @see #getProtocol(String)
+     */
+    public void registerProtocol(String id, Protocol protocol) {
+
+        if (id == null) {
+            throw new IllegalArgumentException("id is null");
+        }
+        if (protocol == null) {
+            throw new IllegalArgumentException("protocol is null");
+        }
+
+        PROTOCOLS.put(id, protocol);
+    }
+
+    /**
+     * Unregisters the protocol with the given ID.
+     * 
+     * @param id the ID of the protocol to remove
+     */
+    public void unregisterProtocol(String id) {
+
+        if (id == null) {
+            throw new IllegalArgumentException("id is null");
+        }
+
+        PROTOCOLS.remove(id);
+    }
+
+    /**
+     * Gets the protocol with the given ID.
+     * 
+     * @param id the protocol ID
+     * 
+     * @return Protocol a protocol
+     * 
+     * @throws IllegalStateException if a protocol with the ID cannot be found
+     */
+    public Protocol getProtocol(String id) 
+        throws IllegalStateException {
+
+        if (id == null) {
+            throw new IllegalArgumentException("id is null");
+        }
+
+        Protocol protocol = (Protocol) PROTOCOLS.get(id);
+
+        if (protocol == null) {
+            protocol = lazyRegisterProtocol(id);
+        }
+
+        return protocol;
+    } 
+
+    /**
+     * Lazily registers the protocol with the given id.
+     * 
+     * @param id the protocol ID
+     * 
+     * @return the lazily registered protocol
+     * 
+     * @throws IllegalStateException if the protocol with id is not recognized
+     */
+    private Protocol lazyRegisterProtocol(String id) 
+        throws IllegalStateException {
+
+        if ("http".equals(id)) {
+            final Protocol http 
+                = new Protocol("http", DefaultProtocolSocketFactory.getSocketFactory(), 80);
+            Protocol.registerProtocol("http", http);
+            return http;
+        }
+
+        if ("https".equals(id)) {
+            final Protocol https 
+                = new Protocol("https", SSLProtocolSocketFactory.getSocketFactory(), 443);
+            Protocol.registerProtocol("https", https);
+            return https;
+        }
+
+        throw new IllegalStateException("unsupported protocol: '" + id + "'");
+    }
+}

Propchange: incubator/lcf/trunk/upstream/commons-httpclient-3x/src/java/org/apache/commons/httpclient/protocol/ProtocolFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native