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.AttributeType; 046 import org.opends.server.types.ByteString; 047 import org.opends.server.types.DirectoryException; 048 import org.opends.server.types.InitializationException; 049 import org.opends.server.types.NameForm; 050 import org.opends.server.types.ObjectClass; 051 import org.opends.server.types.ObjectClassType; 052 import org.opends.server.types.ResultCode; 053 import org.opends.server.types.Schema; 054 055 import static org.opends.server.loggers.debug.DebugLogger.*; 056 import org.opends.server.loggers.debug.DebugTracer; 057 import org.opends.server.types.DebugLogLevel; 058 import static org.opends.messages.SchemaMessages.*; 059 import org.opends.messages.MessageBuilder; 060 import static org.opends.server.schema.SchemaConstants.*; 061 import static org.opends.server.util.StaticUtils.*; 062 063 064 065 /** 066 * This class implements the name form description syntax, which is used to 067 * hold name form definitions in the server schema. The format of this syntax 068 * is defined in RFC 2252. 069 */ 070 public class NameFormSyntax 071 extends AttributeSyntax<AttributeSyntaxCfg> 072 { 073 /** 074 * The tracer object for the debug logger. 075 */ 076 private static final DebugTracer TRACER = getTracer(); 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 NameFormSyntax() 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_NAME_FORM_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_NAME_FORM_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_NAME_FORM_NAME); 134 throw new InitializationException(message); 135 } 136 } 137 138 139 140 /** 141 * {@inheritDoc} 142 */ 143 public String getSyntaxName() 144 { 145 return SYNTAX_NAME_FORM_NAME; 146 } 147 148 149 150 /** 151 * {@inheritDoc} 152 */ 153 public String getOID() 154 { 155 return SYNTAX_NAME_FORM_OID; 156 } 157 158 159 160 /** 161 * {@inheritDoc} 162 */ 163 public String getDescription() 164 { 165 return SYNTAX_NAME_FORM_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 decodeNameForm method to determine if the value is 218 // acceptable. 219 try 220 { 221 decodeNameForm(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 name form 240 * definition according to the rules of this syntax. Note that the provided 241 * octet string value does not need to be normalized (and in fact, it should 242 * not be in order to allow the desired capitalization to be 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 structural objectclass and/or 251 * required or optional attribute types which 252 * are not defined in the server schema. This 253 * should only be true when called by 254 * {@code valueIsAcceptable}. 255 * 256 * @return The decoded name form definition. 257 * 258 * @throws DirectoryException If the provided value cannot be decoded as an 259 * name form definition. 260 */ 261 public static NameForm decodeNameForm(ByteString value, 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_NAME_FORM_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_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get( 296 valueStr, (pos-1), 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_NAME_FORM_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 OID. Strictly speaking, this 319 // should only be a numeric OID, but we'll also allow for the 320 // "ocname-oid" case as well. Look at the first character to figure out 321 // which we will be using. 322 int oidStartPos = pos; 323 if (isDigit(c)) 324 { 325 // This must be a numeric OID. In that case, we will accept only digits 326 // and periods, but not consecutive periods. 327 boolean lastWasPeriod = false; 328 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' ')) 329 { 330 if (c == '.') 331 { 332 if (lastWasPeriod) 333 { 334 Message message = 335 ERR_ATTR_SYNTAX_NAME_FORM_DOUBLE_PERIOD_IN_NUMERIC_OID. 336 get(valueStr, (pos-1)); 337 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 338 message); 339 } 340 else 341 { 342 lastWasPeriod = true; 343 } 344 } 345 else if (! isDigit(c)) 346 { 347 // This must have been an illegal character. 348 Message message = 349 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_NUMERIC_OID. 350 get(valueStr, 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 = 375 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_STRING_OID. 376 get(valueStr, c, (pos-1)); 377 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 378 message); 379 } 380 } 381 } 382 383 384 // If we're at the end of the value, then it isn't a valid name form 385 // description. Otherwise, parse out the OID. 386 String oid; 387 if (pos >= length) 388 { 389 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 390 throw new DirectoryException( 391 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 392 } 393 else 394 { 395 oid = lowerStr.substring(oidStartPos, (pos-1)); 396 } 397 398 399 // Skip over the space(s) after the OID. 400 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 401 { 402 pos++; 403 } 404 405 if (pos >= length) 406 { 407 // This means that the end of the value was reached before we could find 408 // the OID. Ths is illegal. 409 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 410 throw new DirectoryException( 411 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 412 } 413 414 415 // At this point, we should have a pretty specific syntax that describes 416 // what may come next, but some of the components are optional and it would 417 // be pretty easy to put something in the wrong order, so we will be very 418 // flexible about what we can accept. Just look at the next token, figure 419 // out what it is and how to treat what comes after it, then repeat until 420 // we get to the end of the value. But before we start, set default values 421 // for everything else we might need to know. 422 LinkedHashMap<String,String> names = new LinkedHashMap<String,String>(); 423 String description = null; 424 boolean isObsolete = false; 425 ObjectClass structuralClass = null; 426 LinkedHashSet<AttributeType> requiredAttributes = 427 new LinkedHashSet<AttributeType>(); 428 LinkedHashSet<AttributeType> optionalAttributes = 429 new LinkedHashSet<AttributeType>(); 430 LinkedHashMap<String,List<String>> extraProperties = 431 new LinkedHashMap<String,List<String>>(); 432 433 434 while (true) 435 { 436 StringBuilder tokenNameBuffer = new StringBuilder(); 437 pos = readTokenName(valueStr, tokenNameBuffer, pos); 438 String tokenName = tokenNameBuffer.toString(); 439 String lowerTokenName = toLowerCase(tokenName); 440 if (tokenName.equals(")")) 441 { 442 // We must be at the end of the value. If not, then that's a problem. 443 if (pos < length) 444 { 445 Message message = 446 ERR_ATTR_SYNTAX_NAME_FORM_UNEXPECTED_CLOSE_PARENTHESIS. 447 get(valueStr, (pos-1)); 448 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 449 message); 450 } 451 452 break; 453 } 454 else if (lowerTokenName.equals("name")) 455 { 456 // This specifies the set of names for the name form. It may be a 457 // single name in single quotes, or it may be an open parenthesis 458 // followed by one or more names in single quotes separated by spaces. 459 c = valueStr.charAt(pos++); 460 if (c == '\'') 461 { 462 StringBuilder userBuffer = new StringBuilder(); 463 StringBuilder lowerBuffer = new StringBuilder(); 464 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, 465 (pos-1)); 466 names.put(lowerBuffer.toString(), userBuffer.toString()); 467 } 468 else if (c == '(') 469 { 470 StringBuilder userBuffer = new StringBuilder(); 471 StringBuilder lowerBuffer = new StringBuilder(); 472 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, 473 pos); 474 names.put(lowerBuffer.toString(), userBuffer.toString()); 475 476 477 while (true) 478 { 479 if (valueStr.charAt(pos) == ')') 480 { 481 // Skip over any spaces after the parenthesis. 482 pos++; 483 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 484 { 485 pos++; 486 } 487 488 break; 489 } 490 else 491 { 492 userBuffer = new StringBuilder(); 493 lowerBuffer = new StringBuilder(); 494 495 pos = readQuotedString(valueStr, lowerStr, userBuffer, 496 lowerBuffer, pos); 497 names.put(lowerBuffer.toString(), userBuffer.toString()); 498 } 499 } 500 } 501 else 502 { 503 // This is an illegal character. 504 Message message = 505 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(valueStr, c, (pos-1)); 506 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 507 message); 508 } 509 } 510 else if (lowerTokenName.equals("desc")) 511 { 512 // This specifies the description for the name form. It is an 513 // arbitrary string of characters enclosed in single quotes. 514 StringBuilder descriptionBuffer = new StringBuilder(); 515 pos = readQuotedString(valueStr, descriptionBuffer, pos); 516 description = descriptionBuffer.toString(); 517 } 518 else if (lowerTokenName.equals("obsolete")) 519 { 520 // This indicates whether the name form should be considered obsolete. 521 // We do not need to do any more parsing for this token. 522 isObsolete = true; 523 } 524 else if (lowerTokenName.equals("oc")) 525 { 526 // This specifies the name or OID of the structural objectclass for this 527 // name form. 528 StringBuilder woidBuffer = new StringBuilder(); 529 pos = readWOID(lowerStr, woidBuffer, pos); 530 structuralClass = schema.getObjectClass(woidBuffer.toString()); 531 if (structuralClass == null) 532 { 533 // This is bad because we don't know what the structural objectclass 534 // is. 535 if (allowUnknownElements) 536 { 537 structuralClass = DirectoryServer.getDefaultObjectClass( 538 woidBuffer.toString()); 539 } 540 else 541 { 542 Message message = 543 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS. 544 get(String.valueOf(oid), String.valueOf(woidBuffer)); 545 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 546 message); 547 } 548 } 549 else if (structuralClass.getObjectClassType() != 550 ObjectClassType.STRUCTURAL) 551 { 552 // This is bad because the associated structural class type is not 553 // structural. 554 Message message = 555 ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL. 556 get(String.valueOf(oid), String.valueOf(woidBuffer), 557 structuralClass.getNameOrOID(), 558 String.valueOf(structuralClass.getObjectClassType())); 559 throw new DirectoryException( 560 ResultCode.CONSTRAINT_VIOLATION, message); 561 } 562 } 563 else if (lowerTokenName.equals("must")) 564 { 565 LinkedList<AttributeType> attrs = new LinkedList<AttributeType>(); 566 567 // This specifies the set of required attributes for the name from. 568 // It may be a single name or OID (not in quotes), or it may be an 569 // open parenthesis followed by one or more names separated by spaces 570 // and the dollar sign character, followed by a closing parenthesis. 571 c = valueStr.charAt(pos++); 572 if (c == '(') 573 { 574 while (true) 575 { 576 StringBuilder woidBuffer = new StringBuilder(); 577 pos = readWOID(lowerStr, woidBuffer, (pos)); 578 579 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 580 if (attr == null) 581 { 582 // This isn't good because it means that the name form requires 583 // an attribute type that we don't know anything about. 584 if (allowUnknownElements) 585 { 586 attr = DirectoryServer.getDefaultAttributeType( 587 woidBuffer.toString()); 588 } 589 else 590 { 591 Message message = 592 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR. 593 get(oid, woidBuffer.toString()); 594 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 595 message); 596 } 597 } 598 599 attrs.add(attr); 600 601 602 // The next character must be either a dollar sign or a closing 603 // parenthesis. 604 c = valueStr.charAt(pos++); 605 if (c == ')') 606 { 607 // This denotes the end of the list. 608 break; 609 } 610 else if (c != '$') 611 { 612 Message message = ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get( 613 valueStr, c, (pos-1)); 614 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 615 message); 616 } 617 } 618 } 619 else 620 { 621 StringBuilder woidBuffer = new StringBuilder(); 622 pos = readWOID(lowerStr, woidBuffer, (pos-1)); 623 624 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 625 if (attr == null) 626 { 627 // This isn't good because it means that the name form requires an 628 // attribute type that we don't know anything about. 629 if (allowUnknownElements) 630 { 631 attr = DirectoryServer.getDefaultAttributeType( 632 woidBuffer.toString()); 633 } 634 else 635 { 636 Message message = ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR. 637 get(oid, woidBuffer.toString()); 638 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 639 message); 640 } 641 } 642 643 attrs.add(attr); 644 } 645 646 requiredAttributes.addAll(attrs); 647 } 648 else if (lowerTokenName.equals("may")) 649 { 650 LinkedList<AttributeType> attrs = new LinkedList<AttributeType>(); 651 652 // This specifies the set of optional attributes for the name form. It 653 // may be a single name or OID (not in quotes), or it may be an open 654 // parenthesis followed by one or more names separated by spaces and the 655 // dollar sign character, followed by a closing parenthesis. 656 c = valueStr.charAt(pos++); 657 if (c == '(') 658 { 659 while (true) 660 { 661 StringBuilder woidBuffer = new StringBuilder(); 662 pos = readWOID(lowerStr, woidBuffer, (pos)); 663 664 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 665 if (attr == null) 666 { 667 // This isn't good because it means that the name form allows an 668 // attribute type that we don't know anything about. 669 if (allowUnknownElements) 670 { 671 attr = DirectoryServer.getDefaultAttributeType( 672 woidBuffer.toString()); 673 } 674 else 675 { 676 Message message = 677 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR. 678 get(oid, woidBuffer.toString()); 679 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 680 message); 681 } 682 } 683 684 attrs.add(attr); 685 686 687 // The next character must be either a dollar sign or a closing 688 // parenthesis. 689 c = valueStr.charAt(pos++); 690 if (c == ')') 691 { 692 // This denotes the end of the list. 693 break; 694 } 695 else if (c != '$') 696 { 697 Message message = ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get( 698 valueStr, c, (pos-1)); 699 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 700 message); 701 } 702 } 703 } 704 else 705 { 706 StringBuilder woidBuffer = new StringBuilder(); 707 pos = readWOID(lowerStr, woidBuffer, (pos-1)); 708 709 AttributeType attr = schema.getAttributeType(woidBuffer.toString()); 710 if (attr == null) 711 { 712 // This isn't good because it means that the name form allows an 713 // attribute type that we don't know anything about. 714 if (allowUnknownElements) 715 { 716 attr = DirectoryServer.getDefaultAttributeType( 717 woidBuffer.toString()); 718 } 719 else 720 { 721 Message message = ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR. 722 get(oid, woidBuffer.toString()); 723 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, 724 message); 725 } 726 } 727 728 attrs.add(attr); 729 } 730 731 optionalAttributes.addAll(attrs); 732 } 733 else 734 { 735 // This must be a non-standard property and it must be followed by 736 // either a single value in single quotes or an open parenthesis 737 // followed by one or more values in single quotes separated by spaces 738 // followed by a close parenthesis. 739 LinkedList<String> valueList = new LinkedList<String>(); 740 pos = readExtraParameterValues(valueStr, valueList, pos); 741 extraProperties.put(tokenName, valueList); 742 } 743 } 744 745 746 // Make sure that a structural class was specified. If not, then it cannot 747 // be valid. 748 if (structuralClass == null) 749 { 750 Message message = 751 ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS.get(valueStr); 752 throw new DirectoryException( 753 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 754 } 755 756 757 return new NameForm(value.stringValue(), names, oid, description, 758 isObsolete, structuralClass, requiredAttributes, 759 optionalAttributes, extraProperties); 760 } 761 762 763 764 /** 765 * Reads the next token name from the name form definition, skipping over any 766 * leading or trailing spaces, and appends it to the provided buffer. 767 * 768 * @param valueStr The string representation of the name form definition. 769 * @param tokenName The buffer into which the token name will be written. 770 * @param startPos The position in the provided string at which to start 771 * reading the token name. 772 * 773 * @return The position of the first character that is not part of the token 774 * name or one of the trailing spaces after it. 775 * 776 * @throws DirectoryException If a problem is encountered while reading the 777 * token name. 778 */ 779 private static int readTokenName(String valueStr, StringBuilder tokenName, 780 int startPos) 781 throws DirectoryException 782 { 783 // Skip over any spaces at the beginning of the value. 784 char c = '\u0000'; 785 int length = valueStr.length(); 786 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 787 { 788 startPos++; 789 } 790 791 if (startPos >= length) 792 { 793 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 794 throw new DirectoryException( 795 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 796 } 797 798 799 // Read until we find the next space. 800 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' ')) 801 { 802 tokenName.append(c); 803 } 804 805 806 // Skip over any trailing spaces after the value. 807 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 808 { 809 startPos++; 810 } 811 812 813 // Return the position of the first non-space character after the token. 814 return startPos; 815 } 816 817 818 819 /** 820 * Reads the value of a string enclosed in single quotes, skipping over the 821 * quotes and any leading or trailing spaces, and appending the string to the 822 * provided buffer. 823 * 824 * @param valueStr The user-provided representation of the name form 825 * definition. 826 * @param valueBuffer The buffer into which the user-provided representation 827 * of the value will be placed. 828 * @param startPos The position in the provided string at which to start 829 * reading the quoted string. 830 * 831 * @return The position of the first character that is not part of the quoted 832 * string or one of the trailing spaces after it. 833 * 834 * @throws DirectoryException If a problem is encountered while reading the 835 * quoted string. 836 */ 837 private static int readQuotedString(String valueStr, 838 StringBuilder valueBuffer, int startPos) 839 throws DirectoryException 840 { 841 // Skip over any spaces at the beginning of the value. 842 char c = '\u0000'; 843 int length = valueStr.length(); 844 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 845 { 846 startPos++; 847 } 848 849 if (startPos >= length) 850 { 851 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 852 throw new DirectoryException( 853 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 854 } 855 856 857 // The next character must be a single quote. 858 if (c != '\'') 859 { 860 Message message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_QUOTE_AT_POS.get( 861 valueStr, startPos, c); 862 throw new DirectoryException( 863 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 864 } 865 866 867 // Read until we find the closing quote. 868 startPos++; 869 while ((startPos < length) && ((c = valueStr.charAt(startPos)) != '\'')) 870 { 871 valueBuffer.append(c); 872 startPos++; 873 } 874 875 876 // Skip over any trailing spaces after the value. 877 startPos++; 878 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 879 { 880 startPos++; 881 } 882 883 884 // If we're at the end of the value, then that's illegal. 885 if (startPos >= length) 886 { 887 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 888 throw new DirectoryException( 889 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 890 } 891 892 893 // Return the position of the first non-space character after the token. 894 return startPos; 895 } 896 897 898 899 /** 900 * Reads the value of a string enclosed in single quotes, skipping over the 901 * quotes and any leading or trailing spaces, and appending the string to the 902 * provided buffer. 903 * 904 * @param valueStr The user-provided representation of the name form 905 * definition. 906 * @param lowerStr The all-lowercase representation of the name form 907 * definition. 908 * @param userBuffer The buffer into which the user-provided representation 909 * of the value will be placed. 910 * @param lowerBuffer The buffer into which the all-lowercase representation 911 * of the value will be placed. 912 * @param startPos The position in the provided string at which to start 913 * reading the quoted string. 914 * 915 * @return The position of the first character that is not part of the quoted 916 * string or one of the trailing spaces after it. 917 * 918 * @throws DirectoryException If a problem is encountered while reading the 919 * quoted string. 920 */ 921 private static int readQuotedString(String valueStr, String lowerStr, 922 StringBuilder userBuffer, 923 StringBuilder lowerBuffer, int startPos) 924 throws DirectoryException 925 { 926 // Skip over any spaces at the beginning of the value. 927 char c = '\u0000'; 928 int length = lowerStr.length(); 929 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 930 { 931 startPos++; 932 } 933 934 if (startPos >= length) 935 { 936 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr); 937 throw new DirectoryException( 938 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 939 } 940 941 942 // The next character must be a single quote. 943 if (c != '\'') 944 { 945 Message message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_QUOTE_AT_POS.get( 946 valueStr, startPos, c); 947 throw new DirectoryException( 948 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 949 } 950 951 952 // Read until we find the closing quote. 953 startPos++; 954 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) != '\'')) 955 { 956 lowerBuffer.append(c); 957 userBuffer.append(valueStr.charAt(startPos)); 958 startPos++; 959 } 960 961 962 // Skip over any trailing spaces after the value. 963 startPos++; 964 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 965 { 966 startPos++; 967 } 968 969 970 // If we're at the end of the value, then that's illegal. 971 if (startPos >= length) 972 { 973 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr); 974 throw new DirectoryException( 975 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 976 } 977 978 979 // Return the position of the first non-space character after the token. 980 return startPos; 981 } 982 983 984 985 /** 986 * Reads the attribute type description or numeric OID from the provided 987 * string, skipping over any leading or trailing spaces, and appending the 988 * value to the provided buffer. 989 * 990 * @param lowerStr The string from which the name or OID is to be read. 991 * @param woidBuffer The buffer into which the name or OID should be 992 * appended. 993 * @param startPos The position at which to start reading. 994 * 995 * @return The position of the first character after the name or OID that is 996 * not a space. 997 * 998 * @throws DirectoryException If a problem is encountered while reading the 999 * name or OID. 1000 */ 1001 private static int readWOID(String lowerStr, StringBuilder woidBuffer, 1002 int startPos) 1003 throws DirectoryException 1004 { 1005 // Skip over any spaces at the beginning of the value. 1006 char c = '\u0000'; 1007 int length = lowerStr.length(); 1008 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 1009 { 1010 startPos++; 1011 } 1012 1013 if (startPos >= length) 1014 { 1015 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr); 1016 throw new DirectoryException( 1017 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1018 } 1019 1020 1021 // The next character must be either numeric (for an OID) or alphabetic (for 1022 // an attribute type description). 1023 if (isDigit(c)) 1024 { 1025 // This must be a numeric OID. In that case, we will accept only digits 1026 // and periods, but not consecutive periods. 1027 boolean lastWasPeriod = false; 1028 while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' ')) 1029 { 1030 if (c == '.') 1031 { 1032 if (lastWasPeriod) 1033 { 1034 Message message = 1035 ERR_ATTR_SYNTAX_NAME_FORM_DOUBLE_PERIOD_IN_NUMERIC_OID. 1036 get(lowerStr, (startPos-1)); 1037 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1038 message); 1039 } 1040 else 1041 { 1042 woidBuffer.append(c); 1043 lastWasPeriod = true; 1044 } 1045 } 1046 else if (! isDigit(c)) 1047 { 1048 // Technically, this must be an illegal character. However, it is 1049 // possible that someone just got sloppy and did not include a space 1050 // between the name/OID and a closing parenthesis. In that case, 1051 // we'll assume it's the end of the value. What's more, we'll have 1052 // to prematurely return to nasty side effects from stripping off 1053 // additional characters. 1054 if (c == ')') 1055 { 1056 return (startPos-1); 1057 } 1058 1059 // This must have been an illegal character. 1060 Message message = 1061 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_NUMERIC_OID. 1062 get(lowerStr, c, (startPos-1)); 1063 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1064 message); 1065 } 1066 else 1067 { 1068 woidBuffer.append(c); 1069 lastWasPeriod = false; 1070 } 1071 } 1072 } 1073 else if (isAlpha(c)) 1074 { 1075 // This must be an attribute type description. In this case, we will only 1076 // accept alphabetic characters, numeric digits, and the hyphen. 1077 while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' ')) 1078 { 1079 if (isAlpha(c) || isDigit(c) || (c == '-') || 1080 ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) 1081 { 1082 woidBuffer.append(c); 1083 } 1084 else 1085 { 1086 // Technically, this must be an illegal character. However, it is 1087 // possible that someone just got sloppy and did not include a space 1088 // between the name/OID and a closing parenthesis. In that case, 1089 // we'll assume it's the end of the value. What's more, we'll have 1090 // to prematurely return to nasty side effects from stripping off 1091 // additional characters. 1092 if (c == ')') 1093 { 1094 return (startPos-1); 1095 } 1096 1097 // This must have been an illegal character. 1098 Message message = 1099 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_STRING_OID. 1100 get(lowerStr, c, (startPos-1)); 1101 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1102 message); 1103 } 1104 } 1105 } 1106 else 1107 { 1108 Message message = 1109 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(lowerStr, c, startPos); 1110 throw new DirectoryException( 1111 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1112 } 1113 1114 1115 // Skip over any trailing spaces after the value. 1116 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' ')) 1117 { 1118 startPos++; 1119 } 1120 1121 1122 // If we're at the end of the value, then that's illegal. 1123 if (startPos >= length) 1124 { 1125 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr); 1126 throw new DirectoryException( 1127 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1128 } 1129 1130 1131 // Return the position of the first non-space character after the token. 1132 return startPos; 1133 } 1134 1135 1136 1137 /** 1138 * Reads the value for an "extra" parameter. It will handle a single unquoted 1139 * word (which is technically illegal, but we'll allow it), a single quoted 1140 * string, or an open parenthesis followed by a space-delimited set of quoted 1141 * strings or unquoted words followed by a close parenthesis. 1142 * 1143 * @param valueStr The string containing the information to be read. 1144 * @param valueList The list of "extra" parameter values read so far. 1145 * @param startPos The position in the value string at which to start 1146 * reading. 1147 * 1148 * @return The "extra" parameter value that was read. 1149 * 1150 * @throws DirectoryException If a problem occurs while attempting to read 1151 * the value. 1152 */ 1153 private static int readExtraParameterValues(String valueStr, 1154 List<String> valueList, int startPos) 1155 throws DirectoryException 1156 { 1157 // Skip over any leading spaces. 1158 int length = valueStr.length(); 1159 char c = valueStr.charAt(startPos++); 1160 while ((startPos < length) && (c == ' ')) 1161 { 1162 c = valueStr.charAt(startPos++); 1163 } 1164 1165 if (startPos >= length) 1166 { 1167 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 1168 throw new DirectoryException( 1169 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1170 } 1171 1172 1173 // Look at the next character. If it is a quote, then parse until the next 1174 // quote and end. If it is an open parenthesis, then parse individual 1175 // values until the close parenthesis and end. Otherwise, parse until the 1176 // next space and end. 1177 if (c == '\'') 1178 { 1179 // Parse until the closing quote. 1180 StringBuilder valueBuffer = new StringBuilder(); 1181 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != '\'')) 1182 { 1183 valueBuffer.append(c); 1184 } 1185 1186 valueList.add(valueBuffer.toString()); 1187 } 1188 else if (c == '(') 1189 { 1190 while (true) 1191 { 1192 // Skip over any leading spaces; 1193 startPos++; 1194 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 1195 { 1196 startPos++; 1197 } 1198 1199 if (startPos >= length) 1200 { 1201 Message message = 1202 ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 1203 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1204 message); 1205 } 1206 1207 1208 if (c == ')') 1209 { 1210 // This is the end of the list. 1211 break; 1212 } 1213 else if (c == '(') 1214 { 1215 // This is an illegal character. 1216 Message message = 1217 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(valueStr, c, startPos); 1218 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 1219 message); 1220 } 1221 else 1222 { 1223 // We'll recursively call this method to deal with this. 1224 startPos = readExtraParameterValues(valueStr, valueList, startPos); 1225 } 1226 } 1227 } 1228 else 1229 { 1230 // Parse until the next space. 1231 StringBuilder valueBuffer = new StringBuilder(); 1232 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' ')) 1233 { 1234 valueBuffer.append(c); 1235 } 1236 1237 valueList.add(valueBuffer.toString()); 1238 } 1239 1240 1241 1242 // Skip over any trailing spaces. 1243 while ((startPos < length) && (valueStr.charAt(startPos) == ' ')) 1244 { 1245 startPos++; 1246 } 1247 1248 if (startPos >= length) 1249 { 1250 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr); 1251 throw new DirectoryException( 1252 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1253 } 1254 1255 1256 return startPos; 1257 } 1258 } 1259