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