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