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