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