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