1/* **************************************************************************
2 * $OpenLDAP: /com/novell/sasl/client/DirectiveList.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
3 *
4 * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
5 *
6 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
7 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
8 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
9 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
10 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
11 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
12 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
13 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
14 ******************************************************************************/
15package com.novell.sasl.client;
16
17import java.util.*;
18import org.apache.harmony.javax.security.sasl.*;
19import java.io.UnsupportedEncodingException;
20
21/**
22 * Implements the DirectiveList class whihc will be used by the
23 * DigestMD5SaslClient class
24 */
25class DirectiveList extends Object
26{
27    private static final int STATE_LOOKING_FOR_FIRST_DIRECTIVE  = 1;
28    private static final int STATE_LOOKING_FOR_DIRECTIVE        = 2;
29    private static final int STATE_SCANNING_NAME                = 3;
30    private static final int STATE_LOOKING_FOR_EQUALS            = 4;
31    private static final int STATE_LOOKING_FOR_VALUE            = 5;
32    private static final int STATE_LOOKING_FOR_COMMA            = 6;
33    private static final int STATE_SCANNING_QUOTED_STRING_VALUE    = 7;
34    private static final int STATE_SCANNING_TOKEN_VALUE            = 8;
35    private static final int STATE_NO_UTF8_SUPPORT              = 9;
36
37    private int        m_curPos;
38    private int        m_errorPos;
39    private String     m_directives;
40    private int        m_state;
41    private ArrayList  m_directiveList;
42    private String     m_curName;
43    private int        m_scanStart;
44
45    /**
46     *  Constructs a new DirectiveList.
47     */
48     DirectiveList(
49        byte[] directives)
50    {
51        m_curPos = 0;
52        m_state = STATE_LOOKING_FOR_FIRST_DIRECTIVE;
53        m_directiveList = new ArrayList(10);
54        m_scanStart = 0;
55        m_errorPos = -1;
56        try
57        {
58            m_directives = new String(directives, "UTF-8");
59        }
60        catch(UnsupportedEncodingException e)
61        {
62            m_state = STATE_NO_UTF8_SUPPORT;
63        }
64    }
65
66    /**
67     * This function takes a US-ASCII character string containing a list of comma
68     * separated directives, and parses the string into the individual directives
69     * and their values. A directive consists of a token specifying the directive
70     * name followed by an equal sign (=) and the directive value. The value is
71     * either a token or a quoted string
72     *
73     * @exception SaslException  If an error Occurs
74     */
75    void parseDirectives() throws SaslException
76    {
77        char        prevChar;
78        char        currChar;
79        int            rc = 0;
80        boolean        haveQuotedPair = false;
81        String      currentName = "<no name>";
82
83        if (m_state == STATE_NO_UTF8_SUPPORT)
84            throw new SaslException("No UTF-8 support on platform");
85
86        prevChar = 0;
87
88        while (m_curPos < m_directives.length())
89        {
90            currChar = m_directives.charAt(m_curPos);
91            switch (m_state)
92            {
93            case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
94            case STATE_LOOKING_FOR_DIRECTIVE:
95                if (isWhiteSpace(currChar))
96                {
97                    break;
98                }
99                else if (isValidTokenChar(currChar))
100                {
101                    m_scanStart = m_curPos;
102                    m_state = STATE_SCANNING_NAME;
103                }
104                else
105                {
106                     m_errorPos = m_curPos;
107                    throw new SaslException("Parse error: Invalid name character");
108                }
109                break;
110
111            case STATE_SCANNING_NAME:
112                if (isValidTokenChar(currChar))
113                {
114                    break;
115                }
116                else if (isWhiteSpace(currChar))
117                {
118                    currentName = m_directives.substring(m_scanStart, m_curPos);
119                    m_state = STATE_LOOKING_FOR_EQUALS;
120                }
121                else if ('=' == currChar)
122                {
123                    currentName = m_directives.substring(m_scanStart, m_curPos);
124                    m_state = STATE_LOOKING_FOR_VALUE;
125                }
126                else
127                {
128                     m_errorPos = m_curPos;
129                    throw new SaslException("Parse error: Invalid name character");
130                }
131                break;
132
133            case STATE_LOOKING_FOR_EQUALS:
134                if (isWhiteSpace(currChar))
135                {
136                    break;
137                }
138                else if ('=' == currChar)
139                {
140                    m_state = STATE_LOOKING_FOR_VALUE;
141                }
142                else
143                {
144                    m_errorPos = m_curPos;
145                    throw new SaslException("Parse error: Expected equals sign '='.");
146                }
147                break;
148
149            case STATE_LOOKING_FOR_VALUE:
150                if (isWhiteSpace(currChar))
151                {
152                    break;
153                }
154                else if ('"' == currChar)
155                {
156                    m_scanStart = m_curPos+1; /* don't include the quote */
157                    m_state = STATE_SCANNING_QUOTED_STRING_VALUE;
158                }
159                else if (isValidTokenChar(currChar))
160                {
161                    m_scanStart = m_curPos;
162                    m_state = STATE_SCANNING_TOKEN_VALUE;
163                }
164                else
165                {
166                    m_errorPos = m_curPos;
167                    throw new SaslException("Parse error: Unexpected character");
168                }
169                break;
170
171            case STATE_SCANNING_TOKEN_VALUE:
172                if (isValidTokenChar(currChar))
173                {
174                    break;
175                }
176                else if (isWhiteSpace(currChar))
177                {
178                    addDirective(currentName, false);
179                    m_state = STATE_LOOKING_FOR_COMMA;
180                }
181                else if (',' == currChar)
182                {
183                    addDirective(currentName, false);
184                    m_state = STATE_LOOKING_FOR_DIRECTIVE;
185                }
186                else
187                {
188                     m_errorPos = m_curPos;
189                    throw new SaslException("Parse error: Invalid value character");
190                }
191                break;
192
193            case STATE_SCANNING_QUOTED_STRING_VALUE:
194                if ('\\' == currChar)
195                    haveQuotedPair = true;
196                if ( ('"' == currChar) &&
197                     ('\\' != prevChar) )
198                {
199                    addDirective(currentName, haveQuotedPair);
200                    haveQuotedPair = false;
201                    m_state = STATE_LOOKING_FOR_COMMA;
202                }
203                break;
204
205            case STATE_LOOKING_FOR_COMMA:
206                if (isWhiteSpace(currChar))
207                    break;
208                else if (currChar == ',')
209                    m_state = STATE_LOOKING_FOR_DIRECTIVE;
210                else
211                {
212                    m_errorPos = m_curPos;
213                    throw new SaslException("Parse error: Expected a comma.");
214                }
215                break;
216            }
217            if (0 != rc)
218                break;
219            prevChar = currChar;
220            m_curPos++;
221        } /* end while loop */
222
223
224        if (rc == 0)
225        {
226            /* check the ending state */
227            switch (m_state)
228            {
229            case STATE_SCANNING_TOKEN_VALUE:
230                addDirective(currentName, false);
231                break;
232
233            case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
234            case STATE_LOOKING_FOR_COMMA:
235                break;
236
237            case STATE_LOOKING_FOR_DIRECTIVE:
238                    throw new SaslException("Parse error: Trailing comma.");
239
240            case STATE_SCANNING_NAME:
241            case STATE_LOOKING_FOR_EQUALS:
242            case STATE_LOOKING_FOR_VALUE:
243                    throw new SaslException("Parse error: Missing value.");
244
245            case STATE_SCANNING_QUOTED_STRING_VALUE:
246                    throw new SaslException("Parse error: Missing closing quote.");
247            }
248        }
249
250    }
251
252    /**
253     * This function returns TRUE if the character is a valid token character.
254     *
255     *     token          = 1*<any CHAR except CTLs or separators>
256     *
257     *      separators     = "(" | ")" | "<" | ">" | "@"
258     *                     | "," | ";" | ":" | "\" | <">
259     *                     | "/" | "[" | "]" | "?" | "="
260     *                     | "{" | "}" | SP | HT
261     *
262     *      CTL            = <any US-ASCII control character
263     *                       (octets 0 - 31) and DEL (127)>
264     *
265     *      CHAR           = <any US-ASCII character (octets 0 - 127)>
266     *
267     * @param c  character to be tested
268     *
269     * @return Returns TRUE if the character is a valid token character.
270     */
271    boolean isValidTokenChar(
272        char c)
273    {
274        if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
275             ( (c >= '\u003a') && (c <= '\u0040') ) ||
276             ( (c >= '\u005b') && (c <= '\u005d') ) ||
277             ('\u002c' == c) ||
278             ('\u0025' == c) ||
279             ('\u0028' == c) ||
280             ('\u0029' == c) ||
281             ('\u007b' == c) ||
282             ('\u007d' == c) ||
283             ('\u007f' == c) )
284            return false;
285
286        return true;
287    }
288
289    /**
290     * This function returns TRUE if the character is linear white space (LWS).
291     *         LWS = [CRLF] 1*( SP | HT )
292     * @param c  Input charcter to be tested
293     *
294     * @return Returns TRUE if the character is linear white space (LWS)
295     */
296    boolean isWhiteSpace(
297        char c)
298    {
299        if ( ('\t' == c) ||  // HORIZONTAL TABULATION.
300             ('\n' == c) ||  // LINE FEED.
301             ('\r' == c) ||  // CARRIAGE RETURN.
302             ('\u0020' == c) )
303            return true;
304
305        return false;
306    }
307
308    /**
309     * This function creates a directive record and adds it to the list, the
310     * value will be added later after it is parsed.
311     *
312     * @param name  Name
313     * @param haveQuotedPair true if quoted pair is there else false
314     */
315    void addDirective(
316        String    name,
317        boolean   haveQuotedPair)
318    {
319        String value;
320        int    inputIndex;
321        int    valueIndex;
322        char   valueChar;
323        int    type;
324
325        if (!haveQuotedPair)
326        {
327            value = m_directives.substring(m_scanStart, m_curPos);
328        }
329        else
330        { //copy one character at a time skipping backslash excapes.
331            StringBuffer valueBuf = new StringBuffer(m_curPos - m_scanStart);
332            valueIndex = 0;
333            inputIndex = m_scanStart;
334            while (inputIndex < m_curPos)
335            {
336                if ('\\' == (valueChar = m_directives.charAt(inputIndex)))
337                    inputIndex++;
338                valueBuf.setCharAt(valueIndex, m_directives.charAt(inputIndex));
339                valueIndex++;
340                inputIndex++;
341            }
342            value = new String(valueBuf);
343        }
344
345        if (m_state == STATE_SCANNING_QUOTED_STRING_VALUE)
346            type = ParsedDirective.QUOTED_STRING_VALUE;
347        else
348            type = ParsedDirective.TOKEN_VALUE;
349        m_directiveList.add(new ParsedDirective(name, value, type));
350    }
351
352
353    /**
354     * Returns the List iterator.
355     *
356     * @return     Returns the Iterator Object for the List.
357     */
358    Iterator getIterator()
359    {
360        return m_directiveList.iterator();
361    }
362}
363
364