/* **************************************************************************
 * $OpenLDAP: /com/novell/sasl/client/TokenParser.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
 *
 * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 ******************************************************************************/
package com.novell.sasl.client;

import org.apache.harmony.javax.security.sasl.*;

/**
 * The TokenParser class will parse individual tokens from a list of tokens that
 * are a directive value for a DigestMD5 authentication.The tokens are separated
 * commas.
 */
class TokenParser extends Object {
    private static final int STATE_LOOKING_FOR_FIRST_TOKEN = 1;
    private static final int STATE_LOOKING_FOR_TOKEN = 2;
    private static final int STATE_SCANNING_TOKEN = 3;
    private static final int STATE_LOOKING_FOR_COMMA = 4;
    private static final int STATE_PARSING_ERROR = 5;
    private static final int STATE_DONE = 6;

    private int m_curPos;
    private int m_scanStart;
    private int m_state;
    private String m_tokens;


    TokenParser(
            String tokens) {
        m_tokens = tokens;
        m_curPos = 0;
        m_scanStart = 0;
        m_state = STATE_LOOKING_FOR_FIRST_TOKEN;
    }

    /**
     * This function parses the next token from the tokens string and returns
     * it as a string. If there are no more tokens a null reference is returned.
     *
     * @return the parsed token or a null reference if there are no more
     * tokens
     * @throws SASLException if an error occurs while parsing
     */
    String parseToken() throws SaslException {
        char currChar;
        String token = null;


        if (m_state == STATE_DONE)
            return null;

        while (m_curPos < m_tokens.length() && (token == null)) {
            currChar = m_tokens.charAt(m_curPos);
            switch (m_state) {
                case STATE_LOOKING_FOR_FIRST_TOKEN:
                case STATE_LOOKING_FOR_TOKEN:
                    if (isWhiteSpace(currChar)) {
                        break;
                    } else if (isValidTokenChar(currChar)) {
                        m_scanStart = m_curPos;
                        m_state = STATE_SCANNING_TOKEN;
                    } else {
                        m_state = STATE_PARSING_ERROR;
                        throw new SaslException("Invalid token character at position " + m_curPos);
                    }
                    break;

                case STATE_SCANNING_TOKEN:
                    if (isValidTokenChar(currChar)) {
                        break;
                    } else if (isWhiteSpace(currChar)) {
                        token = m_tokens.substring(m_scanStart, m_curPos);
                        m_state = STATE_LOOKING_FOR_COMMA;
                    } else if (',' == currChar) {
                        token = m_tokens.substring(m_scanStart, m_curPos);
                        m_state = STATE_LOOKING_FOR_TOKEN;
                    } else {
                        m_state = STATE_PARSING_ERROR;
                        throw new SaslException("Invalid token character at position " + m_curPos);
                    }
                    break;


                case STATE_LOOKING_FOR_COMMA:
                    if (isWhiteSpace(currChar))
                        break;
                    else if (currChar == ',')
                        m_state = STATE_LOOKING_FOR_TOKEN;
                    else {
                        m_state = STATE_PARSING_ERROR;
                        throw new SaslException("Expected a comma, found '" +
                                currChar + "' at postion " +
                                m_curPos);
                    }
                    break;
            }
            m_curPos++;
        } /* end while loop */

        if (token == null) {    /* check the ending state */
            switch (m_state) {
                case STATE_SCANNING_TOKEN:
                    token = m_tokens.substring(m_scanStart);
                    m_state = STATE_DONE;
                    break;

                case STATE_LOOKING_FOR_FIRST_TOKEN:
                case STATE_LOOKING_FOR_COMMA:
                    break;

                case STATE_LOOKING_FOR_TOKEN:
                    throw new SaslException("Trialing comma");
            }
        }

        return token;
    }

    /**
     * This function returns TRUE if the character is a valid token character.
     * <p/>
     * token          = 1*<any CHAR except CTLs or separators>
     * <p/>
     * separators     = "(" | ")" | "<" | ">" | "@"
     * | "," | ";" | ":" | "\" | <">
     * | "/" | "[" | "]" | "?" | "="
     * | "{" | "}" | SP | HT
     * <p/>
     * CTL            = <any US-ASCII control character
     * (octets 0 - 31) and DEL (127)>
     * <p/>
     * CHAR           = <any US-ASCII character (octets 0 - 127)>
     *
     * @param c character to be validated
     * @return True if character is valid Token character else it returns
     * false
     */
    boolean isValidTokenChar(
            char c) {
        if (((c >= '\u0000') && (c <= '\u0020')) ||
                ((c >= '\u003a') && (c <= '\u0040')) ||
                ((c >= '\u005b') && (c <= '\u005d')) ||
                ('\u002c' == c) ||
                ('\u0025' == c) ||
                ('\u0028' == c) ||
                ('\u0029' == c) ||
                ('\u007b' == c) ||
                ('\u007d' == c) ||
                ('\u007f' == c))
            return false;

        return true;
    }

    /**
     * This function returns TRUE if the character is linear white space (LWS).
     * LWS = [CRLF] 1*( SP | HT )
     *
     * @param c character to be validated
     * @return True if character is liner whitespace else it returns false
     */
    boolean isWhiteSpace(
            char c) {
        if (('\t' == c) || // HORIZONTAL TABULATION.
                ('\n' == c) || // LINE FEED.
                ('\r' == c) || // CARRIAGE RETURN.
                ('\u0020' == c))
            return true;

        return false;
    }

}

