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-2015 ForgeRock AS.
016 */
017package org.opends.server.schema;
018import static org.opends.messages.SchemaMessages.*;
019import static org.opends.server.schema.SchemaConstants.*;
020import static org.opends.server.util.StaticUtils.*;
021
022import java.util.LinkedHashMap;
023import java.util.LinkedHashSet;
024import java.util.LinkedList;
025import java.util.List;
026
027import org.forgerock.i18n.LocalizableMessage;
028import org.forgerock.opendj.ldap.ByteSequence;
029import org.forgerock.opendj.ldap.ResultCode;
030import org.forgerock.opendj.ldap.schema.Syntax;
031import org.opends.server.admin.std.server.AttributeSyntaxCfg;
032import org.opends.server.api.AttributeSyntax;
033import org.opends.server.core.DirectoryServer;
034import org.opends.server.types.DITStructureRule;
035import org.opends.server.types.DirectoryException;
036import org.opends.server.types.NameForm;
037import org.opends.server.types.Schema;
038
039/**
040 * This class implements the DIT structure rule description syntax, which is
041 * used to hold DIT structure rule definitions in the server schema.  The format
042 * of this syntax is defined in RFC 2252.
043 */
044public class DITStructureRuleSyntax
045       extends AttributeSyntax<AttributeSyntaxCfg>
046{
047
048  /**
049   * Creates a new instance of this syntax.  Note that the only thing that
050   * should be done here is to invoke the default constructor for the
051   * superclass.  All initialization should be performed in the
052   * <CODE>initializeSyntax</CODE> method.
053   */
054  public DITStructureRuleSyntax()
055  {
056    super();
057  }
058
059  /** {@inheritDoc} */
060  @Override
061  public Syntax getSDKSyntax(org.forgerock.opendj.ldap.schema.Schema schema)
062  {
063    return schema.getSyntax(SchemaConstants.SYNTAX_DIT_STRUCTURE_RULE_OID);
064  }
065
066  /** {@inheritDoc} */
067  @Override
068  public String getName()
069  {
070    return SYNTAX_DIT_STRUCTURE_RULE_NAME;
071  }
072
073  /** {@inheritDoc} */
074  @Override
075  public String getOID()
076  {
077    return SYNTAX_DIT_STRUCTURE_RULE_OID;
078  }
079
080  /** {@inheritDoc} */
081  @Override
082  public String getDescription()
083  {
084    return SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION;
085  }
086
087  /**
088   * Decodes the contents of the provided ASN.1 octet string as a DIT structure
089   * rule definition according to the rules of this syntax.  Note that the
090   * provided octet string value does not need to be normalized (and in fact, it
091   * should not be in order to allow the desired capitalization to be
092   * preserved).
093   *
094   * @param  value                 The ASN.1 octet string containing the value
095   *                               to decode (it does not need to be
096   *                               normalized).
097   * @param  schema                The schema to use to resolve references to
098   *                               other schema elements.
099   * @param  allowUnknownElements  Indicates whether to allow values that
100   *                               reference a name form and/or superior rules
101   *                               which are not defined in the server schema.
102   *                               This should only be true when called by
103   *                               {@code valueIsAcceptable}.
104   *
105   * @return  The decoded DIT structure rule definition.
106   *
107   * @throws  DirectoryException  If the provided value cannot be decoded as an
108   *                              DIT structure rule definition.
109   */
110  public static DITStructureRule decodeDITStructureRule(ByteSequence value,
111                                      Schema schema,
112                                      boolean allowUnknownElements)
113         throws DirectoryException
114  {
115    // Get string representations of the provided value using the provided form
116    // and with all lowercase characters.
117    String valueStr = value.toString();
118    String lowerStr = toLowerCase(valueStr);
119
120
121    // We'll do this a character at a time.  First, skip over any leading
122    // whitespace.
123    int pos    = 0;
124    int length = valueStr.length();
125    while (pos < length && valueStr.charAt(pos) == ' ')
126    {
127      pos++;
128    }
129
130    if (pos >= length)
131    {
132      // This means that the value was empty or contained only whitespace.  That
133      // is illegal.
134      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get();
135      throw new DirectoryException(
136              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
137    }
138
139
140    // The next character must be an open parenthesis.  If it is not, then that
141    // is an error.
142    char c = valueStr.charAt(pos++);
143    if (c != '(')
144    {
145      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
146          valueStr, pos-1, c);
147      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
148    }
149
150
151    // Skip over any spaces immediately following the opening parenthesis.
152    while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
153    {
154      pos++;
155    }
156
157    if (pos >= length)
158    {
159      // This means that the end of the value was reached before we could find
160      // the OID.  Ths is illegal.
161      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
162      throw new DirectoryException(
163              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
164    }
165
166
167    // The next set of characters must be the rule ID, which is an integer.
168    int ruleIDStartPos = pos;
169    while (pos < length && ((c = valueStr.charAt(pos++)) != ' '))
170    {
171      if (! isDigit(c))
172      {
173        LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.get(
174            valueStr, c, pos-1);
175        throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
176                                     message);
177      }
178    }
179
180    // If we're at the end of the value, then it isn't a valid DIT structure
181    // rule description.  Otherwise, parse out the rule ID.
182    int ruleID;
183    if (pos >= length)
184    {
185      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
186      throw new DirectoryException(
187              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
188    }
189    else
190    {
191      ruleID = Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1));
192    }
193
194
195    // Skip over the space(s) after the rule ID.
196    while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
197    {
198      pos++;
199    }
200
201    if (pos >= length)
202    {
203      // This means that the end of the value was reached before we could find
204      // the rule ID.  Ths is illegal.
205      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
206      throw new DirectoryException(
207              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
208    }
209
210
211    // At this point, we should have a pretty specific syntax that describes
212    // what may come next, but some of the components are optional and it would
213    // be pretty easy to put something in the wrong order, so we will be very
214    // flexible about what we can accept.  Just look at the next token, figure
215    // out what it is and how to treat what comes after it, then repeat until
216    // we get to the end of the value.  But before we start, set default values
217    // for everything else we might need to know.
218    LinkedHashMap<String,String> names = new LinkedHashMap<>();
219    String description = null;
220    boolean isObsolete = false;
221    NameForm nameForm = null;
222    boolean nameFormGiven = false;
223    LinkedHashSet<DITStructureRule> superiorRules = null;
224    LinkedHashMap<String,List<String>> extraProperties = new LinkedHashMap<>();
225
226
227    while (true)
228    {
229      StringBuilder tokenNameBuffer = new StringBuilder();
230      pos = readTokenName(valueStr, tokenNameBuffer, pos);
231      String tokenName = tokenNameBuffer.toString();
232      String lowerTokenName = toLowerCase(tokenName);
233      if (tokenName.equals(")"))
234      {
235        // We must be at the end of the value.  If not, then that's a problem.
236        if (pos < length)
237        {
238          LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_UNEXPECTED_CLOSE_PARENTHESIS.
239              get(valueStr, pos-1);
240          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
241                                       message);
242        }
243
244        break;
245      }
246      else if (lowerTokenName.equals("name"))
247      {
248        // This specifies the set of names for the DIT structure rule.  It may
249        // be a single name in single quotes, or it may be an open parenthesis
250        // followed by one or more names in single quotes separated by spaces.
251        c = valueStr.charAt(pos++);
252        if (c == '\'')
253        {
254          StringBuilder userBuffer  = new StringBuilder();
255          StringBuilder lowerBuffer = new StringBuilder();
256          pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, pos-1);
257          names.put(lowerBuffer.toString(), userBuffer.toString());
258        }
259        else if (c == '(')
260        {
261          StringBuilder userBuffer  = new StringBuilder();
262          StringBuilder lowerBuffer = new StringBuilder();
263          pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
264                                 pos);
265          names.put(lowerBuffer.toString(), userBuffer.toString());
266
267
268          while (true)
269          {
270            if (valueStr.charAt(pos) == ')')
271            {
272              // Skip over any spaces after the parenthesis.
273              pos++;
274              while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
275              {
276                pos++;
277              }
278
279              break;
280            }
281            else
282            {
283              userBuffer  = new StringBuilder();
284              lowerBuffer = new StringBuilder();
285
286              pos = readQuotedString(valueStr, lowerStr, userBuffer,
287                                     lowerBuffer, pos);
288              names.put(lowerBuffer.toString(), userBuffer.toString());
289            }
290          }
291        }
292        else
293        {
294          // This is an illegal character.
295          LocalizableMessage message =
296              ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(valueStr, c, pos-1);
297          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
298        }
299      }
300      else if (lowerTokenName.equals("desc"))
301      {
302        // This specifies the description for the DIT structure rule.  It is an
303        // arbitrary string of characters enclosed in single quotes.
304        StringBuilder descriptionBuffer = new StringBuilder();
305        pos = readQuotedString(valueStr, descriptionBuffer, pos);
306        description = descriptionBuffer.toString();
307      }
308      else if (lowerTokenName.equals("obsolete"))
309      {
310        // This indicates whether the DIT structure rule should be considered
311        // obsolete.  We do not need to do any more parsing for this token.
312        isObsolete = true;
313      }
314      else if (lowerTokenName.equals("form"))
315      {
316        // This should be the OID of the associated name form.
317        StringBuilder woidBuffer = new StringBuilder();
318        pos = readWOID(lowerStr, woidBuffer, pos);
319
320        nameFormGiven = true;
321        nameForm = schema.getNameForm(woidBuffer.toString());
322        if (nameForm == null && ! allowUnknownElements)
323        {
324          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
325              ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(valueStr, woidBuffer));
326        }
327      }
328      else if (lowerTokenName.equals("sup"))
329      {
330        LinkedList<DITStructureRule> superiorList = new LinkedList<>();
331
332        // This specifies the set of superior rule IDs (which are integers) for
333        // this DIT structure rule.  It may be a single rule ID or a set of
334        // rule IDs enclosed in parentheses and separated by spaces.
335        c = valueStr.charAt(pos++);
336        if (c == '(')
337        {
338          while (true)
339          {
340            // Skip over any leading spaces.
341            while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
342            {
343              pos++;
344            }
345
346            if (pos >= length)
347            {
348              LocalizableMessage message =
349                  ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
350              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
351                                           message);
352            }
353
354            // Read the next integer value.
355            ruleIDStartPos = pos;
356            while (pos < length && ((c = valueStr.charAt(pos++)) != ' '))
357            {
358              if (! isDigit(c))
359              {
360                LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.
361                    get(valueStr, c, pos-1);
362                throw new DirectoryException(
363                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
364              }
365            }
366
367            // If we're at the end of the value, then it isn't a valid DIT
368            // structure rule description.  Otherwise, parse out the rule ID.
369            int supRuleID;
370            if (pos >= length)
371            {
372              LocalizableMessage message =
373                  ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
374              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
375                                           message);
376            }
377            else
378            {
379              supRuleID =
380                   Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1));
381            }
382
383
384            // Get the DIT structure rule with the specified rule ID.
385            DITStructureRule superiorRule =
386                 schema.getDITStructureRule(supRuleID);
387            if (superiorRule == null)
388            {
389              if (! allowUnknownElements)
390              {
391                LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(
392                    valueStr, supRuleID);
393                throw new DirectoryException(
394                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
395              }
396            }
397            else
398            {
399              superiorList.add(superiorRule);
400            }
401
402
403            // Skip over any trailing spaces.
404            while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
405            {
406              pos++;
407            }
408
409            if (pos >= length)
410            {
411              LocalizableMessage message =
412                  ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
413              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
414                                           message);
415            }
416
417
418            // If the next character is a closing parenthesis, then read any
419            // spaces after it and break out of the loop.
420            if (c == ')')
421            {
422              pos++;
423              while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
424              {
425                pos++;
426              }
427
428              if (pos >= length)
429              {
430                LocalizableMessage message =
431                    ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
432                throw new DirectoryException(
433                               ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
434              }
435
436              break;
437            }
438          }
439        }
440        else
441        {
442          if (pos >= length)
443          {
444            LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
445            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
446                                         message);
447          }
448
449          // Read the next integer value.
450          ruleIDStartPos = pos - 1;
451          while (pos < length && ((c = valueStr.charAt(pos++)) != ' '))
452          {
453            if (! isDigit(c))
454            {
455              LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.get(valueStr, c, pos-1);
456              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
457                                           message);
458            }
459          }
460
461          // If we're at the end of the value, then it isn't a valid DIT
462          // structure rule description.  Otherwise, parse out the rule ID.
463          int supRuleID;
464          if (pos >= length)
465          {
466            LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
467            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
468                                         message);
469          }
470          else
471          {
472            supRuleID =
473                 Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1));
474          }
475
476
477          // Get the DIT structure rule with the specified rule ID.
478          DITStructureRule superiorRule = schema.getDITStructureRule(supRuleID);
479          if (superiorRule == null)
480          {
481            if (! allowUnknownElements)
482            {
483              LocalizableMessage message =
484                  ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(valueStr, supRuleID);
485              throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
486                                           message);
487            }
488          }
489          else
490          {
491            superiorList.add(superiorRule);
492          }
493
494
495          // Skip over any trailing spaces.
496          while (pos < length && ((c = valueStr.charAt(pos)) == ' '))
497          {
498            pos++;
499          }
500
501          if (pos >= length)
502          {
503            LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
504            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
505                                         message);
506          }
507        }
508
509        superiorRules = new LinkedHashSet<>(superiorList);
510      }
511      else
512      {
513        // This must be a non-standard property and it must be followed by
514        // either a single value in single quotes or an open parenthesis
515        // followed by one or more values in single quotes separated by spaces
516        // followed by a close parenthesis.
517        LinkedList<String> valueList = new LinkedList<>();
518        pos = readExtraParameterValues(valueStr, valueList, pos);
519        extraProperties.put(tokenName, valueList);
520      }
521    }
522
523
524    if (nameForm == null && !nameFormGiven)
525    {
526      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(valueStr);
527      throw new DirectoryException(
528              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
529    }
530
531
532    return new DITStructureRule(value.toString(), names, ruleID, description,
533                                isObsolete, nameForm, superiorRules,
534                                extraProperties);
535  }
536
537  /**
538   * Reads the next token name from the DIT content rule definition, skipping
539   * over any leading or trailing spaces, and appends it to the provided buffer.
540   *
541   * @param  valueStr   The string representation of the DIT content rule
542   *                    definition.
543   * @param  tokenName  The buffer into which the token name will be written.
544   * @param  startPos   The position in the provided string at which to start
545   *                    reading the token name.
546   *
547   * @return  The position of the first character that is not part of the token
548   *          name or one of the trailing spaces after it.
549   *
550   * @throws  DirectoryException  If a problem is encountered while reading the
551   *                              token name.
552   */
553  private static int readTokenName(String valueStr, StringBuilder tokenName,
554                                   int startPos)
555          throws DirectoryException
556  {
557    // Skip over any spaces at the beginning of the value.
558    char c = '\u0000';
559    int  length = valueStr.length();
560    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
561    {
562      startPos++;
563    }
564
565    if (startPos >= length)
566    {
567      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
568      throw new DirectoryException(
569              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
570    }
571
572
573    // Read until we find the next space.
574    while (startPos < length && ((c = valueStr.charAt(startPos++)) != ' '))
575    {
576      tokenName.append(c);
577    }
578
579
580    // Skip over any trailing spaces after the value.
581    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
582    {
583      startPos++;
584    }
585
586
587    // Return the position of the first non-space character after the token.
588    return startPos;
589  }
590
591  /**
592   * Reads the value of a string enclosed in single quotes, skipping over the
593   * quotes and any leading or trailing spaces, and appending the string to the
594   * provided buffer.
595   *
596   * @param  valueStr     The user-provided representation of the DIT content
597   *                      rule definition.
598   * @param  valueBuffer  The buffer into which the user-provided representation
599   *                      of the value will be placed.
600   * @param  startPos     The position in the provided string at which to start
601   *                      reading the quoted string.
602   *
603   * @return  The position of the first character that is not part of the quoted
604   *          string or one of the trailing spaces after it.
605   *
606   * @throws  DirectoryException  If a problem is encountered while reading the
607   *                              quoted string.
608   */
609  private static int readQuotedString(String valueStr,
610                                      StringBuilder valueBuffer, int startPos)
611          throws DirectoryException
612  {
613    // Skip over any spaces at the beginning of the value.
614    char c = '\u0000';
615    int  length = valueStr.length();
616    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
617    {
618      startPos++;
619    }
620
621    if (startPos >= length)
622    {
623      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
624      throw new DirectoryException(
625              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
626    }
627
628
629    // The next character must be a single quote.
630    if (c != '\'')
631    {
632      LocalizableMessage message =
633          ERR_ATTR_SYNTAX_DSR_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c);
634      throw new DirectoryException(
635              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
636    }
637
638
639    // Read until we find the closing quote.
640    startPos++;
641    while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
642    {
643      valueBuffer.append(c);
644      startPos++;
645    }
646
647
648    // Skip over any trailing spaces after the value.
649    startPos++;
650    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
651    {
652      startPos++;
653    }
654
655
656    // If we're at the end of the value, then that's illegal.
657    if (startPos >= length)
658    {
659      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
660      throw new DirectoryException(
661              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
662    }
663
664
665    // Return the position of the first non-space character after the token.
666    return startPos;
667  }
668
669  /**
670   * Reads the value of a string enclosed in single quotes, skipping over the
671   * quotes and any leading or trailing spaces, and appending the string to the
672   * provided buffer.
673   *
674   * @param  valueStr     The user-provided representation of the DIT content
675   *                      rule definition.
676   * @param  lowerStr     The all-lowercase representation of the DIT content
677   *                      rule definition.
678   * @param  userBuffer   The buffer into which the user-provided representation
679   *                      of the value will be placed.
680   * @param  lowerBuffer  The buffer into which the all-lowercase representation
681   *                      of the value will be placed.
682   * @param  startPos     The position in the provided string at which to start
683   *                      reading the quoted string.
684   *
685   * @return  The position of the first character that is not part of the quoted
686   *          string or one of the trailing spaces after it.
687   *
688   * @throws  DirectoryException  If a problem is encountered while reading the
689   *                              quoted string.
690   */
691  private static int readQuotedString(String valueStr, String lowerStr,
692                                      StringBuilder userBuffer,
693                                      StringBuilder lowerBuffer, int startPos)
694          throws DirectoryException
695  {
696    // Skip over any spaces at the beginning of the value.
697    char c = '\u0000';
698    int  length = lowerStr.length();
699    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
700    {
701      startPos++;
702    }
703
704    if (startPos >= length)
705    {
706      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
707      throw new DirectoryException(
708              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
709    }
710
711
712    // The next character must be a single quote.
713    if (c != '\'')
714    {
715      LocalizableMessage message =
716          ERR_ATTR_SYNTAX_DSR_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c);
717      throw new DirectoryException(
718              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
719    }
720
721
722    // Read until we find the closing quote.
723    startPos++;
724    while (startPos < length && ((c = lowerStr.charAt(startPos)) != '\''))
725    {
726      lowerBuffer.append(c);
727      userBuffer.append(valueStr.charAt(startPos));
728      startPos++;
729    }
730
731
732    // Skip over any trailing spaces after the value.
733    startPos++;
734    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
735    {
736      startPos++;
737    }
738
739
740    // If we're at the end of the value, then that's illegal.
741    if (startPos >= length)
742    {
743      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
744      throw new DirectoryException(
745              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
746    }
747
748
749    // Return the position of the first non-space character after the token.
750    return startPos;
751  }
752
753  /**
754   * Reads an attributeType/objectclass description or numeric OID from the
755   * provided string, skipping over any leading or trailing spaces, and
756   * appending the value to the provided buffer.
757   *
758   * @param  lowerStr    The string from which the name or OID is to be read.
759   * @param  woidBuffer  The buffer into which the name or OID should be
760   *                     appended.
761   * @param  startPos    The position at which to start reading.
762   *
763   * @return  The position of the first character after the name or OID that is
764   *          not a space.
765   *
766   * @throws  DirectoryException  If a problem is encountered while reading the
767   *                              name or OID.
768   */
769  private static int readWOID(String lowerStr, StringBuilder woidBuffer,
770                              int startPos)
771          throws DirectoryException
772  {
773    // Skip over any spaces at the beginning of the value.
774    char c = '\u0000';
775    int  length = lowerStr.length();
776    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
777    {
778      startPos++;
779    }
780
781    if (startPos >= length)
782    {
783      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
784      throw new DirectoryException(
785              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
786    }
787
788
789    // The next character must be either numeric (for an OID) or alphabetic (for
790    // an attribute type/objectclass description).
791    if (isDigit(c))
792    {
793      // This must be a numeric OID.  In that case, we will accept only digits
794      // and periods, but not consecutive periods.
795      boolean lastWasPeriod = false;
796      while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' '))
797      {
798        if (c == '.')
799        {
800          if (lastWasPeriod)
801          {
802            LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_DOUBLE_PERIOD_IN_NUMERIC_OID.
803                get(lowerStr, startPos-1);
804            throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
805                                         message);
806          }
807          else
808          {
809            woidBuffer.append(c);
810            lastWasPeriod = true;
811          }
812        }
813        else if (! isDigit(c))
814        {
815          // Technically, this must be an illegal character.  However, it is
816          // possible that someone just got sloppy and did not include a space
817          // between the name/OID and a closing parenthesis.  In that case,
818          // we'll assume it's the end of the value.  What's more, we'll have
819          // to prematurely return to nasty side effects from stripping off
820          // additional characters.
821          if (c == ')')
822          {
823            return startPos-1;
824          }
825
826          // This must have been an illegal character.
827          LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_NUMERIC_OID.get(lowerStr, c, startPos-1);
828          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
829        }
830        else
831        {
832          woidBuffer.append(c);
833          lastWasPeriod = false;
834        }
835      }
836    }
837    else if (isAlpha(c))
838    {
839      // This must be an attribute type/objectclass description.  In this case,
840      // we will only accept alphabetic characters, numeric digits, and the
841      // hyphen.
842      while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' '))
843      {
844        if (isAlpha(c) || isDigit(c) || c == '-' ||
845            (c == '_' && DirectoryServer.allowAttributeNameExceptions()))
846        {
847          woidBuffer.append(c);
848        }
849        else
850        {
851          // Technically, this must be an illegal character.  However, it is
852          // possible that someone just got sloppy and did not include a space
853          // between the name/OID and a closing parenthesis.  In that case,
854          // we'll assume it's the end of the value.  What's more, we'll have
855          // to prematurely return to nasty side effects from stripping off
856          // additional characters.
857          if (c == ')')
858          {
859            return startPos-1;
860          }
861
862          // This must have been an illegal character.
863          LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_STRING_OID.get(lowerStr, c, startPos-1);
864          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
865        }
866      }
867    }
868    else
869    {
870      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(lowerStr, c, startPos);
871      throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
872    }
873
874
875    // Skip over any trailing spaces after the value.
876    while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' '))
877    {
878      startPos++;
879    }
880
881
882    // If we're at the end of the value, then that's illegal.
883    if (startPos >= length)
884    {
885      LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr);
886      throw new DirectoryException(
887              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
888    }
889
890
891    // Return the position of the first non-space character after the token.
892    return startPos;
893  }
894
895  /**
896   * Reads the value for an "extra" parameter.  It will handle a single unquoted
897   * word (which is technically illegal, but we'll allow it), a single quoted
898   * string, or an open parenthesis followed by a space-delimited set of quoted
899   * strings or unquoted words followed by a close parenthesis.
900   *
901   * @param  valueStr   The string containing the information to be read.
902   * @param  valueList  The list of "extra" parameter values read so far.
903   * @param  startPos   The position in the value string at which to start
904   *                    reading.
905   *
906   * @return  The "extra" parameter value that was read.
907   *
908   * @throws  DirectoryException  If a problem occurs while attempting to read
909   *                              the value.
910   */
911  private static int readExtraParameterValues(String valueStr,
912                          List<String> valueList, int startPos)
913          throws DirectoryException
914  {
915    // Skip over any leading spaces.
916    int length = valueStr.length();
917    char c = '\u0000';
918    while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
919    {
920      startPos++;
921    }
922
923    if (startPos >= length)
924    {
925      LocalizableMessage message =
926          ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
927      throw new DirectoryException(
928              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
929    }
930
931
932    // Look at the next character.  If it is a quote, then parse until the next
933    // quote and end.  If it is an open parenthesis, then parse individual
934    // values until the close parenthesis and end.  Otherwise, parse until the
935    // next space and end.
936    if (c == '\'')
937    {
938      // Parse until the closing quote.
939      StringBuilder valueBuffer = new StringBuilder();
940      startPos++;
941      while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
942      {
943        valueBuffer.append(c);
944        startPos++;
945      }
946      startPos++;
947      valueList.add(valueBuffer.toString());
948    }
949    else if (c == '(')
950    {
951      startPos++;
952      // We're expecting a list of values. Quoted, space separated.
953      while (true)
954      {
955        // Skip over any leading spaces;
956        while (startPos < length && ((c = valueStr.charAt(startPos)) == ' '))
957        {
958          startPos++;
959        }
960
961        if (startPos >= length)
962        {
963          LocalizableMessage message =
964              ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
965          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
966                                       message);
967        }
968
969        if (c == ')')
970        {
971          // This is the end of the list.
972          startPos++;
973          break;
974        }
975        else if (c == '(')
976        {
977          // This is an illegal character.
978          LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(valueStr, c, startPos);
979          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
980        }
981        else if (c == '\'')
982        {
983          // We have a quoted string
984          StringBuilder valueBuffer = new StringBuilder();
985          startPos++;
986          while (startPos < length && ((c = valueStr.charAt(startPos)) != '\''))
987          {
988            valueBuffer.append(c);
989            startPos++;
990          }
991
992          valueList.add(valueBuffer.toString());
993          startPos++;
994        }
995        else
996        {
997          //Consider unquoted string
998          StringBuilder valueBuffer = new StringBuilder();
999          while (startPos < length && ((c = valueStr.charAt(startPos)) != ' '))
1000          {
1001            valueBuffer.append(c);
1002            startPos++;
1003          }
1004
1005          valueList.add(valueBuffer.toString());
1006        }
1007
1008        if (startPos >= length)
1009        {
1010          LocalizableMessage message =
1011              ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
1012          throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1013        }
1014      }
1015    }
1016    else
1017    {
1018      // Parse until the next space.
1019      StringBuilder valueBuffer = new StringBuilder();
1020      while (startPos < length && ((c = valueStr.charAt(startPos)) != ' '))
1021      {
1022        valueBuffer.append(c);
1023        startPos++;
1024      }
1025
1026      valueList.add(valueBuffer.toString());
1027    }
1028
1029    // Skip over any trailing spaces.
1030    while (startPos < length && valueStr.charAt(startPos) == ' ')
1031    {
1032      startPos++;
1033    }
1034
1035    if (startPos >= length)
1036    {
1037      LocalizableMessage message =
1038          ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr);
1039      throw new DirectoryException(
1040              ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1041    }
1042
1043    return startPos;
1044  }
1045}
1046