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 2006-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017package org.opends.server.schema;
018
019import static org.opends.messages.SchemaMessages.*;
020import static org.opends.server.schema.SchemaConstants.*;
021import static org.opends.server.util.StaticUtils.*;
022
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.LinkedHashMap;
026import java.util.LinkedList;
027import java.util.List;
028
029import org.forgerock.i18n.LocalizableMessage;
030import org.forgerock.i18n.slf4j.LocalizedLogger;
031import org.forgerock.opendj.ldap.ByteSequence;
032import org.forgerock.opendj.ldap.ByteString;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.schema.MatchingRule;
035import org.forgerock.opendj.ldap.schema.SchemaBuilder;
036import org.forgerock.opendj.ldap.schema.Syntax;
037import org.opends.server.admin.std.server.AttributeSyntaxCfg;
038import org.opends.server.api.AttributeSyntax;
039import org.opends.server.core.DirectoryServer;
040import org.opends.server.types.CommonSchemaElements;
041import org.opends.server.types.DirectoryException;
042import org.opends.server.types.LDAPSyntaxDescription;
043import org.opends.server.types.Schema;
044
045/**
046 * This class defines the LDAP syntax description syntax, which is used to
047 * hold attribute syntax definitions in the server schema.  The format of this
048 * syntax is defined in RFC 2252.
049 */
050public class LDAPSyntaxDescriptionSyntax
051       extends AttributeSyntax<AttributeSyntaxCfg>
052{
053  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
054
055  /** The default equality matching rule for this syntax. */
056  private MatchingRule defaultEqualityMatchingRule;
057
058  /** The default ordering matching rule for this syntax. */
059  private MatchingRule defaultOrderingMatchingRule;
060
061  /** The default substring matching rule for this syntax. */
062  private MatchingRule defaultSubstringMatchingRule;
063
064  /**
065   * Creates a new instance of this syntax.  Note that the only thing that
066   * should be done here is to invoke the default constructor for the
067   * superclass.  All initialization should be performed in the
068   * <CODE>initializeSyntax</CODE> method.
069   */
070  public LDAPSyntaxDescriptionSyntax()
071  {
072    super();
073  }
074
075  /** {@inheritDoc} */
076  @Override
077  public Syntax getSDKSyntax(org.forgerock.opendj.ldap.schema.Schema schema)
078  {
079    return schema.getSyntax(SchemaConstants.SYNTAX_LDAP_SYNTAX_OID);
080  }
081
082  /**
083   * Retrieves the common name for this attribute syntax.
084   *
085   * @return  The common name for this attribute syntax.
086   */
087  @Override
088  public String getName()
089  {
090    return SYNTAX_LDAP_SYNTAX_NAME;
091  }
092
093  /**
094   * Retrieves the OID for this attribute syntax.
095   *
096   * @return  The OID for this attribute syntax.
097   */
098  @Override
099  public String getOID()
100  {
101    return SYNTAX_LDAP_SYNTAX_OID;
102  }
103
104  /**
105   * Retrieves a description for this attribute syntax.
106   *
107   * @return  A description for this attribute syntax.
108   */
109  @Override
110  public String getDescription()
111  {
112    return SYNTAX_LDAP_SYNTAX_DESCRIPTION;
113  }
114
115  /**
116   * Retrieves the default equality matching rule that will be used for
117   * attributes with this syntax.
118   *
119   * @return  The default equality matching rule that will be used for
120   *          attributes with this syntax, or <CODE>null</CODE> if equality
121   *          matches will not be allowed for this type by default.
122   */
123  @Override
124  public MatchingRule getEqualityMatchingRule()
125  {
126    return defaultEqualityMatchingRule;
127  }
128
129  /**
130   * Retrieves the default ordering matching rule that will be used for
131   * attributes with this syntax.
132   *
133   * @return  The default ordering matching rule that will be used for
134   *          attributes with this syntax, or <CODE>null</CODE> if ordering
135   *          matches will not be allowed for this type by default.
136   */
137  @Override
138  public MatchingRule getOrderingMatchingRule()
139  {
140    return defaultOrderingMatchingRule;
141  }
142
143  /**
144   * Retrieves the default substring matching rule that will be used for
145   * attributes with this syntax.
146   *
147   * @return  The default substring matching rule that will be used for
148   *          attributes with this syntax, or <CODE>null</CODE> if substring
149   *          matches will not be allowed for this type by default.
150   */
151  @Override
152  public MatchingRule getSubstringMatchingRule()
153  {
154    return defaultSubstringMatchingRule;
155  }
156
157  /**
158   * Retrieves the default approximate matching rule that will be used for
159   * attributes with this syntax.
160   *
161   * @return  The default approximate matching rule that will be used for
162   *          attributes with this syntax, or <CODE>null</CODE> if approximate
163   *          matches will not be allowed for this type by default.
164   */
165  @Override
166  public MatchingRule getApproximateMatchingRule()
167  {
168    // There is no approximate matching rule by default.
169    return null;
170  }
171
172  /**
173   * Decodes the contents of the provided byte sequence as an ldap syntax
174   * definition according to the rules of this syntax.  Note that the provided
175   * byte sequence value does not need to be normalized (and in fact, it should
176   * not be in order to allow the desired capitalization to be preserved).
177   *
178   * @param  value                 The byte sequence containing the value
179   *                               to decode (it does not need to be
180   *                               normalized).
181   * @param  schema                The schema to use to resolve references to
182   *                               other schema elements.
183   * @param  allowUnknownElements  Indicates whether to allow values that are
184   *                               not defined in the server schema. This
185   *                               should only be true when called by
186   *                               {@code valueIsAcceptable}.
187   *                               Not used for LDAP Syntaxes
188   * @param forDelete
189   *            {@code true} if used for deletion.
190   *
191   * @return  The decoded ldapsyntax definition.
192   *
193   * @throws  DirectoryException  If the provided value cannot be decoded as an
194   *                              ldapsyntax definition.
195   */
196  public static LDAPSyntaxDescription decodeLDAPSyntax(
197      ByteSequence value, Schema schema, boolean allowUnknownElements, boolean forDelete)
198          throws DirectoryException
199  {
200    // Get string representations of the provided value using the provided form.
201    String valueStr = value.toString();
202
203    // We'll do this a character at a time.  First, skip over any leading
204    // whitespace.
205    int pos    = 0;
206    int length = valueStr.length();
207    while (pos < length && valueStr.charAt(pos) == ' ')
208    {
209      pos++;
210    }
211
212    if (pos >= length)
213    {
214      // This means that the value was empty or contained only whitespace.  That
215      // is illegal.
216
217      LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE.get();
218      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
219              message);
220    }
221
222
223    // The next character must be an open parenthesis.  If it is not, then that
224    // is an error.
225    char c = valueStr.charAt(pos++);
226    if (c != '(')
227    {
228      LocalizableMessage message =
229              ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(valueStr, pos-1, c);
230      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
231              message);
232    }
233
234
235    // Skip over any spaces immediately following the opening parenthesis.
236    while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
237    {
238      pos++;
239    }
240
241    if (pos >= length)
242    {
243      // This means that the end of the value was reached before we could find
244      // the OID.  Ths is illegal.
245      LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(
246              valueStr);
247      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
248              message);
249    }
250
251    int oidStartPos = pos;
252    if (isDigit(c))
253    {
254      // This must be a numeric OID.  In that case, we will accept only digits
255      // and periods, but not consecutive periods.
256      boolean lastWasPeriod = false;
257      while (pos < length && ((c = valueStr.charAt(pos)) != ' ')
258              && (c = valueStr.charAt(pos)) != ')')
259      {
260        if (c == '.')
261        {
262          if (lastWasPeriod)
263          {
264            LocalizableMessage message =
265              ERR_ATTR_SYNTAX_LDAPSYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(valueStr, pos-1);
266            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
267          }
268          lastWasPeriod = true;
269        }
270        else if (! isDigit(c))
271        {
272          // This must have been an illegal character.
273          LocalizableMessage message =
274              ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos-1);
275          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
276        }
277        else
278        {
279          lastWasPeriod = false;
280        }
281        pos++;
282      }
283    }
284    else
285    {
286      // This must be a "fake" OID.  In this case, we will only accept
287      // alphabetic characters, numeric digits, and the hyphen.
288      while (pos < length && ((c = valueStr.charAt(pos)) != ' ')
289              && (c=valueStr.charAt(pos))!=')')
290      {
291        if (isAlpha(c) || isDigit(c) || c == '-' ||
292            (c == '_' && DirectoryServer.allowAttributeNameExceptions()))
293        {
294          // This is fine.  It is an acceptable character.
295          pos++;
296        }
297        else
298        {
299          // This must have been an illegal character.
300          LocalizableMessage message =
301                  ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_STRING_OID.
302              get(valueStr, c, pos-1);
303          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
304                                       message);
305        }
306      }
307    }
308
309    // If we're at the end of the value, then it isn't a valid attribute type
310    // description.  Otherwise, parse out the OID.
311    String oid;
312    if (pos >= length)
313    {
314      LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(
315              valueStr);
316      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
317              message);
318    }
319    oid = toLowerCase(valueStr.substring(oidStartPos, pos));
320
321    // Skip over the space(s) after the OID.
322    while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
323    {
324      pos++;
325    }
326
327    if (pos >= length)
328    {
329      // This means that the end of the value was reached before we could find
330      // the OID.  Ths is illegal.
331      LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(
332              valueStr);
333      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
334              message);
335    }
336
337    // At this point, we should have a pretty specific syntax that describes
338    // what may come next, but some of the components are optional and it would
339    // be pretty easy to put something in the wrong order, so we will be very
340    // flexible about what we can accept.  Just look at the next token, figure
341    // out what it is and how to treat what comes after it, then repeat until
342    // we get to the end of the value.  But before we start, set default values
343    // for everything else we might need to know.
344    String description = null;
345    Syntax syntax = null;
346    HashMap<String,List<String>> extraProperties = new LinkedHashMap<>();
347    boolean hasXSyntaxToken = false;
348
349    while (true)
350    {
351      StringBuilder tokenNameBuffer = new StringBuilder();
352      pos = readTokenName(valueStr, tokenNameBuffer, pos);
353      String tokenName = tokenNameBuffer.toString();
354      String lowerTokenName = toLowerCase(tokenName);
355      if (tokenName.equals(")"))
356      {
357        // We must be at the end of the value.  If not, then that's a problem.
358        if (pos < length)
359        {
360          LocalizableMessage message =
361            ERR_ATTR_SYNTAX_LDAPSYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(valueStr, pos-1);
362          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
363        }
364
365        break;
366      }
367      else if (lowerTokenName.equals("desc"))
368      {
369        // This specifies the description for the attribute type.  It is an
370        // arbitrary string of characters enclosed in single quotes.
371        StringBuilder descriptionBuffer = new StringBuilder();
372        pos = readQuotedString(valueStr, descriptionBuffer, pos);
373        description = descriptionBuffer.toString();
374      }
375      else
376      {
377        SchemaBuilder schemaBuilder = new SchemaBuilder(schema.getSchemaNG());
378
379        if (lowerTokenName.equals("x-subst"))
380        {
381          if (hasXSyntaxToken)
382          {
383            // We've already seen syntax extension. More than 1 is not allowed
384            LocalizableMessage message =
385                ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
386            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
387                                         message);
388          }
389          hasXSyntaxToken = true;
390          StringBuilder woidBuffer = new StringBuilder();
391          pos = readQuotedString(valueStr, woidBuffer, pos);
392          String syntaxOID = toLowerCase(woidBuffer.toString());
393          Syntax subSyntax = schema.getSyntax(syntaxOID);
394          if (subSyntax == null)
395          {
396            LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_SYNTAX.get(oid, syntaxOID);
397            throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message);
398          }
399          syntax = forDelete ? schema.getSyntax(oid) : schemaBuilder.buildSyntax(oid)
400              .extraProperties("x-subst", syntaxOID)
401              .addToSchema().toSchema().getSyntax(oid);
402        }
403
404        else if(lowerTokenName.equals("x-pattern"))
405        {
406          if (hasXSyntaxToken)
407          {
408            // We've already seen syntax extension. More than 1 is not allowed
409            LocalizableMessage message =
410                ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
411            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
412                                         message);
413          }
414          hasXSyntaxToken = true;
415          StringBuilder regexBuffer = new StringBuilder();
416          pos = readQuotedString(valueStr, regexBuffer, pos);
417          String regex = regexBuffer.toString().trim();
418          if(regex.length() == 0)
419          {
420            LocalizableMessage message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_NO_PATTERN.get(
421                 valueStr);
422            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
423                                         message);
424          }
425
426          try
427          {
428            syntax = forDelete ? schema.getSyntax(oid) : schemaBuilder.buildSyntax(oid)
429                .extraProperties("x-pattern", regex)
430                .addToSchema().toSchema().getSyntax(oid);
431          }
432          catch(Exception e)
433          {
434            LocalizableMessage message =
435                WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN.get
436                    (valueStr,regex);
437            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
438                                         message);
439          }
440        }
441        else if(lowerTokenName.equals("x-enum"))
442        {
443          if (hasXSyntaxToken)
444          {
445            // We've already seen syntax extension. More than 1 is not allowed
446            LocalizableMessage message =
447                ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr);
448            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
449                                         message);
450          }
451          hasXSyntaxToken = true;
452          LinkedList<String> values = new LinkedList<>();
453          pos = readExtraParameterValues(valueStr, values, pos);
454
455          if (values.isEmpty())
456          {
457            LocalizableMessage message =
458                ERR_ATTR_SYNTAX_LDAPSYNTAX_ENUM_NO_VALUES.get(valueStr);
459            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
460                                         message);
461          }
462          // Parse all enum values, check for uniqueness
463          List<String> entries = new LinkedList<>();
464          for (String v : values)
465          {
466            ByteString entry = ByteString.valueOfUtf8(v);
467            if (entries.contains(entry))
468            {
469              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
470                  WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE.get(
471                      valueStr, entry,pos));
472            }
473            entries.add(v);
474          }
475
476          syntax = forDelete ? schema.getSyntax(oid) : schemaBuilder
477              .addEnumerationSyntax(oid, description, true, entries.toArray(new String[0]))
478              .toSchema().getSyntax(oid);
479        }
480        else if (tokenName.matches("X\\-[_\\p{Alpha}-]+"))
481        {
482          // This must be a non-standard property and it must be followed by
483          // either a single value in single quotes or an open parenthesis
484          // followed by one or more values in single quotes separated by spaces
485          // followed by a close parenthesis.
486          List<String> valueList = new ArrayList<>();
487          pos = readExtraParameterValues(valueStr, valueList, pos);
488          extraProperties.put(tokenName, valueList);
489        }
490        else
491        {
492          // Unknown Token
493          LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_EXT.get(
494              valueStr, tokenName, pos);
495          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
496                message);
497        }
498      }
499    }
500    if (syntax == null)
501    {
502      // TODO : add localized message
503      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
504          LocalizableMessage.raw("no LDAP syntax for:" + value));
505    }
506
507    CommonSchemaElements.checkSafeProperties(extraProperties);
508
509    //Since we reached here it means everything is OK.
510    return new LDAPSyntaxDescription(valueStr, syntax.getOID(), extraProperties);
511  }
512
513  /**
514   * Reads the next token name from the attribute syntax definition, skipping
515   * over any leading or trailing spaces, and appends it to the provided buffer.
516   *
517   * @param  valueStr   The string representation of the attribute syntax
518   *                    definition.
519   * @param  tokenName  The buffer into which the token name will be written.
520   * @param  startPos   The position in the provided string at which to start
521   *                    reading the token name.
522   *
523   * @return  The position of the first character that is not part of the token
524   *          name or one of the trailing spaces after it.
525   *
526   * @throws  DirectoryException  If a problem is encountered while reading the
527   *                              token name.
528   */
529  private static int readTokenName(String valueStr, StringBuilder tokenName,
530                                   int startPos)
531          throws DirectoryException
532  {
533    // Skip over any spaces at the beginning of the value.
534    char c = '\u0000';
535    int  length = valueStr.length();
536    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
537    {
538      startPos++;
539    }
540
541    if (startPos >= length)
542    {
543      LocalizableMessage message =
544          ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
545      throw new DirectoryException(
546              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
547    }
548
549
550    // Read until we find the next space.
551    while (startPos < length && ((c = valueStr.charAt(startPos++)) != ' '))
552    {
553      tokenName.append(c);
554    }
555
556
557    // Skip over any trailing spaces after the value.
558    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
559    {
560      startPos++;
561    }
562
563
564    // Return the position of the first non-space character after the token.
565    return startPos;
566  }
567
568  /**
569   * Reads the value of a string enclosed in single quotes, skipping over the
570   * quotes and any leading or trailing spaces, and appending the string to the
571   * provided buffer.
572   *
573   * @param  valueStr     The user-provided representation of the attribute type
574   *                      definition.
575   * @param  valueBuffer  The buffer into which the user-provided representation
576   *                      of the value will be placed.
577   * @param  startPos     The position in the provided string at which to start
578   *                      reading the quoted string.
579   *
580   * @return  The position of the first character that is not part of the quoted
581   *          string or one of the trailing spaces after it.
582   *
583   * @throws  DirectoryException  If a problem is encountered while reading the
584   *                              quoted string.
585   */
586  private static int readQuotedString(String valueStr,
587                                      StringBuilder valueBuffer, int startPos)
588          throws DirectoryException
589  {
590    // Skip over any spaces at the beginning of the value.
591    char c = '\u0000';
592    int  length = valueStr.length();
593    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
594    {
595      startPos++;
596    }
597
598    if (startPos >= length)
599    {
600      LocalizableMessage message =
601          ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
602      throw new DirectoryException(
603              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
604    }
605
606
607    // The next character must be a single quote.
608    if (c != '\'')
609    {
610      LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c);
611      throw new DirectoryException(
612              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
613    }
614
615
616    // Read until we find the closing quote.
617    startPos++;
618    while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
619    {
620      valueBuffer.append(c);
621      startPos++;
622    }
623
624
625    // Skip over any trailing spaces after the value.
626    startPos++;
627    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
628    {
629      startPos++;
630    }
631
632
633    // If we're at the end of the value, then that's illegal.
634    if (startPos >= length)
635    {
636      LocalizableMessage message =
637          ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
638      throw new DirectoryException(
639              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
640    }
641
642
643    // Return the position of the first non-space character after the token.
644    return startPos;
645  }
646
647
648  /**
649   * Reads the value for an "extra" parameter.  It will handle a single unquoted
650   * word (which is technically illegal, but we'll allow it), a single quoted
651   * string, or an open parenthesis followed by a space-delimited set of quoted
652   * strings or unquoted words followed by a close parenthesis.
653   *
654   * @param  valueStr   The string containing the information to be read.
655   * @param  valueList  The list of "extra" parameter values read so far.
656   * @param  startPos   The position in the value string at which to start
657   *                    reading.
658   *
659   * @return  The "extra" parameter value that was read.
660   *
661   * @throws  DirectoryException  If a problem occurs while attempting to read
662   *                              the value.
663   */
664  private static int readExtraParameterValues(String valueStr,
665                          List<String> valueList, int startPos)
666          throws DirectoryException
667  {
668    // Skip over any leading spaces.
669    int length = valueStr.length();
670    char c = '\u0000';
671    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
672    {
673      startPos++;
674    }
675
676    if (startPos >= length)
677    {
678      LocalizableMessage message =
679          ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
680      throw new DirectoryException(
681              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
682    }
683
684
685    // Look at the next character.  If it is a quote, then parse until the next
686    // quote and end.  If it is an open parenthesis, then parse individual
687    // values until the close parenthesis and end.  Otherwise, parse until the
688    // next space and end.
689    if (c == '\'')
690    {
691      // Parse until the closing quote.
692      StringBuilder valueBuffer = new StringBuilder();
693      startPos++;
694      while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
695      {
696        valueBuffer.append(c);
697        startPos++;
698      }
699      startPos++;
700      valueList.add(valueBuffer.toString());
701    }
702    else if (c == '(')
703    {
704      startPos++;
705      // We're expecting a list of values. Quoted, space separated.
706      while (true)
707      {
708        // Skip over any leading spaces;
709        while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
710        {
711          startPos++;
712        }
713
714        if (startPos >= length)
715        {
716          LocalizableMessage message =
717              ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
718          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
719                                       message);
720        }
721
722        if (c == ')')
723        {
724          // This is the end of the list.
725          startPos++;
726          break;
727        }
728        else if (c == '(')
729        {
730          // This is an illegal character.
731          LocalizableMessage message =
732              ERR_ATTR_SYNTAX_LDAPSYNTAX_EXTENSION_INVALID_CHARACTER.get(
733                      valueStr, startPos);
734          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
735                                       message);
736        }
737        else if (c == '\'')
738        {
739          // We have a quoted string
740          StringBuilder valueBuffer = new StringBuilder();
741          startPos++;
742          while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
743          {
744            valueBuffer.append(c);
745            startPos++;
746          }
747
748          valueList.add(valueBuffer.toString());
749          startPos++;
750        }
751        else
752        {
753          //Consider unquoted string
754          StringBuilder valueBuffer = new StringBuilder();
755          while (startPos < length && ((c = valueStr.charAt(startPos)) != ' '))
756          {
757            valueBuffer.append(c);
758            startPos++;
759          }
760
761          valueList.add(valueBuffer.toString());
762        }
763
764        if (startPos >= length)
765        {
766          LocalizableMessage message =
767              ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
768          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
769                                       message);
770        }
771      }
772    }
773    else
774    {
775      // Parse until the next space.
776      StringBuilder valueBuffer = new StringBuilder();
777      while (startPos < length && ((c = valueStr.charAt(startPos)) != ' '))
778      {
779        valueBuffer.append(c);
780        startPos++;
781      }
782
783      valueList.add(valueBuffer.toString());
784    }
785
786    // Skip over any trailing spaces.
787    while (startPos < length && valueStr.charAt(startPos) == ' ')
788    {
789      startPos++;
790    }
791
792    if (startPos >= length)
793    {
794      LocalizableMessage message =
795          ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr);
796      throw new DirectoryException(
797              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
798    }
799
800    return startPos;
801  }
802}