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; 028import static org.opends.messages.SchemaMessages.*; 029import static org.opends.server.schema.SchemaConstants.*; 030import static org.opends.server.util.StaticUtils.*; 031 032import java.util.LinkedHashMap; 033import java.util.LinkedHashSet; 034import java.util.LinkedList; 035import java.util.List; 036 037import org.forgerock.i18n.LocalizableMessage; 038import org.forgerock.opendj.ldap.ByteSequence; 039import org.forgerock.opendj.ldap.ResultCode; 040import org.forgerock.opendj.ldap.schema.Syntax; 041import org.opends.server.admin.std.server.AttributeSyntaxCfg; 042import org.opends.server.api.AttributeSyntax; 043import org.opends.server.core.DirectoryServer; 044import org.opends.server.types.DITStructureRule; 045import org.opends.server.types.DirectoryException; 046import org.opends.server.types.NameForm; 047import org.opends.server.types.Schema; 048 049/** 050 * This class implements the DIT structure rule description syntax, which is 051 * used to hold DIT structure rule definitions in the server schema. The format 052 * of this syntax is defined in RFC 2252. 053 */ 054public class DITStructureRuleSyntax 055 extends AttributeSyntax<AttributeSyntaxCfg> 056{ 057 058 /** 059 * Creates a new instance of this syntax. Note that the only thing that 060 * should be done here is to invoke the default constructor for the 061 * superclass. All initialization should be performed in the 062 * <CODE>initializeSyntax</CODE> method. 063 */ 064 public DITStructureRuleSyntax() 065 { 066 super(); 067 } 068 069 /** {@inheritDoc} */ 070 @Override 071 public Syntax getSDKSyntax(org.forgerock.opendj.ldap.schema.Schema schema) 072 { 073 return schema.getSyntax(SchemaConstants.SYNTAX_DIT_STRUCTURE_RULE_OID); 074 } 075 076 /** {@inheritDoc} */ 077 @Override 078 public String getName() 079 { 080 return SYNTAX_DIT_STRUCTURE_RULE_NAME; 081 } 082 083 /** {@inheritDoc} */ 084 @Override 085 public String getOID() 086 { 087 return SYNTAX_DIT_STRUCTURE_RULE_OID; 088 } 089 090 /** {@inheritDoc} */ 091 @Override 092 public String getDescription() 093 { 094 return SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION; 095 } 096 097 /** 098 * Decodes the contents of the provided ASN.1 octet string as a DIT structure 099 * rule definition according to the rules of this syntax. Note that the 100 * provided octet string value does not need to be normalized (and in fact, it 101 * should not be in order to allow the desired capitalization to be 102 * preserved). 103 * 104 * @param value The ASN.1 octet string containing the value 105 * to decode (it does not need to be 106 * normalized). 107 * @param schema The schema to use to resolve references to 108 * other schema elements. 109 * @param allowUnknownElements Indicates whether to allow values that 110 * reference a name form and/or superior rules 111 * which are not defined in the server schema. 112 * This should only be true when called by 113 * {@code valueIsAcceptable}. 114 * 115 * @return The decoded DIT structure rule definition. 116 * 117 * @throws DirectoryException If the provided value cannot be decoded as an 118 * DIT structure rule definition. 119 */ 120 public static DITStructureRule decodeDITStructureRule(ByteSequence value, 121 Schema schema, 122 boolean allowUnknownElements) 123 throws DirectoryException 124 { 125 // Get string representations of the provided value using the provided form 126 // and with all lowercase characters. 127 String valueStr = value.toString(); 128 String lowerStr = toLowerCase(valueStr); 129 130 131 // We'll do this a character at a time. First, skip over any leading 132 // whitespace. 133 int pos = 0; 134 int length = valueStr.length(); 135 while (pos < length && valueStr.charAt(pos) == ' ') 136 { 137 pos++; 138 } 139 140 if (pos >= length) 141 { 142 // This means that the value was empty or contained only whitespace. That 143 // is illegal. 144 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get(); 145 throw new DirectoryException( 146 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 147 } 148 149 150 // The next character must be an open parenthesis. If it is not, then that 151 // is an error. 152 char c = valueStr.charAt(pos++); 153 if (c != '(') 154 { 155 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get( 156 valueStr, pos-1, c); 157 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 158 } 159 160 161 // Skip over any spaces immediately following the opening parenthesis. 162 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 163 { 164 pos++; 165 } 166 167 if (pos >= length) 168 { 169 // This means that the end of the value was reached before we could find 170 // the OID. Ths is illegal. 171 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 172 throw new DirectoryException( 173 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 174 } 175 176 177 // The next set of characters must be the rule ID, which is an integer. 178 int ruleIDStartPos = pos; 179 while (pos < length && ((c = valueStr.charAt(pos++)) != ' ')) 180 { 181 if (! isDigit(c)) 182 { 183 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.get( 184 valueStr, c, pos-1); 185 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 186 message); 187 } 188 } 189 190 // If we're at the end of the value, then it isn't a valid DIT structure 191 // rule description. Otherwise, parse out the rule ID. 192 int ruleID; 193 if (pos >= length) 194 { 195 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 196 throw new DirectoryException( 197 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 198 } 199 else 200 { 201 ruleID = Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1)); 202 } 203 204 205 // Skip over the space(s) after the rule ID. 206 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 207 { 208 pos++; 209 } 210 211 if (pos >= length) 212 { 213 // This means that the end of the value was reached before we could find 214 // the rule ID. Ths is illegal. 215 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 216 throw new DirectoryException( 217 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 218 } 219 220 221 // At this point, we should have a pretty specific syntax that describes 222 // what may come next, but some of the components are optional and it would 223 // be pretty easy to put something in the wrong order, so we will be very 224 // flexible about what we can accept. Just look at the next token, figure 225 // out what it is and how to treat what comes after it, then repeat until 226 // we get to the end of the value. But before we start, set default values 227 // for everything else we might need to know. 228 LinkedHashMap<String,String> names = new LinkedHashMap<>(); 229 String description = null; 230 boolean isObsolete = false; 231 NameForm nameForm = null; 232 boolean nameFormGiven = false; 233 LinkedHashSet<DITStructureRule> superiorRules = null; 234 LinkedHashMap<String,List<String>> extraProperties = new LinkedHashMap<>(); 235 236 237 while (true) 238 { 239 StringBuilder tokenNameBuffer = new StringBuilder(); 240 pos = readTokenName(valueStr, tokenNameBuffer, pos); 241 String tokenName = tokenNameBuffer.toString(); 242 String lowerTokenName = toLowerCase(tokenName); 243 if (tokenName.equals(")")) 244 { 245 // We must be at the end of the value. If not, then that's a problem. 246 if (pos < length) 247 { 248 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_UNEXPECTED_CLOSE_PARENTHESIS. 249 get(valueStr, pos-1); 250 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 251 message); 252 } 253 254 break; 255 } 256 else if (lowerTokenName.equals("name")) 257 { 258 // This specifies the set of names for the DIT structure rule. It may 259 // be a single name in single quotes, or it may be an open parenthesis 260 // followed by one or more names in single quotes separated by spaces. 261 c = valueStr.charAt(pos++); 262 if (c == '\'') 263 { 264 StringBuilder userBuffer = new StringBuilder(); 265 StringBuilder lowerBuffer = new StringBuilder(); 266 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, pos-1); 267 names.put(lowerBuffer.toString(), userBuffer.toString()); 268 } 269 else if (c == '(') 270 { 271 StringBuilder userBuffer = new StringBuilder(); 272 StringBuilder lowerBuffer = new StringBuilder(); 273 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer, 274 pos); 275 names.put(lowerBuffer.toString(), userBuffer.toString()); 276 277 278 while (true) 279 { 280 if (valueStr.charAt(pos) == ')') 281 { 282 // Skip over any spaces after the parenthesis. 283 pos++; 284 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 285 { 286 pos++; 287 } 288 289 break; 290 } 291 else 292 { 293 userBuffer = new StringBuilder(); 294 lowerBuffer = new StringBuilder(); 295 296 pos = readQuotedString(valueStr, lowerStr, userBuffer, 297 lowerBuffer, pos); 298 names.put(lowerBuffer.toString(), userBuffer.toString()); 299 } 300 } 301 } 302 else 303 { 304 // This is an illegal character. 305 LocalizableMessage message = 306 ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(valueStr, c, pos-1); 307 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 308 } 309 } 310 else if (lowerTokenName.equals("desc")) 311 { 312 // This specifies the description for the DIT structure rule. It is an 313 // arbitrary string of characters enclosed in single quotes. 314 StringBuilder descriptionBuffer = new StringBuilder(); 315 pos = readQuotedString(valueStr, descriptionBuffer, pos); 316 description = descriptionBuffer.toString(); 317 } 318 else if (lowerTokenName.equals("obsolete")) 319 { 320 // This indicates whether the DIT structure rule should be considered 321 // obsolete. We do not need to do any more parsing for this token. 322 isObsolete = true; 323 } 324 else if (lowerTokenName.equals("form")) 325 { 326 // This should be the OID of the associated name form. 327 StringBuilder woidBuffer = new StringBuilder(); 328 pos = readWOID(lowerStr, woidBuffer, pos); 329 330 nameFormGiven = true; 331 nameForm = schema.getNameForm(woidBuffer.toString()); 332 if (nameForm == null && ! allowUnknownElements) 333 { 334 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 335 ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(valueStr, woidBuffer)); 336 } 337 } 338 else if (lowerTokenName.equals("sup")) 339 { 340 LinkedList<DITStructureRule> superiorList = new LinkedList<>(); 341 342 // This specifies the set of superior rule IDs (which are integers) for 343 // this DIT structure rule. It may be a single rule ID or a set of 344 // rule IDs enclosed in parentheses and separated by spaces. 345 c = valueStr.charAt(pos++); 346 if (c == '(') 347 { 348 while (true) 349 { 350 // Skip over any leading spaces. 351 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 352 { 353 pos++; 354 } 355 356 if (pos >= length) 357 { 358 LocalizableMessage message = 359 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr); 360 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 361 message); 362 } 363 364 // Read the next integer value. 365 ruleIDStartPos = pos; 366 while (pos < length && ((c = valueStr.charAt(pos++)) != ' ')) 367 { 368 if (! isDigit(c)) 369 { 370 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID. 371 get(valueStr, c, pos-1); 372 throw new DirectoryException( 373 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 374 } 375 } 376 377 // If we're at the end of the value, then it isn't a valid DIT 378 // structure rule description. Otherwise, parse out the rule ID. 379 int supRuleID; 380 if (pos >= length) 381 { 382 LocalizableMessage message = 383 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 384 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 385 message); 386 } 387 else 388 { 389 supRuleID = 390 Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1)); 391 } 392 393 394 // Get the DIT structure rule with the specified rule ID. 395 DITStructureRule superiorRule = 396 schema.getDITStructureRule(supRuleID); 397 if (superiorRule == null) 398 { 399 if (! allowUnknownElements) 400 { 401 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get( 402 valueStr, supRuleID); 403 throw new DirectoryException( 404 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 405 } 406 } 407 else 408 { 409 superiorList.add(superiorRule); 410 } 411 412 413 // Skip over any trailing spaces. 414 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 415 { 416 pos++; 417 } 418 419 if (pos >= length) 420 { 421 LocalizableMessage message = 422 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr); 423 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 424 message); 425 } 426 427 428 // If the next character is a closing parenthesis, then read any 429 // spaces after it and break out of the loop. 430 if (c == ')') 431 { 432 pos++; 433 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 434 { 435 pos++; 436 } 437 438 if (pos >= length) 439 { 440 LocalizableMessage message = 441 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr); 442 throw new DirectoryException( 443 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 444 } 445 446 break; 447 } 448 } 449 } 450 else 451 { 452 if (pos >= length) 453 { 454 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr); 455 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 456 message); 457 } 458 459 // Read the next integer value. 460 ruleIDStartPos = pos - 1; 461 while (pos < length && ((c = valueStr.charAt(pos++)) != ' ')) 462 { 463 if (! isDigit(c)) 464 { 465 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_RULE_ID.get(valueStr, c, pos-1); 466 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 467 message); 468 } 469 } 470 471 // If we're at the end of the value, then it isn't a valid DIT 472 // structure rule description. Otherwise, parse out the rule ID. 473 int supRuleID; 474 if (pos >= length) 475 { 476 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 477 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 478 message); 479 } 480 else 481 { 482 supRuleID = 483 Integer.parseInt(valueStr.substring(ruleIDStartPos, pos-1)); 484 } 485 486 487 // Get the DIT structure rule with the specified rule ID. 488 DITStructureRule superiorRule = schema.getDITStructureRule(supRuleID); 489 if (superiorRule == null) 490 { 491 if (! allowUnknownElements) 492 { 493 LocalizableMessage message = 494 ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(valueStr, supRuleID); 495 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 496 message); 497 } 498 } 499 else 500 { 501 superiorList.add(superiorRule); 502 } 503 504 505 // Skip over any trailing spaces. 506 while (pos < length && ((c = valueStr.charAt(pos)) == ' ')) 507 { 508 pos++; 509 } 510 511 if (pos >= length) 512 { 513 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr); 514 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 515 message); 516 } 517 } 518 519 superiorRules = new LinkedHashSet<>(superiorList); 520 } 521 else 522 { 523 // This must be a non-standard property and it must be followed by 524 // either a single value in single quotes or an open parenthesis 525 // followed by one or more values in single quotes separated by spaces 526 // followed by a close parenthesis. 527 LinkedList<String> valueList = new LinkedList<>(); 528 pos = readExtraParameterValues(valueStr, valueList, pos); 529 extraProperties.put(tokenName, valueList); 530 } 531 } 532 533 534 if (nameForm == null && !nameFormGiven) 535 { 536 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(valueStr); 537 throw new DirectoryException( 538 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 539 } 540 541 542 return new DITStructureRule(value.toString(), names, ruleID, description, 543 isObsolete, nameForm, superiorRules, 544 extraProperties); 545 } 546 547 /** 548 * Reads the next token name from the DIT content rule definition, skipping 549 * over any leading or trailing spaces, and appends it to the provided buffer. 550 * 551 * @param valueStr The string representation of the DIT content rule 552 * 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_DSR_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 DIT content 607 * rule 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_DSR_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 = 643 ERR_ATTR_SYNTAX_DSR_EXPECTED_QUOTE_AT_POS.get(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_DSR_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 DIT content 685 * rule definition. 686 * @param lowerStr The all-lowercase representation of the DIT content 687 * rule 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_DSR_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 = 726 ERR_ATTR_SYNTAX_DSR_EXPECTED_QUOTE_AT_POS.get(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_DSR_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 an attributeType/objectclass description or numeric OID from the 765 * provided string, skipping over any leading or trailing spaces, and 766 * appending the 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_DSR_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/objectclass 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 = ERR_ATTR_SYNTAX_DSR_DOUBLE_PERIOD_IN_NUMERIC_OID. 813 get(lowerStr, startPos-1); 814 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 815 message); 816 } 817 else 818 { 819 woidBuffer.append(c); 820 lastWasPeriod = true; 821 } 822 } 823 else if (! isDigit(c)) 824 { 825 // Technically, this must be an illegal character. However, it is 826 // possible that someone just got sloppy and did not include a space 827 // between the name/OID and a closing parenthesis. In that case, 828 // we'll assume it's the end of the value. What's more, we'll have 829 // to prematurely return to nasty side effects from stripping off 830 // additional characters. 831 if (c == ')') 832 { 833 return startPos-1; 834 } 835 836 // This must have been an illegal character. 837 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_NUMERIC_OID.get(lowerStr, c, startPos-1); 838 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 839 } 840 else 841 { 842 woidBuffer.append(c); 843 lastWasPeriod = false; 844 } 845 } 846 } 847 else if (isAlpha(c)) 848 { 849 // This must be an attribute type/objectclass description. In this case, 850 // we will only accept alphabetic characters, numeric digits, and the 851 // hyphen. 852 while (startPos < length && ((c = lowerStr.charAt(startPos++)) != ' ')) 853 { 854 if (isAlpha(c) || isDigit(c) || c == '-' || 855 (c == '_' && DirectoryServer.allowAttributeNameExceptions())) 856 { 857 woidBuffer.append(c); 858 } 859 else 860 { 861 // Technically, this must be an illegal character. However, it is 862 // possible that someone just got sloppy and did not include a space 863 // between the name/OID and a closing parenthesis. In that case, 864 // we'll assume it's the end of the value. What's more, we'll have 865 // to prematurely return to nasty side effects from stripping off 866 // additional characters. 867 if (c == ')') 868 { 869 return startPos-1; 870 } 871 872 // This must have been an illegal character. 873 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR_IN_STRING_OID.get(lowerStr, c, startPos-1); 874 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 875 } 876 } 877 } 878 else 879 { 880 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(lowerStr, c, startPos); 881 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 882 } 883 884 885 // Skip over any trailing spaces after the value. 886 while (startPos < length && ((c = lowerStr.charAt(startPos)) == ' ')) 887 { 888 startPos++; 889 } 890 891 892 // If we're at the end of the value, then that's illegal. 893 if (startPos >= length) 894 { 895 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(lowerStr); 896 throw new DirectoryException( 897 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 898 } 899 900 901 // Return the position of the first non-space character after the token. 902 return startPos; 903 } 904 905 /** 906 * Reads the value for an "extra" parameter. It will handle a single unquoted 907 * word (which is technically illegal, but we'll allow it), a single quoted 908 * string, or an open parenthesis followed by a space-delimited set of quoted 909 * strings or unquoted words followed by a close parenthesis. 910 * 911 * @param valueStr The string containing the information to be read. 912 * @param valueList The list of "extra" parameter values read so far. 913 * @param startPos The position in the value string at which to start 914 * reading. 915 * 916 * @return The "extra" parameter value that was read. 917 * 918 * @throws DirectoryException If a problem occurs while attempting to read 919 * the value. 920 */ 921 private static int readExtraParameterValues(String valueStr, 922 List<String> valueList, int startPos) 923 throws DirectoryException 924 { 925 // Skip over any leading spaces. 926 int length = valueStr.length(); 927 char c = '\u0000'; 928 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 929 { 930 startPos++; 931 } 932 933 if (startPos >= length) 934 { 935 LocalizableMessage message = 936 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 937 throw new DirectoryException( 938 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 939 } 940 941 942 // Look at the next character. If it is a quote, then parse until the next 943 // quote and end. If it is an open parenthesis, then parse individual 944 // values until the close parenthesis and end. Otherwise, parse until the 945 // next space and end. 946 if (c == '\'') 947 { 948 // Parse until the closing quote. 949 StringBuilder valueBuffer = new StringBuilder(); 950 startPos++; 951 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 952 { 953 valueBuffer.append(c); 954 startPos++; 955 } 956 startPos++; 957 valueList.add(valueBuffer.toString()); 958 } 959 else if (c == '(') 960 { 961 startPos++; 962 // We're expecting a list of values. Quoted, space separated. 963 while (true) 964 { 965 // Skip over any leading spaces; 966 while (startPos < length && ((c = valueStr.charAt(startPos)) == ' ')) 967 { 968 startPos++; 969 } 970 971 if (startPos >= length) 972 { 973 LocalizableMessage message = 974 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 975 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 976 message); 977 } 978 979 if (c == ')') 980 { 981 // This is the end of the list. 982 startPos++; 983 break; 984 } 985 else if (c == '(') 986 { 987 // This is an illegal character. 988 LocalizableMessage message = ERR_ATTR_SYNTAX_DSR_ILLEGAL_CHAR.get(valueStr, c, startPos); 989 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 990 } 991 else if (c == '\'') 992 { 993 // We have a quoted string 994 StringBuilder valueBuffer = new StringBuilder(); 995 startPos++; 996 while (startPos < length && ((c = valueStr.charAt(startPos)) != '\'')) 997 { 998 valueBuffer.append(c); 999 startPos++; 1000 } 1001 1002 valueList.add(valueBuffer.toString()); 1003 startPos++; 1004 } 1005 else 1006 { 1007 //Consider unquoted string 1008 StringBuilder valueBuffer = new StringBuilder(); 1009 while (startPos < length && ((c = valueStr.charAt(startPos)) != ' ')) 1010 { 1011 valueBuffer.append(c); 1012 startPos++; 1013 } 1014 1015 valueList.add(valueBuffer.toString()); 1016 } 1017 1018 if (startPos >= length) 1019 { 1020 LocalizableMessage message = 1021 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 1022 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1023 } 1024 } 1025 } 1026 else 1027 { 1028 // Parse until the next space. 1029 StringBuilder valueBuffer = new StringBuilder(); 1030 while (startPos < length && ((c = valueStr.charAt(startPos)) != ' ')) 1031 { 1032 valueBuffer.append(c); 1033 startPos++; 1034 } 1035 1036 valueList.add(valueBuffer.toString()); 1037 } 1038 1039 // Skip over any trailing spaces. 1040 while (startPos < length && valueStr.charAt(startPos) == ' ') 1041 { 1042 startPos++; 1043 } 1044 1045 if (startPos >= length) 1046 { 1047 LocalizableMessage message = 1048 ERR_ATTR_SYNTAX_DSR_TRUNCATED_VALUE.get(valueStr); 1049 throw new DirectoryException( 1050 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 1051 } 1052 1053 return startPos; 1054 } 1055} 1056