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 2014-2015 ForgeRock AS 026 * Portions Copyright 2013-2014 Manuel Gaupp 027 */ 028package org.opends.server.types; 029 030import static org.opends.messages.CoreMessages.*; 031import static org.opends.server.util.ServerConstants.*; 032import static org.opends.server.util.StaticUtils.*; 033 034import java.util.ArrayList; 035import java.util.Collection; 036import java.util.Collections; 037import java.util.HashSet; 038import java.util.LinkedHashSet; 039import java.util.LinkedList; 040import java.util.List; 041import java.util.Set; 042 043import org.forgerock.i18n.LocalizableMessage; 044import org.forgerock.i18n.slf4j.LocalizedLogger; 045import org.forgerock.opendj.ldap.Assertion; 046import org.forgerock.opendj.ldap.ByteString; 047import org.forgerock.opendj.ldap.ByteStringBuilder; 048import org.forgerock.opendj.ldap.ConditionResult; 049import org.forgerock.opendj.ldap.ResultCode; 050import org.forgerock.opendj.ldap.schema.MatchingRule; 051import org.opends.server.core.DirectoryServer; 052 053/** 054 * This class defines a data structure for storing and interacting 055 * with a search filter that may serve as criteria for locating 056 * entries in the Directory Server. 057 */ 058@org.opends.server.types.PublicAPI( 059 stability=org.opends.server.types.StabilityLevel.UNCOMMITTED, 060 mayInstantiate=true, 061 mayExtend=false, 062 mayInvoke=true) 063public final class SearchFilter 064{ 065 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 066 067 private static SearchFilter objectClassPresent; 068 069 /** The attribute type for this filter. */ 070 private final AttributeType attributeType; 071 072 /** The assertion value for this filter. */ 073 private final ByteString assertionValue; 074 075 /** Indicates whether to match on DN attributes for extensible match filters. */ 076 private final boolean dnAttributes; 077 078 /** The subInitial element for substring filters. */ 079 private final ByteString subInitialElement; 080 /** The set of subAny components for substring filters. */ 081 private final List<ByteString> subAnyElements; 082 /** The subFinal element for substring filters. */ 083 private final ByteString subFinalElement; 084 085 /** The search filter type for this filter. */ 086 private final FilterType filterType; 087 088 /** The set of filter components for AND and OR filters. */ 089 private final LinkedHashSet<SearchFilter> filterComponents; 090 /** The not filter component for this search filter. */ 091 private final SearchFilter notComponent; 092 093 /** The set of options for the attribute type in this filter. */ 094 private final Set<String> attributeOptions; 095 096 /** The matching rule ID for this search filter. */ 097 private final String matchingRuleID; 098 099 100 101 /** 102 * Creates a new search filter with the provided information. 103 * 104 * @param filterType The filter type for this search 105 * filter. 106 * @param filterComponents The set of filter components for AND 107 * and OR filters. 108 * @param notComponent The filter component for NOT filters. 109 * @param attributeType The attribute type for this filter. 110 * @param attributeOptions The set of attribute options for the 111 * associated attribute type. 112 * @param assertionValue The assertion value for this filter. 113 * @param subInitialElement The subInitial element for substring 114 * filters. 115 * @param subAnyElements The subAny elements for substring 116 * filters. 117 * @param subFinalElement The subFinal element for substring 118 * filters. 119 * @param matchingRuleID The matching rule ID for this search 120 * filter. 121 * @param dnAttributes Indicates whether to match on DN 122 * attributes for extensible match 123 * filters. 124 * 125 * FIXME: this should be private. 126 */ 127 public SearchFilter(FilterType filterType, 128 Collection<SearchFilter> filterComponents, 129 SearchFilter notComponent, 130 AttributeType attributeType, 131 Set<String> attributeOptions, 132 ByteString assertionValue, 133 ByteString subInitialElement, 134 List<ByteString> subAnyElements, 135 ByteString subFinalElement, 136 String matchingRuleID, boolean dnAttributes) 137 { 138 // This used to happen in getSubAnyElements, but we do it here 139 // so that we can make this.subAnyElements final. 140 if (subAnyElements == null) { 141 subAnyElements = new ArrayList<>(0); 142 } 143 144 // This used to happen in getFilterComponents, but we do it here 145 // so that we can make this.filterComponents final. 146 if (filterComponents == null) { 147 filterComponents = Collections.emptyList(); 148 } 149 150 this.filterType = filterType; 151 this.filterComponents = new LinkedHashSet<>(filterComponents); 152 this.notComponent = notComponent; 153 this.attributeType = attributeType; 154 this.attributeOptions = attributeOptions; 155 this.assertionValue = assertionValue; 156 this.subInitialElement = subInitialElement; 157 this.subAnyElements = subAnyElements; 158 this.subFinalElement = subFinalElement; 159 this.matchingRuleID = matchingRuleID; 160 this.dnAttributes = dnAttributes; 161 } 162 163 164 /** 165 * Creates a new AND search filter with the provided information. 166 * 167 * @param filterComponents The set of filter components for the 168 * AND filter. 169 * 170 * @return The constructed search filter. 171 */ 172 public static SearchFilter createANDFilter(Collection<SearchFilter> 173 filterComponents) 174 { 175 return new SearchFilter(FilterType.AND, filterComponents, null, 176 null, null, null, null, null, null, null, 177 false); 178 } 179 180 181 182 /** 183 * Creates a new OR search filter with the provided information. 184 * 185 * @param filterComponents The set of filter components for the OR 186 * filter. 187 * 188 * @return The constructed search filter. 189 */ 190 public static SearchFilter createORFilter(Collection<SearchFilter> 191 filterComponents) 192 { 193 return new SearchFilter(FilterType.OR, filterComponents, null, 194 null, null, null, null, null, null, null, 195 false); 196 } 197 198 199 200 /** 201 * Creates a new NOT search filter with the provided information. 202 * 203 * @param notComponent The filter component for this NOT filter. 204 * 205 * @return The constructed search filter. 206 */ 207 public static SearchFilter createNOTFilter( 208 SearchFilter notComponent) 209 { 210 return new SearchFilter(FilterType.NOT, null, notComponent, null, 211 null, null, null, null, null, null, 212 false); 213 } 214 215 216 217 /** 218 * Creates a new equality search filter with the provided 219 * information. 220 * 221 * @param attributeType The attribute type for this equality 222 * filter. 223 * @param assertionValue The assertion value for this equality 224 * filter. 225 * 226 * @return The constructed search filter. 227 */ 228 public static SearchFilter createEqualityFilter( 229 AttributeType attributeType, 230 ByteString assertionValue) 231 { 232 return new SearchFilter(FilterType.EQUALITY, null, null, 233 attributeType, null, assertionValue, null, 234 null, null, null, false); 235 } 236 237 238 239 /** 240 * Creates a new equality search filter with the provided 241 * information. 242 * 243 * @param attributeType The attribute type for this equality 244 * filter. 245 * @param attributeOptions The set of attribute options for this 246 * equality filter. 247 * @param assertionValue The assertion value for this equality 248 * filter. 249 * 250 * @return The constructed search filter. 251 */ 252 public static SearchFilter createEqualityFilter( 253 AttributeType attributeType, 254 Set<String> attributeOptions, 255 ByteString assertionValue) 256 { 257 return new SearchFilter(FilterType.EQUALITY, null, null, 258 attributeType, attributeOptions, 259 assertionValue, null, null, null, null, 260 false); 261 } 262 263 264 265 /** 266 * Creates a new substring search filter with the provided 267 * information. 268 * 269 * @param attributeType The attribute type for this filter. 270 * @param subInitialElement The subInitial element for substring 271 * filters. 272 * @param subAnyElements The subAny elements for substring 273 * filters. 274 * @param subFinalElement The subFinal element for substring 275 * filters. 276 * 277 * @return The constructed search filter. 278 */ 279 public static SearchFilter 280 createSubstringFilter(AttributeType attributeType, 281 ByteString subInitialElement, 282 List<ByteString> subAnyElements, 283 ByteString subFinalElement) 284 { 285 return new SearchFilter(FilterType.SUBSTRING, null, null, 286 attributeType, null, null, 287 subInitialElement, subAnyElements, 288 subFinalElement, null, false); 289 } 290 291 292 293 /** 294 * Creates a new substring search filter with the provided 295 * information. 296 * 297 * @param attributeType The attribute type for this filter. 298 * @param attributeOptions The set of attribute options for this 299 * search filter. 300 * @param subInitialElement The subInitial element for substring 301 * filters. 302 * @param subAnyElements The subAny elements for substring 303 * filters. 304 * @param subFinalElement The subFinal element for substring 305 * filters. 306 * 307 * @return The constructed search filter. 308 */ 309 public static SearchFilter 310 createSubstringFilter(AttributeType attributeType, 311 Set<String> attributeOptions, 312 ByteString subInitialElement, 313 List<ByteString> subAnyElements, 314 ByteString subFinalElement) 315 { 316 return new SearchFilter(FilterType.SUBSTRING, null, null, 317 attributeType, attributeOptions, null, 318 subInitialElement, subAnyElements, 319 subFinalElement, null, false); 320 } 321 322 323 324 /** 325 * Creates a greater-or-equal search filter with the provided 326 * information. 327 * 328 * @param attributeType The attribute type for this 329 * greater-or-equal filter. 330 * @param assertionValue The assertion value for this 331 * greater-or-equal filter. 332 * 333 * @return The constructed search filter. 334 */ 335 public static SearchFilter createGreaterOrEqualFilter( 336 AttributeType attributeType, 337 ByteString assertionValue) 338 { 339 return new SearchFilter(FilterType.GREATER_OR_EQUAL, null, null, 340 attributeType, null, assertionValue, null, 341 null, null, null, false); 342 } 343 344 345 346 /** 347 * Creates a greater-or-equal search filter with the provided 348 * information. 349 * 350 * @param attributeType The attribute type for this 351 * greater-or-equal filter. 352 * @param attributeOptions The set of attribute options for this 353 * search filter. 354 * @param assertionValue The assertion value for this 355 * greater-or-equal filter. 356 * 357 * @return The constructed search filter. 358 */ 359 public static SearchFilter createGreaterOrEqualFilter( 360 AttributeType attributeType, 361 Set<String> attributeOptions, 362 ByteString assertionValue) 363 { 364 return new SearchFilter(FilterType.GREATER_OR_EQUAL, null, null, 365 attributeType, attributeOptions, 366 assertionValue, null, null, null, null, 367 false); 368 } 369 370 371 372 /** 373 * Creates a less-or-equal search filter with the provided 374 * information. 375 * 376 * @param attributeType The attribute type for this less-or-equal 377 * filter. 378 * @param assertionValue The assertion value for this 379 * less-or-equal filter. 380 * 381 * @return The constructed search filter. 382 */ 383 public static SearchFilter createLessOrEqualFilter( 384 AttributeType attributeType, 385 ByteString assertionValue) 386 { 387 return new SearchFilter(FilterType.LESS_OR_EQUAL, null, null, 388 attributeType, null, assertionValue, null, 389 null, null, null, false); 390 } 391 392 393 394 /** 395 * Creates a less-or-equal search filter with the provided 396 * information. 397 * 398 * @param attributeType The attribute type for this 399 * less-or-equal filter. 400 * @param attributeOptions The set of attribute options for this 401 * search filter. 402 * @param assertionValue The assertion value for this 403 * less-or-equal filter. 404 * 405 * @return The constructed search filter. 406 */ 407 public static SearchFilter createLessOrEqualFilter( 408 AttributeType attributeType, 409 Set<String> attributeOptions, 410 ByteString assertionValue) 411 { 412 return new SearchFilter(FilterType.LESS_OR_EQUAL, null, null, 413 attributeType, attributeOptions, 414 assertionValue, null, null, null, null, 415 false); 416 } 417 418 419 420 /** 421 * Creates a presence search filter with the provided information. 422 * 423 * @param attributeType The attribute type for this presence 424 * filter. 425 * 426 * @return The constructed search filter. 427 */ 428 public static SearchFilter createPresenceFilter( 429 AttributeType attributeType) 430 { 431 return new SearchFilter(FilterType.PRESENT, null, null, 432 attributeType, null, null, null, null, 433 null, null, false); 434 } 435 436 437 438 /** 439 * Creates a presence search filter with the provided information. 440 * 441 * @param attributeType The attribute type for this presence 442 * filter. 443 * @param attributeOptions The attribute options for this presence 444 * filter. 445 * 446 * @return The constructed search filter. 447 */ 448 public static SearchFilter createPresenceFilter( 449 AttributeType attributeType, 450 Set<String> attributeOptions) 451 { 452 return new SearchFilter(FilterType.PRESENT, null, null, 453 attributeType, attributeOptions, null, 454 null, null, null, null, false); 455 } 456 457 458 459 /** 460 * Creates an approximate search filter with the provided 461 * information. 462 * 463 * @param attributeType The attribute type for this approximate 464 * filter. 465 * @param assertionValue The assertion value for this approximate 466 * filter. 467 * 468 * @return The constructed search filter. 469 */ 470 public static SearchFilter createApproximateFilter( 471 AttributeType attributeType, 472 ByteString assertionValue) 473 { 474 return new SearchFilter(FilterType.APPROXIMATE_MATCH, null, null, 475 attributeType, null, assertionValue, null, 476 null, null, null, false); 477 } 478 479 480 481 /** 482 * Creates an approximate search filter with the provided 483 * information. 484 * 485 * @param attributeType The attribute type for this approximate 486 * filter. 487 * @param attributeOptions The attribute options for this 488 * approximate filter. 489 * @param assertionValue The assertion value for this 490 * approximate filter. 491 * 492 * @return The constructed search filter. 493 */ 494 public static SearchFilter createApproximateFilter( 495 AttributeType attributeType, 496 Set<String> attributeOptions, 497 ByteString assertionValue) 498 { 499 return new SearchFilter(FilterType.APPROXIMATE_MATCH, null, null, 500 attributeType, attributeOptions, 501 assertionValue, null, null, null, null, 502 false); 503 } 504 505 506 507 /** 508 * Creates an extensible matching filter with the provided 509 * information. 510 * 511 * @param attributeType The attribute type for this extensible 512 * match filter. 513 * @param assertionValue The assertion value for this extensible 514 * match filter. 515 * @param matchingRuleID The matching rule ID for this search 516 * filter. 517 * @param dnAttributes Indicates whether to match on DN 518 * attributes for extensible match filters. 519 * 520 * @return The constructed search filter. 521 * 522 * @throws DirectoryException If the provided information is not 523 * sufficient to create an extensible 524 * match filter. 525 */ 526 public static SearchFilter createExtensibleMatchFilter( 527 AttributeType attributeType, 528 ByteString assertionValue, 529 String matchingRuleID, 530 boolean dnAttributes) 531 throws DirectoryException 532 { 533 if (attributeType == null && matchingRuleID == null) 534 { 535 LocalizableMessage message = 536 ERR_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR.get(); 537 throw new DirectoryException( 538 ResultCode.PROTOCOL_ERROR, message); 539 } 540 541 return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null, 542 attributeType, null, assertionValue, null, 543 null, null, matchingRuleID, dnAttributes); 544 } 545 546 547 548 /** 549 * Creates an extensible matching filter with the provided 550 * information. 551 * 552 * @param attributeType The attribute type for this extensible 553 * match filter. 554 * @param attributeOptions The set of attribute options for this 555 * extensible match filter. 556 * @param assertionValue The assertion value for this extensible 557 * match filter. 558 * @param matchingRuleID The matching rule ID for this search 559 * filter. 560 * @param dnAttributes Indicates whether to match on DN 561 * attributes for extensible match 562 * filters. 563 * 564 * @return The constructed search filter. 565 * 566 * @throws DirectoryException If the provided information is not 567 * sufficient to create an extensible 568 * match filter. 569 */ 570 public static SearchFilter createExtensibleMatchFilter( 571 AttributeType attributeType, 572 Set<String> attributeOptions, 573 ByteString assertionValue, 574 String matchingRuleID, 575 boolean dnAttributes) 576 throws DirectoryException 577 { 578 if (attributeType == null && matchingRuleID == null) 579 { 580 LocalizableMessage message = 581 ERR_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR.get(); 582 throw new DirectoryException( 583 ResultCode.PROTOCOL_ERROR, message); 584 } 585 586 return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null, 587 attributeType, attributeOptions, 588 assertionValue, null, null, null, 589 matchingRuleID, dnAttributes); 590 } 591 592 593 594 /** 595 * Decodes the provided filter string as a search filter. 596 * 597 * @param filterString The filter string to be decoded as a search 598 * filter. 599 * 600 * @return The search filter decoded from the provided string. 601 * 602 * @throws DirectoryException If a problem occurs while attempting 603 * to decode the provided string as a 604 * search filter. 605 */ 606 public static SearchFilter createFilterFromString( 607 String filterString) 608 throws DirectoryException 609 { 610 if (filterString == null) 611 { 612 LocalizableMessage message = ERR_SEARCH_FILTER_NULL.get(); 613 throw new DirectoryException( 614 ResultCode.PROTOCOL_ERROR, message); 615 } 616 617 618 try 619 { 620 return createFilterFromString(filterString, 0, 621 filterString.length()); 622 } 623 catch (DirectoryException de) 624 { 625 logger.traceException(de); 626 627 throw de; 628 } 629 catch (Exception e) 630 { 631 logger.traceException(e); 632 633 LocalizableMessage message = ERR_SEARCH_FILTER_UNCAUGHT_EXCEPTION.get(filterString, e); 634 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message, e); 635 } 636 } 637 638 639 640 /** 641 * Creates a new search filter from the specified portion of the 642 * provided string. 643 * 644 * @param filterString The string containing the filter 645 * information to be decoded. 646 * @param startPos The index of the first character in the 647 * string that is part of the search filter. 648 * @param endPos The index of the first character after the 649 * start position that is not part of the 650 * search filter. 651 * 652 * @return The decoded search filter. 653 * 654 * @throws DirectoryException If a problem occurs while attempting 655 * to decode the provided string as a 656 * search filter. 657 */ 658 private static SearchFilter createFilterFromString( 659 String filterString, int startPos, 660 int endPos) 661 throws DirectoryException 662 { 663 // Make sure that the length is sufficient for a valid search 664 // filter. 665 int length = endPos - startPos; 666 if (length <= 0) 667 { 668 LocalizableMessage message = ERR_SEARCH_FILTER_NULL.get(); 669 throw new DirectoryException( 670 ResultCode.PROTOCOL_ERROR, message); 671 } 672 673 674 // If the filter is surrounded by parentheses (which it should 675 // be), then strip them off. 676 if (filterString.charAt(startPos) == '(') 677 { 678 if (filterString.charAt(endPos-1) == ')') 679 { 680 startPos++; 681 endPos--; 682 } 683 else 684 { 685 LocalizableMessage message = ERR_SEARCH_FILTER_MISMATCHED_PARENTHESES. 686 get(filterString, startPos, endPos); 687 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 688 message); 689 } 690 } 691 692 693 // Look at the first character. If it is a '&' then it is an AND 694 // search. If it is a '|' then it is an OR search. If it is a 695 // '!' then it is a NOT search. 696 char c = filterString.charAt(startPos); 697 if (c == '&') 698 { 699 return decodeCompoundFilter(FilterType.AND, filterString, 700 startPos+1, endPos); 701 } 702 else if (c == '|') 703 { 704 return decodeCompoundFilter(FilterType.OR, filterString, 705 startPos+1, endPos); 706 } 707 else if (c == '!') 708 { 709 return decodeCompoundFilter(FilterType.NOT, filterString, 710 startPos+1, endPos); 711 } 712 713 714 // If we've gotten here, then it must be a simple filter. It must 715 // have an equal sign at some point, so find it. 716 int equalPos = -1; 717 for (int i=startPos; i < endPos; i++) 718 { 719 if (filterString.charAt(i) == '=') 720 { 721 equalPos = i; 722 break; 723 } 724 } 725 726 if (equalPos <= startPos) 727 { 728 LocalizableMessage message = ERR_SEARCH_FILTER_NO_EQUAL_SIGN.get( 729 filterString, startPos, endPos); 730 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 731 message); 732 } 733 734 735 // Look at the character immediately before the equal sign, 736 // because it may help determine the filter type. 737 int attrEndPos; 738 FilterType filterType; 739 switch (filterString.charAt(equalPos-1)) 740 { 741 case '~': 742 filterType = FilterType.APPROXIMATE_MATCH; 743 attrEndPos = equalPos-1; 744 break; 745 case '>': 746 filterType = FilterType.GREATER_OR_EQUAL; 747 attrEndPos = equalPos-1; 748 break; 749 case '<': 750 filterType = FilterType.LESS_OR_EQUAL; 751 attrEndPos = equalPos-1; 752 break; 753 case ':': 754 return decodeExtensibleMatchFilter(filterString, startPos, 755 equalPos, endPos); 756 default: 757 filterType = FilterType.EQUALITY; 758 attrEndPos = equalPos; 759 break; 760 } 761 762 763 // The part of the filter string before the equal sign should be 764 // the attribute type (with or without options). Decode it. 765 String attrType = filterString.substring(startPos, attrEndPos); 766 StringBuilder lowerType = new StringBuilder(attrType.length()); 767 Set<String> attributeOptions = new HashSet<>(); 768 769 int semicolonPos = attrType.indexOf(';'); 770 if (semicolonPos < 0) 771 { 772 for (int i=0; i < attrType.length(); i++) 773 { 774 lowerType.append(Character.toLowerCase(attrType.charAt(i))); 775 } 776 } 777 else 778 { 779 for (int i=0; i < semicolonPos; i++) 780 { 781 lowerType.append(Character.toLowerCase(attrType.charAt(i))); 782 } 783 784 int nextPos = attrType.indexOf(';', semicolonPos+1); 785 while (nextPos > 0) 786 { 787 attributeOptions.add(attrType.substring(semicolonPos+1, 788 nextPos)); 789 semicolonPos = nextPos; 790 nextPos = attrType.indexOf(';', semicolonPos+1); 791 } 792 793 attributeOptions.add(attrType.substring(semicolonPos+1)); 794 } 795 796 // Get the attribute value. 797 AttributeType attributeType = getAttributeType(attrType, lowerType); 798 String valueStr = filterString.substring(equalPos+1, endPos); 799 if (valueStr.length() == 0) 800 { 801 return new SearchFilter(filterType, null, null, attributeType, 802 attributeOptions, ByteString.empty(), 803 null, null, null, null, false); 804 } 805 else if (valueStr.equals("*")) 806 { 807 return new SearchFilter(FilterType.PRESENT, null, null, 808 attributeType, attributeOptions, null, 809 null, null, null, null, false); 810 } 811 else if (valueStr.indexOf('*') >= 0) 812 { 813 return decodeSubstringFilter(filterString, attributeType, 814 attributeOptions, equalPos, 815 endPos); 816 } 817 else 818 { 819 boolean hasEscape = false; 820 byte[] valueBytes = getBytes(valueStr); 821 for (byte valueByte : valueBytes) 822 { 823 if (valueByte == 0x5C) // The backslash character 824 { 825 hasEscape = true; 826 break; 827 } 828 } 829 830 ByteString userValue; 831 if (hasEscape) 832 { 833 ByteStringBuilder valueBuffer = 834 new ByteStringBuilder(valueStr.length()); 835 for (int i=0; i < valueBytes.length; i++) 836 { 837 if (valueBytes[i] == 0x5C) // The backslash character 838 { 839 // The next two bytes must be the hex characters that 840 // comprise the binary value. 841 if (i + 2 >= valueBytes.length) 842 { 843 LocalizableMessage message = 844 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 845 get(filterString, equalPos+i+1); 846 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 847 message); 848 } 849 850 byte byteValue = 0; 851 switch (valueBytes[++i]) 852 { 853 case 0x30: // '0' 854 break; 855 case 0x31: // '1' 856 byteValue = (byte) 0x10; 857 break; 858 case 0x32: // '2' 859 byteValue = (byte) 0x20; 860 break; 861 case 0x33: // '3' 862 byteValue = (byte) 0x30; 863 break; 864 case 0x34: // '4' 865 byteValue = (byte) 0x40; 866 break; 867 case 0x35: // '5' 868 byteValue = (byte) 0x50; 869 break; 870 case 0x36: // '6' 871 byteValue = (byte) 0x60; 872 break; 873 case 0x37: // '7' 874 byteValue = (byte) 0x70; 875 break; 876 case 0x38: // '8' 877 byteValue = (byte) 0x80; 878 break; 879 case 0x39: // '9' 880 byteValue = (byte) 0x90; 881 break; 882 case 0x41: // 'A' 883 case 0x61: // 'a' 884 byteValue = (byte) 0xA0; 885 break; 886 case 0x42: // 'B' 887 case 0x62: // 'b' 888 byteValue = (byte) 0xB0; 889 break; 890 case 0x43: // 'C' 891 case 0x63: // 'c' 892 byteValue = (byte) 0xC0; 893 break; 894 case 0x44: // 'D' 895 case 0x64: // 'd' 896 byteValue = (byte) 0xD0; 897 break; 898 case 0x45: // 'E' 899 case 0x65: // 'e' 900 byteValue = (byte) 0xE0; 901 break; 902 case 0x46: // 'F' 903 case 0x66: // 'f' 904 byteValue = (byte) 0xF0; 905 break; 906 default: 907 LocalizableMessage message = 908 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 909 get(filterString, equalPos+i+1); 910 throw new DirectoryException( 911 ResultCode.PROTOCOL_ERROR, message); 912 } 913 914 switch (valueBytes[++i]) 915 { 916 case 0x30: // '0' 917 break; 918 case 0x31: // '1' 919 byteValue |= (byte) 0x01; 920 break; 921 case 0x32: // '2' 922 byteValue |= (byte) 0x02; 923 break; 924 case 0x33: // '3' 925 byteValue |= (byte) 0x03; 926 break; 927 case 0x34: // '4' 928 byteValue |= (byte) 0x04; 929 break; 930 case 0x35: // '5' 931 byteValue |= (byte) 0x05; 932 break; 933 case 0x36: // '6' 934 byteValue |= (byte) 0x06; 935 break; 936 case 0x37: // '7' 937 byteValue |= (byte) 0x07; 938 break; 939 case 0x38: // '8' 940 byteValue |= (byte) 0x08; 941 break; 942 case 0x39: // '9' 943 byteValue |= (byte) 0x09; 944 break; 945 case 0x41: // 'A' 946 case 0x61: // 'a' 947 byteValue |= (byte) 0x0A; 948 break; 949 case 0x42: // 'B' 950 case 0x62: // 'b' 951 byteValue |= (byte) 0x0B; 952 break; 953 case 0x43: // 'C' 954 case 0x63: // 'c' 955 byteValue |= (byte) 0x0C; 956 break; 957 case 0x44: // 'D' 958 case 0x64: // 'd' 959 byteValue |= (byte) 0x0D; 960 break; 961 case 0x45: // 'E' 962 case 0x65: // 'e' 963 byteValue |= (byte) 0x0E; 964 break; 965 case 0x46: // 'F' 966 case 0x66: // 'f' 967 byteValue |= (byte) 0x0F; 968 break; 969 default: 970 LocalizableMessage message = 971 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 972 get(filterString, equalPos+i+1); 973 throw new DirectoryException( 974 ResultCode.PROTOCOL_ERROR, message); 975 } 976 977 valueBuffer.append(byteValue); 978 } 979 else 980 { 981 valueBuffer.append(valueBytes[i]); 982 } 983 } 984 985 userValue = valueBuffer.toByteString(); 986 } 987 else 988 { 989 userValue = ByteString.wrap(valueBytes); 990 } 991 992 return new SearchFilter(filterType, null, null, attributeType, 993 attributeOptions, userValue, null, null, 994 null, null, false); 995 } 996 } 997 998 999 1000 /** 1001 * Decodes a set of filters from the provided filter string within 1002 * the indicated range. 1003 * 1004 * @param filterType The filter type for this compound filter. 1005 * It must be an AND, OR or NOT filter. 1006 * @param filterString The string containing the filter 1007 * information to decode. 1008 * @param startPos The position of the first character in the 1009 * set of filters to decode. 1010 * @param endPos The position of the first character after 1011 * the end of the set of filters to decode. 1012 * 1013 * @return The decoded search filter. 1014 * 1015 * @throws DirectoryException If a problem occurs while attempting 1016 * to decode the compound filter. 1017 */ 1018 private static SearchFilter decodeCompoundFilter( 1019 FilterType filterType, 1020 String filterString, int startPos, 1021 int endPos) 1022 throws DirectoryException 1023 { 1024 // Create a list to hold the returned components. 1025 List<SearchFilter> filterComponents = new ArrayList<>(); 1026 1027 1028 // If the end pos is equal to the start pos, then there are no components. 1029 if (startPos == endPos) 1030 { 1031 if (filterType == FilterType.NOT) 1032 { 1033 LocalizableMessage message = ERR_SEARCH_FILTER_NOT_EXACTLY_ONE.get( 1034 filterString, startPos, endPos); 1035 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 1036 } 1037 else 1038 { 1039 // This is valid and will be treated as a TRUE/FALSE filter. 1040 return new SearchFilter(filterType, filterComponents, null, 1041 null, null, null, null, null, null, 1042 null, false); 1043 } 1044 } 1045 1046 1047 // The first and last characters must be parentheses. If not, 1048 // then that's an error. 1049 if (filterString.charAt(startPos) != '(' || 1050 filterString.charAt(endPos-1) != ')') 1051 { 1052 LocalizableMessage message = 1053 ERR_SEARCH_FILTER_COMPOUND_MISSING_PARENTHESES. 1054 get(filterString, startPos, endPos); 1055 throw new DirectoryException( 1056 ResultCode.PROTOCOL_ERROR, message); 1057 } 1058 1059 1060 // Iterate through the characters in the value. Whenever an open 1061 // parenthesis is found, locate the corresponding close 1062 // parenthesis by counting the number of intermediate open/close 1063 // parentheses. 1064 int pendingOpens = 0; 1065 int openPos = -1; 1066 for (int i=startPos; i < endPos; i++) 1067 { 1068 char c = filterString.charAt(i); 1069 if (c == '(') 1070 { 1071 if (openPos < 0) 1072 { 1073 openPos = i; 1074 } 1075 1076 pendingOpens++; 1077 } 1078 else if (c == ')') 1079 { 1080 pendingOpens--; 1081 if (pendingOpens == 0) 1082 { 1083 filterComponents.add(createFilterFromString(filterString, 1084 openPos, i+1)); 1085 openPos = -1; 1086 } 1087 else if (pendingOpens < 0) 1088 { 1089 LocalizableMessage message = 1090 ERR_SEARCH_FILTER_NO_CORRESPONDING_OPEN_PARENTHESIS. 1091 get(filterString, i); 1092 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1093 message); 1094 } 1095 } 1096 else if (pendingOpens <= 0) 1097 { 1098 LocalizableMessage message = 1099 ERR_SEARCH_FILTER_COMPOUND_MISSING_PARENTHESES. 1100 get(filterString, startPos, endPos); 1101 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1102 message); 1103 } 1104 } 1105 1106 1107 // At this point, we have parsed the entire set of filter 1108 // components. The list of open parenthesis positions must be 1109 // empty. 1110 if (pendingOpens != 0) 1111 { 1112 LocalizableMessage message = 1113 ERR_SEARCH_FILTER_NO_CORRESPONDING_CLOSE_PARENTHESIS. 1114 get(filterString, openPos); 1115 throw new DirectoryException( 1116 ResultCode.PROTOCOL_ERROR, message); 1117 } 1118 1119 1120 // We should have everything we need, so return the list. 1121 if (filterType == FilterType.NOT) 1122 { 1123 if (filterComponents.size() != 1) 1124 { 1125 LocalizableMessage message = ERR_SEARCH_FILTER_NOT_EXACTLY_ONE.get( 1126 filterString, startPos, endPos); 1127 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1128 message); 1129 } 1130 SearchFilter notComponent = filterComponents.get(0); 1131 return new SearchFilter(filterType, null, notComponent, null, 1132 null, null, null, null, null, null, 1133 false); 1134 } 1135 else 1136 { 1137 return new SearchFilter(filterType, filterComponents, null, 1138 null, null, null, null, null, null, 1139 null, false); 1140 } 1141 } 1142 1143 1144 /** 1145 * Decodes a substring search filter component based on the provided 1146 * information. 1147 * 1148 * @param filterString The filter string containing the 1149 * information to decode. 1150 * @param attrType The attribute type for this substring 1151 * filter component. 1152 * @param options The set of attribute options for the 1153 * associated attribute type. 1154 * @param equalPos The location of the equal sign separating 1155 * the attribute type from the value. 1156 * @param endPos The position of the first character after 1157 * the end of the substring value. 1158 * 1159 * @return The decoded search filter. 1160 * 1161 * @throws DirectoryException If a problem occurs while attempting 1162 * to decode the substring filter. 1163 */ 1164 private static SearchFilter decodeSubstringFilter( 1165 String filterString, 1166 AttributeType attrType, 1167 Set<String> options, int equalPos, 1168 int endPos) 1169 throws DirectoryException 1170 { 1171 // Get a binary representation of the value. 1172 byte[] valueBytes = 1173 getBytes(filterString.substring(equalPos+1, endPos)); 1174 1175 1176 // Find the locations of all the asterisks in the value. Also, 1177 // check to see if there are any escaped values, since they will 1178 // need special treatment. 1179 boolean hasEscape = false; 1180 LinkedList<Integer> asteriskPositions = new LinkedList<>(); 1181 for (int i=0; i < valueBytes.length; i++) 1182 { 1183 if (valueBytes[i] == 0x2A) // The asterisk. 1184 { 1185 asteriskPositions.add(i); 1186 } 1187 else if (valueBytes[i] == 0x5C) // The backslash. 1188 { 1189 hasEscape = true; 1190 } 1191 } 1192 1193 1194 // If there were no asterisks, then this isn't a substring filter. 1195 if (asteriskPositions.isEmpty()) 1196 { 1197 LocalizableMessage message = ERR_SEARCH_FILTER_SUBSTRING_NO_ASTERISKS.get( 1198 filterString, equalPos+1, endPos); 1199 throw new DirectoryException( 1200 ResultCode.PROTOCOL_ERROR, message); 1201 } 1202 else 1203 { 1204 // The rest of the processing will be only on the value bytes, 1205 // so re-adjust the end position. 1206 endPos = valueBytes.length; 1207 } 1208 1209 1210 // If the value starts with an asterisk, then there is no 1211 // subInitial component. Otherwise, parse out the subInitial. 1212 ByteString subInitial; 1213 int firstPos = asteriskPositions.removeFirst(); 1214 if (firstPos == 0) 1215 { 1216 subInitial = null; 1217 } 1218 else 1219 { 1220 if (hasEscape) 1221 { 1222 ByteStringBuilder buffer = new ByteStringBuilder(firstPos); 1223 for (int i=0; i < firstPos; i++) 1224 { 1225 if (valueBytes[i] == 0x5C) 1226 { 1227 // The next two bytes must be the hex characters that 1228 // comprise the binary value. 1229 if (i + 2 >= valueBytes.length) 1230 { 1231 LocalizableMessage message = 1232 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1233 get(filterString, equalPos+i+1); 1234 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1235 message); 1236 } 1237 1238 byte byteValue = 0; 1239 switch (valueBytes[++i]) 1240 { 1241 case 0x30: // '0' 1242 break; 1243 case 0x31: // '1' 1244 byteValue = (byte) 0x10; 1245 break; 1246 case 0x32: // '2' 1247 byteValue = (byte) 0x20; 1248 break; 1249 case 0x33: // '3' 1250 byteValue = (byte) 0x30; 1251 break; 1252 case 0x34: // '4' 1253 byteValue = (byte) 0x40; 1254 break; 1255 case 0x35: // '5' 1256 byteValue = (byte) 0x50; 1257 break; 1258 case 0x36: // '6' 1259 byteValue = (byte) 0x60; 1260 break; 1261 case 0x37: // '7' 1262 byteValue = (byte) 0x70; 1263 break; 1264 case 0x38: // '8' 1265 byteValue = (byte) 0x80; 1266 break; 1267 case 0x39: // '9' 1268 byteValue = (byte) 0x90; 1269 break; 1270 case 0x41: // 'A' 1271 case 0x61: // 'a' 1272 byteValue = (byte) 0xA0; 1273 break; 1274 case 0x42: // 'B' 1275 case 0x62: // 'b' 1276 byteValue = (byte) 0xB0; 1277 break; 1278 case 0x43: // 'C' 1279 case 0x63: // 'c' 1280 byteValue = (byte) 0xC0; 1281 break; 1282 case 0x44: // 'D' 1283 case 0x64: // 'd' 1284 byteValue = (byte) 0xD0; 1285 break; 1286 case 0x45: // 'E' 1287 case 0x65: // 'e' 1288 byteValue = (byte) 0xE0; 1289 break; 1290 case 0x46: // 'F' 1291 case 0x66: // 'f' 1292 byteValue = (byte) 0xF0; 1293 break; 1294 default: 1295 LocalizableMessage message = 1296 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1297 get(filterString, equalPos+i+1); 1298 throw new DirectoryException( 1299 ResultCode.PROTOCOL_ERROR, message); 1300 } 1301 1302 switch (valueBytes[++i]) 1303 { 1304 case 0x30: // '0' 1305 break; 1306 case 0x31: // '1' 1307 byteValue |= (byte) 0x01; 1308 break; 1309 case 0x32: // '2' 1310 byteValue |= (byte) 0x02; 1311 break; 1312 case 0x33: // '3' 1313 byteValue |= (byte) 0x03; 1314 break; 1315 case 0x34: // '4' 1316 byteValue |= (byte) 0x04; 1317 break; 1318 case 0x35: // '5' 1319 byteValue |= (byte) 0x05; 1320 break; 1321 case 0x36: // '6' 1322 byteValue |= (byte) 0x06; 1323 break; 1324 case 0x37: // '7' 1325 byteValue |= (byte) 0x07; 1326 break; 1327 case 0x38: // '8' 1328 byteValue |= (byte) 0x08; 1329 break; 1330 case 0x39: // '9' 1331 byteValue |= (byte) 0x09; 1332 break; 1333 case 0x41: // 'A' 1334 case 0x61: // 'a' 1335 byteValue |= (byte) 0x0A; 1336 break; 1337 case 0x42: // 'B' 1338 case 0x62: // 'b' 1339 byteValue |= (byte) 0x0B; 1340 break; 1341 case 0x43: // 'C' 1342 case 0x63: // 'c' 1343 byteValue |= (byte) 0x0C; 1344 break; 1345 case 0x44: // 'D' 1346 case 0x64: // 'd' 1347 byteValue |= (byte) 0x0D; 1348 break; 1349 case 0x45: // 'E' 1350 case 0x65: // 'e' 1351 byteValue |= (byte) 0x0E; 1352 break; 1353 case 0x46: // 'F' 1354 case 0x66: // 'f' 1355 byteValue |= (byte) 0x0F; 1356 break; 1357 default: 1358 LocalizableMessage message = 1359 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1360 get(filterString, equalPos+i+1); 1361 throw new DirectoryException( 1362 ResultCode.PROTOCOL_ERROR, message); 1363 } 1364 1365 buffer.append(byteValue); 1366 } 1367 else 1368 { 1369 buffer.append(valueBytes[i]); 1370 } 1371 } 1372 1373 subInitial = buffer.toByteString(); 1374 } 1375 else 1376 { 1377 subInitial = ByteString.wrap(valueBytes, 0, firstPos); 1378 } 1379 } 1380 1381 1382 // Next, process through the rest of the asterisks to get the subAny values. 1383 List<ByteString> subAny = new ArrayList<>(); 1384 for (int asteriskPos : asteriskPositions) 1385 { 1386 int length = asteriskPos - firstPos - 1; 1387 1388 if (hasEscape) 1389 { 1390 ByteStringBuilder buffer = new ByteStringBuilder(length); 1391 for (int i=firstPos+1; i < asteriskPos; i++) 1392 { 1393 if (valueBytes[i] == 0x5C) 1394 { 1395 // The next two bytes must be the hex characters that 1396 // comprise the binary value. 1397 if (i + 2 >= valueBytes.length) 1398 { 1399 LocalizableMessage message = 1400 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1401 get(filterString, equalPos+i+1); 1402 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1403 message); 1404 } 1405 1406 byte byteValue = 0; 1407 switch (valueBytes[++i]) 1408 { 1409 case 0x30: // '0' 1410 break; 1411 case 0x31: // '1' 1412 byteValue = (byte) 0x10; 1413 break; 1414 case 0x32: // '2' 1415 byteValue = (byte) 0x20; 1416 break; 1417 case 0x33: // '3' 1418 byteValue = (byte) 0x30; 1419 break; 1420 case 0x34: // '4' 1421 byteValue = (byte) 0x40; 1422 break; 1423 case 0x35: // '5' 1424 byteValue = (byte) 0x50; 1425 break; 1426 case 0x36: // '6' 1427 byteValue = (byte) 0x60; 1428 break; 1429 case 0x37: // '7' 1430 byteValue = (byte) 0x70; 1431 break; 1432 case 0x38: // '8' 1433 byteValue = (byte) 0x80; 1434 break; 1435 case 0x39: // '9' 1436 byteValue = (byte) 0x90; 1437 break; 1438 case 0x41: // 'A' 1439 case 0x61: // 'a' 1440 byteValue = (byte) 0xA0; 1441 break; 1442 case 0x42: // 'B' 1443 case 0x62: // 'b' 1444 byteValue = (byte) 0xB0; 1445 break; 1446 case 0x43: // 'C' 1447 case 0x63: // 'c' 1448 byteValue = (byte) 0xC0; 1449 break; 1450 case 0x44: // 'D' 1451 case 0x64: // 'd' 1452 byteValue = (byte) 0xD0; 1453 break; 1454 case 0x45: // 'E' 1455 case 0x65: // 'e' 1456 byteValue = (byte) 0xE0; 1457 break; 1458 case 0x46: // 'F' 1459 case 0x66: // 'f' 1460 byteValue = (byte) 0xF0; 1461 break; 1462 default: 1463 LocalizableMessage message = 1464 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1465 get(filterString, equalPos+i+1); 1466 throw new DirectoryException( 1467 ResultCode.PROTOCOL_ERROR, message); 1468 } 1469 1470 switch (valueBytes[++i]) 1471 { 1472 case 0x30: // '0' 1473 break; 1474 case 0x31: // '1' 1475 byteValue |= (byte) 0x01; 1476 break; 1477 case 0x32: // '2' 1478 byteValue |= (byte) 0x02; 1479 break; 1480 case 0x33: // '3' 1481 byteValue |= (byte) 0x03; 1482 break; 1483 case 0x34: // '4' 1484 byteValue |= (byte) 0x04; 1485 break; 1486 case 0x35: // '5' 1487 byteValue |= (byte) 0x05; 1488 break; 1489 case 0x36: // '6' 1490 byteValue |= (byte) 0x06; 1491 break; 1492 case 0x37: // '7' 1493 byteValue |= (byte) 0x07; 1494 break; 1495 case 0x38: // '8' 1496 byteValue |= (byte) 0x08; 1497 break; 1498 case 0x39: // '9' 1499 byteValue |= (byte) 0x09; 1500 break; 1501 case 0x41: // 'A' 1502 case 0x61: // 'a' 1503 byteValue |= (byte) 0x0A; 1504 break; 1505 case 0x42: // 'B' 1506 case 0x62: // 'b' 1507 byteValue |= (byte) 0x0B; 1508 break; 1509 case 0x43: // 'C' 1510 case 0x63: // 'c' 1511 byteValue |= (byte) 0x0C; 1512 break; 1513 case 0x44: // 'D' 1514 case 0x64: // 'd' 1515 byteValue |= (byte) 0x0D; 1516 break; 1517 case 0x45: // 'E' 1518 case 0x65: // 'e' 1519 byteValue |= (byte) 0x0E; 1520 break; 1521 case 0x46: // 'F' 1522 case 0x66: // 'f' 1523 byteValue |= (byte) 0x0F; 1524 break; 1525 default: 1526 LocalizableMessage message = 1527 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1528 get(filterString, equalPos+i+1); 1529 throw new DirectoryException( 1530 ResultCode.PROTOCOL_ERROR, message); 1531 } 1532 1533 buffer.append(byteValue); 1534 } 1535 else 1536 { 1537 buffer.append(valueBytes[i]); 1538 } 1539 } 1540 1541 subAny.add(buffer.toByteString()); 1542 buffer.clear(); 1543 } 1544 else 1545 { 1546 subAny.add(ByteString.wrap(valueBytes, firstPos+1, length)); 1547 } 1548 1549 1550 firstPos = asteriskPos; 1551 } 1552 1553 1554 // Finally, see if there is anything after the last asterisk, 1555 // which would be the subFinal value. 1556 ByteString subFinal; 1557 if (firstPos == (endPos-1)) 1558 { 1559 subFinal = null; 1560 } 1561 else 1562 { 1563 int length = endPos - firstPos - 1; 1564 1565 if (hasEscape) 1566 { 1567 ByteStringBuilder buffer = new ByteStringBuilder(length); 1568 for (int i=firstPos+1; i < endPos; i++) 1569 { 1570 if (valueBytes[i] == 0x5C) 1571 { 1572 // The next two bytes must be the hex characters that 1573 // comprise the binary value. 1574 if (i + 2 >= valueBytes.length) 1575 { 1576 LocalizableMessage message = 1577 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1578 get(filterString, equalPos+i+1); 1579 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1580 message); 1581 } 1582 1583 byte byteValue = 0; 1584 switch (valueBytes[++i]) 1585 { 1586 case 0x30: // '0' 1587 break; 1588 case 0x31: // '1' 1589 byteValue = (byte) 0x10; 1590 break; 1591 case 0x32: // '2' 1592 byteValue = (byte) 0x20; 1593 break; 1594 case 0x33: // '3' 1595 byteValue = (byte) 0x30; 1596 break; 1597 case 0x34: // '4' 1598 byteValue = (byte) 0x40; 1599 break; 1600 case 0x35: // '5' 1601 byteValue = (byte) 0x50; 1602 break; 1603 case 0x36: // '6' 1604 byteValue = (byte) 0x60; 1605 break; 1606 case 0x37: // '7' 1607 byteValue = (byte) 0x70; 1608 break; 1609 case 0x38: // '8' 1610 byteValue = (byte) 0x80; 1611 break; 1612 case 0x39: // '9' 1613 byteValue = (byte) 0x90; 1614 break; 1615 case 0x41: // 'A' 1616 case 0x61: // 'a' 1617 byteValue = (byte) 0xA0; 1618 break; 1619 case 0x42: // 'B' 1620 case 0x62: // 'b' 1621 byteValue = (byte) 0xB0; 1622 break; 1623 case 0x43: // 'C' 1624 case 0x63: // 'c' 1625 byteValue = (byte) 0xC0; 1626 break; 1627 case 0x44: // 'D' 1628 case 0x64: // 'd' 1629 byteValue = (byte) 0xD0; 1630 break; 1631 case 0x45: // 'E' 1632 case 0x65: // 'e' 1633 byteValue = (byte) 0xE0; 1634 break; 1635 case 0x46: // 'F' 1636 case 0x66: // 'f' 1637 byteValue = (byte) 0xF0; 1638 break; 1639 default: 1640 LocalizableMessage message = 1641 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1642 get(filterString, equalPos+i+1); 1643 throw new DirectoryException( 1644 ResultCode.PROTOCOL_ERROR, message); 1645 } 1646 1647 switch (valueBytes[++i]) 1648 { 1649 case 0x30: // '0' 1650 break; 1651 case 0x31: // '1' 1652 byteValue |= (byte) 0x01; 1653 break; 1654 case 0x32: // '2' 1655 byteValue |= (byte) 0x02; 1656 break; 1657 case 0x33: // '3' 1658 byteValue |= (byte) 0x03; 1659 break; 1660 case 0x34: // '4' 1661 byteValue |= (byte) 0x04; 1662 break; 1663 case 0x35: // '5' 1664 byteValue |= (byte) 0x05; 1665 break; 1666 case 0x36: // '6' 1667 byteValue |= (byte) 0x06; 1668 break; 1669 case 0x37: // '7' 1670 byteValue |= (byte) 0x07; 1671 break; 1672 case 0x38: // '8' 1673 byteValue |= (byte) 0x08; 1674 break; 1675 case 0x39: // '9' 1676 byteValue |= (byte) 0x09; 1677 break; 1678 case 0x41: // 'A' 1679 case 0x61: // 'a' 1680 byteValue |= (byte) 0x0A; 1681 break; 1682 case 0x42: // 'B' 1683 case 0x62: // 'b' 1684 byteValue |= (byte) 0x0B; 1685 break; 1686 case 0x43: // 'C' 1687 case 0x63: // 'c' 1688 byteValue |= (byte) 0x0C; 1689 break; 1690 case 0x44: // 'D' 1691 case 0x64: // 'd' 1692 byteValue |= (byte) 0x0D; 1693 break; 1694 case 0x45: // 'E' 1695 case 0x65: // 'e' 1696 byteValue |= (byte) 0x0E; 1697 break; 1698 case 0x46: // 'F' 1699 case 0x66: // 'f' 1700 byteValue |= (byte) 0x0F; 1701 break; 1702 default: 1703 LocalizableMessage message = 1704 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1705 get(filterString, equalPos+i+1); 1706 throw new DirectoryException( 1707 ResultCode.PROTOCOL_ERROR, message); 1708 } 1709 1710 buffer.append(byteValue); 1711 } 1712 else 1713 { 1714 buffer.append(valueBytes[i]); 1715 } 1716 } 1717 1718 subFinal = buffer.toByteString(); 1719 } 1720 else 1721 { 1722 subFinal = ByteString.wrap(valueBytes, firstPos+1, length); 1723 } 1724 } 1725 1726 1727 return new SearchFilter(FilterType.SUBSTRING, null, null, 1728 attrType, options, null, subInitial, 1729 subAny, subFinal, null, false); 1730 } 1731 1732 1733 1734 /** 1735 * Decodes an extensible match filter component based on the 1736 * provided information. 1737 * 1738 * @param filterString The filter string containing the 1739 * information to decode. 1740 * @param startPos The position in the filter string of the 1741 * first character in the extensible match 1742 * filter. 1743 * @param equalPos The position of the equal sign in the 1744 * extensible match filter. 1745 * @param endPos The position of the first character after 1746 * the end of the extensible match filter. 1747 * 1748 * @return The decoded search filter. 1749 * 1750 * @throws DirectoryException If a problem occurs while attempting 1751 * to decode the extensible match 1752 * filter. 1753 */ 1754 private static SearchFilter decodeExtensibleMatchFilter( 1755 String filterString, int startPos, 1756 int equalPos, int endPos) 1757 throws DirectoryException 1758 { 1759 AttributeType attributeType = null; 1760 Set<String> attributeOptions = new HashSet<>(); 1761 boolean dnAttributes = false; 1762 String matchingRuleID = null; 1763 1764 1765 // Look at the first character. If it is a colon, then it must be 1766 // followed by either the string "dn" or the matching rule ID. If 1767 // it is not, then it must be the attribute type. 1768 String lowerLeftStr = 1769 toLowerCase(filterString.substring(startPos, equalPos)); 1770 if (filterString.charAt(startPos) == ':') 1771 { 1772 // See if it starts with ":dn". Otherwise, it much be the 1773 // matching rule 1774 // ID. 1775 if (lowerLeftStr.startsWith(":dn:")) 1776 { 1777 dnAttributes = true; 1778 1779 matchingRuleID = 1780 filterString.substring(startPos+4, equalPos-1); 1781 } 1782 else 1783 { 1784 matchingRuleID = 1785 filterString.substring(startPos+1, equalPos-1); 1786 } 1787 } 1788 else 1789 { 1790 int colonPos = filterString.indexOf(':',startPos); 1791 if (colonPos < 0) 1792 { 1793 LocalizableMessage message = ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_COLON. 1794 get(filterString, startPos); 1795 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1796 message); 1797 } 1798 1799 1800 String attrType = filterString.substring(startPos, colonPos); 1801 StringBuilder lowerType = new StringBuilder(attrType.length()); 1802 1803 int semicolonPos = attrType.indexOf(';'); 1804 if (semicolonPos <0) 1805 { 1806 for (int i=0; i < attrType.length(); i++) 1807 { 1808 lowerType.append(Character.toLowerCase(attrType.charAt(i))); 1809 } 1810 } 1811 else 1812 { 1813 for (int i=0; i < semicolonPos; i++) 1814 { 1815 lowerType.append(Character.toLowerCase(attrType.charAt(i))); 1816 } 1817 1818 int nextPos = attrType.indexOf(';', semicolonPos+1); 1819 while (nextPos > 0) 1820 { 1821 attributeOptions.add(attrType.substring(semicolonPos+1, 1822 nextPos)); 1823 semicolonPos = nextPos; 1824 nextPos = attrType.indexOf(';', semicolonPos+1); 1825 } 1826 1827 attributeOptions.add(attrType.substring(semicolonPos+1)); 1828 } 1829 1830 1831 // Get the attribute type for the specified name. 1832 attributeType = getAttributeType(attrType, lowerType); 1833 1834 // If there is anything left, then it should be ":dn" and/or ":" 1835 // followed by the matching rule ID. 1836 if (colonPos < equalPos-1) 1837 { 1838 if (lowerLeftStr.startsWith(":dn:", colonPos)) 1839 { 1840 dnAttributes = true; 1841 1842 if (colonPos+4 < equalPos-1) 1843 { 1844 matchingRuleID = 1845 filterString.substring(colonPos+4, equalPos-1); 1846 } 1847 } 1848 else 1849 { 1850 matchingRuleID = 1851 filterString.substring(colonPos+1, equalPos-1); 1852 } 1853 } 1854 } 1855 1856 1857 // Parse out the attribute value. 1858 byte[] valueBytes = getBytes(filterString.substring(equalPos+1, 1859 endPos)); 1860 boolean hasEscape = false; 1861 for (byte valueByte : valueBytes) 1862 { 1863 if (valueByte == 0x5C) 1864 { 1865 hasEscape = true; 1866 break; 1867 } 1868 } 1869 1870 ByteString userValue; 1871 if (hasEscape) 1872 { 1873 ByteStringBuilder valueBuffer = 1874 new ByteStringBuilder(valueBytes.length); 1875 for (int i=0; i < valueBytes.length; i++) 1876 { 1877 if (valueBytes[i] == 0x5C) // The backslash character 1878 { 1879 // The next two bytes must be the hex characters that 1880 // comprise the binary value. 1881 if (i + 2 >= valueBytes.length) 1882 { 1883 LocalizableMessage message = ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1884 get(filterString, equalPos+i+1); 1885 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1886 message); 1887 } 1888 1889 byte byteValue = 0; 1890 switch (valueBytes[++i]) 1891 { 1892 case 0x30: // '0' 1893 break; 1894 case 0x31: // '1' 1895 byteValue = (byte) 0x10; 1896 break; 1897 case 0x32: // '2' 1898 byteValue = (byte) 0x20; 1899 break; 1900 case 0x33: // '3' 1901 byteValue = (byte) 0x30; 1902 break; 1903 case 0x34: // '4' 1904 byteValue = (byte) 0x40; 1905 break; 1906 case 0x35: // '5' 1907 byteValue = (byte) 0x50; 1908 break; 1909 case 0x36: // '6' 1910 byteValue = (byte) 0x60; 1911 break; 1912 case 0x37: // '7' 1913 byteValue = (byte) 0x70; 1914 break; 1915 case 0x38: // '8' 1916 byteValue = (byte) 0x80; 1917 break; 1918 case 0x39: // '9' 1919 byteValue = (byte) 0x90; 1920 break; 1921 case 0x41: // 'A' 1922 case 0x61: // 'a' 1923 byteValue = (byte) 0xA0; 1924 break; 1925 case 0x42: // 'B' 1926 case 0x62: // 'b' 1927 byteValue = (byte) 0xB0; 1928 break; 1929 case 0x43: // 'C' 1930 case 0x63: // 'c' 1931 byteValue = (byte) 0xC0; 1932 break; 1933 case 0x44: // 'D' 1934 case 0x64: // 'd' 1935 byteValue = (byte) 0xD0; 1936 break; 1937 case 0x45: // 'E' 1938 case 0x65: // 'e' 1939 byteValue = (byte) 0xE0; 1940 break; 1941 case 0x46: // 'F' 1942 case 0x66: // 'f' 1943 byteValue = (byte) 0xF0; 1944 break; 1945 default: 1946 LocalizableMessage message = 1947 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 1948 get(filterString, equalPos+i+1); 1949 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 1950 message); 1951 } 1952 1953 switch (valueBytes[++i]) 1954 { 1955 case 0x30: // '0' 1956 break; 1957 case 0x31: // '1' 1958 byteValue |= (byte) 0x01; 1959 break; 1960 case 0x32: // '2' 1961 byteValue |= (byte) 0x02; 1962 break; 1963 case 0x33: // '3' 1964 byteValue |= (byte) 0x03; 1965 break; 1966 case 0x34: // '4' 1967 byteValue |= (byte) 0x04; 1968 break; 1969 case 0x35: // '5' 1970 byteValue |= (byte) 0x05; 1971 break; 1972 case 0x36: // '6' 1973 byteValue |= (byte) 0x06; 1974 break; 1975 case 0x37: // '7' 1976 byteValue |= (byte) 0x07; 1977 break; 1978 case 0x38: // '8' 1979 byteValue |= (byte) 0x08; 1980 break; 1981 case 0x39: // '9' 1982 byteValue |= (byte) 0x09; 1983 break; 1984 case 0x41: // 'A' 1985 case 0x61: // 'a' 1986 byteValue |= (byte) 0x0A; 1987 break; 1988 case 0x42: // 'B' 1989 case 0x62: // 'b' 1990 byteValue |= (byte) 0x0B; 1991 break; 1992 case 0x43: // 'C' 1993 case 0x63: // 'c' 1994 byteValue |= (byte) 0x0C; 1995 break; 1996 case 0x44: // 'D' 1997 case 0x64: // 'd' 1998 byteValue |= (byte) 0x0D; 1999 break; 2000 case 0x45: // 'E' 2001 case 0x65: // 'e' 2002 byteValue |= (byte) 0x0E; 2003 break; 2004 case 0x46: // 'F' 2005 case 0x66: // 'f' 2006 byteValue |= (byte) 0x0F; 2007 break; 2008 default: 2009 LocalizableMessage message = 2010 ERR_SEARCH_FILTER_INVALID_ESCAPED_BYTE. 2011 get(filterString, equalPos+i+1); 2012 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 2013 message); 2014 } 2015 2016 valueBuffer.append(byteValue); 2017 } 2018 else 2019 { 2020 valueBuffer.append(valueBytes[i]); 2021 } 2022 } 2023 2024 userValue = valueBuffer.toByteString(); 2025 } 2026 else 2027 { 2028 userValue = ByteString.wrap(valueBytes); 2029 } 2030 2031 // Make sure that the filter contains at least one of an attribute 2032 // type or a matching rule ID. Also, construct the appropriate 2033 // attribute value. 2034 if (attributeType == null) 2035 { 2036 if (matchingRuleID == null) 2037 { 2038 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 2039 ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR.get(filterString, startPos)); 2040 } 2041 2042 MatchingRule mr = DirectoryServer.getMatchingRule(toLowerCase(matchingRuleID)); 2043 if (mr == null) 2044 { 2045 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 2046 ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_SUCH_MR.get(filterString, startPos, matchingRuleID)); 2047 } 2048 } 2049 2050 return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null, 2051 attributeType, attributeOptions, userValue, 2052 null, null, null, matchingRuleID, 2053 dnAttributes); 2054 } 2055 2056 private static AttributeType getAttributeType(String attrType, StringBuilder lowerType) 2057 { 2058 AttributeType attributeType = DirectoryServer.getAttributeType(lowerType.toString()); 2059 if (attributeType == null) 2060 { 2061 String typeStr = attrType.substring(0, lowerType.length()); 2062 attributeType = DirectoryServer.getDefaultAttributeType(typeStr); 2063 } 2064 return attributeType; 2065 } 2066 2067 /** 2068 * Retrieves the filter type for this search filter. 2069 * 2070 * @return The filter type for this search filter. 2071 */ 2072 public FilterType getFilterType() 2073 { 2074 return filterType; 2075 } 2076 2077 2078 2079 /** 2080 * Retrieves the set of filter components for this AND or OR filter. 2081 * The returned list can be modified by the caller. 2082 * 2083 * @return The set of filter components for this AND or OR filter. 2084 */ 2085 public Set<SearchFilter> getFilterComponents() 2086 { 2087 return filterComponents; 2088 } 2089 2090 2091 2092 /** 2093 * Retrieves the filter component for this NOT filter. 2094 * 2095 * @return The filter component for this NOT filter, or 2096 * <CODE>null</CODE> if this is not a NOT filter. 2097 */ 2098 public SearchFilter getNotComponent() 2099 { 2100 return notComponent; 2101 } 2102 2103 2104 2105 /** 2106 * Retrieves the attribute type for this filter. 2107 * 2108 * @return The attribute type for this filter, or <CODE>null</CODE> 2109 * if there is none. 2110 */ 2111 public AttributeType getAttributeType() 2112 { 2113 return attributeType; 2114 } 2115 2116 2117 2118 /** 2119 * Retrieves the assertion value for this filter. 2120 * 2121 * @return The assertion value for this filter, or 2122 * <CODE>null</CODE> if there is none. 2123 */ 2124 public ByteString getAssertionValue() 2125 { 2126 return assertionValue; 2127 } 2128 2129 /** 2130 * Retrieves the subInitial element for this substring filter. 2131 * 2132 * @return The subInitial element for this substring filter, or 2133 * <CODE>null</CODE> if there is none. 2134 */ 2135 public ByteString getSubInitialElement() 2136 { 2137 return subInitialElement; 2138 } 2139 2140 2141 2142 /** 2143 * Retrieves the set of subAny elements for this substring filter. 2144 * The returned list may be altered by the caller. 2145 * 2146 * @return The set of subAny elements for this substring filter. 2147 */ 2148 public List<ByteString> getSubAnyElements() 2149 { 2150 return subAnyElements; 2151 } 2152 2153 2154 2155 /** 2156 * Retrieves the subFinal element for this substring filter. 2157 * 2158 * @return The subFinal element for this substring filter. 2159 */ 2160 public ByteString getSubFinalElement() 2161 { 2162 return subFinalElement; 2163 } 2164 2165 2166 2167 /** 2168 * Retrieves the matching rule ID for this extensible matching 2169 * filter. 2170 * 2171 * @return The matching rule ID for this extensible matching 2172 * filter. 2173 */ 2174 public String getMatchingRuleID() 2175 { 2176 return matchingRuleID; 2177 } 2178 2179 2180 2181 /** 2182 * Retrieves the dnAttributes flag for this extensible matching 2183 * filter. 2184 * 2185 * @return The dnAttributes flag for this extensible matching 2186 * filter. 2187 */ 2188 public boolean getDNAttributes() 2189 { 2190 return dnAttributes; 2191 } 2192 2193 2194 2195 /** 2196 * Indicates whether this search filter matches the provided entry. 2197 * 2198 * @param entry The entry for which to make the determination. 2199 * 2200 * @return <CODE>true</CODE> if this search filter matches the 2201 * provided entry, or <CODE>false</CODE> if it does not. 2202 * 2203 * @throws DirectoryException If a problem is encountered during 2204 * processing. 2205 */ 2206 public boolean matchesEntry(Entry entry) 2207 throws DirectoryException 2208 { 2209 ConditionResult result = matchesEntryInternal(this, entry, 0); 2210 switch (result) 2211 { 2212 case TRUE: 2213 return true; 2214 case FALSE: 2215 case UNDEFINED: 2216 return false; 2217 default: 2218 logger.error(ERR_SEARCH_FILTER_INVALID_RESULT_TYPE, entry.getName(), this, result); 2219 return false; 2220 } 2221 } 2222 2223 2224 2225 /** 2226 * Indicates whether the this filter matches the provided entry. 2227 * 2228 * @param completeFilter The complete filter being checked, of 2229 * which this filter may be a subset. 2230 * @param entry The entry for which to make the 2231 * determination. 2232 * @param depth The current depth of the evaluation, 2233 * which is used to prevent infinite 2234 * recursion due to highly nested filters 2235 * and eventually running out of stack 2236 * space. 2237 * 2238 * @return <CODE>TRUE</CODE> if this filter matches the provided 2239 * entry, <CODE>FALSE</CODE> if it does not, or 2240 * <CODE>UNDEFINED</CODE> if the result is undefined. 2241 * 2242 * @throws DirectoryException If a problem is encountered during 2243 * processing. 2244 */ 2245 private ConditionResult matchesEntryInternal( 2246 SearchFilter completeFilter, 2247 Entry entry, int depth) 2248 throws DirectoryException 2249 { 2250 switch (filterType) 2251 { 2252 case AND: 2253 return processAND(completeFilter, entry, depth); 2254 2255 case OR: 2256 return processOR(completeFilter, entry, depth); 2257 2258 case NOT: 2259 return processNOT(completeFilter, entry, depth); 2260 2261 case EQUALITY: 2262 return processEquality(completeFilter, entry); 2263 2264 case SUBSTRING: 2265 return processSubstring(completeFilter, entry); 2266 2267 case GREATER_OR_EQUAL: 2268 return processGreaterOrEqual(completeFilter, entry); 2269 2270 case LESS_OR_EQUAL: 2271 return processLessOrEqual(completeFilter, entry); 2272 2273 case PRESENT: 2274 return processPresent(completeFilter, entry); 2275 2276 case APPROXIMATE_MATCH: 2277 return processApproximate(completeFilter, entry); 2278 2279 case EXTENSIBLE_MATCH: 2280 return processExtensibleMatch(completeFilter, entry); 2281 2282 2283 default: 2284 // This is an invalid filter type. 2285 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 2286 ERR_SEARCH_FILTER_INVALID_FILTER_TYPE.get(entry.getName(), this, filterType)); 2287 } 2288 } 2289 2290 2291 2292 /** 2293 * Indicates whether the this AND filter matches the provided entry. 2294 * 2295 * @param completeFilter The complete filter being checked, of 2296 * which this filter may be a subset. 2297 * @param entry The entry for which to make the 2298 * determination. 2299 * @param depth The current depth of the evaluation, 2300 * which is used to prevent infinite 2301 * recursion due to highly nested filters 2302 * and eventually running out of stack 2303 * space. 2304 * 2305 * @return <CODE>TRUE</CODE> if this filter matches the provided 2306 * entry, <CODE>FALSE</CODE> if it does not, or 2307 * <CODE>UNDEFINED</CODE> if the result is undefined. 2308 * 2309 * @throws DirectoryException If a problem is encountered during 2310 * processing. 2311 */ 2312 private ConditionResult processAND(SearchFilter completeFilter, 2313 Entry entry, int depth) 2314 throws DirectoryException 2315 { 2316 if (filterComponents == null) 2317 { 2318 // The set of subcomponents was null. This is not allowed. 2319 LocalizableMessage message = 2320 ERR_SEARCH_FILTER_COMPOUND_COMPONENTS_NULL. 2321 get(entry.getName(), completeFilter, filterType); 2322 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); 2323 } 2324 else if (filterComponents.isEmpty()) 2325 { 2326 // An AND filter with no elements like "(&)" is specified as 2327 // "undefined" in RFC 2251, but is considered one of the 2328 // TRUE/FALSE filters in RFC 4526, in which case we should 2329 // always return true. 2330 if (logger.isTraceEnabled()) 2331 { 2332 logger.trace("Returning TRUE for LDAP TRUE " + 2333 "filter (&)"); 2334 } 2335 return ConditionResult.TRUE; 2336 } 2337 else 2338 { 2339 // We will have to evaluate one or more subcomponents. In 2340 // this case, first check our depth to make sure we're not 2341 // nesting too deep. 2342 if (depth >= MAX_NESTED_FILTER_DEPTH) 2343 { 2344 LocalizableMessage message = ERR_SEARCH_FILTER_NESTED_TOO_DEEP. 2345 get(entry.getName(), completeFilter); 2346 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); 2347 } 2348 2349 for (SearchFilter f : filterComponents) 2350 { 2351 ConditionResult result = 2352 f.matchesEntryInternal(completeFilter, entry, depth + 1); 2353 switch (result) 2354 { 2355 case TRUE: 2356 break; 2357 case FALSE: 2358 if (logger.isTraceEnabled()) 2359 { 2360 logger.trace( 2361 "Returning FALSE for AND component %s in " + 2362 "filter %s for entry %s", 2363 f, completeFilter, entry.getName()); 2364 } 2365 return result; 2366 case UNDEFINED: 2367 if (logger.isTraceEnabled()) 2368 { 2369 logger.trace( 2370 "Undefined result for AND component %s in filter " + 2371 "%s for entry %s", f, completeFilter, entry.getName()); 2372 } 2373 return result; 2374 default: 2375 LocalizableMessage message = 2376 ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 2377 get(entry.getName(), completeFilter, result); 2378 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), message); 2379 } 2380 } 2381 2382 // If we have gotten here, then all the components must have 2383 // matched. 2384 if (logger.isTraceEnabled()) 2385 { 2386 logger.trace( 2387 "Returning TRUE for AND component %s in filter %s " + 2388 "for entry %s", this, completeFilter, entry.getName()); 2389 } 2390 return ConditionResult.TRUE; 2391 } 2392 } 2393 2394 2395 2396 /** 2397 * Indicates whether the this OR filter matches the provided entry. 2398 * 2399 * @param completeFilter The complete filter being checked, of 2400 * which this filter may be a subset. 2401 * @param entry The entry for which to make the 2402 * determination. 2403 * @param depth The current depth of the evaluation, 2404 * which is used to prevent infinite 2405 * recursion due to highly nested filters 2406 * and eventually running out of stack 2407 * space. 2408 * 2409 * @return <CODE>TRUE</CODE> if this filter matches the provided 2410 * entry, <CODE>FALSE</CODE> if it does not, or 2411 * <CODE>UNDEFINED</CODE> if the result is undefined. 2412 * 2413 * @throws DirectoryException If a problem is encountered during 2414 * processing. 2415 */ 2416 private ConditionResult processOR(SearchFilter completeFilter, 2417 Entry entry, int depth) 2418 throws DirectoryException 2419 { 2420 if (filterComponents == null) 2421 { 2422 // The set of subcomponents was null. This is not allowed. 2423 LocalizableMessage message = 2424 ERR_SEARCH_FILTER_COMPOUND_COMPONENTS_NULL. 2425 get(entry.getName(), completeFilter, filterType); 2426 throw new DirectoryException( 2427 DirectoryServer.getServerErrorResultCode(), 2428 message); 2429 } 2430 else if (filterComponents.isEmpty()) 2431 { 2432 // An OR filter with no elements like "(|)" is specified as 2433 // "undefined" in RFC 2251, but is considered one of the 2434 // TRUE/FALSE filters in RFC 4526, in which case we should 2435 // always return false. 2436 if (logger.isTraceEnabled()) 2437 { 2438 logger.trace("Returning FALSE for LDAP FALSE " + 2439 "filter (|)"); 2440 } 2441 return ConditionResult.FALSE; 2442 } 2443 else 2444 { 2445 // We will have to evaluate one or more subcomponents. In 2446 // this case, first check our depth to make sure we're not 2447 // nesting too deep. 2448 if (depth >= MAX_NESTED_FILTER_DEPTH) 2449 { 2450 LocalizableMessage message = ERR_SEARCH_FILTER_NESTED_TOO_DEEP. 2451 get(entry.getName(), completeFilter); 2452 throw new DirectoryException( 2453 DirectoryServer.getServerErrorResultCode(), 2454 message); 2455 } 2456 2457 ConditionResult result = ConditionResult.FALSE; 2458 for (SearchFilter f : filterComponents) 2459 { 2460 switch (f.matchesEntryInternal(completeFilter, entry, 2461 depth+1)) 2462 { 2463 case TRUE: 2464 if (logger.isTraceEnabled()) 2465 { 2466 logger.trace( 2467 "Returning TRUE for OR component %s in filter " + 2468 "%s for entry %s", 2469 f, completeFilter, entry.getName()); 2470 } 2471 return ConditionResult.TRUE; 2472 case FALSE: 2473 break; 2474 case UNDEFINED: 2475 if (logger.isTraceEnabled()) 2476 { 2477 logger.trace( 2478 "Undefined result for OR component %s in filter " + 2479 "%s for entry %s", 2480 f, completeFilter, entry.getName()); 2481 } 2482 result = ConditionResult.UNDEFINED; 2483 break; 2484 default: 2485 LocalizableMessage message = 2486 ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 2487 get(entry.getName(), completeFilter, result); 2488 throw new 2489 DirectoryException( 2490 DirectoryServer.getServerErrorResultCode(), 2491 message); 2492 } 2493 } 2494 2495 2496 if (logger.isTraceEnabled()) 2497 { 2498 logger.trace( 2499 "Returning %s for OR component %s in filter %s for " + 2500 "entry %s", result, this, completeFilter, 2501 entry.getName()); 2502 } 2503 return result; 2504 } 2505 } 2506 2507 2508 2509 /** 2510 * Indicates whether the this NOT filter matches the provided entry. 2511 * 2512 * @param completeFilter The complete filter being checked, of 2513 * which this filter may be a subset. 2514 * @param entry The entry for which to make the 2515 * determination. 2516 * @param depth The current depth of the evaluation, 2517 * which is used to prevent infinite 2518 * recursion due to highly nested filters 2519 * and eventually running out of stack 2520 * space. 2521 * 2522 * @return <CODE>TRUE</CODE> if this filter matches the provided 2523 * entry, <CODE>FALSE</CODE> if it does not, or 2524 * <CODE>UNDEFINED</CODE> if the result is undefined. 2525 * 2526 * @throws DirectoryException If a problem is encountered during 2527 * processing. 2528 */ 2529 private ConditionResult processNOT(SearchFilter completeFilter, 2530 Entry entry, int depth) 2531 throws DirectoryException 2532 { 2533 if (notComponent == null) 2534 { 2535 // The NOT subcomponent was null. This is not allowed. 2536 LocalizableMessage message = ERR_SEARCH_FILTER_NOT_COMPONENT_NULL. 2537 get(entry.getName(), completeFilter); 2538 throw new DirectoryException( 2539 DirectoryServer.getServerErrorResultCode(), 2540 message); 2541 } 2542 else 2543 { 2544 // The subcomponent for the NOT filter can be an AND, OR, or 2545 // NOT filter that would require more nesting. Make sure 2546 // that we don't go too deep. 2547 if (depth >= MAX_NESTED_FILTER_DEPTH) 2548 { 2549 LocalizableMessage message = ERR_SEARCH_FILTER_NESTED_TOO_DEEP. 2550 get(entry.getName(), completeFilter); 2551 throw new DirectoryException( 2552 DirectoryServer.getServerErrorResultCode(), 2553 message); 2554 } 2555 2556 ConditionResult result = 2557 notComponent.matchesEntryInternal(completeFilter, 2558 entry, depth+1); 2559 switch (result) 2560 { 2561 case TRUE: 2562 if (logger.isTraceEnabled()) 2563 { 2564 logger.trace( 2565 "Returning FALSE for NOT component %s in filter " + 2566 "%s for entry %s", 2567 notComponent, completeFilter, entry.getName()); 2568 } 2569 return ConditionResult.FALSE; 2570 case FALSE: 2571 if (logger.isTraceEnabled()) 2572 { 2573 logger.trace( 2574 "Returning TRUE for NOT component %s in filter " + 2575 "%s for entry %s", 2576 notComponent, completeFilter, entry.getName()); 2577 } 2578 return ConditionResult.TRUE; 2579 case UNDEFINED: 2580 if (logger.isTraceEnabled()) 2581 { 2582 logger.trace( 2583 "Undefined result for NOT component %s in filter " + 2584 "%s for entry %s", 2585 notComponent, completeFilter, entry.getName()); 2586 } 2587 return ConditionResult.UNDEFINED; 2588 default: 2589 LocalizableMessage message = ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 2590 get(entry.getName(), completeFilter, result); 2591 throw new 2592 DirectoryException( 2593 DirectoryServer.getServerErrorResultCode(), 2594 message); 2595 } 2596 } 2597 } 2598 2599 2600 2601 /** 2602 * Indicates whether the this equality filter matches the provided 2603 * entry. 2604 * 2605 * @param completeFilter The complete filter being checked, of 2606 * which this filter may be a subset. 2607 * @param entry The entry for which to make the 2608 * determination. 2609 * 2610 * @return <CODE>TRUE</CODE> if this filter matches the provided 2611 * entry, <CODE>FALSE</CODE> if it does not, or 2612 * <CODE>UNDEFINED</CODE> if the result is undefined. 2613 * 2614 * @throws DirectoryException If a problem is encountered during 2615 * processing. 2616 */ 2617 private ConditionResult processEquality(SearchFilter completeFilter, 2618 Entry entry) 2619 throws DirectoryException 2620 { 2621 // Make sure that an attribute type has been defined. 2622 if (attributeType == null) 2623 { 2624 LocalizableMessage message = 2625 ERR_SEARCH_FILTER_EQUALITY_NO_ATTRIBUTE_TYPE. 2626 get(entry.getName(), toString()); 2627 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2628 } 2629 2630 // Make sure that an assertion value has been defined. 2631 if (assertionValue == null) 2632 { 2633 LocalizableMessage message = 2634 ERR_SEARCH_FILTER_EQUALITY_NO_ASSERTION_VALUE. 2635 get(entry.getName(), toString(), attributeType.getNameOrOID()); 2636 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2637 } 2638 2639 // See if the entry has an attribute with the requested type. 2640 List<Attribute> attrs = entry.getAttribute(attributeType, 2641 attributeOptions); 2642 if (attrs == null || attrs.isEmpty()) 2643 { 2644 if (logger.isTraceEnabled()) 2645 { 2646 logger.trace( 2647 "Returning FALSE for equality component %s in " + 2648 "filter %s because entry %s didn't have attribute " + 2649 "type %s", 2650 this, completeFilter, entry.getName(), 2651 attributeType.getNameOrOID()); 2652 } 2653 return ConditionResult.FALSE; 2654 } 2655 2656 // Get the equality matching rule for the given attribute type 2657 MatchingRule matchingRule = attributeType.getEqualityMatchingRule(); 2658 if (matchingRule == null) 2659 { 2660 if (logger.isTraceEnabled()) 2661 { 2662 logger.trace( 2663 "Attribute type %s does not have an equality matching " + 2664 "rule -- returning undefined.", 2665 attributeType.getNameOrOID()); 2666 } 2667 return ConditionResult.UNDEFINED; 2668 } 2669 2670 // Iterate through all the attributes and see if we can find a match. 2671 ConditionResult result = ConditionResult.FALSE; 2672 for (Attribute a : attrs) 2673 { 2674 final ConditionResult cr = a.matchesEqualityAssertion(assertionValue); 2675 if (cr == ConditionResult.TRUE) 2676 { 2677 if (logger.isTraceEnabled()) 2678 { 2679 logger.trace( 2680 "Returning TRUE for equality component %s in filter %s " + 2681 "for entry %s", this, completeFilter, entry.getName()); 2682 } 2683 return ConditionResult.TRUE; 2684 } 2685 else if (cr == ConditionResult.UNDEFINED) 2686 { 2687 result = ConditionResult.UNDEFINED; 2688 } 2689 } 2690 2691 if (logger.isTraceEnabled()) 2692 { 2693 logger.trace( 2694 "Returning %s for equality component %s in filter %s " + 2695 "because entry %s didn't have attribute type %s with value %s", 2696 result, this, completeFilter, entry.getName(), attributeType.getNameOrOID(), assertionValue); 2697 } 2698 return result; 2699 } 2700 2701 2702 2703 /** 2704 * Indicates whether the this substring filter matches the provided 2705 * entry. 2706 * 2707 * @param completeFilter The complete filter being checked, of 2708 * which this filter may be a subset. 2709 * @param entry The entry for which to make the 2710 * determination. 2711 * 2712 * @return <CODE>TRUE</CODE> if this filter matches the provided 2713 * entry, <CODE>FALSE</CODE> if it does not, or 2714 * <CODE>UNDEFINED</CODE> if the result is undefined. 2715 * 2716 * @throws DirectoryException If a problem is encountered during 2717 * processing. 2718 */ 2719 private ConditionResult processSubstring( 2720 SearchFilter completeFilter, 2721 Entry entry) 2722 throws DirectoryException 2723 { 2724 // Make sure that an attribute type has been defined. 2725 if (attributeType == null) 2726 { 2727 LocalizableMessage message = 2728 ERR_SEARCH_FILTER_SUBSTRING_NO_ATTRIBUTE_TYPE. 2729 get(entry.getName(), toString()); 2730 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2731 } 2732 2733 // Make sure that at least one substring element has been defined. 2734 if (subInitialElement == null && 2735 subFinalElement == null && 2736 (subAnyElements == null || subAnyElements.isEmpty())) 2737 { 2738 LocalizableMessage message = 2739 ERR_SEARCH_FILTER_SUBSTRING_NO_SUBSTRING_COMPONENTS. 2740 get(entry.getName(), toString(), attributeType.getNameOrOID()); 2741 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2742 } 2743 2744 // See if the entry has an attribute with the requested type. 2745 List<Attribute> attrs = entry.getAttribute(attributeType, attributeOptions); 2746 if (attrs == null || attrs.isEmpty()) 2747 { 2748 if (logger.isTraceEnabled()) 2749 { 2750 logger.trace( 2751 "Returning FALSE for substring component %s in " + 2752 "filter %s because entry %s didn't have attribute " + 2753 "type %s", 2754 this, completeFilter, entry.getName(), 2755 attributeType.getNameOrOID()); 2756 } 2757 return ConditionResult.FALSE; 2758 } 2759 2760 // Iterate through all the attributes and see if we can find a 2761 // match. 2762 ConditionResult result = ConditionResult.FALSE; 2763 for (Attribute a : attrs) 2764 { 2765 switch (a.matchesSubstring(subInitialElement, 2766 subAnyElements, 2767 subFinalElement)) 2768 { 2769 case TRUE: 2770 if (logger.isTraceEnabled()) 2771 { 2772 logger.trace( 2773 "Returning TRUE for substring component %s in " + 2774 "filter %s for entry %s", 2775 this, completeFilter, entry.getName()); 2776 } 2777 return ConditionResult.TRUE; 2778 case FALSE: 2779 break; 2780 case UNDEFINED: 2781 if (logger.isTraceEnabled()) 2782 { 2783 logger.trace( 2784 "Undefined result encountered for substring " + 2785 "component %s in filter %s for entry %s", 2786 this, completeFilter, entry.getName()); 2787 } 2788 result = ConditionResult.UNDEFINED; 2789 break; 2790 default: 2791 } 2792 } 2793 2794 if (logger.isTraceEnabled()) 2795 { 2796 logger.trace( 2797 "Returning %s for substring component %s in filter " + 2798 "%s for entry %s", 2799 result, this, completeFilter, entry.getName()); 2800 } 2801 return result; 2802 } 2803 2804 2805 2806 /** 2807 * Indicates whether the this greater-or-equal filter matches the 2808 * provided entry. 2809 * 2810 * @param completeFilter The complete filter being checked, of 2811 * which this filter may be a subset. 2812 * @param entry The entry for which to make the 2813 * determination. 2814 * 2815 * @return <CODE>TRUE</CODE> if this filter matches the provided 2816 * entry, <CODE>FALSE</CODE> if it does not, or 2817 * <CODE>UNDEFINED</CODE> if the result is undefined. 2818 * 2819 * @throws DirectoryException If a problem is encountered during 2820 * processing. 2821 */ 2822 private ConditionResult processGreaterOrEqual( 2823 SearchFilter completeFilter, 2824 Entry entry) 2825 throws DirectoryException 2826 { 2827 // Make sure that an attribute type has been defined. 2828 if (attributeType == null) 2829 { 2830 LocalizableMessage message = 2831 ERR_SEARCH_FILTER_GREATER_OR_EQUAL_NO_ATTRIBUTE_TYPE. 2832 get(entry.getName(), toString()); 2833 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2834 } 2835 2836 // Make sure that an assertion value has been defined. 2837 if (assertionValue == null) 2838 { 2839 LocalizableMessage message = 2840 ERR_SEARCH_FILTER_GREATER_OR_EQUAL_NO_VALUE. 2841 get(entry.getName(), toString(), attributeType.getNameOrOID()); 2842 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2843 } 2844 2845 // See if the entry has an attribute with the requested type. 2846 List<Attribute> attrs = entry.getAttribute(attributeType, attributeOptions); 2847 if (attrs == null || attrs.isEmpty()) 2848 { 2849 if (logger.isTraceEnabled()) 2850 { 2851 logger.trace("Returning FALSE for " + 2852 "greater-or-equal component %s in filter %s " + 2853 "because entry %s didn't have attribute type %s", 2854 this, completeFilter, entry.getName(), 2855 attributeType.getNameOrOID()); 2856 } 2857 return ConditionResult.FALSE; 2858 } 2859 2860 // Iterate through all the attributes and see if we can find a 2861 // match. 2862 ConditionResult result = ConditionResult.FALSE; 2863 for (Attribute a : attrs) 2864 { 2865 switch (a.greaterThanOrEqualTo(assertionValue)) 2866 { 2867 case TRUE: 2868 if (logger.isTraceEnabled()) 2869 { 2870 logger.trace( 2871 "Returning TRUE for greater-or-equal component " + 2872 "%s in filter %s for entry %s", 2873 this, completeFilter, entry.getName()); 2874 } 2875 return ConditionResult.TRUE; 2876 case FALSE: 2877 break; 2878 case UNDEFINED: 2879 if (logger.isTraceEnabled()) 2880 { 2881 logger.trace( 2882 "Undefined result encountered for " + 2883 "greater-or-equal component %s in filter %s " + 2884 "for entry %s", this, completeFilter, 2885 entry.getName()); 2886 } 2887 result = ConditionResult.UNDEFINED; 2888 break; 2889 default: 2890 } 2891 } 2892 2893 if (logger.isTraceEnabled()) 2894 { 2895 logger.trace( 2896 "Returning %s for greater-or-equal component %s in " + 2897 "filter %s for entry %s", 2898 result, this, completeFilter, entry.getName()); 2899 } 2900 return result; 2901 } 2902 2903 2904 2905 /** 2906 * Indicates whether the this less-or-equal filter matches the 2907 * provided entry. 2908 * 2909 * @param completeFilter The complete filter being checked, of 2910 * which this filter may be a subset. 2911 * @param entry The entry for which to make the 2912 * determination. 2913 * 2914 * @return <CODE>TRUE</CODE> if this filter matches the provided 2915 * entry, <CODE>FALSE</CODE> if it does not, or 2916 * <CODE>UNDEFINED</CODE> if the result is undefined. 2917 * 2918 * @throws DirectoryException If a problem is encountered during 2919 * processing. 2920 */ 2921 private ConditionResult processLessOrEqual( 2922 SearchFilter completeFilter, 2923 Entry entry) 2924 throws DirectoryException 2925 { 2926 // Make sure that an attribute type has been defined. 2927 if (attributeType == null) 2928 { 2929 LocalizableMessage message = 2930 ERR_SEARCH_FILTER_LESS_OR_EQUAL_NO_ATTRIBUTE_TYPE. 2931 get(entry.getName(), toString()); 2932 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2933 } 2934 2935 // Make sure that an assertion value has been defined. 2936 if (assertionValue == null) 2937 { 2938 LocalizableMessage message = 2939 ERR_SEARCH_FILTER_LESS_OR_EQUAL_NO_ASSERTION_VALUE. 2940 get(entry.getName(), toString(), attributeType.getNameOrOID()); 2941 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 2942 } 2943 2944 // See if the entry has an attribute with the requested type. 2945 List<Attribute> attrs = 2946 entry.getAttribute(attributeType, attributeOptions); 2947 if (attrs == null || attrs.isEmpty()) 2948 { 2949 if (logger.isTraceEnabled()) 2950 { 2951 logger.trace( 2952 "Returning FALSE for less-or-equal component %s in " + 2953 "filter %s because entry %s didn't have attribute " + 2954 "type %s", this, completeFilter, entry.getName(), 2955 attributeType.getNameOrOID()); 2956 } 2957 return ConditionResult.FALSE; 2958 } 2959 2960 // Iterate through all the attributes and see if we can find a 2961 // match. 2962 ConditionResult result = ConditionResult.FALSE; 2963 for (Attribute a : attrs) 2964 { 2965 switch (a.lessThanOrEqualTo(assertionValue)) 2966 { 2967 case TRUE: 2968 if (logger.isTraceEnabled()) 2969 { 2970 logger.trace( 2971 "Returning TRUE for less-or-equal component %s " + 2972 "in filter %s for entry %s", 2973 this, completeFilter, entry.getName()); 2974 } 2975 return ConditionResult.TRUE; 2976 case FALSE: 2977 break; 2978 case UNDEFINED: 2979 if (logger.isTraceEnabled()) 2980 { 2981 logger.trace( 2982 "Undefined result encountered for " + 2983 "less-or-equal component %s in filter %s " + 2984 "for entry %s", 2985 this, completeFilter, entry.getName()); 2986 } 2987 result = ConditionResult.UNDEFINED; 2988 break; 2989 default: 2990 } 2991 } 2992 2993 if (logger.isTraceEnabled()) 2994 { 2995 logger.trace( 2996 "Returning %s for less-or-equal component %s in " + 2997 "filter %s for entry %s", 2998 result, this, completeFilter, entry.getName()); 2999 } 3000 return result; 3001 } 3002 3003 3004 3005 /** 3006 * Indicates whether the this present filter matches the provided 3007 * entry. 3008 * 3009 * @param completeFilter The complete filter being checked, of 3010 * which this filter may be a subset. 3011 * @param entry The entry for which to make the 3012 * determination. 3013 * 3014 * @return <CODE>TRUE</CODE> if this filter matches the provided 3015 * entry, <CODE>FALSE</CODE> if it does not, or 3016 * <CODE>UNDEFINED</CODE> if the result is undefined. 3017 * 3018 * @throws DirectoryException If a problem is encountered during 3019 * processing. 3020 */ 3021 private ConditionResult processPresent(SearchFilter completeFilter, 3022 Entry entry) 3023 throws DirectoryException 3024 { 3025 // Make sure that an attribute type has been defined. 3026 if (attributeType == null) 3027 { 3028 LocalizableMessage message = 3029 ERR_SEARCH_FILTER_PRESENCE_NO_ATTRIBUTE_TYPE. 3030 get(entry.getName(), toString()); 3031 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 3032 } 3033 3034 3035 // See if the entry has an attribute with the requested type. 3036 // If so, then it's a match. If not, then it's not a match. 3037 ConditionResult result = ConditionResult.valueOf( 3038 entry.hasAttribute(attributeType, attributeOptions)); 3039 if (logger.isTraceEnabled()) 3040 { 3041 logger.trace( 3042 "Returning %s for presence component %s in filter %s for entry %s", 3043 result, this, completeFilter, entry.getName()); 3044 } 3045 return result; 3046 } 3047 3048 3049 3050 /** 3051 * Indicates whether the this approximate filter matches the 3052 * provided entry. 3053 * 3054 * @param completeFilter The complete filter being checked, of 3055 * which this filter may be a subset. 3056 * @param entry The entry for which to make the 3057 * determination. 3058 * 3059 * @return <CODE>TRUE</CODE> if this filter matches the provided 3060 * entry, <CODE>FALSE</CODE> if it does not, or 3061 * <CODE>UNDEFINED</CODE> if the result is undefined. 3062 * 3063 * @throws DirectoryException If a problem is encountered during 3064 * processing. 3065 */ 3066 private ConditionResult processApproximate( 3067 SearchFilter completeFilter, 3068 Entry entry) 3069 throws DirectoryException 3070 { 3071 // Make sure that an attribute type has been defined. 3072 if (attributeType == null) 3073 { 3074 LocalizableMessage message = 3075 ERR_SEARCH_FILTER_APPROXIMATE_NO_ATTRIBUTE_TYPE. 3076 get(entry.getName(), toString()); 3077 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 3078 } 3079 3080 // Make sure that an assertion value has been defined. 3081 if (assertionValue == null) 3082 { 3083 LocalizableMessage message = 3084 ERR_SEARCH_FILTER_APPROXIMATE_NO_ASSERTION_VALUE. 3085 get(entry.getName(), toString(), attributeType.getNameOrOID()); 3086 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message); 3087 } 3088 3089 // See if the entry has an attribute with the requested type. 3090 List<Attribute> attrs = 3091 entry.getAttribute(attributeType, attributeOptions); 3092 if (attrs == null || attrs.isEmpty()) 3093 { 3094 if (logger.isTraceEnabled()) 3095 { 3096 logger.trace( 3097 "Returning FALSE for approximate component %s in " + 3098 "filter %s because entry %s didn't have attribute " + 3099 "type %s", this, completeFilter, entry.getName(), 3100 attributeType.getNameOrOID()); 3101 } 3102 return ConditionResult.FALSE; 3103 } 3104 3105 // Iterate through all the attributes and see if we can find a 3106 // match. 3107 ConditionResult result = ConditionResult.FALSE; 3108 for (Attribute a : attrs) 3109 { 3110 switch (a.approximatelyEqualTo(assertionValue)) 3111 { 3112 case TRUE: 3113 if (logger.isTraceEnabled()) 3114 { 3115 logger.trace( 3116 "Returning TRUE for approximate component %s in " + 3117 "filter %s for entry %s", 3118 this, completeFilter, entry.getName()); 3119 } 3120 return ConditionResult.TRUE; 3121 case FALSE: 3122 break; 3123 case UNDEFINED: 3124 if (logger.isTraceEnabled()) 3125 { 3126 logger.trace( 3127 "Undefined result encountered for approximate " + 3128 "component %s in filter %s for entry %s", 3129 this, completeFilter, entry.getName()); 3130 } 3131 result = ConditionResult.UNDEFINED; 3132 break; 3133 default: 3134 } 3135 } 3136 3137 if (logger.isTraceEnabled()) 3138 { 3139 logger.trace( 3140 "Returning %s for approximate component %s in filter " + 3141 "%s for entry %s", 3142 result, this, completeFilter, entry.getName()); 3143 } 3144 return result; 3145 } 3146 3147 3148 3149 /** 3150 * Indicates whether this extensibleMatch filter matches the 3151 * provided entry. 3152 * 3153 * @param completeFilter The complete filter in which this 3154 * extensibleMatch filter may be a 3155 * subcomponent. 3156 * @param entry The entry for which to make the 3157 * determination. 3158 * 3159 * @return <CODE>TRUE</CODE> if this extensibleMatch filter matches 3160 * the provided entry, <CODE>FALSE</CODE> if it does not, or 3161 * <CODE>UNDEFINED</CODE> if the result cannot be 3162 * determined. 3163 * 3164 * @throws DirectoryException If a problem occurs while evaluating 3165 * this filter against the provided 3166 * entry. 3167 */ 3168 private ConditionResult processExtensibleMatch( 3169 SearchFilter completeFilter, 3170 Entry entry) 3171 throws DirectoryException 3172 { 3173 // We must have an assertion value for which to make the 3174 // determination. 3175 if (assertionValue == null) 3176 { 3177 LocalizableMessage message = 3178 ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_ASSERTION_VALUE. 3179 get(entry.getName(), completeFilter); 3180 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 3181 message); 3182 } 3183 3184 3185 MatchingRule matchingRule = null; 3186 3187 if (matchingRuleID != null) 3188 { 3189 matchingRule = 3190 DirectoryServer.getMatchingRule( 3191 toLowerCase(matchingRuleID)); 3192 if (matchingRule == null) 3193 { 3194 if (logger.isTraceEnabled()) 3195 { 3196 logger.trace( 3197 "Unknown matching rule %s defined in extensibleMatch " + 3198 "component of filter %s -- returning undefined.", 3199 matchingRuleID, this); 3200 } 3201 return ConditionResult.UNDEFINED; 3202 } 3203 } 3204 else 3205 { 3206 if (attributeType == null) 3207 { 3208 LocalizableMessage message = 3209 ERR_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_RULE_OR_TYPE. 3210 get(entry.getName(), completeFilter); 3211 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 3212 message); 3213 } 3214 else 3215 { 3216 matchingRule = attributeType.getEqualityMatchingRule(); 3217 if (matchingRule == null) 3218 { 3219 if (logger.isTraceEnabled()) 3220 { 3221 logger.trace( 3222 "Attribute type %s does not have an equality matching " + 3223 "rule -- returning undefined.", 3224 attributeType.getNameOrOID()); 3225 } 3226 return ConditionResult.UNDEFINED; 3227 } 3228 } 3229 } 3230 3231 3232 // If there is an attribute type, then check to see if there is a 3233 // corresponding matching rule use for the matching rule and 3234 // determine if it allows that attribute type. 3235 if (attributeType != null) 3236 { 3237 MatchingRuleUse mru = 3238 DirectoryServer.getMatchingRuleUse(matchingRule); 3239 if (mru != null && !mru.appliesToAttribute(attributeType)) 3240 { 3241 if (logger.isTraceEnabled()) 3242 { 3243 logger.trace( 3244 "Attribute type %s is not allowed for use with " + 3245 "matching rule %s because of matching rule use " + 3246 "definition %s", attributeType.getNameOrOID(), 3247 matchingRule.getNameOrOID(), mru.getNameOrOID()); 3248 } 3249 return ConditionResult.UNDEFINED; 3250 } 3251 } 3252 3253 3254 // Normalize the assertion value using the matching rule. 3255 Assertion assertion; 3256 try 3257 { 3258 assertion = matchingRule.getAssertion(assertionValue); 3259 } 3260 catch (Exception e) 3261 { 3262 logger.traceException(e); 3263 3264 // We can't normalize the assertion value, so the result must be 3265 // undefined. 3266 return ConditionResult.UNDEFINED; 3267 } 3268 3269 3270 // If there is an attribute type, then we should only check for 3271 // that attribute. Otherwise, we should check against all 3272 // attributes in the entry. 3273 ConditionResult result = ConditionResult.FALSE; 3274 if (attributeType == null) 3275 { 3276 for (List<Attribute> attrList : 3277 entry.getUserAttributes().values()) 3278 { 3279 for (Attribute a : attrList) 3280 { 3281 for (ByteString v : a) 3282 { 3283 try 3284 { 3285 ByteString nv = matchingRule.normalizeAttributeValue(v); 3286 ConditionResult r = assertion.matches(nv); 3287 switch (r) 3288 { 3289 case TRUE: 3290 return ConditionResult.TRUE; 3291 case FALSE: 3292 break; 3293 case UNDEFINED: 3294 result = ConditionResult.UNDEFINED; 3295 break; 3296 default: 3297 LocalizableMessage message = 3298 ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 3299 get(entry.getName(), completeFilter, r); 3300 throw new DirectoryException( 3301 ResultCode.PROTOCOL_ERROR, message); 3302 } 3303 } 3304 catch (Exception e) 3305 { 3306 logger.traceException(e); 3307 3308 // We couldn't normalize one of the values. If we don't 3309 // find a definite match, then we should return 3310 // undefined. 3311 result = ConditionResult.UNDEFINED; 3312 } 3313 } 3314 } 3315 } 3316 3317 for (List<Attribute> attrList : 3318 entry.getOperationalAttributes().values()) 3319 { 3320 for (Attribute a : attrList) 3321 { 3322 for (ByteString v : a) 3323 { 3324 try 3325 { 3326 ByteString nv = matchingRule.normalizeAttributeValue(v); 3327 ConditionResult r = assertion.matches(nv); 3328 switch (r) 3329 { 3330 case TRUE: 3331 return ConditionResult.TRUE; 3332 case FALSE: 3333 break; 3334 case UNDEFINED: 3335 result = ConditionResult.UNDEFINED; 3336 break; 3337 default: 3338 LocalizableMessage message = 3339 ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 3340 get(entry.getName(), completeFilter, r); 3341 throw new DirectoryException( 3342 ResultCode.PROTOCOL_ERROR, message); 3343 } 3344 } 3345 catch (Exception e) 3346 { 3347 logger.traceException(e); 3348 3349 // We couldn't normalize one of the values. If we don't 3350 // find a definite match, then we should return 3351 // undefined. 3352 result = ConditionResult.UNDEFINED; 3353 } 3354 } 3355 } 3356 } 3357 3358 Attribute a = entry.getObjectClassAttribute(); 3359 for (ByteString v : a) 3360 { 3361 try 3362 { 3363 ByteString nv = matchingRule.normalizeAttributeValue(v); 3364 ConditionResult r = assertion.matches(nv); 3365 switch (r) 3366 { 3367 case TRUE: 3368 return ConditionResult.TRUE; 3369 case FALSE: 3370 break; 3371 case UNDEFINED: 3372 result = ConditionResult.UNDEFINED; 3373 break; 3374 default: 3375 LocalizableMessage message = ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 3376 get(entry.getName(), completeFilter, r); 3377 throw new DirectoryException(ResultCode.PROTOCOL_ERROR, 3378 message); 3379 } 3380 } 3381 catch (Exception e) 3382 { 3383 logger.traceException(e); 3384 3385 // We couldn't normalize one of the values. If we don't 3386 // find a definite match, then we should return undefined. 3387 result = ConditionResult.UNDEFINED; 3388 } 3389 } 3390 } 3391 else 3392 { 3393 List<Attribute> attrList = entry.getAttribute(attributeType, 3394 attributeOptions); 3395 if (attrList != null) 3396 { 3397 for (Attribute a : attrList) 3398 { 3399 for (ByteString v : a) 3400 { 3401 try 3402 { 3403 ByteString nv = matchingRule.normalizeAttributeValue(v); 3404 ConditionResult r = assertion.matches(nv); 3405 switch (r) 3406 { 3407 case TRUE: 3408 return ConditionResult.TRUE; 3409 case FALSE: 3410 break; 3411 case UNDEFINED: 3412 result = ConditionResult.UNDEFINED; 3413 break; 3414 default: 3415 LocalizableMessage message = 3416 ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 3417 get(entry.getName(), completeFilter, r); 3418 throw new DirectoryException( 3419 ResultCode.PROTOCOL_ERROR, message); 3420 } 3421 } 3422 catch (Exception e) 3423 { 3424 logger.traceException(e); 3425 3426 // We couldn't normalize one of the values. If we don't 3427 // find a definite match, then we should return 3428 // undefined. 3429 result = ConditionResult.UNDEFINED; 3430 } 3431 } 3432 } 3433 } 3434 } 3435 3436 3437 // If we've gotten here, then we know that there is no definite 3438 // match in the set of attributes. If we should check DN 3439 // attributes, then do so. 3440 if (dnAttributes) 3441 { 3442 DN entryDN = entry.getName(); 3443 int count = entryDN.size(); 3444 for (int rdnIndex = 0; rdnIndex < count; rdnIndex++) 3445 { 3446 RDN rdn = entryDN.getRDN(rdnIndex); 3447 int numAVAs = rdn.getNumValues(); 3448 for (int i=0; i < numAVAs; i++) 3449 { 3450 try 3451 { 3452 if (attributeType == null || attributeType.equals(rdn.getAttributeType(i))) 3453 { 3454 ByteString v = rdn.getAttributeValue(i); 3455 ByteString nv = matchingRule.normalizeAttributeValue(v); 3456 ConditionResult r = assertion.matches(nv); 3457 switch (r) 3458 { 3459 case TRUE: 3460 return ConditionResult.TRUE; 3461 case FALSE: 3462 break; 3463 case UNDEFINED: 3464 result = ConditionResult.UNDEFINED; 3465 break; 3466 default: 3467 LocalizableMessage message = 3468 ERR_SEARCH_FILTER_INVALID_RESULT_TYPE. 3469 get(entry.getName(), completeFilter, r); 3470 throw new DirectoryException( 3471 ResultCode.PROTOCOL_ERROR, message); 3472 } 3473 } 3474 } 3475 catch (Exception e) 3476 { 3477 logger.traceException(e); 3478 3479 // We couldn't normalize one of the values. If we don't 3480 // find a definite match, then we should return undefined. 3481 result = ConditionResult.UNDEFINED; 3482 } 3483 } 3484 } 3485 } 3486 3487 3488 // If we've gotten here, then there is no definitive match, so 3489 // we'll either return FALSE or UNDEFINED. 3490 return result; 3491 } 3492 3493 3494 /** 3495 * Indicates whether this search filter is equal to the provided 3496 * object. 3497 * 3498 * @param o The object for which to make the determination. 3499 * 3500 * @return <CODE>true</CODE> if the provide object is equal to this 3501 * search filter, or <CODE>false</CODE> if it is not. 3502 */ 3503 @Override 3504 public boolean equals(Object o) 3505 { 3506 if (o == null) 3507 { 3508 return false; 3509 } 3510 3511 if (o == this) 3512 { 3513 return true; 3514 } 3515 3516 if (! (o instanceof SearchFilter)) 3517 { 3518 return false; 3519 } 3520 3521 3522 SearchFilter f = (SearchFilter) o; 3523 if (filterType != f.filterType) 3524 { 3525 return false; 3526 } 3527 3528 3529 switch (filterType) 3530 { 3531 case AND: 3532 case OR: 3533 return andOrEqual(f); 3534 case NOT: 3535 return notComponent.equals(f.notComponent); 3536 case EQUALITY: 3537 return typeAndOptionsAndAssertionEqual(f); 3538 case SUBSTRING: 3539 return substringEqual(f); 3540 case GREATER_OR_EQUAL: 3541 return typeAndOptionsAndAssertionEqual(f); 3542 case LESS_OR_EQUAL: 3543 return typeAndOptionsAndAssertionEqual(f); 3544 case PRESENT: 3545 return attributeType.equals(f.attributeType) && 3546 optionsEqual(attributeOptions, f.attributeOptions); 3547 case APPROXIMATE_MATCH: 3548 return typeAndOptionsAndAssertionEqual(f); 3549 case EXTENSIBLE_MATCH: 3550 return extensibleEqual(f); 3551 default: 3552 return false; 3553 } 3554 } 3555 3556 private boolean andOrEqual(SearchFilter f) 3557 { 3558 if (filterComponents.size() != f.filterComponents.size()) 3559 { 3560 return false; 3561 } 3562 3563 for (SearchFilter outerFilter : filterComponents) 3564 { 3565 if (!f.filterComponents.contains(outerFilter)) 3566 { 3567 return false; 3568 } 3569 } 3570 return true; 3571 } 3572 3573 3574 private boolean typeAndOptionsAndAssertionEqual(SearchFilter f) 3575 { 3576 return attributeType.equals(f.attributeType) 3577 && optionsEqual(attributeOptions, f.attributeOptions) 3578 && assertionValue.equals(f.assertionValue); 3579 } 3580 3581 3582 private boolean substringEqual(SearchFilter other) 3583 { 3584 if (! attributeType.equals(other.attributeType)) 3585 { 3586 return false; 3587 } 3588 3589 MatchingRule rule = attributeType.getSubstringMatchingRule(); 3590 if (rule == null) 3591 { 3592 return false; 3593 } 3594 if (! optionsEqual(attributeOptions, other.attributeOptions)) 3595 { 3596 return false; 3597 } 3598 3599 boolean initialCheck = subInitialElement == null ? 3600 other.subInitialElement == null : subInitialElement.equals(other.subInitialElement); 3601 if (!initialCheck) 3602 { 3603 return false; 3604 } 3605 boolean finalCheck = subFinalElement == null ? 3606 other.subFinalElement == null : subFinalElement.equals(other.subFinalElement); 3607 if (!finalCheck) 3608 { 3609 return false; 3610 } 3611 boolean anyCheck = subAnyElements == null ? 3612 other.subAnyElements == null : subAnyElements.size() == other.subAnyElements.size(); 3613 if (!anyCheck) 3614 { 3615 return false; 3616 } 3617 if (subAnyElements != null) 3618 { 3619 for (int i = 0; i < subAnyElements.size(); i++) 3620 { 3621 if (! subAnyElements.get(i).equals(other.subAnyElements.get(i))) 3622 { 3623 return false; 3624 } 3625 } 3626 } 3627 return true; 3628 } 3629 3630 private boolean extensibleEqual(SearchFilter f) 3631 { 3632 if (attributeType == null) 3633 { 3634 if (f.attributeType != null) 3635 { 3636 return false; 3637 } 3638 } 3639 else 3640 { 3641 if (! attributeType.equals(f.attributeType)) 3642 { 3643 return false; 3644 } 3645 3646 if (! optionsEqual(attributeOptions, f.attributeOptions)) 3647 { 3648 return false; 3649 } 3650 } 3651 3652 if (dnAttributes != f.dnAttributes) 3653 { 3654 return false; 3655 } 3656 3657 if (matchingRuleID == null) 3658 { 3659 if (f.matchingRuleID != null) 3660 { 3661 return false; 3662 } 3663 } 3664 else 3665 { 3666 if (! matchingRuleID.equals(f.matchingRuleID)) 3667 { 3668 return false; 3669 } 3670 } 3671 3672 if (assertionValue == null) 3673 { 3674 if (f.assertionValue != null) 3675 { 3676 return false; 3677 } 3678 } 3679 else 3680 { 3681 if (matchingRuleID == null) 3682 { 3683 if (! assertionValue.equals(f.assertionValue)) 3684 { 3685 return false; 3686 } 3687 } 3688 else 3689 { 3690 MatchingRule mrule = DirectoryServer.getMatchingRule(toLowerCase(matchingRuleID)); 3691 if (mrule == null) 3692 { 3693 return false; 3694 } 3695 else 3696 { 3697 try 3698 { 3699 Assertion assertion = mrule.getAssertion(f.assertionValue); 3700 return assertion.matches(mrule.normalizeAttributeValue(assertionValue)).toBoolean(); 3701 } 3702 catch (Exception e) 3703 { 3704 return false; 3705 } 3706 } 3707 } 3708 } 3709 3710 return true; 3711 } 3712 3713 3714 /** 3715 * Indicates whether the two provided sets of attribute options 3716 * should be considered equal. 3717 * 3718 * @param options1 The first set of attribute options for which to 3719 * make the determination. 3720 * @param options2 The second set of attribute options for which 3721 * to make the determination. 3722 * 3723 * @return {@code true} if the sets of attribute options are equal, 3724 * or {@code false} if not. 3725 */ 3726 private static boolean optionsEqual(Set<String> options1, 3727 Set<String> options2) 3728 { 3729 if (options1 == null || options1.isEmpty()) 3730 { 3731 return options2 == null || options2.isEmpty(); 3732 } 3733 else if (options2 == null || options2.isEmpty()) 3734 { 3735 return false; 3736 } 3737 else 3738 { 3739 if (options1.size() != options2.size()) 3740 { 3741 return false; 3742 } 3743 3744 HashSet<String> lowerOptions = new HashSet<>(options1.size()); 3745 for (String option : options1) 3746 { 3747 lowerOptions.add(toLowerCase(option)); 3748 } 3749 3750 for (String option : options2) 3751 { 3752 if (! lowerOptions.remove(toLowerCase(option))) 3753 { 3754 return false; 3755 } 3756 } 3757 3758 return lowerOptions.isEmpty(); 3759 } 3760 } 3761 3762 3763 /** 3764 * Retrieves the hash code for this search filter. 3765 * 3766 * @return The hash code for this search filter. 3767 */ 3768 @Override 3769 public int hashCode() 3770 { 3771 switch (filterType) 3772 { 3773 case AND: 3774 case OR: 3775 int hashCode = 0; 3776 3777 for (SearchFilter filterComp : filterComponents) 3778 { 3779 hashCode += filterComp.hashCode(); 3780 } 3781 3782 return hashCode; 3783 case NOT: 3784 return notComponent.hashCode(); 3785 case EQUALITY: 3786 return typeAndAssertionHashCode(); 3787 case SUBSTRING: 3788 return substringHashCode(); 3789 case GREATER_OR_EQUAL: 3790 return typeAndAssertionHashCode(); 3791 case LESS_OR_EQUAL: 3792 return typeAndAssertionHashCode(); 3793 case PRESENT: 3794 return attributeType.hashCode(); 3795 case APPROXIMATE_MATCH: 3796 return typeAndAssertionHashCode(); 3797 case EXTENSIBLE_MATCH: 3798 return extensibleHashCode(); 3799 default: 3800 return 1; 3801 } 3802 } 3803 3804 3805 /** Returns the hash code for extensible filter. */ 3806 private int extensibleHashCode() 3807 { 3808 int hashCode = 0; 3809 3810 if (attributeType != null) 3811 { 3812 hashCode += attributeType.hashCode(); 3813 } 3814 3815 if (dnAttributes) 3816 { 3817 hashCode++; 3818 } 3819 3820 if (matchingRuleID != null) 3821 { 3822 hashCode += matchingRuleID.hashCode(); 3823 } 3824 3825 if (assertionValue != null) 3826 { 3827 hashCode += assertionValue.hashCode(); 3828 } 3829 return hashCode; 3830 } 3831 3832 3833 private int typeAndAssertionHashCode() 3834 { 3835 return attributeType.hashCode() + assertionValue.hashCode(); 3836 } 3837 3838 /** Returns hash code to use for substring filter. */ 3839 private int substringHashCode() 3840 { 3841 int hashCode = attributeType.hashCode(); 3842 if (subInitialElement != null) 3843 { 3844 hashCode += subInitialElement.hashCode(); 3845 } 3846 if (subAnyElements != null) 3847 { 3848 for (ByteString e : subAnyElements) 3849 { 3850 hashCode += e.hashCode(); 3851 } 3852 } 3853 if (subFinalElement != null) 3854 { 3855 hashCode += subFinalElement.hashCode(); 3856 } 3857 return hashCode; 3858 } 3859 3860 3861 3862 /** 3863 * Retrieves a string representation of this search filter. 3864 * 3865 * @return A string representation of this search filter. 3866 */ 3867 @Override 3868 public String toString() 3869 { 3870 StringBuilder buffer = new StringBuilder(); 3871 toString(buffer); 3872 return buffer.toString(); 3873 } 3874 3875 3876 3877 /** 3878 * Appends a string representation of this search filter to the 3879 * provided buffer. 3880 * 3881 * @param buffer The buffer to which the information should be 3882 * appended. 3883 */ 3884 public void toString(StringBuilder buffer) 3885 { 3886 switch (filterType) 3887 { 3888 case AND: 3889 buffer.append("(&"); 3890 for (SearchFilter f : filterComponents) 3891 { 3892 f.toString(buffer); 3893 } 3894 buffer.append(")"); 3895 break; 3896 case OR: 3897 buffer.append("(|"); 3898 for (SearchFilter f : filterComponents) 3899 { 3900 f.toString(buffer); 3901 } 3902 buffer.append(")"); 3903 break; 3904 case NOT: 3905 buffer.append("(!"); 3906 notComponent.toString(buffer); 3907 buffer.append(")"); 3908 break; 3909 case EQUALITY: 3910 buffer.append("("); 3911 buffer.append(attributeType.getNameOrOID()); 3912 appendOptions(buffer); 3913 buffer.append("="); 3914 valueToFilterString(buffer, assertionValue); 3915 buffer.append(")"); 3916 break; 3917 case SUBSTRING: 3918 buffer.append("("); 3919 buffer.append(attributeType.getNameOrOID()); 3920 appendOptions(buffer); 3921 buffer.append("="); 3922 3923 if (subInitialElement != null) 3924 { 3925 valueToFilterString(buffer, subInitialElement); 3926 } 3927 3928 if (subAnyElements != null && !subAnyElements.isEmpty()) 3929 { 3930 for (ByteString s : subAnyElements) 3931 { 3932 buffer.append("*"); 3933 valueToFilterString(buffer, s); 3934 } 3935 } 3936 3937 buffer.append("*"); 3938 3939 if (subFinalElement != null) 3940 { 3941 valueToFilterString(buffer, subFinalElement); 3942 } 3943 3944 buffer.append(")"); 3945 break; 3946 case GREATER_OR_EQUAL: 3947 buffer.append("("); 3948 buffer.append(attributeType.getNameOrOID()); 3949 appendOptions(buffer); 3950 buffer.append(">="); 3951 valueToFilterString(buffer, assertionValue); 3952 buffer.append(")"); 3953 break; 3954 case LESS_OR_EQUAL: 3955 buffer.append("("); 3956 buffer.append(attributeType.getNameOrOID()); 3957 appendOptions(buffer); 3958 buffer.append("<="); 3959 valueToFilterString(buffer, assertionValue); 3960 buffer.append(")"); 3961 break; 3962 case PRESENT: 3963 buffer.append("("); 3964 buffer.append(attributeType.getNameOrOID()); 3965 appendOptions(buffer); 3966 buffer.append("=*)"); 3967 break; 3968 case APPROXIMATE_MATCH: 3969 buffer.append("("); 3970 buffer.append(attributeType.getNameOrOID()); 3971 appendOptions(buffer); 3972 buffer.append("~="); 3973 valueToFilterString(buffer, assertionValue); 3974 buffer.append(")"); 3975 break; 3976 case EXTENSIBLE_MATCH: 3977 buffer.append("("); 3978 3979 if (attributeType != null) 3980 { 3981 buffer.append(attributeType.getNameOrOID()); 3982 appendOptions(buffer); 3983 } 3984 3985 if (dnAttributes) 3986 { 3987 buffer.append(":dn"); 3988 } 3989 3990 if (matchingRuleID != null) 3991 { 3992 buffer.append(":"); 3993 buffer.append(matchingRuleID); 3994 } 3995 3996 buffer.append(":="); 3997 valueToFilterString(buffer, assertionValue); 3998 buffer.append(")"); 3999 break; 4000 } 4001 } 4002 4003 4004 private void appendOptions(StringBuilder buffer) 4005 { 4006 if (attributeOptions != null && !attributeOptions.isEmpty()) 4007 { 4008 for (String option : attributeOptions) 4009 { 4010 buffer.append(";"); 4011 buffer.append(option); 4012 } 4013 } 4014 } 4015 4016 4017 4018 /** 4019 * Appends a properly-cleaned version of the provided value to the 4020 * given buffer so that it can be safely used in string 4021 * representations of this search filter. The formatting changes 4022 * that may be performed will be in compliance with the 4023 * specification in RFC 2254. 4024 * 4025 * @param buffer The buffer to which the "safe" version of the 4026 * value will be appended. 4027 * @param value The value to be appended to the buffer. 4028 */ 4029 private void valueToFilterString(StringBuilder buffer, 4030 ByteString value) 4031 { 4032 if (value == null) 4033 { 4034 return; 4035 } 4036 4037 4038 // Get the binary representation of the value and iterate through 4039 // it to see if there are any unsafe characters. If there are, 4040 // then escape them and replace them with a two-digit hex 4041 // equivalent. 4042 buffer.ensureCapacity(buffer.length() + value.length()); 4043 byte b; 4044 for (int i = 0; i < value.length(); i++) 4045 { 4046 b = value.byteAt(i); 4047 if (((b & 0x7F) != b) || // Not 7-bit clean 4048 (b <= 0x1F) || // Below the printable character range 4049 (b == 0x28) || // Open parenthesis 4050 (b == 0x29) || // Close parenthesis 4051 (b == 0x2A) || // Asterisk 4052 (b == 0x5C) || // Backslash 4053 (b == 0x7F)) // Delete character 4054 { 4055 buffer.append("\\"); 4056 buffer.append(byteToHex(b)); 4057 } 4058 else 4059 { 4060 buffer.append((char) b); 4061 } 4062 } 4063 } 4064 4065 /** 4066 * Returns the {@code objectClass} presence filter {@code (objectClass=*)}. 4067 * 4068 * @return The {@code objectClass} presence filter {@code (objectClass=*)}. 4069 */ 4070 public static SearchFilter objectClassPresent() 4071 { 4072 if (objectClassPresent == null) 4073 { 4074 try 4075 { 4076 objectClassPresent = SearchFilter.createFilterFromString("(objectclass=*)"); 4077 } 4078 catch (DirectoryException canNeverHappen) 4079 { 4080 logger.traceException(canNeverHappen); 4081 } 4082 } 4083 return objectClassPresent; 4084 } 4085}