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