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