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