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 &lt;identifier&gt; 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 ; &quot; (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}