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