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