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