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