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