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