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