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