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