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