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