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 ******************************************************************************/ 15 package com.novell.sasl.client; 16 17 import java.util.*; 18 import org.apache.harmony.javax.security.sasl.*; 19 import java.io.UnsupportedEncodingException; 20 21 /** 22 * Implements the DirectiveList class whihc will be used by the 23 * DigestMD5SaslClient class 24 */ 25 class 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