/*
|
* The contents of this file are subject to the terms of the Common Development and
|
* Distribution License (the License). You may not use this file except in compliance with the
|
* License.
|
*
|
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
|
* specific language governing permission and limitations under the License.
|
*
|
* When distributing Covered Software, include this CDDL Header Notice in each file and include
|
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
|
* Header, with the fields enclosed by brackets [] replaced by your own identifying
|
* information: "Portions Copyright [year] [name of copyright owner]".
|
*
|
* Copyright 2013-2014 Manuel Gaupp
|
* Portions Copyright 2014-2016 ForgeRock AS.
|
*/
|
package org.opends.server.protocols.asn1;
|
|
import static org.forgerock.util.Reject.*;
|
import static org.opends.messages.ProtocolMessages.*;
|
|
import java.math.BigInteger;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
|
import org.forgerock.i18n.LocalizableMessage;
|
|
/**
|
* This class implements a parser for strings which are encoded using the
|
* Generic String Encoding Rules (GSER) defined in RFC 3641.
|
*
|
* @see <a href="http://tools.ietf.org/html/rfc3641">RFC 3641 -
|
* Generic String Encoding Rules (GSER) for ASN.1 Types
|
* </a>
|
*/
|
public class GSERParser
|
{
|
|
private String gserValue;
|
private int pos;
|
private int length;
|
|
/**
|
* Pattern to match an identifier defined in RFC 3641, section 3.4.
|
* <pre>
|
* 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 = "-"
|
* </pre>
|
*/
|
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.
|
* <pre>
|
* IdentifiedChoiceValue = identifier ":" Value
|
* </pre>
|
*/
|
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.
|
* <pre>
|
* sp = *%x20 ; zero, one or more space characters
|
* </pre>
|
*/
|
private static Pattern GSER_SP = Pattern.compile("^( *)");
|
|
|
|
/**
|
* Pattern to match "msp", containing at least one space character.
|
* <pre>
|
* msp = 1*%x20 ; one or more space characters
|
* </pre>
|
*/
|
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:
|
* <pre>
|
* 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)
|
* </pre>
|
*/
|
private static Pattern GSER_STRING = Pattern
|
.compile("^(\"([^\"]|(\"\"))*\")");
|
|
|
|
/**
|
* Pattern to match the beginning of a GSER encoded Sequence.
|
* <pre>
|
* SequenceValue = ComponentList
|
* ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}"
|
* </pre>
|
*/
|
private static Pattern GSER_SEQUENCE_START = Pattern.compile("^(\\{)");
|
|
|
|
/**
|
* Pattern to match the end of a GSER encoded Sequence.
|
* <pre>
|
* SequenceValue = ComponentList
|
* ComponentList = "{" [ sp NamedValue *( "," sp NamedValue) ] sp "}"
|
* </pre>
|
*/
|
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 <code>true</code> if there is at least one remaining character or
|
* <code>false</code> 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 <code>true</code> if the remaining string matches the pattern or
|
* <code>false</code> 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);
|
}
|
|
|
}
|