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.ArrayList; 024import java.util.HashMap; 025import java.util.LinkedHashMap; 026import java.util.LinkedList; 027import java.util.List; 028 029import org.forgerock.i18n.LocalizableMessage; 030import org.forgerock.i18n.slf4j.LocalizedLogger; 031import org.forgerock.opendj.ldap.ByteSequence; 032import org.forgerock.opendj.ldap.ByteString; 033import org.forgerock.opendj.ldap.ResultCode; 034import org.forgerock.opendj.ldap.schema.MatchingRule; 035import org.forgerock.opendj.ldap.schema.SchemaBuilder; 036import org.forgerock.opendj.ldap.schema.Syntax; 037import org.opends.server.admin.std.server.AttributeSyntaxCfg; 038import org.opends.server.api.AttributeSyntax; 039import org.opends.server.core.DirectoryServer; 040import org.opends.server.types.CommonSchemaElements; 041import org.opends.server.types.DirectoryException; 042import org.opends.server.types.LDAPSyntaxDescription; 043import org.opends.server.types.Schema; 044 045/** 046 * This class defines the LDAP syntax description syntax, which is used to 047 * hold attribute syntax definitions in the server schema. The format of this 048 * syntax is defined in RFC 2252. 049 */ 050public class LDAPSyntaxDescriptionSyntax 051 extends AttributeSyntax<AttributeSyntaxCfg> 052{ 053 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 054 055 /** The default equality matching rule for this syntax. */ 056 private MatchingRule defaultEqualityMatchingRule; 057 058 /** The default ordering matching rule for this syntax. */ 059 private MatchingRule defaultOrderingMatchingRule; 060 061 /** The default substring matching rule for this syntax. */ 062 private MatchingRule defaultSubstringMatchingRule; 063 064 /** 065 * Creates a new instance of this syntax. Note that the only thing that 066 * should be done here is to invoke the default constructor for the 067 * superclass. All initialization should be performed in the 068 * <CODE>initializeSyntax</CODE> method. 069 */ 070 public LDAPSyntaxDescriptionSyntax() 071 { 072 super(); 073 } 074 075 /** {@inheritDoc} */ 076 @Override 077 public Syntax getSDKSyntax(org.forgerock.opendj.ldap.schema.Schema schema) 078 { 079 return schema.getSyntax(SchemaConstants.SYNTAX_LDAP_SYNTAX_OID); 080 } 081 082 /** 083 * Retrieves the common name for this attribute syntax. 084 * 085 * @return The common name for this attribute syntax. 086 */ 087 @Override 088 public String getName() 089 { 090 return SYNTAX_LDAP_SYNTAX_NAME; 091 } 092 093 /** 094 * Retrieves the OID for this attribute syntax. 095 * 096 * @return The OID for this attribute syntax. 097 */ 098 @Override 099 public String getOID() 100 { 101 return SYNTAX_LDAP_SYNTAX_OID; 102 } 103 104 /** 105 * Retrieves a description for this attribute syntax. 106 * 107 * @return A description for this attribute syntax. 108 */ 109 @Override 110 public String getDescription() 111 { 112 return SYNTAX_LDAP_SYNTAX_DESCRIPTION; 113 } 114 115 /** 116 * Retrieves the default equality matching rule that will be used for 117 * attributes with this syntax. 118 * 119 * @return The default equality matching rule that will be used for 120 * attributes with this syntax, or <CODE>null</CODE> if equality 121 * matches will not be allowed for this type by default. 122 */ 123 @Override 124 public MatchingRule getEqualityMatchingRule() 125 { 126 return defaultEqualityMatchingRule; 127 } 128 129 /** 130 * Retrieves the default ordering matching rule that will be used for 131 * attributes with this syntax. 132 * 133 * @return The default ordering matching rule that will be used for 134 * attributes with this syntax, or <CODE>null</CODE> if ordering 135 * matches will not be allowed for this type by default. 136 */ 137 @Override 138 public MatchingRule getOrderingMatchingRule() 139 { 140 return defaultOrderingMatchingRule; 141 } 142 143 /** 144 * Retrieves the default substring matching rule that will be used for 145 * attributes with this syntax. 146 * 147 * @return The default substring matching rule that will be used for 148 * attributes with this syntax, or <CODE>null</CODE> if substring 149 * matches will not be allowed for this type by default. 150 */ 151 @Override 152 public MatchingRule getSubstringMatchingRule() 153 { 154 return defaultSubstringMatchingRule; 155 } 156 157 /** 158 * Retrieves the default approximate matching rule that will be used for 159 * attributes with this syntax. 160 * 161 * @return The default approximate matching rule that will be used for 162 * attributes with this syntax, or <CODE>null</CODE> if approximate 163 * matches will not be allowed for this type by default. 164 */ 165 @Override 166 public MatchingRule getApproximateMatchingRule() 167 { 168 // There is no approximate matching rule by default. 169 return null; 170 } 171 172 /** 173 * Decodes the contents of the provided byte sequence as an ldap syntax 174 * definition according to the rules of this syntax. Note that the provided 175 * byte sequence value does not need to be normalized (and in fact, it should 176 * not be in order to allow the desired capitalization to be preserved). 177 * 178 * @param value The byte sequence containing the value 179 * to decode (it does not need to be 180 * normalized). 181 * @param schema The schema to use to resolve references to 182 * other schema elements. 183 * @param allowUnknownElements Indicates whether to allow values that are 184 * not defined in the server schema. This 185 * should only be true when called by 186 * {@code valueIsAcceptable}. 187 * Not used for LDAP Syntaxes 188 * @param forDelete 189 * {@code true} if used for deletion. 190 * 191 * @return The decoded ldapsyntax definition. 192 * 193 * @throws DirectoryException If the provided value cannot be decoded as an 194 * ldapsyntax definition. 195 */ 196 public static LDAPSyntaxDescription decodeLDAPSyntax( 197 ByteSequence value, Schema schema, boolean allowUnknownElements, boolean forDelete) 198 throws DirectoryException 199 { 200 // Get string representations of the provided value using the provided form. 201 String valueStr = value.toString(); 202 203 // We'll do this a character at a time. First, skip over any leading 204 // whitespace. 205 int pos = 0; 206 int length = valueStr.length(); 207 while (pos < length && valueStr.charAt(pos) == ' ') 208 { 209 pos++; 210 } 211 212 if (pos >= length) 213 { 214 // This means that the value was empty or contained only whitespace. That 215 // is illegal. 216 217 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EMPTY_VALUE.get(); 218 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 219 message); 220 } 221 222 223 // The next character must be an open parenthesis. If it is not, then that 224 // is an error. 225 char c = valueStr.charAt(pos++); 226 if (c != '(') 227 { 228 LocalizableMessage message = 229 ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(valueStr, pos-1, c); 230 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 231 message); 232 } 233 234 235 // Skip over any spaces immediately following the opening parenthesis. 236 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 237 { 238 pos++; 239 } 240 241 if (pos >= length) 242 { 243 // This means that the end of the value was reached before we could find 244 // the OID. Ths is illegal. 245 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get( 246 valueStr); 247 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 248 message); 249 } 250 251 int oidStartPos = pos; 252 if (isDigit(c)) 253 { 254 // This must be a numeric OID. In that case, we will accept only digits 255 // and periods, but not consecutive periods. 256 boolean lastWasPeriod = false; 257 while (pos < length && ((c = valueStr.charAt(pos)) != ' ') 258 && (c = valueStr.charAt(pos)) != ')') 259 { 260 if (c == '.') 261 { 262 if (lastWasPeriod) 263 { 264 LocalizableMessage message = 265 ERR_ATTR_SYNTAX_LDAPSYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get(valueStr, pos-1); 266 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 267 } 268 lastWasPeriod = true; 269 } 270 else if (! isDigit(c)) 271 { 272 // This must have been an illegal character. 273 LocalizableMessage message = 274 ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get(valueStr, c, pos-1); 275 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 276 } 277 else 278 { 279 lastWasPeriod = false; 280 } 281 pos++; 282 } 283 } 284 else 285 { 286 // This must be a "fake" OID. In this case, we will only accept 287 // alphabetic characters, numeric digits, and the hyphen. 288 while (pos < length && ((c = valueStr.charAt(pos)) != ' ') 289 && (c=valueStr.charAt(pos))!=')') 290 { 291 if (isAlpha(c) || isDigit(c) || c == '-' || 292 (c == '_' && DirectoryServer.allowAttributeNameExceptions())) 293 { 294 // This is fine. It is an acceptable character. 295 pos++; 296 } 297 else 298 { 299 // This must have been an illegal character. 300 LocalizableMessage message = 301 ERR_ATTR_SYNTAX_LDAPSYNTAX_ILLEGAL_CHAR_IN_STRING_OID. 302 get(valueStr, c, pos-1); 303 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 304 message); 305 } 306 } 307 } 308 309 // If we're at the end of the value, then it isn't a valid attribute type 310 // description. Otherwise, parse out the OID. 311 String oid; 312 if (pos >= length) 313 { 314 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get( 315 valueStr); 316 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 317 message); 318 } 319 oid = toLowerCase(valueStr.substring(oidStartPos, pos)); 320 321 // Skip over the space(s) after the OID. 322 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 323 { 324 pos++; 325 } 326 327 if (pos >= length) 328 { 329 // This means that the end of the value was reached before we could find 330 // the OID. Ths is illegal. 331 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get( 332 valueStr); 333 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 334 message); 335 } 336 337 // At this point, we should have a pretty specific syntax that describes 338 // what may come next, but some of the components are optional and it would 339 // be pretty easy to put something in the wrong order, so we will be very 340 // flexible about what we can accept. Just look at the next token, figure 341 // out what it is and how to treat what comes after it, then repeat until 342 // we get to the end of the value. But before we start, set default values 343 // for everything else we might need to know. 344 String description = null; 345 Syntax syntax = null; 346 HashMap<String,List<String>> extraProperties = new LinkedHashMap<>(); 347 boolean hasXSyntaxToken = false; 348 349 while (true) 350 { 351 StringBuilder tokenNameBuffer = new StringBuilder(); 352 pos = readTokenName(valueStr, tokenNameBuffer, pos); 353 String tokenName = tokenNameBuffer.toString(); 354 String lowerTokenName = toLowerCase(tokenName); 355 if (tokenName.equals(")")) 356 { 357 // We must be at the end of the value. If not, then that's a problem. 358 if (pos < length) 359 { 360 LocalizableMessage message = 361 ERR_ATTR_SYNTAX_LDAPSYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(valueStr, pos-1); 362 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 363 } 364 365 break; 366 } 367 else if (lowerTokenName.equals("desc")) 368 { 369 // This specifies the description for the attribute type. It is an 370 // arbitrary string of characters enclosed in single quotes. 371 StringBuilder descriptionBuffer = new StringBuilder(); 372 pos = readQuotedString(valueStr, descriptionBuffer, pos); 373 description = descriptionBuffer.toString(); 374 } 375 else 376 { 377 SchemaBuilder schemaBuilder = new SchemaBuilder(schema.getSchemaNG()); 378 379 if (lowerTokenName.equals("x-subst")) 380 { 381 if (hasXSyntaxToken) 382 { 383 // We've already seen syntax extension. More than 1 is not allowed 384 LocalizableMessage message = 385 ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr); 386 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 387 message); 388 } 389 hasXSyntaxToken = true; 390 StringBuilder woidBuffer = new StringBuilder(); 391 pos = readQuotedString(valueStr, woidBuffer, pos); 392 String syntaxOID = toLowerCase(woidBuffer.toString()); 393 Syntax subSyntax = schema.getSyntax(syntaxOID); 394 if (subSyntax == null) 395 { 396 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_SYNTAX.get(oid, syntaxOID); 397 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION, message); 398 } 399 syntax = forDelete ? schema.getSyntax(oid) : schemaBuilder.buildSyntax(oid) 400 .extraProperties("x-subst", syntaxOID) 401 .addToSchema().toSchema().getSyntax(oid); 402 } 403 404 else if(lowerTokenName.equals("x-pattern")) 405 { 406 if (hasXSyntaxToken) 407 { 408 // We've already seen syntax extension. More than 1 is not allowed 409 LocalizableMessage message = 410 ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr); 411 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 412 message); 413 } 414 hasXSyntaxToken = true; 415 StringBuilder regexBuffer = new StringBuilder(); 416 pos = readQuotedString(valueStr, regexBuffer, pos); 417 String regex = regexBuffer.toString().trim(); 418 if(regex.length() == 0) 419 { 420 LocalizableMessage message = WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_NO_PATTERN.get( 421 valueStr); 422 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 423 message); 424 } 425 426 try 427 { 428 syntax = forDelete ? schema.getSyntax(oid) : schemaBuilder.buildSyntax(oid) 429 .extraProperties("x-pattern", regex) 430 .addToSchema().toSchema().getSyntax(oid); 431 } 432 catch(Exception e) 433 { 434 LocalizableMessage message = 435 WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN.get 436 (valueStr,regex); 437 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 438 message); 439 } 440 } 441 else if(lowerTokenName.equals("x-enum")) 442 { 443 if (hasXSyntaxToken) 444 { 445 // We've already seen syntax extension. More than 1 is not allowed 446 LocalizableMessage message = 447 ERR_ATTR_SYNTAX_LDAPSYNTAX_TOO_MANY_EXTENSIONS.get(valueStr); 448 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 449 message); 450 } 451 hasXSyntaxToken = true; 452 LinkedList<String> values = new LinkedList<>(); 453 pos = readExtraParameterValues(valueStr, values, pos); 454 455 if (values.isEmpty()) 456 { 457 LocalizableMessage message = 458 ERR_ATTR_SYNTAX_LDAPSYNTAX_ENUM_NO_VALUES.get(valueStr); 459 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 460 message); 461 } 462 // Parse all enum values, check for uniqueness 463 List<String> entries = new LinkedList<>(); 464 for (String v : values) 465 { 466 ByteString entry = ByteString.valueOfUtf8(v); 467 if (entries.contains(entry)) 468 { 469 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 470 WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE.get( 471 valueStr, entry,pos)); 472 } 473 entries.add(v); 474 } 475 476 syntax = forDelete ? schema.getSyntax(oid) : schemaBuilder 477 .addEnumerationSyntax(oid, description, true, entries.toArray(new String[0])) 478 .toSchema().getSyntax(oid); 479 } 480 else if (tokenName.matches("X\\-[_\\p{Alpha}-]+")) 481 { 482 // This must be a non-standard property and it must be followed by 483 // either a single value in single quotes or an open parenthesis 484 // followed by one or more values in single quotes separated by spaces 485 // followed by a close parenthesis. 486 List<String> valueList = new ArrayList<>(); 487 pos = readExtraParameterValues(valueStr, valueList, pos); 488 extraProperties.put(tokenName, valueList); 489 } 490 else 491 { 492 // Unknown Token 493 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_UNKNOWN_EXT.get( 494 valueStr, tokenName, pos); 495 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 496 message); 497 } 498 } 499 } 500 if (syntax == null) 501 { 502 // TODO : add localized message 503 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, 504 LocalizableMessage.raw("no LDAP syntax for:" + value)); 505 } 506 507 CommonSchemaElements.checkSafeProperties(extraProperties); 508 509 //Since we reached here it means everything is OK. 510 return new LDAPSyntaxDescription(valueStr, syntax.getOID(), extraProperties); 511 } 512 513 /** 514 * Reads the next token name from the attribute syntax definition, skipping 515 * over any leading or trailing spaces, and appends it to the provided buffer. 516 * 517 * @param valueStr The string representation of the attribute syntax 518 * definition. 519 * @param tokenName The buffer into which the token name will be written. 520 * @param startPos The position in the provided string at which to start 521 * reading the token name. 522 * 523 * @return The position of the first character that is not part of the token 524 * name or one of the trailing spaces after it. 525 * 526 * @throws DirectoryException If a problem is encountered while reading the 527 * token name. 528 */ 529 private static int readTokenName(String valueStr, StringBuilder tokenName, 530 int startPos) 531 throws DirectoryException 532 { 533 // Skip over any spaces at the beginning of the value. 534 char c = '\u0000'; 535 int length = valueStr.length(); 536 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 537 { 538 startPos++; 539 } 540 541 if (startPos >= length) 542 { 543 LocalizableMessage message = 544 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 545 throw new DirectoryException( 546 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 547 } 548 549 550 // Read until we find the next space. 551 while (startPos < length && ((c = valueStr.charAt(startPos++)) != ' ')) 552 { 553 tokenName.append(c); 554 } 555 556 557 // Skip over any trailing spaces after the value. 558 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 559 { 560 startPos++; 561 } 562 563 564 // Return the position of the first non-space character after the token. 565 return startPos; 566 } 567 568 /** 569 * Reads the value of a string enclosed in single quotes, skipping over the 570 * quotes and any leading or trailing spaces, and appending the string to the 571 * provided buffer. 572 * 573 * @param valueStr The user-provided representation of the attribute type 574 * definition. 575 * @param valueBuffer The buffer into which the user-provided representation 576 * of the value will be placed. 577 * @param startPos The position in the provided string at which to start 578 * reading the quoted string. 579 * 580 * @return The position of the first character that is not part of the quoted 581 * string or one of the trailing spaces after it. 582 * 583 * @throws DirectoryException If a problem is encountered while reading the 584 * quoted string. 585 */ 586 private static int readQuotedString(String valueStr, 587 StringBuilder valueBuffer, int startPos) 588 throws DirectoryException 589 { 590 // Skip over any spaces at the beginning of the value. 591 char c = '\u0000'; 592 int length = valueStr.length(); 593 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 594 { 595 startPos++; 596 } 597 598 if (startPos >= length) 599 { 600 LocalizableMessage message = 601 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 602 throw new DirectoryException( 603 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 604 } 605 606 607 // The next character must be a single quote. 608 if (c != '\'') 609 { 610 LocalizableMessage message = ERR_ATTR_SYNTAX_LDAPSYNTAX_EXPECTED_QUOTE_AT_POS.get(valueStr, startPos, c); 611 throw new DirectoryException( 612 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 613 } 614 615 616 // Read until we find the closing quote. 617 startPos++; 618 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 619 { 620 valueBuffer.append(c); 621 startPos++; 622 } 623 624 625 // Skip over any trailing spaces after the value. 626 startPos++; 627 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 628 { 629 startPos++; 630 } 631 632 633 // If we're at the end of the value, then that's illegal. 634 if (startPos >= length) 635 { 636 LocalizableMessage message = 637 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 638 throw new DirectoryException( 639 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 640 } 641 642 643 // Return the position of the first non-space character after the token. 644 return startPos; 645 } 646 647 648 /** 649 * Reads the value for an "extra" parameter. It will handle a single unquoted 650 * word (which is technically illegal, but we'll allow it), a single quoted 651 * string, or an open parenthesis followed by a space-delimited set of quoted 652 * strings or unquoted words followed by a close parenthesis. 653 * 654 * @param valueStr The string containing the information to be read. 655 * @param valueList The list of "extra" parameter values read so far. 656 * @param startPos The position in the value string at which to start 657 * reading. 658 * 659 * @return The "extra" parameter value that was read. 660 * 661 * @throws DirectoryException If a problem occurs while attempting to read 662 * the value. 663 */ 664 private static int readExtraParameterValues(String valueStr, 665 List<String> valueList, int startPos) 666 throws DirectoryException 667 { 668 // Skip over any leading spaces. 669 int length = valueStr.length(); 670 char c = '\u0000'; 671 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 672 { 673 startPos++; 674 } 675 676 if (startPos >= length) 677 { 678 LocalizableMessage message = 679 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 680 throw new DirectoryException( 681 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 682 } 683 684 685 // Look at the next character. If it is a quote, then parse until the next 686 // quote and end. If it is an open parenthesis, then parse individual 687 // values until the close parenthesis and end. Otherwise, parse until the 688 // next space and end. 689 if (c == '\'') 690 { 691 // Parse until the closing quote. 692 StringBuilder valueBuffer = new StringBuilder(); 693 startPos++; 694 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 695 { 696 valueBuffer.append(c); 697 startPos++; 698 } 699 startPos++; 700 valueList.add(valueBuffer.toString()); 701 } 702 else if (c == '(') 703 { 704 startPos++; 705 // We're expecting a list of values. Quoted, space separated. 706 while (true) 707 { 708 // Skip over any leading spaces; 709 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 710 { 711 startPos++; 712 } 713 714 if (startPos >= length) 715 { 716 LocalizableMessage message = 717 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 718 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 719 message); 720 } 721 722 if (c == ')') 723 { 724 // This is the end of the list. 725 startPos++; 726 break; 727 } 728 else if (c == '(') 729 { 730 // This is an illegal character. 731 LocalizableMessage message = 732 ERR_ATTR_SYNTAX_LDAPSYNTAX_EXTENSION_INVALID_CHARACTER.get( 733 valueStr, startPos); 734 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 735 message); 736 } 737 else if (c == '\'') 738 { 739 // We have a quoted string 740 StringBuilder valueBuffer = new StringBuilder(); 741 startPos++; 742 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 743 { 744 valueBuffer.append(c); 745 startPos++; 746 } 747 748 valueList.add(valueBuffer.toString()); 749 startPos++; 750 } 751 else 752 { 753 //Consider unquoted string 754 StringBuilder valueBuffer = new StringBuilder(); 755 while (startPos < length && ((c = valueStr.charAt(startPos)) != ' ')) 756 { 757 valueBuffer.append(c); 758 startPos++; 759 } 760 761 valueList.add(valueBuffer.toString()); 762 } 763 764 if (startPos >= length) 765 { 766 LocalizableMessage message = 767 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 768 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 769 message); 770 } 771 } 772 } 773 else 774 { 775 // Parse until the next space. 776 StringBuilder valueBuffer = new StringBuilder(); 777 while (startPos < length && ((c = valueStr.charAt(startPos)) != ' ')) 778 { 779 valueBuffer.append(c); 780 startPos++; 781 } 782 783 valueList.add(valueBuffer.toString()); 784 } 785 786 // Skip over any trailing spaces. 787 while (startPos < length && valueStr.charAt(startPos) == ' ') 788 { 789 startPos++; 790 } 791 792 if (startPos >= length) 793 { 794 LocalizableMessage message = 795 ERR_ATTR_SYNTAX_LDAPSYNTAX_TRUNCATED_VALUE.get(valueStr); 796 throw new DirectoryException( 797 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 798 } 799 800 return startPos; 801 } 802}