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