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