/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2013-2014 Manuel Gaupp * Copyright 2014 ForgeRock AS * Portions Copyright 2014 ForgeRock AS */ package org.opends.server.protocols.asn1; import java.math.BigInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.forgerock.i18n.LocalizableMessage; import static org.opends.messages.ProtocolMessages.*; import static org.forgerock.util.Reject.*; /** * This class implements a parser for strings which are encoded using the * Generic String Encoding Rules (GSER) defined in RFC 3641. * * @see RFC 3641 - * Generic String Encoding Rules (GSER) for ASN.1 Types * */ public class GSERParser { private String gserValue; private int pos; private int length; /** * Pattern to match an identifier defined in RFC 3641, section 3.4. *
* An <identifier> conforms to the definition of an identifier in ASN.1 * notation (Clause 11.3 of X.680 [8]). It begins with a lowercase * letter and is followed by zero or more letters, digits, and hyphens. * A hyphen is not permitted to be the last character, nor is it to be * followed by another hyphen. The case of letters in an identifier is * always significant. * * identifier = lowercase *alphanumeric *(hyphen 1*alphanumeric) * alphanumeric = uppercase / lowercase / decimal-digit * uppercase = %x41-5A ; "A" to "Z" * lowercase = %x61-7A ; "a" to "z" * decimal-digit = %x30-39 ; "0" to "9" * hyphen = "-" **/ private static Pattern GSER_IDENTIFIER = Pattern .compile("^([a-z]([A-Za-z0-9]|(-[A-Za-z0-9]))*)"); /** * Pattern to match the identifier part (including the colon) of an * IdentifiedChoiceValue defined in RFC 3641, section 3.12. *
* IdentifiedChoiceValue = identifier ":" Value **/ private static Pattern GSER_CHOICE_IDENTIFIER = Pattern .compile("^([a-z]([A-Za-z0-9]|(-[A-Za-z0-9]))*:)"); /** * Pattern to match "sp", containing zero, one or more space characters. *
* sp = *%x20 ; zero, one or more space characters **/ private static Pattern GSER_SP = Pattern.compile("^( *)"); /** * Pattern to match "msp", containing at least one space character. *
* msp = 1*%x20 ; one or more space characters **/ private static Pattern GSER_MSP = Pattern.compile("^( +)"); /** * Pattern to match an Integer value. */ private static Pattern GSER_INTEGER = Pattern.compile("^(\\d+)"); /** * Pattern to match a GSER StringValue, defined in RFC 3641, section 3.2: *
* Any embedded double quotes in the resulting UTF-8 character string * are escaped by repeating the double quote characters. * * [...] * * StringValue = dquote *SafeUTF8Character dquote * dquote = %x22 ; " (double quote) **/ private static Pattern GSER_STRING = Pattern .compile("^(\"([^\"]|(\"\"))*\")"); /** * Pattern to match the beginning of a GSER encoded Sequence. *
* SequenceValue = ComponentList
* ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}"
*
*/
private static Pattern GSER_SEQUENCE_START = Pattern.compile("^(\\{)");
/**
* Pattern to match the end of a GSER encoded Sequence.
*
* SequenceValue = ComponentList
* ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}"
*
*/
private static Pattern GSER_SEQUENCE_END = Pattern.compile("^(\\})");
/**
* Pattern to match the separator used in GSER encoded sequences.
*/
private static Pattern GSER_SEP = Pattern.compile("^(,)");
/**
* Creates a new GSER Parser.
*
* @param value the GSER encoded String value
*/
public GSERParser(String value)
{
ifNull(value);
this.gserValue = value;
this.pos = 0;
this.length = value.length();
}
/**
* Determines if the GSER String contains at least one character to be read.
*
* @return true if there is at least one remaining character or
* false otherwise.
*/
public boolean hasNext()
{
return (pos < length);
}
/**
* Determines if the remaining GSER String matches the provided pattern.
*
* @param pattern the pattern to search for
*
* @return true if the remaining string matches the pattern or
* false otherwise.
*/
private boolean hasNext(Pattern pattern)
{
if (!hasNext())
{
return false;
}
Matcher matcher = pattern.matcher(gserValue.substring(pos,length));
return matcher.find();
}
/**
* Returns the String matched by the first capturing group of the pattern.
* The parser advances past the input matched by the first capturing group.
*
* @param pattern the pattern to search for
*
* @return the String matched by the first capturing group of the pattern
*
* @throws GSERException
* If no match could be found
*/
private String next(Pattern pattern) throws GSERException
{
Matcher matcher = pattern.matcher(gserValue.substring(pos,length));
if (matcher.find() && matcher.groupCount() >= 1)
{
pos += matcher.end(1);
return matcher.group(1);
}
else
{
LocalizableMessage msg = ERR_GSER_PATTERN_NO_MATCH.get(pattern.pattern(),
gserValue.substring(pos,length));
throw new GSERException(msg);
}
}
/**
* Skips the input matched by the first capturing group.
*
* @param pattern the pattern to search for
*
* @throws GSERException
* If no match could be found
*/
private void skip(Pattern pattern) throws GSERException
{
Matcher matcher = pattern.matcher(gserValue.substring(pos,length));
if (matcher.find() && matcher.groupCount() >= 1)
{
pos += matcher.end(1);
}
else
{
LocalizableMessage msg = ERR_GSER_PATTERN_NO_MATCH.get(pattern.pattern(),
gserValue.substring(pos,length));
throw new GSERException(msg);
}
}
/**
* Skips the input matching zero, one or more space characters.
*
* @return reference to this GSERParser
*
* @throws GSERException
* If no match could be found
*/
public GSERParser skipSP() throws GSERException
{
skip(GSER_SP);
return this;
}
/**
* Skips the input matching one or more space characters.
*
* @return reference to this GSERParser
*
* @throws GSERException
* If no match could be found
*/
public GSERParser skipMSP() throws GSERException
{
skip(GSER_MSP);
return this;
}
/**
* Skips the input matching the start of a sequence and subsequent space
* characters.
*
* @return reference to this GSERParser
*
* @throws GSERException
* If the input does not match the start of a sequence
*/
public GSERParser readStartSequence() throws GSERException
{
next(GSER_SEQUENCE_START);
skip(GSER_SP);
return this;
}
/**
* Skips the input matching the end of a sequence and preceding space
* characters.
*
* @return reference to this GSERParser
*
* @throws GSERException
* If the input does not match the end of a sequence
*/
public GSERParser readEndSequence() throws GSERException
{
skip(GSER_SP);
next(GSER_SEQUENCE_END);
return this;
}
/**
* Skips the input matching the separator pattern (",") and subsequenct space
* characters.
*
* @return reference to this GSERParser
*
* @throws GSERException
* If the input does not match the separator pattern.
*/
public GSERParser skipSeparator() throws GSERException
{
if (!hasNext(GSER_SEP))
{
LocalizableMessage msg = ERR_GSER_NO_VALID_SEPARATOR.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
skip(GSER_SEP);
skip(GSER_SP);
return this;
}
/**
* Returns the next element as a String.
*
* @return the input matching the String pattern
*
* @throws GSERException
* If the input does not match the string pattern.
*/
public String nextString() throws GSERException
{
if (!hasNext(GSER_STRING))
{
LocalizableMessage msg = ERR_GSER_NO_VALID_STRING.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
String str = next(GSER_STRING);
// Strip leading and trailing dquotes; unescape double dquotes
return str.substring(1, str.length() - 1).replace("\"\"","\"");
}
/**
* Returns the next element as an Integer.
*
* @return the input matching the integer pattern
*
* @throws GSERException
* If the input does not match the integer pattern
*/
public int nextInteger() throws GSERException
{
if (!hasNext(GSER_INTEGER))
{
LocalizableMessage msg = ERR_GSER_NO_VALID_INTEGER.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
return Integer.valueOf(next(GSER_INTEGER)).intValue();
}
/**
* Returns the next element as a BigInteger.
*
* @return the input matching the integer pattern
*
* @throws GSERException
* If the input does not match the integer pattern
*/
public BigInteger nextBigInteger() throws GSERException
{
if (!hasNext(GSER_INTEGER))
{
LocalizableMessage msg = ERR_GSER_NO_VALID_INTEGER.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
return new BigInteger(next(GSER_INTEGER));
}
/**
* Returns the identifier of the next NamedValue element.
*
* @return the identifier of the NamedValue element
*
* @throws GSERException
* If the input does not match the identifier pattern of a
* NamedValue
*/
public String nextNamedValueIdentifier() throws GSERException
{
if (!hasNext(GSER_IDENTIFIER))
{
LocalizableMessage msg = ERR_GSER_NO_VALID_IDENTIFIER.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
String identifier = next(GSER_IDENTIFIER);
if (!hasNext(GSER_MSP))
{
LocalizableMessage msg = ERR_GSER_SPACE_CHAR_EXPECTED.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
skipMSP();
return identifier;
}
/**
* Return the identifier of the next IdentifiedChoiceValue element.
*
* @return the identifier of the IdentifiedChoiceValue element
*
* @throws GSERException
* If the input does not match the identifier pattern of an
* IdentifiedChoiceValue
*/
public String nextChoiceValueIdentifier() throws GSERException
{
if (!hasNext(GSER_CHOICE_IDENTIFIER))
{
LocalizableMessage msg = ERR_GSER_NO_VALID_IDENTIFIEDCHOICE.get(gserValue
.substring(pos,length));
throw new GSERException(msg);
}
String identifier = next(GSER_CHOICE_IDENTIFIER);
// Remove the colon at the end of the identifier
return identifier.substring(0, identifier.length() - 1);
}
}