/* * 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 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); } }