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