001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2013-2014 Manuel Gaupp 025 * Copyright 2014-2015 ForgeRock AS 026 * Portions Copyright 2014 ForgeRock AS 027 */ 028package org.opends.server.protocols.asn1; 029 030import java.math.BigInteger; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033import org.forgerock.i18n.LocalizableMessage; 034import static org.opends.messages.ProtocolMessages.*; 035import static org.forgerock.util.Reject.*; 036 037/** 038 * This class implements a parser for strings which are encoded using the 039 * Generic String Encoding Rules (GSER) defined in RFC 3641. 040 * 041 * @see <a href="http://tools.ietf.org/html/rfc3641">RFC 3641 - 042 * Generic String Encoding Rules (GSER) for ASN.1 Types 043 * </a> 044 */ 045public class GSERParser 046{ 047 048 private String gserValue; 049 private int pos; 050 private int length; 051 052 /** 053 * Pattern to match an identifier defined in RFC 3641, section 3.4. 054 * <pre> 055 * An <identifier> conforms to the definition of an identifier in ASN.1 056 * notation (Clause 11.3 of X.680 [8]). It begins with a lowercase 057 * letter and is followed by zero or more letters, digits, and hyphens. 058 * A hyphen is not permitted to be the last character, nor is it to be 059 * followed by another hyphen. The case of letters in an identifier is 060 * always significant. 061 * 062 * identifier = lowercase *alphanumeric *(hyphen 1*alphanumeric) 063 * alphanumeric = uppercase / lowercase / decimal-digit 064 * uppercase = %x41-5A ; "A" to "Z" 065 * lowercase = %x61-7A ; "a" to "z" 066 * decimal-digit = %x30-39 ; "0" to "9" 067 * hyphen = "-" 068 * </pre> 069 */ 070 private static Pattern GSER_IDENTIFIER = Pattern 071 .compile("^([a-z]([A-Za-z0-9]|(-[A-Za-z0-9]))*)"); 072 073 /** 074 * Pattern to match the identifier part (including the colon) of an 075 * IdentifiedChoiceValue defined in RFC 3641, section 3.12. 076 * <pre> 077 * IdentifiedChoiceValue = identifier ":" Value 078 * </pre> 079 */ 080 private static Pattern GSER_CHOICE_IDENTIFIER = Pattern 081 .compile("^([a-z]([A-Za-z0-9]|(-[A-Za-z0-9]))*:)"); 082 083 084 085 /** 086 * Pattern to match "sp", containing zero, one or more space characters. 087 * <pre> 088 * sp = *%x20 ; zero, one or more space characters 089 * </pre> 090 */ 091 private static Pattern GSER_SP = Pattern.compile("^( *)"); 092 093 094 095 /** 096 * Pattern to match "msp", containing at least one space character. 097 * <pre> 098 * msp = 1*%x20 ; one or more space characters 099 * </pre> 100 */ 101 private static Pattern GSER_MSP = Pattern.compile("^( +)"); 102 103 104 105 /** 106 * Pattern to match an Integer value. 107 */ 108 private static Pattern GSER_INTEGER = Pattern.compile("^(\\d+)"); 109 110 111 112 /** 113 * Pattern to match a GSER StringValue, defined in RFC 3641, section 3.2: 114 * <pre> 115 * Any embedded double quotes in the resulting UTF-8 character string 116 * are escaped by repeating the double quote characters. 117 * 118 * [...] 119 * 120 * StringValue = dquote *SafeUTF8Character dquote 121 * dquote = %x22 ; " (double quote) 122 * </pre> 123 */ 124 private static Pattern GSER_STRING = Pattern 125 .compile("^(\"([^\"]|(\"\"))*\")"); 126 127 128 129 /** 130 * Pattern to match the beginning of a GSER encoded Sequence. 131 * <pre> 132 * SequenceValue = ComponentList 133 * ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}" 134 * </pre> 135 */ 136 private static Pattern GSER_SEQUENCE_START = Pattern.compile("^(\\{)"); 137 138 139 140 /** 141 * Pattern to match the end of a GSER encoded Sequence. 142 * <pre> 143 * SequenceValue = ComponentList 144 * ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}" 145 * </pre> 146 */ 147 private static Pattern GSER_SEQUENCE_END = Pattern.compile("^(\\})"); 148 149 150 151 /** 152 * Pattern to match the separator used in GSER encoded sequences. 153 */ 154 private static Pattern GSER_SEP = Pattern.compile("^(,)"); 155 156 157 158 /** 159 * Creates a new GSER Parser. 160 * 161 * @param value the GSER encoded String value 162 */ 163 public GSERParser(String value) 164 { 165 ifNull(value); 166 this.gserValue = value; 167 this.pos = 0; 168 this.length = value.length(); 169 } 170 171 172 173 /** 174 * Determines if the GSER String contains at least one character to be read. 175 * 176 * @return <code>true</code> if there is at least one remaining character or 177 * <code>false</code> otherwise. 178 */ 179 public boolean hasNext() 180 { 181 return pos < length; 182 } 183 184 185 186 /** 187 * Determines if the remaining GSER String matches the provided pattern. 188 * 189 * @param pattern the pattern to search for 190 * 191 * @return <code>true</code> if the remaining string matches the pattern or 192 * <code>false</code> otherwise. 193 */ 194 private boolean hasNext(Pattern pattern) 195 { 196 if (!hasNext()) 197 { 198 return false; 199 } 200 201 Matcher matcher = pattern.matcher(gserValue.substring(pos,length)); 202 203 return matcher.find(); 204 } 205 206 207 208 /** 209 * Returns the String matched by the first capturing group of the pattern. 210 * The parser advances past the input matched by the first capturing group. 211 * 212 * @param pattern the pattern to search for 213 * 214 * @return the String matched by the first capturing group of the pattern 215 * 216 * @throws GSERException 217 * If no match could be found 218 */ 219 private String next(Pattern pattern) throws GSERException 220 { 221 Matcher matcher = pattern.matcher(gserValue.substring(pos,length)); 222 if (matcher.find() && matcher.groupCount() >= 1) 223 { 224 pos += matcher.end(1); 225 return matcher.group(1); 226 } 227 else 228 { 229 LocalizableMessage msg = ERR_GSER_PATTERN_NO_MATCH.get(pattern.pattern(), 230 gserValue.substring(pos,length)); 231 throw new GSERException(msg); 232 } 233 } 234 235 236 237 /** 238 * Skips the input matched by the first capturing group. 239 * 240 * @param pattern the pattern to search for 241 * 242 * @throws GSERException 243 * If no match could be found 244 */ 245 private void skip(Pattern pattern) throws GSERException 246 { 247 Matcher matcher = pattern.matcher(gserValue.substring(pos,length)); 248 249 if (matcher.find() && matcher.groupCount() >= 1) 250 { 251 pos += matcher.end(1); 252 } 253 else 254 { 255 LocalizableMessage msg = ERR_GSER_PATTERN_NO_MATCH.get(pattern.pattern(), 256 gserValue.substring(pos,length)); 257 throw new GSERException(msg); 258 } 259 } 260 261 262 263 /** 264 * Skips the input matching zero, one or more space characters. 265 * 266 * @return reference to this GSERParser 267 * 268 * @throws GSERException 269 * If no match could be found 270 */ 271 public GSERParser skipSP() throws GSERException 272 { 273 skip(GSER_SP); 274 return this; 275 } 276 277 278 279 /** 280 * Skips the input matching one or more space characters. 281 * 282 * @return reference to this GSERParser 283 * 284 * @throws GSERException 285 * If no match could be found 286 */ 287 public GSERParser skipMSP() throws GSERException 288 { 289 skip(GSER_MSP); 290 return this; 291 } 292 293 294 295 /** 296 * Skips the input matching the start of a sequence and subsequent space 297 * characters. 298 * 299 * @return reference to this GSERParser 300 * 301 * @throws GSERException 302 * If the input does not match the start of a sequence 303 */ 304 public GSERParser readStartSequence() throws GSERException 305 { 306 next(GSER_SEQUENCE_START); 307 skip(GSER_SP); 308 return this; 309 } 310 311 312 313 /** 314 * Skips the input matching the end of a sequence and preceding space 315 * characters. 316 * 317 * @return reference to this GSERParser 318 * 319 * @throws GSERException 320 * If the input does not match the end of a sequence 321 */ 322 public GSERParser readEndSequence() throws GSERException 323 { 324 skip(GSER_SP); 325 next(GSER_SEQUENCE_END); 326 return this; 327 } 328 329 330 /** 331 * Skips the input matching the separator pattern (",") and subsequenct space 332 * characters. 333 * 334 * @return reference to this GSERParser 335 * 336 * @throws GSERException 337 * If the input does not match the separator pattern. 338 */ 339 public GSERParser skipSeparator() throws GSERException 340 { 341 if (!hasNext(GSER_SEP)) 342 { 343 LocalizableMessage msg = ERR_GSER_NO_VALID_SEPARATOR.get(gserValue 344 .substring(pos,length)); 345 throw new GSERException(msg); 346 } 347 skip(GSER_SEP); 348 skip(GSER_SP); 349 return this; 350 } 351 352 353 354 /** 355 * Returns the next element as a String. 356 * 357 * @return the input matching the String pattern 358 * 359 * @throws GSERException 360 * If the input does not match the string pattern. 361 */ 362 public String nextString() throws GSERException 363 { 364 if (!hasNext(GSER_STRING)) 365 { 366 LocalizableMessage msg = ERR_GSER_NO_VALID_STRING.get(gserValue 367 .substring(pos,length)); 368 throw new GSERException(msg); 369 } 370 371 String str = next(GSER_STRING); 372 373 // Strip leading and trailing dquotes; unescape double dquotes 374 return str.substring(1, str.length() - 1).replace("\"\"","\""); 375 } 376 377 378 /** 379 * Returns the next element as an Integer. 380 * 381 * @return the input matching the integer pattern 382 * 383 * @throws GSERException 384 * If the input does not match the integer pattern 385 */ 386 public int nextInteger() throws GSERException 387 { 388 if (!hasNext(GSER_INTEGER)) 389 { 390 LocalizableMessage msg = ERR_GSER_NO_VALID_INTEGER.get(gserValue 391 .substring(pos,length)); 392 throw new GSERException(msg); 393 } 394 return Integer.valueOf(next(GSER_INTEGER)).intValue(); 395 } 396 397 398 399 /** 400 * Returns the next element as a BigInteger. 401 * 402 * @return the input matching the integer pattern 403 * 404 * @throws GSERException 405 * If the input does not match the integer pattern 406 */ 407 public BigInteger nextBigInteger() throws GSERException 408 { 409 if (!hasNext(GSER_INTEGER)) 410 { 411 LocalizableMessage msg = ERR_GSER_NO_VALID_INTEGER.get(gserValue 412 .substring(pos,length)); 413 throw new GSERException(msg); 414 } 415 return new BigInteger(next(GSER_INTEGER)); 416 } 417 418 419 /** 420 * Returns the identifier of the next NamedValue element. 421 * 422 * @return the identifier of the NamedValue element 423 * 424 * @throws GSERException 425 * If the input does not match the identifier pattern of a 426 * NamedValue 427 */ 428 public String nextNamedValueIdentifier() throws GSERException 429 { 430 if (!hasNext(GSER_IDENTIFIER)) 431 { 432 LocalizableMessage msg = ERR_GSER_NO_VALID_IDENTIFIER.get(gserValue 433 .substring(pos,length)); 434 throw new GSERException(msg); 435 } 436 String identifier = next(GSER_IDENTIFIER); 437 if (!hasNext(GSER_MSP)) 438 { 439 LocalizableMessage msg = ERR_GSER_SPACE_CHAR_EXPECTED.get(gserValue 440 .substring(pos,length)); 441 throw new GSERException(msg); 442 } 443 skipMSP(); 444 return identifier; 445 } 446 447 448 /** 449 * Return the identifier of the next IdentifiedChoiceValue element. 450 * 451 * @return the identifier of the IdentifiedChoiceValue element 452 * 453 * @throws GSERException 454 * If the input does not match the identifier pattern of an 455 * IdentifiedChoiceValue 456 */ 457 public String nextChoiceValueIdentifier() throws GSERException 458 { 459 if (!hasNext(GSER_CHOICE_IDENTIFIER)) 460 { 461 LocalizableMessage msg = ERR_GSER_NO_VALID_IDENTIFIEDCHOICE.get(gserValue 462 .substring(pos,length)); 463 throw new GSERException(msg); 464 } 465 String identifier = next(GSER_CHOICE_IDENTIFIER); 466 467 // Remove the colon at the end of the identifier 468 return identifier.substring(0, identifier.length() - 1); 469 } 470 471 472}