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 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.schema; 028 import org.opends.messages.Message; 029 030 031 032 import org.opends.server.admin.std.server.AttributeSyntaxCfg; 033 import org.opends.server.api.ApproximateMatchingRule; 034 import org.opends.server.api.AttributeSyntax; 035 import org.opends.server.api.EqualityMatchingRule; 036 import org.opends.server.api.OrderingMatchingRule; 037 import org.opends.server.api.SubstringMatchingRule; 038 import org.opends.server.config.ConfigException; 039 import org.opends.server.core.DirectoryServer; 040 import org.opends.server.types.ByteString; 041 import org.opends.server.types.DirectoryException; 042 043 044 import org.opends.server.types.ResultCode; 045 046 import static org.opends.server.loggers.debug.DebugLogger.*; 047 import org.opends.server.loggers.debug.DebugTracer; 048 import static org.opends.server.loggers.ErrorLogger.*; 049 import org.opends.server.types.DebugLogLevel; 050 import static org.opends.messages.SchemaMessages.*; 051 import org.opends.messages.MessageBuilder; 052 import static org.opends.server.schema.SchemaConstants.*; 053 import static org.opends.server.util.StaticUtils.*; 054 055 056 /** 057 * This class defines the LDAP syntax description syntax, which is used to 058 * hold attribute syntax definitions in the server schema. The format of this 059 * syntax is defined in RFC 2252. 060 */ 061 public class LDAPSyntaxDescriptionSyntax 062 extends AttributeSyntax<AttributeSyntaxCfg> 063 { 064 /** 065 * The tracer object for the debug logger. 066 */ 067 private static final DebugTracer TRACER = getTracer(); 068 069 070 071 072 // The default equality matching rule for this syntax. 073 private EqualityMatchingRule defaultEqualityMatchingRule; 074 075 // The default ordering matching rule for this syntax. 076 private OrderingMatchingRule defaultOrderingMatchingRule; 077 078 // The default substring matching rule for this syntax. 079 private SubstringMatchingRule defaultSubstringMatchingRule; 080 081 082 083 /** 084 * Creates a new instance of this syntax. Note that the only thing that 085 * should be done here is to invoke the default constructor for the 086 * superclass. All initialization should be performed in the 087 * <CODE>initializeSyntax</CODE> method. 088 */ 089 public LDAPSyntaxDescriptionSyntax() 090 { 091 super(); 092 } 093 094 095 096 /** 097 * {@inheritDoc} 098 */ 099 public void initializeSyntax(AttributeSyntaxCfg configuration) 100 throws ConfigException 101 { 102 defaultEqualityMatchingRule = 103 DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID); 104 if (defaultEqualityMatchingRule == null) 105 { 106 logError(ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get( 107 EMR_CASE_IGNORE_OID, SYNTAX_LDAP_SYNTAX_NAME)); 108 } 109 110 defaultOrderingMatchingRule = 111 DirectoryServer.getOrderingMatchingRule(OMR_CASE_IGNORE_OID); 112 if (defaultOrderingMatchingRule == null) 113 { 114 logError(ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get( 115 OMR_CASE_IGNORE_OID, SYNTAX_LDAP_SYNTAX_NAME)); 116 } 117 118 defaultSubstringMatchingRule = 119 DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID); 120 if (defaultSubstringMatchingRule == null) 121 { 122 logError(ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get( 123 SMR_CASE_IGNORE_OID, SYNTAX_LDAP_SYNTAX_NAME)); 124 } 125 } 126 127 128 129 /** 130 * Retrieves the common name for this attribute syntax. 131 * 132 * @return The common name for this attribute syntax. 133 */ 134 public String getSyntaxName() 135 { 136 return SYNTAX_LDAP_SYNTAX_NAME; 137 } 138 139 140 141 /** 142 * Retrieves the OID for this attribute syntax. 143 * 144 * @return The OID for this attribute syntax. 145 */ 146 public String getOID() 147 { 148 return SYNTAX_LDAP_SYNTAX_OID; 149 } 150 151 152 153 /** 154 * Retrieves a description for this attribute syntax. 155 * 156 * @return A description for this attribute syntax. 157 */ 158 public String getDescription() 159 { 160 return SYNTAX_LDAP_SYNTAX_DESCRIPTION; 161 } 162 163 164 165 /** 166 * Retrieves the default equality matching rule that will be used for 167 * attributes with this syntax. 168 * 169 * @return The default equality matching rule that will be used for 170 * attributes with this syntax, or <CODE>null</CODE> if equality 171 * matches will not be allowed for this type by default. 172 */ 173 public EqualityMatchingRule getEqualityMatchingRule() 174 { 175 return defaultEqualityMatchingRule; 176 } 177 178 179 180 /** 181 * Retrieves the default ordering matching rule that will be used for 182 * attributes with this syntax. 183 * 184 * @return The default ordering matching rule that will be used for 185 * attributes with this syntax, or <CODE>null</CODE> if ordering 186 * matches will not be allowed for this type by default. 187 */ 188 public OrderingMatchingRule getOrderingMatchingRule() 189 { 190 return defaultOrderingMatchingRule; 191 } 192 193 194 195 /** 196 * Retrieves the default substring matching rule that will be used for 197 * attributes with this syntax. 198 * 199 * @return The default substring matching rule that will be used for 200 * attributes with this syntax, or <CODE>null</CODE> if substring 201 * matches will not be allowed for this type by default. 202 */ 203 public SubstringMatchingRule getSubstringMatchingRule() 204 { 205 return defaultSubstringMatchingRule; 206 } 207 208 209 210 /** 211 * Retrieves the default approximate matching rule that will be used for 212 * attributes with this syntax. 213 * 214 * @return The default approximate matching rule that will be used for 215 * attributes with this syntax, or <CODE>null</CODE> if approximate 216 * matches will not be allowed for this type by default. 217 */ 218 public ApproximateMatchingRule getApproximateMatchingRule() 219 { 220 // There is no approximate matching rule by default. 221 return null; 222 } 223 224 225 226 /** 227 * Indicates whether the provided value is acceptable for use in an attribute 228 * with this syntax. If it is not, then the reason may be appended to the 229 * provided buffer. 230 * 231 * @param value The value for which to make the determination. 232 * @param invalidReason The buffer to which the invalid reason should be 233 * appended. 234 * 235 * @return <CODE>true</CODE> if the provided value is acceptable for use with 236 * this syntax, or <CODE>false</CODE> if not. 237 */ 238 public boolean valueIsAcceptable(ByteString value, 239 MessageBuilder invalidReason) 240 { 241 // Get string representations of the provided value using the provided form 242 // and with all lowercase characters. 243 String valueStr = value.stringValue(); 244 String lowerStr = toLowerCase(valueStr); 245 246 247 // We'll do this a character at a time. First, skip over any leading 248 // whitespace. 249 int pos = 0; 250 int length = valueStr.length(); 251 while ((pos < length) && (valueStr.charAt(pos) == ' ')) 252 { 253 pos++; 254 } 255 256 if (pos >= length) 257 { 258 // This means that the value was empty or contained only whitespace. That 259 // is illegal. 260 261 invalidReason.append(ERR_ATTR_SYNTAX_ATTRSYNTAX_EMPTY_VALUE.get()); 262 return false; 263 } 264 265 266 // The next character must be an open parenthesis. If it is not, then that 267 // is an error. 268 char c = valueStr.charAt(pos++); 269 if (c != '(') 270 { 271 272 invalidReason.append( 273 ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get( 274 valueStr, (pos-1), String.valueOf(c))); 275 return false; 276 } 277 278 279 // Skip over any spaces immediately following the opening parenthesis. 280 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 281 { 282 pos++; 283 } 284 285 if (pos >= length) 286 { 287 // This means that the end of the value was reached before we could find 288 // the OID. Ths is illegal. 289 invalidReason.append(ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get( 290 valueStr)); 291 return false; 292 } 293 294 295 // The next set of characters must be the OID. Strictly speaking, this 296 // should only be a numeric OID, but we'll also allow for the 297 // "attrname-oid" case as well. Look at the first character to figure out 298 // which we will be using. 299 int oidStartPos = pos; 300 if (isDigit(c)) 301 { 302 // This must be a numeric OID. In that case, we will accept only digits 303 // and periods, but not consecutive periods. 304 boolean lastWasPeriod = false; 305 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' ')) 306 { 307 if (c == '.') 308 { 309 if (lastWasPeriod) 310 { 311 invalidReason.append( 312 ERR_ATTR_SYNTAX_ATTRSYNTAX_DOUBLE_PERIOD_IN_NUMERIC_OID.get( 313 valueStr, (pos-1))); 314 return false; 315 } 316 else 317 { 318 lastWasPeriod = true; 319 } 320 } 321 else if (! isDigit(c)) 322 { 323 // This must have been an illegal character. 324 invalidReason.append( 325 ERR_ATTR_SYNTAX_ATTRSYNTAX_ILLEGAL_CHAR_IN_NUMERIC_OID.get( 326 valueStr, String.valueOf(c), (pos-1))); 327 return false; 328 } 329 else 330 { 331 lastWasPeriod = false; 332 } 333 } 334 } 335 else 336 { 337 // This must be a "fake" OID. In this case, we will only accept 338 // alphabetic characters, numeric digits, and the hyphen. 339 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' ')) 340 { 341 if (isAlpha(c) || isDigit(c) || (c == '-') || 342 ((c == '_') && DirectoryServer.allowAttributeNameExceptions())) 343 { 344 // This is fine. It is an acceptable character. 345 } 346 else 347 { 348 // This must have been an illegal character. 349 350 invalidReason.append( 351 ERR_ATTR_SYNTAX_ATTRSYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get( 352 valueStr, String.valueOf(c), (pos-1))); 353 return false; 354 } 355 } 356 } 357 358 359 // If we're at the end of the value, then it isn't a valid attribute type 360 // description. Otherwise, parse out the OID. 361 String oid; 362 if (pos >= length) 363 { 364 invalidReason.append(ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get( 365 valueStr)); 366 return false; 367 } 368 else 369 { 370 oid = lowerStr.substring(oidStartPos, pos); 371 } 372 373 374 // Skip over the space(s) after the OID. 375 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' ')) 376 { 377 pos++; 378 } 379 380 if (pos >= length) 381 { 382 // This means that the end of the value was reached before we could find 383 // the OID. Ths is illegal. 384 invalidReason.append(ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get( 385 valueStr)); 386 return false; 387 } 388 389 390 // If the next character is a closing parenthesis, then we must be at the 391 // end of the value. 392 if (c == ')') 393 { 394 if (pos < length) 395 { 396 invalidReason.append( 397 ERR_ATTR_SYNTAX_ATTRSYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get( 398 valueStr, (pos-1))); 399 return false; 400 } 401 402 return true; 403 } 404 405 406 // The next token must be "DESC" followed by a quoted string. 407 String tokenName; 408 try 409 { 410 StringBuilder tokenNameBuffer = new StringBuilder(); 411 pos = readTokenName(lowerStr, tokenNameBuffer, pos); 412 tokenName = tokenNameBuffer.toString(); 413 } 414 catch (Exception e) 415 { 416 if (debugEnabled()) 417 { 418 TRACER.debugCaught(DebugLogLevel.ERROR, e); 419 } 420 421 invalidReason.append( 422 ERR_ATTR_SYNTAX_ATTRSYNTAX_CANNOT_READ_DESC_TOKEN.get( 423 valueStr, pos, getExceptionMessage(e))); 424 return false; 425 } 426 427 if (! tokenName.equals("desc")) 428 { 429 invalidReason.append(ERR_ATTR_SYNTAX_ATTRSYNTAX_TOKEN_NOT_DESC.get( 430 valueStr, tokenName)); 431 return false; 432 } 433 434 435 // The next component must be the quoted description. 436 try 437 { 438 StringBuilder descriptionBuffer = new StringBuilder(); 439 pos = readQuotedString(valueStr, descriptionBuffer, pos); 440 } 441 catch (Exception e) 442 { 443 if (debugEnabled()) 444 { 445 TRACER.debugCaught(DebugLogLevel.ERROR, e); 446 } 447 448 invalidReason.append( 449 ERR_ATTR_SYNTAX_ATTRSYNTAX_CANNOT_READ_DESC_VALUE.get( 450 valueStr, pos, getExceptionMessage(e))); 451 return false; 452 } 453 //Check if we have a RFC 4512 style extension. 454 if ((c = valueStr.charAt(pos)) != ')') 455 { 456 try { 457 pos=parseExtension(valueStr, pos); 458 } catch (Exception e) { 459 if (debugEnabled()) 460 { 461 TRACER.debugCaught(DebugLogLevel.ERROR, e); 462 } 463 invalidReason.append( 464 ERR_ATTR_SYNTAX_ATTRSYNTAX_INVALID_EXTENSION.get( 465 getExceptionMessage(e))); 466 return false; 467 } 468 } 469 470 // The next character must be the closing parenthesis and there should not 471 // be anything after it (except maybe some spaces). 472 if ((c = valueStr.charAt(pos++)) != ')') 473 { 474 475 invalidReason.append( 476 ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_CLOSE_PARENTHESIS.get( 477 valueStr, pos, String.valueOf(c))); 478 return false; 479 } 480 481 while (pos < length) 482 { 483 c = valueStr.charAt(pos++); 484 if (c != ' ') 485 { 486 487 invalidReason.append( 488 ERR_ATTR_SYNTAX_ATTRSYNTAX_ILLEGAL_CHAR_AFTER_CLOSE.get( 489 valueStr, String.valueOf(c), pos)); 490 return false; 491 } 492 } 493 494 495 // If we've gotten here, then the value is OK. 496 return true; 497 } 498 499 500 501 /** 502 * Reads the next token name from the attribute syntax definition, skipping 503 * over any leading or trailing spaces, and appends it to the provided buffer. 504 * 505 * @param valueStr The string representation of the attribute syntax 506 * definition. 507 * @param tokenName The buffer into which the token name will be written. 508 * @param startPos The position in the provided string at which to start 509 * reading the token name. 510 * 511 * @return The position of the first character that is not part of the token 512 * name or one of the trailing spaces after it. 513 * 514 * @throws DirectoryException If a problem is encountered while reading the 515 * token name. 516 */ 517 private static int readTokenName(String valueStr, StringBuilder tokenName, 518 int startPos) 519 throws DirectoryException 520 { 521 // Skip over any spaces at the beginning of the value. 522 char c = '\u0000'; 523 int length = valueStr.length(); 524 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 525 { 526 startPos++; 527 } 528 529 if (startPos >= length) 530 { 531 Message message = 532 ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get(valueStr); 533 throw new DirectoryException( 534 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 535 } 536 537 538 // Read until we find the next space. 539 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' ')) 540 { 541 tokenName.append(c); 542 } 543 544 545 // Skip over any trailing spaces after the value. 546 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 547 { 548 startPos++; 549 } 550 551 552 // Return the position of the first non-space character after the token. 553 return startPos; 554 } 555 556 557 558 /** 559 * Reads the value of a string enclosed in single quotes, skipping over the 560 * quotes and any leading or trailing spaces, and appending the string to the 561 * provided buffer. 562 * 563 * @param valueStr The user-provided representation of the attribute type 564 * definition. 565 * @param valueBuffer The buffer into which the user-provided representation 566 * of the value will be placed. 567 * @param startPos The position in the provided string at which to start 568 * reading the quoted string. 569 * 570 * @return The position of the first character that is not part of the quoted 571 * string or one of the trailing spaces after it. 572 * 573 * @throws DirectoryException If a problem is encountered while reading the 574 * quoted string. 575 */ 576 private static int readQuotedString(String valueStr, 577 StringBuilder valueBuffer, int startPos) 578 throws DirectoryException 579 { 580 // Skip over any spaces at the beginning of the value. 581 char c = '\u0000'; 582 int length = valueStr.length(); 583 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 584 { 585 startPos++; 586 } 587 588 if (startPos >= length) 589 { 590 Message message = 591 ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get(valueStr); 592 throw new DirectoryException( 593 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 594 } 595 596 597 // The next character must be a single quote. 598 if (c != '\'') 599 { 600 Message message = WARN_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_QUOTE_AT_POS.get( 601 valueStr, startPos, String.valueOf(c)); 602 throw new DirectoryException( 603 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 604 } 605 606 607 // Read until we find the closing quote. 608 startPos++; 609 while ((startPos < length) && ((c = valueStr.charAt(startPos)) != '\'')) 610 { 611 valueBuffer.append(c); 612 startPos++; 613 } 614 615 616 // Skip over any trailing spaces after the value. 617 startPos++; 618 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' ')) 619 { 620 startPos++; 621 } 622 623 624 // If we're at the end of the value, then that's illegal. 625 if (startPos >= length) 626 { 627 Message message = 628 ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get(valueStr); 629 throw new DirectoryException( 630 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); 631 } 632 633 634 // Return the position of the first non-space character after the token. 635 return startPos; 636 } 637 638 /** Parses a RFC 4512 extensions (see 4.1.5 and 4.1 of the RFC) definition. 639 * 640 * From 4.1.5 of the spec: 641 * 642 * LDAP syntax definitions are written according to the ABNF: 643 * 644 * SyntaxDescription = LPAREN WSP 645 * numericoid ; object identifier 646 * [ SP "DESC" SP qdstring ] ; description 647 * extensions WSP RPAREN ; extensions 648 * 649 * @param valueStr The user-provided representation of the extensions 650 * definition. 651 * 652 * @param startPos The position in the provided string at which to start 653 * reading the quoted string. 654 * 655 * @return The position of the first character that is not part of the quoted 656 * string or one of the trailing spaces after it. 657 * 658 * @throws DirectoryException If the extensions definition could not be 659 * parsed. 660 */ 661 private static int parseExtension(String valueStr, int startPos) 662 throws DirectoryException { 663 664 int pos=startPos, len=valueStr.length(); 665 char c; 666 while(true) 667 { 668 StringBuilder tokenNameBuffer = new StringBuilder(); 669 pos = readTokenName(valueStr, tokenNameBuffer, pos); 670 String tokenName = tokenNameBuffer.toString(); 671 if((tokenName.length() <= 2) || (!tokenName.startsWith("X-"))) 672 { 673 Message message = 674 ERR_ATTR_SYNTAX_ATTRSYNTAX_EXTENSION_INVALID_CHARACTER.get( 675 valueStr, pos); 676 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 677 message); 678 } 679 String xstring = tokenName.substring(2); 680 //Only allow a-z,A-Z,-,_ characters after X- 681 if(xstring.split("^[A-Za-z_-]+").length > 0) 682 { 683 Message message = 684 ERR_ATTR_SYNTAX_ATTRSYNTAX_EXTENSION_INVALID_CHARACTER.get( 685 valueStr, pos); 686 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 687 message); 688 } 689 if((c=valueStr.charAt(pos)) == '\'') 690 { 691 StringBuilder qdString = new StringBuilder(); 692 pos = readQuotedString(valueStr, qdString, pos); 693 694 } else if(c == '(') 695 { 696 pos++; 697 StringBuilder qdString = new StringBuilder(); 698 while ((c=valueStr.charAt(pos)) != ')') 699 pos = readQuotedString(valueStr, qdString, pos); 700 pos++; 701 } else 702 { 703 Message message = 704 ERR_ATTR_SYNTAX_ATTRSYNTAX_EXTENSION_INVALID_CHARACTER.get( 705 valueStr, pos); 706 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 707 message); 708 } 709 if (pos >= len) 710 { 711 Message message = 712 ERR_ATTR_SYNTAX_ATTRSYNTAX_TRUNCATED_VALUE.get(valueStr); 713 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, 714 message); 715 } 716 if(valueStr.charAt(pos) == ')') 717 break; 718 } 719 return pos; 720 } 721 } 722