/*
 * $Header: /home/cvs/jakarta-slide/src/webdav/client/src/org/apache/commons/httpclient/Authenticator.java,v 1.2 2002/02/09 16:20:21 dirkv Exp $
 * $Revision: 1.2 $
 * $Date: 2002/02/09 16:20:21 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.commons.httpclient;

/**
 * Authenticate helper.
 *
 * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
 * @author Robert Owen
 */
public class Authenticator {


    // ----------------------------------------------------- Instance Variables


    /**
     * Base 64 encoder.
     */
    protected static Base64 base64 = new Base64();


    // ------------------------------------------------------------- Properties


    /**
     * Generate a response to the given challenge.
     *
     * @param state State
     * @param credentials Credentials to use to answser the challenge
     * @return String response to the challenge
     */
    public static String challengeResponse(HttpMethod method, State state,
                                           Credentials credentials)
        throws HttpException {

        if (credentials == null)
            throw new HttpException(HttpException.NO_CREDENTIALS_GIVEN);

        String challenge = state.getAuthenticateToken();
        if (challenge == null)
            return null;

        int space = challenge.indexOf(' ');
        if (space < 0)
            return null;

        String challengeName = challenge.substring(0, space);

        if (challengeName.equalsIgnoreCase("basic")) {
            return basic(state, credentials);
        } else if (challengeName.equalsIgnoreCase("digest")) {
            return digest(method, state, credentials);
        } else {

        }
        return null;

    }


    /**
     * Generate a basic response.
     *
     * @param credentials Credentials to use to answser the challenge
     */
    public static String basic(State state, Credentials credentials) {

        String authString = credentials.getUserName() + ":"
            + credentials.getPassword();
        return "Basic " + new String(base64.encode(authString.getBytes()));

    }


    /**
     * Generate a digest response.
     *
     * @param credentials Credentials to use to answser the challenge
     */
    public static String digest(HttpMethod method, State state, Credentials credentials) {
		// FIXME / TODO
		// 1. Only supports md5 algorithm.
		// 2. Fixed client nonce and nonce count values.
		// 3. Assumes credentials passed to Authenticator are values
		//    to be used for requested realm.
 
        int p1, p2;
        String realm      = "";
        String nonce      = "";
        String CNonce     = "0a4f113b";
        String algorithm  = "";
        String nonceCount = "00000001";
        String qop        = "";
        String domain     = "";
        String opaque     = "";

        String user       = credentials.getUserName();
        String pw         = credentials.getPassword();
        String methodName = method.getName();
        String uri        = method.getPath();

        String challenge = state.getAuthenticateToken();
        if (challenge == null) return null;

        java.security.MessageDigest md5;
        try {
            md5 = java.security.MessageDigest.getInstance("MD5");
        } catch (java.security.NoSuchAlgorithmException nsa) {
            return null;
        }

        // Assumes that none of the field values has a comma
        java.util.StringTokenizer st = new java.util.StringTokenizer( challenge.substring("digest ".length()), "," );
        String aField, aFieldName, aFieldValue;
        int equals;
        while (st.hasMoreTokens())
        {
            aField = st.nextToken();
            equals = aField.indexOf('=');
            if (equals < 0) continue;

            aFieldName = aField.substring(0,equals).trim();
            aFieldValue = aField.substring(equals+1);
            if (aFieldValue.indexOf('"') >= 0)
            {
                aFieldValue = aFieldValue.replace('"', ' ');
                aFieldValue = aFieldValue.trim();
            }

            if (aFieldName.equalsIgnoreCase("realm"))          realm     = aFieldValue;
            else if (aFieldName.equalsIgnoreCase("domain"))    domain    = aFieldValue;
            else if (aFieldName.equalsIgnoreCase("nonce"))     nonce     = aFieldValue;
            else if (aFieldName.equalsIgnoreCase("algorithm")) algorithm = aFieldValue;
            else if (aFieldName.equalsIgnoreCase("qop"))       qop       = aFieldValue;
            else if (aFieldName.equalsIgnoreCase("opaque"))    opaque    = aFieldValue;
        }
        if (!algorithm.equalsIgnoreCase("md5")) return null;

        MD5Encoder md5enc = new MD5Encoder();
        String A1 = user+":"+realm+":"+pw;
        byte[] HA1 = md5.digest(A1.getBytes());
        String HA1HEX = md5enc.encode(HA1);

        String A2 = methodName+":"+uri;
        byte[] HA2 = md5.digest(A2.getBytes());
        String HA2HEX = md5enc.encode(HA2);

        String request_digest_String = HA1HEX+":"+nonce+":"+nonceCount+":"+CNonce+":"+qop+":"+HA2HEX;
        byte[] request_digest_bytes = md5.digest(request_digest_String.getBytes());
        String request_digest = md5enc.encode(request_digest_bytes);

        StringBuffer retStr = new StringBuffer("Digest username=\""+user+"\",realm=\""+realm+
                        "\",nonce=\""+nonce+"\",uri=\""+uri+"\"");
        if (!qop.equals(""))
        {
            retStr.append(",qop=");
            retStr.append(qop);
            retStr.append(",nc=\"");
            retStr.append(nonceCount);
            retStr.append("\",cnonce=\"");
            retStr.append(CNonce);
            retStr.append("\"");
        }
        retStr.append(",response=\"");
        retStr.append(request_digest);
        retStr.append("\"");
        if (!opaque.equals(""))retStr.append(",opaque=\""+opaque+"\"");
        return retStr.toString();
    }

}

