001/* 002 * Copyright 2007-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk; 037 038 039 040import java.io.Serializable; 041import java.util.ArrayList; 042import java.util.Arrays; 043import java.util.Collection; 044import java.util.HashSet; 045import java.util.LinkedHashSet; 046import java.util.List; 047import java.util.TreeMap; 048 049import com.unboundid.asn1.ASN1Boolean; 050import com.unboundid.asn1.ASN1Buffer; 051import com.unboundid.asn1.ASN1BufferSequence; 052import com.unboundid.asn1.ASN1BufferSet; 053import com.unboundid.asn1.ASN1Element; 054import com.unboundid.asn1.ASN1Exception; 055import com.unboundid.asn1.ASN1OctetString; 056import com.unboundid.asn1.ASN1Sequence; 057import com.unboundid.asn1.ASN1Set; 058import com.unboundid.asn1.ASN1StreamReader; 059import com.unboundid.asn1.ASN1StreamReaderSequence; 060import com.unboundid.asn1.ASN1StreamReaderSet; 061import com.unboundid.ldap.matchingrules.CaseIgnoreStringMatchingRule; 062import com.unboundid.ldap.matchingrules.MatchingRule; 063import com.unboundid.ldap.sdk.schema.Schema; 064import com.unboundid.ldap.sdk.unboundidds.jsonfilter.JSONObjectFilter; 065import com.unboundid.util.ByteStringBuffer; 066import com.unboundid.util.Debug; 067import com.unboundid.util.NotMutable; 068import com.unboundid.util.NotNull; 069import com.unboundid.util.Nullable; 070import com.unboundid.util.StaticUtils; 071import com.unboundid.util.ThreadSafety; 072import com.unboundid.util.ThreadSafetyLevel; 073import com.unboundid.util.Validator; 074import com.unboundid.util.json.JSONObject; 075 076import static com.unboundid.ldap.sdk.LDAPMessages.*; 077 078 079 080/** 081 * This class provides a data structure that represents an LDAP search filter. 082 * It provides methods for creating various types of filters, as well as parsing 083 * a filter from a string. See 084 * <A HREF="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</A> for more 085 * information about representing search filters as strings. 086 * <BR><BR> 087 * The following filter types are defined: 088 * <UL> 089 * <LI><B>AND</B> -- This is used to indicate that a filter should match an 090 * entry only if all of the embedded filter components match that entry. 091 * An AND filter with zero embedded filter components is considered an 092 * LDAP TRUE filter as defined in 093 * <A HREF="http://www.ietf.org/rfc/rfc4526.txt">RFC 4526</A> and will 094 * match any entry. AND filters contain only a set of embedded filter 095 * components, and each of those embedded components can itself be any 096 * type of filter, including an AND, OR, or NOT filter with additional 097 * embedded components.</LI> 098 * <LI><B>OR</B> -- This is used to indicate that a filter should match an 099 * entry only if at least one of the embedded filter components matches 100 * that entry. An OR filter with zero embedded filter components is 101 * considered an LDAP FALSE filter as defined in 102 * <A HREF="http://www.ietf.org/rfc/rfc4526.txt">RFC 4526</A> and will 103 * never match any entry. OR filters contain only a set of embedded 104 * filter components, and each of those embedded components can itself be 105 * any type of filter, including an AND, OR, or NOT filter with additional 106 * embedded components.</LI> 107 * <LI><B>NOT</B> -- This is used to indicate that a filter should match an 108 * entry only if the embedded NOT component does not match the entry. A 109 * NOT filter contains only a single embedded NOT filter component, but 110 * that embedded component can itself be any type of filter, including an 111 * AND, OR, or NOT filter with additional embedded components.</LI> 112 * <LI><B>EQUALITY</B> -- This is used to indicate that a filter should match 113 * an entry only if the entry contains a value for the specified attribute 114 * that is equal to the provided assertion value. An equality filter 115 * contains only an attribute name and an assertion value.</LI> 116 * <LI><B>SUBSTRING</B> -- This is used to indicate that a filter should match 117 * an entry only if the entry contains at least one value for the 118 * specified attribute that matches the provided substring assertion. The 119 * substring assertion must contain at least one element of the following 120 * types: 121 * <UL> 122 * <LI>subInitial -- This indicates that the specified string must 123 * appear at the beginning of the attribute value. There can be at 124 * most one subInitial element in a substring assertion.</LI> 125 * <LI>subAny -- This indicates that the specified string may appear 126 * anywhere in the attribute value. There can be any number of 127 * substring subAny elements in a substring assertion. If there are 128 * multiple subAny elements, then they must match in the order that 129 * they are provided.</LI> 130 * <LI>subFinal -- This indicates that the specified string must appear 131 * at the end of the attribute value. There can be at most one 132 * subFinal element in a substring assertion.</LI> 133 * </UL> 134 * A substring filter contains only an attribute name and subInitial, 135 * subAny, and subFinal elements.</LI> 136 * <LI><B>GREATER-OR-EQUAL</B> -- This is used to indicate that a filter 137 * should match an entry only if that entry contains at least one value 138 * for the specified attribute that is greater than or equal to the 139 * provided assertion value. A greater-or-equal filter contains only an 140 * attribute name and an assertion value.</LI> 141 * <LI><B>LESS-OR-EQUAL</B> -- This is used to indicate that a filter should 142 * match an entry only if that entry contains at least one value for the 143 * specified attribute that is less than or equal to the provided 144 * assertion value. A less-or-equal filter contains only an attribute 145 * name and an assertion value.</LI> 146 * <LI><B>PRESENCE</B> -- This is used to indicate that a filter should match 147 * an entry only if the entry contains at least one value for the 148 * specified attribute. A presence filter contains only an attribute 149 * name.</LI> 150 * <LI><B>APPROXIMATE-MATCH</B> -- This is used to indicate that a filter 151 * should match an entry only if the entry contains at least one value for 152 * the specified attribute that is approximately equal to the provided 153 * assertion value. The definition of "approximately equal to" may vary 154 * from one server to another, and from one attribute to another, but it 155 * is often implemented as a "sounds like" match using a variant of the 156 * metaphone or double-metaphone algorithm. An approximate-match filter 157 * contains only an attribute name and an assertion value.</LI> 158 * <LI><B>EXTENSIBLE-MATCH</B> -- This is used to perform advanced types of 159 * matching against entries, according to the following criteria: 160 * <UL> 161 * <LI>If an attribute name is provided, then the assertion value must 162 * match one of the values for that attribute (potentially including 163 * values contained in the entry's DN). If a matching rule ID is 164 * also provided, then the associated matching rule will be used to 165 * determine whether there is a match; otherwise the default 166 * equality matching rule for that attribute will be used.</LI> 167 * <LI>If no attribute name is provided, then a matching rule ID must be 168 * given, and the corresponding matching rule will be used to 169 * determine whether any attribute in the target entry (potentially 170 * including attributes contained in the entry's DN) has at least 171 * one value that matches the provided assertion value.</LI> 172 * <LI>If the dnAttributes flag is set, then attributes contained in the 173 * entry's DN will also be evaluated to determine if they match the 174 * filter criteria. If it is not set, then attributes contained in 175 * the entry's DN (other than those contained in its RDN which are 176 * also present as separate attributes in the entry) will not be 177* examined.</LI> 178 * </UL> 179 * An extensible match filter contains only an attribute name, matching 180 * rule ID, dnAttributes flag, and an assertion value.</LI> 181 * </UL> 182 * <BR><BR> 183 * There are two primary ways to create a search filter. The first is to create 184 * a filter from its string representation with the 185 * {@link Filter#create(String)} method, using the syntax described in RFC 4515. 186 * For example: 187 * <PRE> 188 * Filter f1 = Filter.create("(objectClass=*)"); 189 * Filter f2 = Filter.create("(uid=john.doe)"); 190 * Filter f3 = Filter.create("(|(givenName=John)(givenName=Johnathan))"); 191 * </PRE> 192 * <BR><BR> 193 * Creating a filter from its string representation is a common approach and 194 * seems to be relatively straightforward, but it does have some hidden dangers. 195 * This primarily comes from the potential for special characters in the filter 196 * string which need to be properly escaped. If this isn't done, then the 197 * search may fail or behave unexpectedly, or worse it could lead to a 198 * vulnerability in the application in which a malicious user could trick the 199 * application into retrieving more information than it should have. To avoid 200 * these problems, it may be better to construct filters from their individual 201 * components rather than their string representations, like: 202 * <PRE> 203 * Filter f1 = Filter.present("objectClass"); 204 * Filter f2 = Filter.equals("uid", "john.doe"); 205 * Filter f3 = Filter.or( 206 * Filter.equals("givenName", "John"), 207 * Filter.equals("givenName", "Johnathan")); 208 * </PRE> 209 * In general, it is recommended to avoid creating filters from their string 210 * representations if any of that string representation may include 211 * user-provided data or special characters including non-ASCII characters, 212 * parentheses, asterisks, or backslashes. 213 */ 214@NotMutable() 215@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 216public final class Filter 217 implements Serializable 218{ 219 /** 220 * The BER type for AND search filters. 221 */ 222 public static final byte FILTER_TYPE_AND = (byte) 0xA0; 223 224 225 226 /** 227 * The BER type for OR search filters. 228 */ 229 public static final byte FILTER_TYPE_OR = (byte) 0xA1; 230 231 232 233 /** 234 * The BER type for NOT search filters. 235 */ 236 public static final byte FILTER_TYPE_NOT = (byte) 0xA2; 237 238 239 240 /** 241 * The BER type for equality search filters. 242 */ 243 public static final byte FILTER_TYPE_EQUALITY = (byte) 0xA3; 244 245 246 247 /** 248 * The BER type for substring search filters. 249 */ 250 public static final byte FILTER_TYPE_SUBSTRING = (byte) 0xA4; 251 252 253 254 /** 255 * The BER type for greaterOrEqual search filters. 256 */ 257 public static final byte FILTER_TYPE_GREATER_OR_EQUAL = (byte) 0xA5; 258 259 260 261 /** 262 * The BER type for lessOrEqual search filters. 263 */ 264 public static final byte FILTER_TYPE_LESS_OR_EQUAL = (byte) 0xA6; 265 266 267 268 /** 269 * The BER type for presence search filters. 270 */ 271 public static final byte FILTER_TYPE_PRESENCE = (byte) 0x87; 272 273 274 275 /** 276 * The BER type for approximate match search filters. 277 */ 278 public static final byte FILTER_TYPE_APPROXIMATE_MATCH = (byte) 0xA8; 279 280 281 282 /** 283 * The BER type for extensible match search filters. 284 */ 285 public static final byte FILTER_TYPE_EXTENSIBLE_MATCH = (byte) 0xA9; 286 287 288 289 /** 290 * The BER type for the subInitial substring filter element. 291 */ 292 private static final byte SUBSTRING_TYPE_SUBINITIAL = (byte) 0x80; 293 294 295 296 /** 297 * The BER type for the subAny substring filter element. 298 */ 299 private static final byte SUBSTRING_TYPE_SUBANY = (byte) 0x81; 300 301 302 303 /** 304 * The BER type for the subFinal substring filter element. 305 */ 306 private static final byte SUBSTRING_TYPE_SUBFINAL = (byte) 0x82; 307 308 309 310 /** 311 * The BER type for the matching rule ID extensible match filter element. 312 */ 313 private static final byte EXTENSIBLE_TYPE_MATCHING_RULE_ID = (byte) 0x81; 314 315 316 317 /** 318 * The BER type for the attribute name extensible match filter element. 319 */ 320 private static final byte EXTENSIBLE_TYPE_ATTRIBUTE_NAME = (byte) 0x82; 321 322 323 324 /** 325 * The BER type for the match value extensible match filter element. 326 */ 327 private static final byte EXTENSIBLE_TYPE_MATCH_VALUE = (byte) 0x83; 328 329 330 331 /** 332 * The BER type for the DN attributes extensible match filter element. 333 */ 334 private static final byte EXTENSIBLE_TYPE_DN_ATTRIBUTES = (byte) 0x84; 335 336 337 338 /** 339 * The set of filters that will be used if there are no subordinate filters. 340 */ 341 @NotNull private static final Filter[] NO_FILTERS = new Filter[0]; 342 343 344 345 /** 346 * The set of subAny components that will be used if there are no subAny 347 * components. 348 */ 349 @NotNull private static final ASN1OctetString[] NO_SUB_ANY = 350 new ASN1OctetString[0]; 351 352 353 354 /** 355 * The serial version UID for this serializable class. 356 */ 357 private static final long serialVersionUID = -2734184402804691970L; 358 359 360 361 // The assertion value for this filter. 362 @Nullable private final ASN1OctetString assertionValue; 363 364 // The subFinal component for this filter. 365 @Nullable private final ASN1OctetString subFinal; 366 367 // The subInitial component for this filter. 368 @Nullable private final ASN1OctetString subInitial; 369 370 // The subAny components for this filter. 371 @NotNull private final ASN1OctetString[] subAny; 372 373 // The dnAttrs element for this filter. 374 private final boolean dnAttributes; 375 376 // The filter component to include in a NOT filter. 377 @Nullable private final Filter notComp; 378 379 // The set of filter components to include in an AND or OR filter. 380 @NotNull private final Filter[] filterComps; 381 382 // The filter type for this search filter. 383 private final byte filterType; 384 385 // The attribute name for this filter. 386 @Nullable private final String attrName; 387 388 // The string representation of this search filter. 389 @Nullable private volatile String filterString; 390 391 // The matching rule ID for this filter. 392 @Nullable private final String matchingRuleID; 393 394 // The normalized string representation of this search filter. 395 @Nullable private volatile String normalizedString; 396 397 398 399 /** 400 * Creates a new filter with the appropriate subset of the provided 401 * information. 402 * 403 * @param filterString The string representation of this search filter. 404 * It may be {@code null} if it is not yet known. 405 * @param filterType The filter type for this filter. 406 * @param filterComps The set of filter components for this filter. 407 * @param notComp The filter component for this NOT filter. 408 * @param attrName The name of the target attribute for this filter. 409 * @param assertionValue Then assertion value for this filter. 410 * @param subInitial The subInitial component for this filter. 411 * @param subAny The set of subAny components for this filter. 412 * @param subFinal The subFinal component for this filter. 413 * @param matchingRuleID The matching rule ID for this filter. 414 * @param dnAttributes The dnAttributes flag. 415 */ 416 private Filter(@Nullable final String filterString, final byte filterType, 417 @NotNull final Filter[] filterComps, 418 @Nullable final Filter notComp, 419 @Nullable final String attrName, 420 @Nullable final ASN1OctetString assertionValue, 421 @Nullable final ASN1OctetString subInitial, 422 @NotNull final ASN1OctetString[] subAny, 423 @Nullable final ASN1OctetString subFinal, 424 @Nullable final String matchingRuleID, 425 final boolean dnAttributes) 426 { 427 this.filterString = filterString; 428 this.filterType = filterType; 429 this.filterComps = filterComps; 430 this.notComp = notComp; 431 this.attrName = attrName; 432 this.assertionValue = assertionValue; 433 this.subInitial = subInitial; 434 this.subAny = subAny; 435 this.subFinal = subFinal; 436 this.matchingRuleID = matchingRuleID; 437 this.dnAttributes = dnAttributes; 438 } 439 440 441 442 /** 443 * Creates a new AND search filter with the provided components. 444 * <BR><BR> 445 * This method does exactly the same thing as 446 * {@link #createANDFilter(Filter...)}, but with a shorter method name for 447 * convenience. 448 * 449 * @param andComponents The set of filter components to include in the AND 450 * filter. It must not be {@code null}. 451 * 452 * @return The created AND search filter. 453 */ 454 @NotNull() 455 public static Filter and(@NotNull final Filter... andComponents) 456 { 457 return createANDFilter(andComponents); 458 } 459 460 461 462 /** 463 * Creates a new AND search filter with the provided components. 464 * <BR><BR> 465 * This method does exactly the same thing as 466 * {@link #createANDFilter(Collection)}, but with a shorter method name for 467 * convenience. 468 * 469 * @param andComponents The set of filter components to include in the AND 470 * filter. It must not be {@code null}. 471 * 472 * @return The created AND search filter. 473 */ 474 @NotNull() 475 public static Filter and(@NotNull final Collection<Filter> andComponents) 476 { 477 return createANDFilter(andComponents); 478 } 479 480 481 482 /** 483 * Creates a new AND search filter with the provided components. 484 * 485 * @param andComponents The set of filter components to include in the AND 486 * filter. It must not be {@code null}. 487 * 488 * @return The created AND search filter. 489 */ 490 @NotNull() 491 public static Filter createANDFilter(@NotNull final Filter... andComponents) 492 { 493 Validator.ensureNotNull(andComponents); 494 495 return new Filter(null, FILTER_TYPE_AND, andComponents, null, null, null, 496 null, NO_SUB_ANY, null, null, false); 497 } 498 499 500 501 /** 502 * Creates a new AND search filter with the provided components. 503 * 504 * @param andComponents The set of filter components to include in the AND 505 * filter. It must not be {@code null}. 506 * 507 * @return The created AND search filter. 508 */ 509 @NotNull() 510 public static Filter createANDFilter( 511 @NotNull final List<Filter> andComponents) 512 { 513 Validator.ensureNotNull(andComponents); 514 515 return new Filter(null, FILTER_TYPE_AND, 516 andComponents.toArray(new Filter[andComponents.size()]), 517 null, null, null, null, NO_SUB_ANY, null, null, false); 518 } 519 520 521 522 /** 523 * Creates a new AND search filter with the provided components. 524 * 525 * @param andComponents The set of filter components to include in the AND 526 * filter. It must not be {@code null}. 527 * 528 * @return The created AND search filter. 529 */ 530 @NotNull() 531 public static Filter createANDFilter( 532 @NotNull final Collection<Filter> andComponents) 533 { 534 Validator.ensureNotNull(andComponents); 535 536 return new Filter(null, FILTER_TYPE_AND, 537 andComponents.toArray(new Filter[andComponents.size()]), 538 null, null, null, null, NO_SUB_ANY, null, null, false); 539 } 540 541 542 543 /** 544 * Creates a new OR search filter with the provided components. 545 * <BR><BR> 546 * This method does exactly the same thing as 547 * {@link #createORFilter(Filter...)}, but with a shorter method name for 548 * convenience. 549 * 550 * @param orComponents The set of filter components to include in the OR 551 * filter. It must not be {@code null}. 552 * 553 * @return The created OR search filter. 554 */ 555 @NotNull() 556 public static Filter or(@NotNull final Filter... orComponents) 557 { 558 return createORFilter(orComponents); 559 } 560 561 562 563 /** 564 * Creates a new OR search filter with the provided components. 565 * <BR><BR> 566 * This method does exactly the same thing as 567 * {@link #createORFilter(Collection)}, but with a shorter method name for 568 * convenience. 569 * 570 * @param orComponents The set of filter components to include in the OR 571 * filter. It must not be {@code null}. 572 * 573 * @return The created OR search filter. 574 */ 575 @NotNull() 576 public static Filter or(@NotNull final Collection<Filter> orComponents) 577 { 578 return createORFilter(orComponents); 579 } 580 581 582 583 /** 584 * Creates a new OR search filter with the provided components. 585 * 586 * @param orComponents The set of filter components to include in the OR 587 * filter. It must not be {@code null}. 588 * 589 * @return The created OR search filter. 590 */ 591 @NotNull() 592 public static Filter createORFilter(@NotNull final Filter... orComponents) 593 { 594 Validator.ensureNotNull(orComponents); 595 596 return new Filter(null, FILTER_TYPE_OR, orComponents, null, null, null, 597 null, NO_SUB_ANY, null, null, false); 598 } 599 600 601 602 /** 603 * Creates a new OR search filter with the provided components. 604 * 605 * @param orComponents The set of filter components to include in the OR 606 * filter. It must not be {@code null}. 607 * 608 * @return The created OR search filter. 609 */ 610 @NotNull() 611 public static Filter createORFilter(@NotNull final List<Filter> orComponents) 612 { 613 Validator.ensureNotNull(orComponents); 614 615 return new Filter(null, FILTER_TYPE_OR, 616 orComponents.toArray(new Filter[orComponents.size()]), 617 null, null, null, null, NO_SUB_ANY, null, null, false); 618 } 619 620 621 622 /** 623 * Creates a new OR search filter with the provided components. 624 * 625 * @param orComponents The set of filter components to include in the OR 626 * filter. It must not be {@code null}. 627 * 628 * @return The created OR search filter. 629 */ 630 @NotNull() 631 public static Filter createORFilter( 632 @NotNull final Collection<Filter> orComponents) 633 { 634 Validator.ensureNotNull(orComponents); 635 636 return new Filter(null, FILTER_TYPE_OR, 637 orComponents.toArray(new Filter[orComponents.size()]), 638 null, null, null, null, NO_SUB_ANY, null, null, false); 639 } 640 641 642 643 /** 644 * Creates a new NOT search filter with the provided component. 645 * <BR><BR> 646 * This method does exactly the same thing as 647 * {@link #createNOTFilter(Filter)}, but with a shorter method name for 648 * convenience. 649 * 650 * @param notComponent The filter component to include in this NOT filter. 651 * It must not be {@code null}. 652 * 653 * @return The created NOT search filter. 654 */ 655 @NotNull() 656 public static Filter not(@NotNull final Filter notComponent) 657 { 658 return createNOTFilter(notComponent); 659 } 660 661 662 663 /** 664 * Creates a new NOT search filter with the provided component. 665 * 666 * @param notComponent The filter component to include in this NOT filter. 667 * It must not be {@code null}. 668 * 669 * @return The created NOT search filter. 670 */ 671 @NotNull() 672 public static Filter createNOTFilter(@NotNull final Filter notComponent) 673 { 674 Validator.ensureNotNull(notComponent); 675 676 return new Filter(null, FILTER_TYPE_NOT, NO_FILTERS, notComponent, null, 677 null, null, NO_SUB_ANY, null, null, false); 678 } 679 680 681 682 /** 683 * Creates a new equality search filter with the provided information. 684 * <BR><BR> 685 * This method does exactly the same thing as 686 * {@link #createEqualityFilter(String,String)}, but with a shorter method 687 * name for convenience. 688 * 689 * @param attributeName The attribute name for this equality filter. It 690 * must not be {@code null}. 691 * @param assertionValue The assertion value for this equality filter. It 692 * must not be {@code null}. 693 * 694 * @return The created equality search filter. 695 */ 696 @NotNull() 697 public static Filter equals(@NotNull final String attributeName, 698 @NotNull final String assertionValue) 699 { 700 return createEqualityFilter(attributeName, assertionValue); 701 } 702 703 704 705 /** 706 * Creates a new equality search filter with the provided information. 707 * <BR><BR> 708 * This method does exactly the same thing as 709 * {@link #createEqualityFilter(String,byte[])}, but with a shorter method 710 * name for convenience. 711 * 712 * @param attributeName The attribute name for this equality filter. It 713 * must not be {@code null}. 714 * @param assertionValue The assertion value for this equality filter. It 715 * must not be {@code null}. 716 * 717 * @return The created equality search filter. 718 */ 719 @NotNull() 720 public static Filter equals(@NotNull final String attributeName, 721 @NotNull final byte[] assertionValue) 722 { 723 return createEqualityFilter(attributeName, assertionValue); 724 } 725 726 727 728 /** 729 * Creates a new equality search filter with the provided information. 730 * 731 * @param attributeName The attribute name for this equality filter. It 732 * must not be {@code null}. 733 * @param assertionValue The assertion value for this equality filter. It 734 * must not be {@code null}. 735 * 736 * @return The created equality search filter. 737 */ 738 @NotNull() 739 public static Filter createEqualityFilter(@NotNull final String attributeName, 740 @NotNull final String assertionValue) 741 { 742 Validator.ensureNotNull(attributeName, assertionValue); 743 744 return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, 745 attributeName, new ASN1OctetString(assertionValue), null, 746 NO_SUB_ANY, null, null, false); 747 } 748 749 750 751 /** 752 * Creates a new equality search filter with the provided information. 753 * 754 * @param attributeName The attribute name for this equality filter. It 755 * must not be {@code null}. 756 * @param assertionValue The assertion value for this equality filter. It 757 * must not be {@code null}. 758 * 759 * @return The created equality search filter. 760 */ 761 @NotNull() 762 public static Filter createEqualityFilter(@NotNull final String attributeName, 763 @NotNull final byte[] assertionValue) 764 { 765 Validator.ensureNotNull(attributeName, assertionValue); 766 767 return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, 768 attributeName, new ASN1OctetString(assertionValue), null, 769 NO_SUB_ANY, null, null, false); 770 } 771 772 773 774 /** 775 * Creates a new equality search filter with the provided information. 776 * 777 * @param attributeName The attribute name for this equality filter. It 778 * must not be {@code null}. 779 * @param assertionValue The assertion value for this equality filter. It 780 * must not be {@code null}. 781 * 782 * @return The created equality search filter. 783 */ 784 @NotNull() 785 static Filter createEqualityFilter(@NotNull final String attributeName, 786 @NotNull final ASN1OctetString assertionValue) 787 { 788 Validator.ensureNotNull(attributeName, assertionValue); 789 790 return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, 791 attributeName, assertionValue, null, NO_SUB_ANY, null, 792 null, false); 793 } 794 795 796 797 /** 798 * Creates a new substring search filter with the provided information. At 799 * least one of the subInitial, subAny, and subFinal components must not be 800 * {@code null}. 801 * <BR><BR> 802 * This method does exactly the same thing as 803 * {@link #createSubstringFilter(String,String,String[],String)}, but with a 804 * shorter method name for convenience. 805 * 806 * @param attributeName The attribute name for this substring filter. It 807 * must not be {@code null}. 808 * @param subInitial The subInitial component for this substring filter. 809 * It may be {@code null} if there is no subInitial 810 * component, but it must not be empty. 811 * @param subAny The set of subAny components for this substring 812 * filter. It may be {@code null} or empty if there 813 * are no subAny components. 814 * @param subFinal The subFinal component for this substring filter. 815 * It may be {@code null} if there is no subFinal 816 * component, but it must not be empty. 817 * 818 * @return The created substring search filter. 819 */ 820 @NotNull() 821 public static Filter substring(@NotNull final String attributeName, 822 @Nullable final String subInitial, 823 @Nullable final String[] subAny, 824 @Nullable final String subFinal) 825 { 826 return createSubstringFilter(attributeName, subInitial, subAny, subFinal); 827 } 828 829 830 831 /** 832 * Creates a new substring search filter with the provided information. At 833 * least one of the subInitial, subAny, and subFinal components must not be 834 * {@code null}. 835 * <BR><BR> 836 * This method does exactly the same thing as 837 * {@link #createSubstringFilter(String,byte[],byte[][],byte[])}, but with a 838 * shorter method name for convenience. 839 * 840 * @param attributeName The attribute name for this substring filter. It 841 * must not be {@code null}. 842 * @param subInitial The subInitial component for this substring filter. 843 * It may be {@code null} if there is no subInitial 844 * component, but it must not be empty. 845 * @param subAny The set of subAny components for this substring 846 * filter. It may be {@code null} or empty if there 847 * are no subAny components. 848 * @param subFinal The subFinal component for this substring filter. 849 * It may be {@code null} if there is no subFinal 850 * component, but it must not be empty. 851 * 852 * @return The created substring search filter. 853 */ 854 @NotNull() 855 public static Filter substring(@NotNull final String attributeName, 856 @Nullable final byte[] subInitial, 857 @Nullable final byte[][] subAny, 858 @Nullable final byte[] subFinal) 859 { 860 return createSubstringFilter(attributeName, subInitial, subAny, subFinal); 861 } 862 863 864 865 /** 866 * Creates a new substring search filter with the provided information. At 867 * least one of the subInitial, subAny, and subFinal components must not be 868 * {@code null}. 869 * 870 * @param attributeName The attribute name for this substring filter. It 871 * must not be {@code null}. 872 * @param subInitial The subInitial component for this substring filter. 873 * It may be {@code null} if there is no subInitial 874 * component, but it must not be empty. 875 * @param subAny The set of subAny components for this substring 876 * filter. It may be {@code null} or empty if there 877 * are no subAny components. 878 * @param subFinal The subFinal component for this substring filter. 879 * It may be {@code null} if there is no subFinal 880 * component, but it must not be empty. 881 * 882 * @return The created substring search filter. 883 */ 884 @NotNull() 885 public static Filter createSubstringFilter( 886 @NotNull final String attributeName, 887 @Nullable final String subInitial, 888 @Nullable final String[] subAny, 889 @Nullable final String subFinal) 890 { 891 Validator.ensureNotNull(attributeName); 892 Validator.ensureTrue( 893 (((subInitial != null) && (subInitial.length() > 0)) || 894 ((subAny != null) && (subAny.length > 0) && 895 (subAny[0].length() > 0)) || 896 ((subFinal != null) && (subFinal.length() > 0))), 897 "At least one substring filter component must be non-null and " + 898 "non-empty"); 899 900 final ASN1OctetString subInitialOS; 901 if ((subInitial == null) || subInitial.isEmpty()) 902 { 903 subInitialOS = null; 904 } 905 else 906 { 907 subInitialOS = new ASN1OctetString(subInitial); 908 } 909 910 final ASN1OctetString[] subAnyArray; 911 if (subAny == null) 912 { 913 subAnyArray = NO_SUB_ANY; 914 } 915 else 916 { 917 if (subAny.length == 1) 918 { 919 if (subAny[0].length() == 0) 920 { 921 subAnyArray = NO_SUB_ANY; 922 } 923 else 924 { 925 subAnyArray = new ASN1OctetString[] 926 { 927 new ASN1OctetString(subAny[0]) 928 }; 929 } 930 } 931 else 932 { 933 subAnyArray = new ASN1OctetString[subAny.length]; 934 for (int i=0; i < subAny.length; i++) 935 { 936 Validator.ensureFalse(subAny[i].isEmpty(), 937 "Individual substring filter components must not be empty"); 938 subAnyArray[i] = new ASN1OctetString(subAny[i]); 939 } 940 } 941 } 942 943 final ASN1OctetString subFinalOS; 944 if ((subFinal == null) || subFinal.isEmpty()) 945 { 946 subFinalOS = null; 947 } 948 else 949 { 950 subFinalOS = new ASN1OctetString(subFinal); 951 } 952 953 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 954 attributeName, null, subInitialOS, subAnyArray, 955 subFinalOS, null, false); 956 } 957 958 959 960 /** 961 * Creates a new substring search filter with the provided information. At 962 * least one of the subInitial, subAny, and subFinal components must not be 963 * {@code null}. 964 * 965 * @param attributeName The attribute name for this substring filter. It 966 * must not be {@code null}. 967 * @param subInitial The subInitial component for this substring filter. 968 * It may be {@code null} if there is no subInitial 969 * component, but it must not be empty. 970 * @param subAny The set of subAny components for this substring 971 * filter. It may be {@code null} or empty if there 972 * are no subAny components. 973 * @param subFinal The subFinal component for this substring filter. 974 * It may be {@code null} if there is no subFinal 975 * component, but it must not be empty. 976 * 977 * @return The created substring search filter. 978 */ 979 @NotNull() 980 public static Filter createSubstringFilter( 981 @NotNull final String attributeName, 982 @Nullable final byte[] subInitial, 983 @Nullable final byte[][] subAny, 984 @Nullable final byte[] subFinal) 985 { 986 Validator.ensureNotNull(attributeName); 987 Validator.ensureTrue( 988 (((subInitial != null) && (subInitial.length > 0)) || 989 ((subAny != null) && (subAny.length > 0) && 990 (subAny[0].length > 0)) || 991 ((subFinal != null) && (subFinal.length > 0))), 992 "At least one substring filter component must be non-null and " + 993 "non-empty"); 994 995 final ASN1OctetString subInitialOS; 996 if ((subInitial == null) || (subInitial.length == 0)) 997 { 998 subInitialOS = null; 999 } 1000 else 1001 { 1002 subInitialOS = new ASN1OctetString(subInitial); 1003 } 1004 1005 final ASN1OctetString[] subAnyArray; 1006 if (subAny == null) 1007 { 1008 subAnyArray = NO_SUB_ANY; 1009 } 1010 else 1011 { 1012 if (subAny.length == 1) 1013 { 1014 if (subAny[0].length == 0) 1015 { 1016 subAnyArray = NO_SUB_ANY; 1017 } 1018 else 1019 { 1020 subAnyArray = new ASN1OctetString[] 1021 { 1022 new ASN1OctetString(subAny[0]) 1023 }; 1024 } 1025 } 1026 else 1027 { 1028 subAnyArray = new ASN1OctetString[subAny.length]; 1029 for (int i=0; i < subAny.length; i++) 1030 { 1031 Validator.ensureTrue((subAny[i].length > 0), 1032 "Individual substring filter components must not be empty"); 1033 subAnyArray[i] = new ASN1OctetString(subAny[i]); 1034 } 1035 } 1036 } 1037 1038 final ASN1OctetString subFinalOS; 1039 if ((subFinal == null) || (subFinal.length == 0)) 1040 { 1041 subFinalOS = null; 1042 } 1043 else 1044 { 1045 subFinalOS = new ASN1OctetString(subFinal); 1046 } 1047 1048 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 1049 attributeName, null, subInitialOS, subAnyArray, 1050 subFinalOS, null, false); 1051 } 1052 1053 1054 1055 /** 1056 * Creates a new substring search filter with the provided information. At 1057 * least one of the subInitial, subAny, and subFinal components must not be 1058 * {@code null}. 1059 * 1060 * @param attributeName The attribute name for this substring filter. It 1061 * must not be {@code null}. 1062 * @param subInitial The subInitial component for this substring filter. 1063 * It may be {@code null} if there is no subInitial 1064 * component, but it must not be empty. 1065 * @param subAny The set of subAny components for this substring 1066 * filter. It may be {@code null} or empty if there 1067 * are no subAny components. 1068 * @param subFinal The subFinal component for this substring filter. 1069 * It may be {@code null} if there is no subFinal 1070 * component, but it must not be empty. 1071 * 1072 * @return The created substring search filter. 1073 */ 1074 @NotNull() 1075 static Filter createSubstringFilter(@NotNull final String attributeName, 1076 @Nullable final ASN1OctetString subInitial, 1077 @Nullable final ASN1OctetString[] subAny, 1078 @Nullable final ASN1OctetString subFinal) 1079 { 1080 Validator.ensureNotNull(attributeName); 1081 Validator.ensureTrue((subInitial != null) || 1082 ((subAny != null) && (subAny.length > 0)) || 1083 (subFinal != null)); 1084 1085 if (subAny == null) 1086 { 1087 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 1088 attributeName, null, subInitial, NO_SUB_ANY, subFinal, 1089 null, false); 1090 } 1091 else 1092 { 1093 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 1094 attributeName, null, subInitial, subAny, subFinal, null, 1095 false); 1096 } 1097 } 1098 1099 1100 1101 /** 1102 * Creates a new substring search filter with only a subInitial (starts with) 1103 * component. 1104 * <BR><BR> 1105 * This method does exactly the same thing as 1106 * {@link #createSubInitialFilter(String,String)}, but with a shorter method 1107 * name for convenience. 1108 * 1109 * @param attributeName The attribute name for this substring filter. It 1110 * must not be {@code null}. 1111 * @param subInitial The subInitial component for this substring filter. 1112 * It must not be {@code null} or empty. 1113 * 1114 * @return The created substring search filter. 1115 */ 1116 @NotNull() 1117 public static Filter subInitial(@NotNull final String attributeName, 1118 @NotNull final String subInitial) 1119 { 1120 return createSubInitialFilter(attributeName, subInitial); 1121 } 1122 1123 1124 1125 /** 1126 * Creates a new substring search filter with only a subInitial (starts with) 1127 * component. 1128 * <BR><BR> 1129 * This method does exactly the same thing as 1130 * {@link #createSubInitialFilter(String,byte[])}, but with a shorter method 1131 * name for convenience. 1132 * 1133 * @param attributeName The attribute name for this substring filter. It 1134 * must not be {@code null}. 1135 * @param subInitial The subInitial component for this substring filter. 1136 * It must not be {@code null} or empty. 1137 * 1138 * @return The created substring search filter. 1139 */ 1140 @NotNull() 1141 public static Filter subInitial(@NotNull final String attributeName, 1142 @NotNull final byte[] subInitial) 1143 { 1144 return createSubInitialFilter(attributeName, subInitial); 1145 } 1146 1147 1148 1149 /** 1150 * Creates a new substring search filter with only a subInitial (starts with) 1151 * component. 1152 * 1153 * @param attributeName The attribute name for this substring filter. It 1154 * must not be {@code null}. 1155 * @param subInitial The subInitial component for this substring filter. 1156 * It must not be {@code null} or empty. 1157 * 1158 * @return The created substring search filter. 1159 */ 1160 @NotNull() 1161 public static Filter createSubInitialFilter( 1162 @NotNull final String attributeName, 1163 @NotNull final String subInitial) 1164 { 1165 return createSubstringFilter(attributeName, subInitial, null, null); 1166 } 1167 1168 1169 1170 /** 1171 * Creates a new substring search filter with only a subInitial (starts with) 1172 * component. 1173 * 1174 * @param attributeName The attribute name for this substring filter. It 1175 * must not be {@code null}. 1176 * @param subInitial The subInitial component for this substring filter. 1177 * It must not be {@code null} or empty. 1178 * 1179 * @return The created substring search filter. 1180 */ 1181 @NotNull() 1182 public static Filter createSubInitialFilter( 1183 @NotNull final String attributeName, 1184 @NotNull final byte[] subInitial) 1185 { 1186 return createSubstringFilter(attributeName, subInitial, null, null); 1187 } 1188 1189 1190 1191 /** 1192 * Creates a new substring search filter with only a subAny (contains) 1193 * component. 1194 * <BR><BR> 1195 * This method does exactly the same thing as 1196 * {@link #createSubAnyFilter(String,String...)}, but with a shorter method 1197 * name for convenience. 1198 * 1199 * @param attributeName The attribute name for this substring filter. It 1200 * must not be {@code null}. 1201 * @param subAny The subAny values for this substring filter. It 1202 * must not be {@code null} or empty. 1203 * 1204 * @return The created substring search filter. 1205 */ 1206 @NotNull() 1207 public static Filter subAny(@NotNull final String attributeName, 1208 @NotNull final String... subAny) 1209 { 1210 return createSubAnyFilter(attributeName, subAny); 1211 } 1212 1213 1214 1215 /** 1216 * Creates a new substring search filter with only a subAny (contains) 1217 * component. 1218 * <BR><BR> 1219 * This method does exactly the same thing as 1220 * {@link #createSubAnyFilter(String,byte[][])}, but with a shorter method 1221 * name for convenience. 1222 * 1223 * @param attributeName The attribute name for this substring filter. It 1224 * must not be {@code null}. 1225 * @param subAny The subAny values for this substring filter. It 1226 * must not be {@code null} or empty. 1227 * 1228 * @return The created substring search filter. 1229 */ 1230 @NotNull() 1231 public static Filter subAny(@NotNull final String attributeName, 1232 @NotNull final byte[]... subAny) 1233 { 1234 return createSubAnyFilter(attributeName, subAny); 1235 } 1236 1237 1238 1239 /** 1240 * Creates a new substring search filter with only a subAny (contains) 1241 * component. 1242 * 1243 * @param attributeName The attribute name for this substring filter. It 1244 * must not be {@code null}. 1245 * @param subAny The subAny values for this substring filter. It 1246 * must not be {@code null} or empty. 1247 * 1248 * @return The created substring search filter. 1249 */ 1250 @NotNull() 1251 public static Filter createSubAnyFilter(@NotNull final String attributeName, 1252 @NotNull final String... subAny) 1253 { 1254 return createSubstringFilter(attributeName, null, subAny, null); 1255 } 1256 1257 1258 1259 /** 1260 * Creates a new substring search filter with only a subAny (contains) 1261 * component. 1262 * 1263 * @param attributeName The attribute name for this substring filter. It 1264 * must not be {@code null}. 1265 * @param subAny The subAny values for this substring filter. It 1266 * must not be {@code null} or empty. 1267 * 1268 * @return The created substring search filter. 1269 */ 1270 @NotNull() 1271 public static Filter createSubAnyFilter(@NotNull final String attributeName, 1272 @NotNull final byte[]... subAny) 1273 { 1274 return createSubstringFilter(attributeName, null, subAny, null); 1275 } 1276 1277 1278 1279 /** 1280 * Creates a new substring search filter with only a subFinal (ends with) 1281 * component. 1282 * <BR><BR> 1283 * This method does exactly the same thing as 1284 * {@link #createSubFinalFilter(String,String)}, but with a shorter method 1285 * name for convenience. 1286 * 1287 * @param attributeName The attribute name for this substring filter. It 1288 * must not be {@code null}. 1289 * @param subFinal The subFinal component for this substring filter. 1290 * It must not be {@code null} or empty. 1291 * 1292 * @return The created substring search filter. 1293 */ 1294 @NotNull() 1295 public static Filter subFinal(@NotNull final String attributeName, 1296 @NotNull final String subFinal) 1297 { 1298 return createSubFinalFilter(attributeName, subFinal); 1299 } 1300 1301 1302 1303 /** 1304 * Creates a new substring search filter with only a subFinal (ends with) 1305 * component. 1306 * <BR><BR> 1307 * This method does exactly the same thing as 1308 * {@link #createSubFinalFilter(String,byte[])}, but with a shorter method 1309 * name for convenience. 1310 * 1311 * @param attributeName The attribute name for this substring filter. It 1312 * must not be {@code null}. 1313 * @param subFinal The subFinal component for this substring filter. 1314 * It must not be {@code null} or empty. 1315 * 1316 * @return The created substring search filter. 1317 */ 1318 @NotNull() 1319 public static Filter subFinal(@NotNull final String attributeName, 1320 @NotNull final byte[] subFinal) 1321 { 1322 return createSubFinalFilter(attributeName, subFinal); 1323 } 1324 1325 1326 1327 /** 1328 * Creates a new substring search filter with only a subFinal (ends with) 1329 * component. 1330 * 1331 * @param attributeName The attribute name for this substring filter. It 1332 * must not be {@code null}. 1333 * @param subFinal The subFinal component for this substring filter. 1334 * It must not be {@code null} or empty. 1335 * 1336 * @return The created substring search filter. 1337 */ 1338 @NotNull() 1339 public static Filter createSubFinalFilter(@NotNull final String attributeName, 1340 @NotNull final String subFinal) 1341 { 1342 return createSubstringFilter(attributeName, null, null, subFinal); 1343 } 1344 1345 1346 1347 /** 1348 * Creates a new substring search filter with only a subFinal (ends with) 1349 * component. 1350 * 1351 * @param attributeName The attribute name for this substring filter. It 1352 * must not be {@code null}. 1353 * @param subFinal The subFinal component for this substring filter. 1354 * It must not be {@code null} or empty. 1355 * 1356 * @return The created substring search filter. 1357 */ 1358 @NotNull() 1359 public static Filter createSubFinalFilter(@NotNull final String attributeName, 1360 @NotNull final byte[] subFinal) 1361 { 1362 return createSubstringFilter(attributeName, null, null, subFinal); 1363 } 1364 1365 1366 1367 /** 1368 * Creates a new greater-or-equal search filter with the provided information. 1369 * <BR><BR> 1370 * This method does exactly the same thing as 1371 * {@link #createGreaterOrEqualFilter(String,String)}, but with a shorter 1372 * method name for convenience. 1373 * 1374 * @param attributeName The attribute name for this greater-or-equal 1375 * filter. It must not be {@code null}. 1376 * @param assertionValue The assertion value for this greater-or-equal 1377 * filter. It must not be {@code null}. 1378 * 1379 * @return The created greater-or-equal search filter. 1380 */ 1381 @NotNull() 1382 public static Filter greaterOrEqual(@NotNull final String attributeName, 1383 @NotNull final String assertionValue) 1384 { 1385 return createGreaterOrEqualFilter(attributeName, assertionValue); 1386 } 1387 1388 1389 1390 /** 1391 * Creates a new greater-or-equal search filter with the provided information. 1392 * <BR><BR> 1393 * This method does exactly the same thing as 1394 * {@link #createGreaterOrEqualFilter(String,byte[])}, but with a shorter 1395 * method name for convenience. 1396 * 1397 * @param attributeName The attribute name for this greater-or-equal 1398 * filter. It must not be {@code null}. 1399 * @param assertionValue The assertion value for this greater-or-equal 1400 * filter. It must not be {@code null}. 1401 * 1402 * @return The created greater-or-equal search filter. 1403 */ 1404 @NotNull() 1405 public static Filter greaterOrEqual(@NotNull final String attributeName, 1406 @NotNull final byte[] assertionValue) 1407 { 1408 return createGreaterOrEqualFilter(attributeName, assertionValue); 1409 } 1410 1411 1412 1413 /** 1414 * Creates a new greater-or-equal search filter with the provided information. 1415 * 1416 * @param attributeName The attribute name for this greater-or-equal 1417 * filter. It must not be {@code null}. 1418 * @param assertionValue The assertion value for this greater-or-equal 1419 * filter. It must not be {@code null}. 1420 * 1421 * @return The created greater-or-equal search filter. 1422 */ 1423 @NotNull() 1424 public static Filter createGreaterOrEqualFilter( 1425 @NotNull final String attributeName, 1426 @NotNull final String assertionValue) 1427 { 1428 Validator.ensureNotNull(attributeName, assertionValue); 1429 1430 return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, 1431 attributeName, new ASN1OctetString(assertionValue), null, 1432 NO_SUB_ANY, null, null, false); 1433 } 1434 1435 1436 1437 /** 1438 * Creates a new greater-or-equal search filter with the provided information. 1439 * 1440 * @param attributeName The attribute name for this greater-or-equal 1441 * filter. It must not be {@code null}. 1442 * @param assertionValue The assertion value for this greater-or-equal 1443 * filter. It must not be {@code null}. 1444 * 1445 * @return The created greater-or-equal search filter. 1446 */ 1447 @NotNull() 1448 public static Filter createGreaterOrEqualFilter( 1449 @NotNull final String attributeName, 1450 @NotNull final byte[] assertionValue) 1451 { 1452 Validator.ensureNotNull(attributeName, assertionValue); 1453 1454 return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, 1455 attributeName, new ASN1OctetString(assertionValue), null, 1456 NO_SUB_ANY, null, null, false); 1457 } 1458 1459 1460 1461 /** 1462 * Creates a new greater-or-equal search filter with the provided information. 1463 * 1464 * @param attributeName The attribute name for this greater-or-equal 1465 * filter. It must not be {@code null}. 1466 * @param assertionValue The assertion value for this greater-or-equal 1467 * filter. It must not be {@code null}. 1468 * 1469 * @return The created greater-or-equal search filter. 1470 */ 1471 @NotNull() 1472 static Filter createGreaterOrEqualFilter( 1473 @NotNull final String attributeName, 1474 @NotNull final ASN1OctetString assertionValue) 1475 { 1476 Validator.ensureNotNull(attributeName, assertionValue); 1477 1478 return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, 1479 attributeName, assertionValue, null, NO_SUB_ANY, null, 1480 null, false); 1481 } 1482 1483 1484 1485 /** 1486 * Creates a new less-or-equal search filter with the provided information. 1487 * <BR><BR> 1488 * This method does exactly the same thing as 1489 * {@link #createLessOrEqualFilter(String,String)}, but with a shorter method 1490 * name for convenience. 1491 * 1492 * @param attributeName The attribute name for this less-or-equal 1493 * filter. It must not be {@code null}. 1494 * @param assertionValue The assertion value for this less-or-equal 1495 * filter. It must not be {@code null}. 1496 * 1497 * @return The created less-or-equal search filter. 1498 */ 1499 @NotNull() 1500 public static Filter lessOrEqual(@NotNull final String attributeName, 1501 @NotNull final String assertionValue) 1502 { 1503 return createLessOrEqualFilter(attributeName, assertionValue); 1504 } 1505 1506 1507 1508 /** 1509 * Creates a new less-or-equal search filter with the provided information. 1510 * <BR><BR> 1511 * This method does exactly the same thing as 1512 * {@link #createLessOrEqualFilter(String,byte[])}, but with a shorter method 1513 * name for convenience. 1514 * 1515 * @param attributeName The attribute name for this less-or-equal 1516 * filter. It must not be {@code null}. 1517 * @param assertionValue The assertion value for this less-or-equal 1518 * filter. It must not be {@code null}. 1519 * 1520 * @return The created less-or-equal search filter. 1521 */ 1522 @NotNull() 1523 public static Filter lessOrEqual(@NotNull final String attributeName, 1524 @NotNull final byte[] assertionValue) 1525 { 1526 return createLessOrEqualFilter(attributeName, assertionValue); 1527 } 1528 1529 1530 1531 /** 1532 * Creates a new less-or-equal search filter with the provided information. 1533 * 1534 * @param attributeName The attribute name for this less-or-equal 1535 * filter. It must not be {@code null}. 1536 * @param assertionValue The assertion value for this less-or-equal 1537 * filter. It must not be {@code null}. 1538 * 1539 * @return The created less-or-equal search filter. 1540 */ 1541 @NotNull() 1542 public static Filter createLessOrEqualFilter( 1543 @NotNull final String attributeName, 1544 @NotNull final String assertionValue) 1545 { 1546 Validator.ensureNotNull(attributeName, assertionValue); 1547 1548 return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, 1549 attributeName, new ASN1OctetString(assertionValue), null, 1550 NO_SUB_ANY, null, null, false); 1551 } 1552 1553 1554 1555 /** 1556 * Creates a new less-or-equal search filter with the provided information. 1557 * 1558 * @param attributeName The attribute name for this less-or-equal 1559 * filter. It must not be {@code null}. 1560 * @param assertionValue The assertion value for this less-or-equal 1561 * filter. It must not be {@code null}. 1562 * 1563 * @return The created less-or-equal search filter. 1564 */ 1565 @NotNull() 1566 public static Filter createLessOrEqualFilter( 1567 @NotNull final String attributeName, 1568 @NotNull final byte[] assertionValue) 1569 { 1570 Validator.ensureNotNull(attributeName, assertionValue); 1571 1572 return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, 1573 attributeName, new ASN1OctetString(assertionValue), null, 1574 NO_SUB_ANY, null, null, false); 1575 } 1576 1577 1578 1579 /** 1580 * Creates a new less-or-equal search filter with the provided information. 1581 * 1582 * @param attributeName The attribute name for this less-or-equal 1583 * filter. It must not be {@code null}. 1584 * @param assertionValue The assertion value for this less-or-equal 1585 * filter. It must not be {@code null}. 1586 * 1587 * @return The created less-or-equal search filter. 1588 */ 1589 @NotNull() 1590 static Filter createLessOrEqualFilter( 1591 @NotNull final String attributeName, 1592 @NotNull final ASN1OctetString assertionValue) 1593 { 1594 Validator.ensureNotNull(attributeName, assertionValue); 1595 1596 return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, 1597 attributeName, assertionValue, null, NO_SUB_ANY, null, 1598 null, false); 1599 } 1600 1601 1602 1603 /** 1604 * Creates a new presence search filter with the provided information. 1605 * <BR><BR> 1606 * This method does exactly the same thing as 1607 * {@link #createPresenceFilter(String)}, but with a shorter method name for 1608 * convenience. 1609 * 1610 * @param attributeName The attribute name for this presence filter. It 1611 * must not be {@code null}. 1612 * 1613 * @return The created presence search filter. 1614 */ 1615 @NotNull() 1616 public static Filter present(@NotNull final String attributeName) 1617 { 1618 return createPresenceFilter(attributeName); 1619 } 1620 1621 1622 1623 /** 1624 * Creates a new presence search filter with the provided information. 1625 * 1626 * @param attributeName The attribute name for this presence filter. It 1627 * must not be {@code null}. 1628 * 1629 * @return The created presence search filter. 1630 */ 1631 @NotNull() 1632 public static Filter createPresenceFilter(@NotNull final String attributeName) 1633 { 1634 Validator.ensureNotNull(attributeName); 1635 1636 return new Filter(null, FILTER_TYPE_PRESENCE, NO_FILTERS, null, 1637 attributeName, null, null, NO_SUB_ANY, null, null, false); 1638 } 1639 1640 1641 1642 /** 1643 * Creates a new approximate match search filter with the provided 1644 * information. 1645 * <BR><BR> 1646 * This method does exactly the same thing as 1647 * {@link #createApproximateMatchFilter(String,String)}, but with a shorter 1648 * method name for convenience. 1649 * 1650 * @param attributeName The attribute name for this approximate match 1651 * filter. It must not be {@code null}. 1652 * @param assertionValue The assertion value for this approximate match 1653 * filter. It must not be {@code null}. 1654 * 1655 * @return The created approximate match search filter. 1656 */ 1657 @NotNull() 1658 public static Filter approximateMatch(@NotNull final String attributeName, 1659 @NotNull final String assertionValue) 1660 { 1661 return createApproximateMatchFilter(attributeName, assertionValue); 1662 } 1663 1664 1665 1666 /** 1667 * Creates a new approximate match search filter with the provided 1668 * information. 1669 * <BR><BR> 1670 * This method does exactly the same thing as 1671 * {@link #createApproximateMatchFilter(String,byte[])}, but with a shorter 1672 * method name for convenience. 1673 * 1674 * @param attributeName The attribute name for this approximate match 1675 * filter. It must not be {@code null}. 1676 * @param assertionValue The assertion value for this approximate match 1677 * filter. It must not be {@code null}. 1678 * 1679 * @return The created approximate match search filter. 1680 */ 1681 @NotNull() 1682 public static Filter approximateMatch(@NotNull final String attributeName, 1683 @NotNull final byte[] assertionValue) 1684 { 1685 return createApproximateMatchFilter(attributeName, assertionValue); 1686 } 1687 1688 1689 1690 /** 1691 * Creates a new approximate match search filter with the provided 1692 * information. 1693 * 1694 * @param attributeName The attribute name for this approximate match 1695 * filter. It must not be {@code null}. 1696 * @param assertionValue The assertion value for this approximate match 1697 * filter. It must not be {@code null}. 1698 * 1699 * @return The created approximate match search filter. 1700 */ 1701 @NotNull() 1702 public static Filter createApproximateMatchFilter( 1703 @NotNull final String attributeName, 1704 @NotNull final String assertionValue) 1705 { 1706 Validator.ensureNotNull(attributeName, assertionValue); 1707 1708 return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, 1709 attributeName, new ASN1OctetString(assertionValue), null, 1710 NO_SUB_ANY, null, null, false); 1711 } 1712 1713 1714 1715 /** 1716 * Creates a new approximate match search filter with the provided 1717 * information. 1718 * 1719 * @param attributeName The attribute name for this approximate match 1720 * filter. It must not be {@code null}. 1721 * @param assertionValue The assertion value for this approximate match 1722 * filter. It must not be {@code null}. 1723 * 1724 * @return The created approximate match search filter. 1725 */ 1726 @NotNull() 1727 public static Filter createApproximateMatchFilter( 1728 @NotNull final String attributeName, 1729 @NotNull final byte[] assertionValue) 1730 { 1731 Validator.ensureNotNull(attributeName, assertionValue); 1732 1733 return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, 1734 attributeName, new ASN1OctetString(assertionValue), null, 1735 NO_SUB_ANY, null, null, false); 1736 } 1737 1738 1739 1740 /** 1741 * Creates a new approximate match search filter with the provided 1742 * information. 1743 * 1744 * @param attributeName The attribute name for this approximate match 1745 * filter. It must not be {@code null}. 1746 * @param assertionValue The assertion value for this approximate match 1747 * filter. It must not be {@code null}. 1748 * 1749 * @return The created approximate match search filter. 1750 */ 1751 @NotNull() 1752 static Filter createApproximateMatchFilter( 1753 @NotNull final String attributeName, 1754 @NotNull final ASN1OctetString assertionValue) 1755 { 1756 Validator.ensureNotNull(attributeName, assertionValue); 1757 1758 return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, 1759 attributeName, assertionValue, null, NO_SUB_ANY, null, 1760 null, false); 1761 } 1762 1763 1764 1765 /** 1766 * Creates a new extensible match search filter with the provided 1767 * information. At least one of the attribute name and matching rule ID must 1768 * be specified, and the assertion value must always be present. 1769 * <BR><BR> 1770 * This method does exactly the same thing as 1771 * {@link #createExtensibleMatchFilter(String,String,boolean,String)}, but 1772 * with a shorter method name for convenience. 1773 * 1774 * @param attributeName The attribute name for this extensible match 1775 * filter. 1776 * @param matchingRuleID The matching rule ID for this extensible match 1777 * filter. 1778 * @param dnAttributes Indicates whether the match should be performed 1779 * against attributes in the target entry's DN. 1780 * @param assertionValue The assertion value for this extensible match 1781 * filter. It must not be {@code null}. 1782 * 1783 * @return The created extensible match search filter. 1784 */ 1785 @NotNull() 1786 public static Filter extensibleMatch(@Nullable final String attributeName, 1787 @Nullable final String matchingRuleID, 1788 final boolean dnAttributes, 1789 @NotNull final String assertionValue) 1790 { 1791 return createExtensibleMatchFilter(attributeName, matchingRuleID, 1792 dnAttributes, assertionValue); 1793 } 1794 1795 1796 1797 /** 1798 * Creates a new extensible match search filter with the provided 1799 * information. At least one of the attribute name and matching rule ID must 1800 * be specified, and the assertion value must always be present. 1801 * <BR><BR> 1802 * This method does exactly the same thing as 1803 * {@link #createExtensibleMatchFilter(String,String,boolean,byte[])}, but 1804 * with a shorter method name for convenience. 1805 * 1806 * @param attributeName The attribute name for this extensible match 1807 * filter. 1808 * @param matchingRuleID The matching rule ID for this extensible match 1809 * filter. 1810 * @param dnAttributes Indicates whether the match should be performed 1811 * against attributes in the target entry's DN. 1812 * @param assertionValue The assertion value for this extensible match 1813 * filter. It must not be {@code null}. 1814 * 1815 * @return The created extensible match search filter. 1816 */ 1817 @NotNull() 1818 public static Filter extensibleMatch(@Nullable final String attributeName, 1819 @Nullable final String matchingRuleID, 1820 final boolean dnAttributes, 1821 @NotNull final byte[] assertionValue) 1822 { 1823 return createExtensibleMatchFilter(attributeName, matchingRuleID, 1824 dnAttributes, assertionValue); 1825 } 1826 1827 1828 1829 /** 1830 * Creates a new extensible match search filter with the provided 1831 * information. At least one of the attribute name and matching rule ID must 1832 * be specified, and the assertion value must always be present. 1833 * 1834 * @param attributeName The attribute name for this extensible match 1835 * filter. 1836 * @param matchingRuleID The matching rule ID for this extensible match 1837 * filter. 1838 * @param dnAttributes Indicates whether the match should be performed 1839 * against attributes in the target entry's DN. 1840 * @param assertionValue The assertion value for this extensible match 1841 * filter. It must not be {@code null}. 1842 * 1843 * @return The created extensible match search filter. 1844 */ 1845 @NotNull() 1846 public static Filter createExtensibleMatchFilter( 1847 @Nullable final String attributeName, 1848 @Nullable final String matchingRuleID, 1849 final boolean dnAttributes, 1850 @NotNull final String assertionValue) 1851 { 1852 Validator.ensureNotNull(assertionValue); 1853 Validator.ensureFalse((attributeName == null) && (matchingRuleID == null)); 1854 1855 return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, 1856 attributeName, new ASN1OctetString(assertionValue), null, 1857 NO_SUB_ANY, null, matchingRuleID, dnAttributes); 1858 } 1859 1860 1861 1862 /** 1863 * Creates a new extensible match search filter with the provided 1864 * information. At least one of the attribute name and matching rule ID must 1865 * be specified, and the assertion value must always be present. 1866 * 1867 * @param attributeName The attribute name for this extensible match 1868 * filter. 1869 * @param matchingRuleID The matching rule ID for this extensible match 1870 * filter. 1871 * @param dnAttributes Indicates whether the match should be performed 1872 * against attributes in the target entry's DN. 1873 * @param assertionValue The assertion value for this extensible match 1874 * filter. It must not be {@code null}. 1875 * 1876 * @return The created extensible match search filter. 1877 */ 1878 @NotNull() 1879 public static Filter createExtensibleMatchFilter( 1880 @Nullable final String attributeName, 1881 @Nullable final String matchingRuleID, 1882 final boolean dnAttributes, 1883 @NotNull final byte[] assertionValue) 1884 { 1885 Validator.ensureNotNull(assertionValue); 1886 Validator.ensureFalse((attributeName == null) && (matchingRuleID == null)); 1887 1888 return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, 1889 attributeName, new ASN1OctetString(assertionValue), null, 1890 NO_SUB_ANY, null, matchingRuleID, dnAttributes); 1891 } 1892 1893 1894 1895 /** 1896 * Creates a new extensible match search filter with the provided 1897 * information. At least one of the attribute name and matching rule ID must 1898 * be specified, and the assertion value must always be present. 1899 * 1900 * @param attributeName The attribute name for this extensible match 1901 * filter. 1902 * @param matchingRuleID The matching rule ID for this extensible match 1903 * filter. 1904 * @param dnAttributes Indicates whether the match should be performed 1905 * against attributes in the target entry's DN. 1906 * @param assertionValue The assertion value for this extensible match 1907 * filter. It must not be {@code null}. 1908 * 1909 * @return The created approximate match search filter. 1910 */ 1911 @NotNull() 1912 static Filter createExtensibleMatchFilter( 1913 @Nullable final String attributeName, 1914 @Nullable final String matchingRuleID, 1915 final boolean dnAttributes, 1916 @NotNull final ASN1OctetString assertionValue) 1917 { 1918 Validator.ensureNotNull(assertionValue); 1919 Validator.ensureFalse((attributeName == null) && (matchingRuleID == null)); 1920 1921 return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, 1922 attributeName, assertionValue, null, NO_SUB_ANY, null, 1923 matchingRuleID, dnAttributes); 1924 } 1925 1926 1927 1928 /** 1929 * Creates a new search filter from the provided string representation. 1930 * 1931 * @param filterString The string representation of the filter to create. 1932 * It must not be {@code null}. 1933 * 1934 * @return The search filter decoded from the provided filter string. 1935 * 1936 * @throws LDAPException If the provided string cannot be decoded as a valid 1937 * LDAP search filter. 1938 */ 1939 @NotNull() 1940 public static Filter create(@NotNull final String filterString) 1941 throws LDAPException 1942 { 1943 Validator.ensureNotNull(filterString); 1944 1945 return create(filterString, 0, (filterString.length() - 1), 0); 1946 } 1947 1948 1949 1950 /** 1951 * Creates a new search filter from the specified portion of the provided 1952 * string representation. 1953 * 1954 * @param filterString The string representation of the filter to create. 1955 * @param startPos The position of the first character to consider as 1956 * part of the filter. 1957 * @param endPos The position of the last character to consider as 1958 * part of the filter. 1959 * @param depth The current nesting depth for this filter. It should 1960 * be increased by one for each AND, OR, or NOT filter 1961 * encountered, in order to prevent stack overflow 1962 * errors from excessive recursion. 1963 * 1964 * @return The decoded search filter. 1965 * 1966 * @throws LDAPException If the provided string cannot be decoded as a valid 1967 * LDAP search filter. 1968 */ 1969 @NotNull() 1970 private static Filter create(@NotNull final String filterString, 1971 final int startPos, final int endPos, 1972 final int depth) 1973 throws LDAPException 1974 { 1975 if (depth > 100) 1976 { 1977 throw new LDAPException(ResultCode.FILTER_ERROR, 1978 ERR_FILTER_TOO_DEEP.get(filterString)); 1979 } 1980 1981 final byte filterType; 1982 final Filter[] filterComps; 1983 final Filter notComp; 1984 final String attrName; 1985 final ASN1OctetString assertionValue; 1986 final ASN1OctetString subInitial; 1987 final ASN1OctetString[] subAny; 1988 final ASN1OctetString subFinal; 1989 final String matchingRuleID; 1990 final boolean dnAttributes; 1991 1992 if (startPos >= endPos) 1993 { 1994 throw new LDAPException(ResultCode.FILTER_ERROR, 1995 ERR_FILTER_TOO_SHORT.get(filterString)); 1996 } 1997 1998 int l = startPos; 1999 int r = endPos; 2000 2001 // First, see if the provided filter string is enclosed in parentheses, like 2002 // it should be. If so, then strip off the outer parentheses. 2003 if (filterString.charAt(l) == '(') 2004 { 2005 if (filterString.charAt(r) == ')') 2006 { 2007 l++; 2008 r--; 2009 } 2010 else 2011 { 2012 throw new LDAPException(ResultCode.FILTER_ERROR, 2013 ERR_FILTER_OPEN_WITHOUT_CLOSE.get(filterString, l, r)); 2014 } 2015 } 2016 else 2017 { 2018 // This is technically an error, and it's a bad practice. If we're 2019 // working on the complete filter string then we'll let it slide, but 2020 // otherwise we'll raise an error. 2021 if (l != 0) 2022 { 2023 throw new LDAPException(ResultCode.FILTER_ERROR, 2024 ERR_FILTER_MISSING_PARENTHESES.get(filterString, 2025 filterString.substring(l, r+1))); 2026 } 2027 } 2028 2029 2030 // Look at the first character of the filter to see if it's an '&', '|', or 2031 // '!'. If we find a parenthesis, then that's an error. 2032 switch (filterString.charAt(l)) 2033 { 2034 case '&': 2035 filterType = FILTER_TYPE_AND; 2036 filterComps = parseFilterComps(filterString, l+1, r, depth+1); 2037 notComp = null; 2038 attrName = null; 2039 assertionValue = null; 2040 subInitial = null; 2041 subAny = NO_SUB_ANY; 2042 subFinal = null; 2043 matchingRuleID = null; 2044 dnAttributes = false; 2045 break; 2046 2047 case '|': 2048 filterType = FILTER_TYPE_OR; 2049 filterComps = parseFilterComps(filterString, l+1, r, depth+1); 2050 notComp = null; 2051 attrName = null; 2052 assertionValue = null; 2053 subInitial = null; 2054 subAny = NO_SUB_ANY; 2055 subFinal = null; 2056 matchingRuleID = null; 2057 dnAttributes = false; 2058 break; 2059 2060 case '!': 2061 filterType = FILTER_TYPE_NOT; 2062 filterComps = NO_FILTERS; 2063 notComp = create(filterString, l+1, r, depth+1); 2064 attrName = null; 2065 assertionValue = null; 2066 subInitial = null; 2067 subAny = NO_SUB_ANY; 2068 subFinal = null; 2069 matchingRuleID = null; 2070 dnAttributes = false; 2071 break; 2072 2073 case '(': 2074 throw new LDAPException(ResultCode.FILTER_ERROR, 2075 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l)); 2076 2077 case ':': 2078 // This must be an extensible matching filter that starts with a 2079 // dnAttributes flag and/or matching rule ID, and we should parse it 2080 // accordingly. 2081 filterType = FILTER_TYPE_EXTENSIBLE_MATCH; 2082 filterComps = NO_FILTERS; 2083 notComp = null; 2084 attrName = null; 2085 subInitial = null; 2086 subAny = NO_SUB_ANY; 2087 subFinal = null; 2088 2089 // The next element must be either the "dn:{matchingruleid}" or just 2090 // "{matchingruleid}", and it must be followed by a colon. 2091 final int dnMRIDStart = ++l; 2092 while ((l <= r) && (filterString.charAt(l) != ':')) 2093 { 2094 l++; 2095 } 2096 2097 if (l > r) 2098 { 2099 throw new LDAPException(ResultCode.FILTER_ERROR, 2100 ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos)); 2101 } 2102 else if (l == dnMRIDStart) 2103 { 2104 throw new LDAPException(ResultCode.FILTER_ERROR, 2105 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 2106 } 2107 final String s = filterString.substring(dnMRIDStart, l++); 2108 if (s.equalsIgnoreCase("dn")) 2109 { 2110 dnAttributes = true; 2111 2112 // The colon must be followed by the matching rule ID and another 2113 // colon. 2114 final int mrIDStart = l; 2115 while ((l < r) && (filterString.charAt(l) != ':')) 2116 { 2117 l++; 2118 } 2119 2120 if (l >= r) 2121 { 2122 throw new LDAPException(ResultCode.FILTER_ERROR, 2123 ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos)); 2124 } 2125 2126 matchingRuleID = filterString.substring(mrIDStart, l); 2127 if (matchingRuleID.isEmpty()) 2128 { 2129 throw new LDAPException(ResultCode.FILTER_ERROR, 2130 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 2131 } 2132 2133 if ((++l > r) || (filterString.charAt(l) != '=')) 2134 { 2135 throw new LDAPException(ResultCode.FILTER_ERROR, 2136 ERR_FILTER_UNEXPECTED_CHAR_AFTER_MRID.get(filterString, 2137 startPos, filterString.charAt(l))); 2138 } 2139 } 2140 else 2141 { 2142 matchingRuleID = s; 2143 dnAttributes = false; 2144 2145 // The colon must be followed by an equal sign. 2146 if ((l > r) || (filterString.charAt(l) != '=')) 2147 { 2148 throw new LDAPException(ResultCode.FILTER_ERROR, 2149 ERR_FILTER_NO_EQUAL_AFTER_MRID.get(filterString, startPos)); 2150 } 2151 } 2152 2153 // Now we should be able to read the value, handling any escape 2154 // characters as we go. 2155 l++; 2156 final ByteStringBuffer valueBuffer = new ByteStringBuffer(r - l + 1); 2157 while (l <= r) 2158 { 2159 final char c = filterString.charAt(l); 2160 if (c == '\\') 2161 { 2162 l = readEscapedHexString(filterString, ++l, valueBuffer); 2163 } 2164 else if (c == '(') 2165 { 2166 throw new LDAPException(ResultCode.FILTER_ERROR, 2167 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l)); 2168 } 2169 else if (c == ')') 2170 { 2171 throw new LDAPException(ResultCode.FILTER_ERROR, 2172 ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l)); 2173 } 2174 else 2175 { 2176 valueBuffer.append(c); 2177 l++; 2178 } 2179 } 2180 assertionValue = new ASN1OctetString(valueBuffer.toByteArray()); 2181 break; 2182 2183 2184 default: 2185 // We know that it's not an AND, OR, or NOT filter, so we can eliminate 2186 // the variables used only for them. 2187 filterComps = NO_FILTERS; 2188 notComp = null; 2189 2190 2191 // We should now be able to read a non-empty attribute name. 2192 final int attrStartPos = l; 2193 int attrEndPos = -1; 2194 byte tempFilterType = 0x00; 2195 boolean filterTypeKnown = false; 2196 boolean equalFound = false; 2197attrNameLoop: 2198 while (l <= r) 2199 { 2200 final char c = filterString.charAt(l++); 2201 switch (c) 2202 { 2203 case ':': 2204 tempFilterType = FILTER_TYPE_EXTENSIBLE_MATCH; 2205 filterTypeKnown = true; 2206 attrEndPos = l - 1; 2207 break attrNameLoop; 2208 2209 case '>': 2210 tempFilterType = FILTER_TYPE_GREATER_OR_EQUAL; 2211 filterTypeKnown = true; 2212 attrEndPos = l - 1; 2213 2214 if (l <= r) 2215 { 2216 if (filterString.charAt(l++) != '=') 2217 { 2218 throw new LDAPException(ResultCode.FILTER_ERROR, 2219 ERR_FILTER_UNEXPECTED_CHAR_AFTER_GT.get(filterString, 2220 startPos, filterString.charAt(l-1))); 2221 } 2222 } 2223 else 2224 { 2225 throw new LDAPException(ResultCode.FILTER_ERROR, 2226 ERR_FILTER_END_AFTER_GT.get(filterString, startPos)); 2227 } 2228 break attrNameLoop; 2229 2230 case '<': 2231 tempFilterType = FILTER_TYPE_LESS_OR_EQUAL; 2232 filterTypeKnown = true; 2233 attrEndPos = l - 1; 2234 2235 if (l <= r) 2236 { 2237 if (filterString.charAt(l++) != '=') 2238 { 2239 throw new LDAPException(ResultCode.FILTER_ERROR, 2240 ERR_FILTER_UNEXPECTED_CHAR_AFTER_LT.get(filterString, 2241 startPos, filterString.charAt(l-1))); 2242 } 2243 } 2244 else 2245 { 2246 throw new LDAPException(ResultCode.FILTER_ERROR, 2247 ERR_FILTER_END_AFTER_LT.get(filterString, startPos)); 2248 } 2249 break attrNameLoop; 2250 2251 case '~': 2252 tempFilterType = FILTER_TYPE_APPROXIMATE_MATCH; 2253 filterTypeKnown = true; 2254 attrEndPos = l - 1; 2255 2256 if (l <= r) 2257 { 2258 if (filterString.charAt(l++) != '=') 2259 { 2260 throw new LDAPException(ResultCode.FILTER_ERROR, 2261 ERR_FILTER_UNEXPECTED_CHAR_AFTER_TILDE.get(filterString, 2262 startPos, filterString.charAt(l-1))); 2263 } 2264 } 2265 else 2266 { 2267 throw new LDAPException(ResultCode.FILTER_ERROR, 2268 ERR_FILTER_END_AFTER_TILDE.get(filterString, startPos)); 2269 } 2270 break attrNameLoop; 2271 2272 case '=': 2273 // It could be either an equality, presence, or substring filter. 2274 // We'll need to look at the value to determine that. 2275 attrEndPos = l - 1; 2276 equalFound = true; 2277 break attrNameLoop; 2278 } 2279 } 2280 2281 if (attrEndPos <= attrStartPos) 2282 { 2283 if (equalFound) 2284 { 2285 throw new LDAPException(ResultCode.FILTER_ERROR, 2286 ERR_FILTER_EMPTY_ATTR_NAME.get(filterString, startPos)); 2287 } 2288 else 2289 { 2290 throw new LDAPException(ResultCode.FILTER_ERROR, 2291 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos)); 2292 } 2293 } 2294 attrName = filterString.substring(attrStartPos, attrEndPos); 2295 2296 2297 // See if we're dealing with an extensible match filter. If so, then 2298 // we may still need to do additional parsing to get the matching rule 2299 // ID and/or the dnAttributes flag. Otherwise, we can rule out any 2300 // variables that are specific to extensible matching filters. 2301 if (filterTypeKnown && (tempFilterType == FILTER_TYPE_EXTENSIBLE_MATCH)) 2302 { 2303 if (l > r) 2304 { 2305 throw new LDAPException(ResultCode.FILTER_ERROR, 2306 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos)); 2307 } 2308 2309 final char c = filterString.charAt(l++); 2310 if (c == '=') 2311 { 2312 matchingRuleID = null; 2313 dnAttributes = false; 2314 } 2315 else 2316 { 2317 // We have either a matching rule ID or a dnAttributes flag, or 2318 // both. Iterate through the filter until we find the equal sign, 2319 // and then figure out what we have from that. 2320 equalFound = false; 2321 final int substrStartPos = l - 1; 2322 while (l <= r) 2323 { 2324 if (filterString.charAt(l++) == '=') 2325 { 2326 equalFound = true; 2327 break; 2328 } 2329 } 2330 2331 if (! equalFound) 2332 { 2333 throw new LDAPException(ResultCode.FILTER_ERROR, 2334 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos)); 2335 } 2336 2337 final String substr = filterString.substring(substrStartPos, l-1); 2338 final String lowerSubstr = StaticUtils.toLowerCase(substr); 2339 if (! substr.endsWith(":")) 2340 { 2341 throw new LDAPException(ResultCode.FILTER_ERROR, 2342 ERR_FILTER_CANNOT_PARSE_MRID.get(filterString, startPos)); 2343 } 2344 2345 if (lowerSubstr.equals("dn:")) 2346 { 2347 matchingRuleID = null; 2348 dnAttributes = true; 2349 } 2350 else if (lowerSubstr.startsWith("dn:")) 2351 { 2352 matchingRuleID = substr.substring(3, substr.length() - 1); 2353 if (matchingRuleID.isEmpty()) 2354 { 2355 throw new LDAPException(ResultCode.FILTER_ERROR, 2356 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 2357 } 2358 2359 dnAttributes = true; 2360 } 2361 else 2362 { 2363 matchingRuleID = substr.substring(0, substr.length() - 1); 2364 dnAttributes = false; 2365 2366 if (matchingRuleID.isEmpty()) 2367 { 2368 throw new LDAPException(ResultCode.FILTER_ERROR, 2369 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 2370 } 2371 } 2372 } 2373 } 2374 else 2375 { 2376 matchingRuleID = null; 2377 dnAttributes = false; 2378 } 2379 2380 2381 // At this point, we're ready to read the value. If we still don't 2382 // know what type of filter we're dealing with, then we can tell that 2383 // based on asterisks in the value. 2384 if (l > r) 2385 { 2386 assertionValue = new ASN1OctetString(); 2387 if (! filterTypeKnown) 2388 { 2389 tempFilterType = FILTER_TYPE_EQUALITY; 2390 } 2391 2392 subInitial = null; 2393 subAny = NO_SUB_ANY; 2394 subFinal = null; 2395 } 2396 else if (l == r) 2397 { 2398 if (filterTypeKnown) 2399 { 2400 switch (filterString.charAt(l)) 2401 { 2402 case '*': 2403 case '(': 2404 case ')': 2405 case '\\': 2406 throw new LDAPException(ResultCode.FILTER_ERROR, 2407 ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString, 2408 startPos, filterString.charAt(l))); 2409 } 2410 2411 assertionValue = 2412 new ASN1OctetString(filterString.substring(l, l+1)); 2413 } 2414 else 2415 { 2416 final char c = filterString.charAt(l); 2417 switch (c) 2418 { 2419 case '*': 2420 tempFilterType = FILTER_TYPE_PRESENCE; 2421 assertionValue = null; 2422 break; 2423 2424 case '\\': 2425 case '(': 2426 case ')': 2427 throw new LDAPException(ResultCode.FILTER_ERROR, 2428 ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString, 2429 startPos, filterString.charAt(l))); 2430 2431 default: 2432 tempFilterType = FILTER_TYPE_EQUALITY; 2433 assertionValue = 2434 new ASN1OctetString(filterString.substring(l, l+1)); 2435 break; 2436 } 2437 } 2438 2439 subInitial = null; 2440 subAny = NO_SUB_ANY; 2441 subFinal = null; 2442 } 2443 else 2444 { 2445 if (! filterTypeKnown) 2446 { 2447 tempFilterType = FILTER_TYPE_EQUALITY; 2448 } 2449 2450 final int valueStartPos = l; 2451 ASN1OctetString tempSubInitial = null; 2452 ASN1OctetString tempSubFinal = null; 2453 final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1); 2454 ByteStringBuffer buffer = new ByteStringBuffer(r - l + 1); 2455 while (l <= r) 2456 { 2457 final char c = filterString.charAt(l++); 2458 switch (c) 2459 { 2460 case '*': 2461 if (filterTypeKnown) 2462 { 2463 throw new LDAPException(ResultCode.FILTER_ERROR, 2464 ERR_FILTER_UNEXPECTED_ASTERISK.get(filterString, 2465 startPos)); 2466 } 2467 else 2468 { 2469 if ((l-1) == valueStartPos) 2470 { 2471 // The first character is an asterisk, so there is no 2472 // subInitial. 2473 } 2474 else 2475 { 2476 if (tempFilterType == FILTER_TYPE_SUBSTRING) 2477 { 2478 // We already know that it's a substring filter, so this 2479 // must be a subAny portion. However, if the buffer is 2480 // empty, then that means that there were two asterisks 2481 // right next to each other, which is invalid. 2482 if (buffer.length() == 0) 2483 { 2484 throw new LDAPException(ResultCode.FILTER_ERROR, 2485 ERR_FILTER_UNEXPECTED_DOUBLE_ASTERISK.get( 2486 filterString, startPos)); 2487 } 2488 else 2489 { 2490 subAnyList.add( 2491 new ASN1OctetString(buffer.toByteArray())); 2492 buffer = new ByteStringBuffer(r - l + 1); 2493 } 2494 } 2495 else 2496 { 2497 // We haven't yet set the filter type, so the buffer must 2498 // contain the subInitial portion. We also know it's not 2499 // empty because of an earlier check. 2500 tempSubInitial = 2501 new ASN1OctetString(buffer.toByteArray()); 2502 buffer = new ByteStringBuffer(r - l + 1); 2503 } 2504 } 2505 2506 tempFilterType = FILTER_TYPE_SUBSTRING; 2507 } 2508 break; 2509 2510 case '\\': 2511 l = readEscapedHexString(filterString, l, buffer); 2512 break; 2513 2514 case '(': 2515 throw new LDAPException(ResultCode.FILTER_ERROR, 2516 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l)); 2517 2518 case ')': 2519 throw new LDAPException(ResultCode.FILTER_ERROR, 2520 ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l)); 2521 2522 default: 2523 if (Character.isHighSurrogate(c)) 2524 { 2525 if (l <= r) 2526 { 2527 final char c2 = filterString.charAt(l); 2528 if (Character.isLowSurrogate(c2)) 2529 { 2530 l++; 2531 final int codePoint = Character.toCodePoint(c, c2); 2532 buffer.append(new String(new int[] { codePoint }, 0, 1)); 2533 break; 2534 } 2535 } 2536 } 2537 2538 buffer.append(c); 2539 break; 2540 } 2541 } 2542 2543 if ((tempFilterType == FILTER_TYPE_SUBSTRING) && 2544 (! buffer.isEmpty())) 2545 { 2546 // The buffer must contain the subFinal portion. 2547 tempSubFinal = new ASN1OctetString(buffer.toByteArray()); 2548 } 2549 2550 subInitial = tempSubInitial; 2551 subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]); 2552 subFinal = tempSubFinal; 2553 2554 if (tempFilterType == FILTER_TYPE_SUBSTRING) 2555 { 2556 assertionValue = null; 2557 } 2558 else 2559 { 2560 assertionValue = new ASN1OctetString(buffer.toByteArray()); 2561 } 2562 } 2563 2564 filterType = tempFilterType; 2565 break; 2566 } 2567 2568 2569 if (startPos == 0) 2570 { 2571 return new Filter(filterString, filterType, filterComps, notComp, 2572 attrName, assertionValue, subInitial, subAny, subFinal, 2573 matchingRuleID, dnAttributes); 2574 } 2575 else 2576 { 2577 return new Filter(filterString.substring(startPos, endPos+1), filterType, 2578 filterComps, notComp, attrName, assertionValue, 2579 subInitial, subAny, subFinal, matchingRuleID, 2580 dnAttributes); 2581 } 2582 } 2583 2584 2585 2586 /** 2587 * Parses the specified portion of the provided filter string to obtain a set 2588 * of filter components for use in an AND or OR filter. 2589 * 2590 * @param filterString The string representation for the set of filters. 2591 * @param startPos The position of the first character to consider as 2592 * part of the first filter. 2593 * @param endPos The position of the last character to consider as 2594 * part of the last filter. 2595 * @param depth The current nesting depth for this filter. It should 2596 * be increased by one for each AND, OR, or NOT filter 2597 * encountered, in order to prevent stack overflow 2598 * errors from excessive recursion. 2599 * 2600 * @return The decoded set of search filters. 2601 * 2602 * @throws LDAPException If the provided string cannot be decoded as a set 2603 * of LDAP search filters. 2604 */ 2605 @NotNull() 2606 private static Filter[] parseFilterComps(@NotNull final String filterString, 2607 final int startPos, final int endPos, 2608 final int depth) 2609 throws LDAPException 2610 { 2611 if (startPos > endPos) 2612 { 2613 // This is acceptable, since it can represent an LDAP TRUE or FALSE filter 2614 // as described in RFC 4526. 2615 return NO_FILTERS; 2616 } 2617 2618 2619 // The set of filters must start with an opening parenthesis, and end with a 2620 // closing parenthesis. 2621 if (filterString.charAt(startPos) != '(') 2622 { 2623 throw new LDAPException(ResultCode.FILTER_ERROR, 2624 ERR_FILTER_EXPECTED_OPEN_PAREN.get(filterString, startPos)); 2625 } 2626 if (filterString.charAt(endPos) != ')') 2627 { 2628 throw new LDAPException(ResultCode.FILTER_ERROR, 2629 ERR_FILTER_EXPECTED_CLOSE_PAREN.get(filterString, startPos)); 2630 } 2631 2632 2633 // Iterate through the specified portion of the filter string and count 2634 // opening and closing parentheses to figure out where one filter ends and 2635 // another begins. 2636 final ArrayList<Filter> filterList = new ArrayList<>(5); 2637 int filterStartPos = startPos; 2638 int pos = startPos; 2639 int numOpen = 0; 2640 while (pos <= endPos) 2641 { 2642 final char c = filterString.charAt(pos++); 2643 if (c == '(') 2644 { 2645 numOpen++; 2646 } 2647 else if (c == ')') 2648 { 2649 numOpen--; 2650 if (numOpen == 0) 2651 { 2652 filterList.add(create(filterString, filterStartPos, pos-1, depth)); 2653 filterStartPos = pos; 2654 } 2655 } 2656 } 2657 2658 if (numOpen != 0) 2659 { 2660 throw new LDAPException(ResultCode.FILTER_ERROR, 2661 ERR_FILTER_MISMATCHED_PARENS.get(filterString, startPos, endPos)); 2662 } 2663 2664 return filterList.toArray(new Filter[filterList.size()]); 2665 } 2666 2667 2668 2669 /** 2670 * Reads one or more hex-encoded bytes from the specified portion of the 2671 * filter string. 2672 * 2673 * @param filterString The string from which the data is to be read. 2674 * @param startPos The position at which to start reading. This should 2675 * be the position of first hex character immediately 2676 * after the initial backslash. 2677 * @param buffer The buffer to which the decoded string portion should 2678 * be appended. 2679 * 2680 * @return The position at which the caller may resume parsing. 2681 * 2682 * @throws LDAPException If a problem occurs while reading hex-encoded 2683 * bytes. 2684 */ 2685 private static int readEscapedHexString(@NotNull final String filterString, 2686 final int startPos, 2687 @NotNull final ByteStringBuffer buffer) 2688 throws LDAPException 2689 { 2690 final byte b; 2691 switch (filterString.charAt(startPos)) 2692 { 2693 case '0': 2694 b = 0x00; 2695 break; 2696 case '1': 2697 b = 0x10; 2698 break; 2699 case '2': 2700 b = 0x20; 2701 break; 2702 case '3': 2703 b = 0x30; 2704 break; 2705 case '4': 2706 b = 0x40; 2707 break; 2708 case '5': 2709 b = 0x50; 2710 break; 2711 case '6': 2712 b = 0x60; 2713 break; 2714 case '7': 2715 b = 0x70; 2716 break; 2717 case '8': 2718 b = (byte) 0x80; 2719 break; 2720 case '9': 2721 b = (byte) 0x90; 2722 break; 2723 case 'a': 2724 case 'A': 2725 b = (byte) 0xA0; 2726 break; 2727 case 'b': 2728 case 'B': 2729 b = (byte) 0xB0; 2730 break; 2731 case 'c': 2732 case 'C': 2733 b = (byte) 0xC0; 2734 break; 2735 case 'd': 2736 case 'D': 2737 b = (byte) 0xD0; 2738 break; 2739 case 'e': 2740 case 'E': 2741 b = (byte) 0xE0; 2742 break; 2743 case 'f': 2744 case 'F': 2745 b = (byte) 0xF0; 2746 break; 2747 default: 2748 throw new LDAPException(ResultCode.FILTER_ERROR, 2749 ERR_FILTER_INVALID_HEX_CHAR.get(filterString, 2750 filterString.charAt(startPos), startPos)); 2751 } 2752 2753 switch (filterString.charAt(startPos+1)) 2754 { 2755 case '0': 2756 buffer.append(b); 2757 break; 2758 case '1': 2759 buffer.append((byte) (b | 0x01)); 2760 break; 2761 case '2': 2762 buffer.append((byte) (b | 0x02)); 2763 break; 2764 case '3': 2765 buffer.append((byte) (b | 0x03)); 2766 break; 2767 case '4': 2768 buffer.append((byte) (b | 0x04)); 2769 break; 2770 case '5': 2771 buffer.append((byte) (b | 0x05)); 2772 break; 2773 case '6': 2774 buffer.append((byte) (b | 0x06)); 2775 break; 2776 case '7': 2777 buffer.append((byte) (b | 0x07)); 2778 break; 2779 case '8': 2780 buffer.append((byte) (b | 0x08)); 2781 break; 2782 case '9': 2783 buffer.append((byte) (b | 0x09)); 2784 break; 2785 case 'a': 2786 case 'A': 2787 buffer.append((byte) (b | 0x0A)); 2788 break; 2789 case 'b': 2790 case 'B': 2791 buffer.append((byte) (b | 0x0B)); 2792 break; 2793 case 'c': 2794 case 'C': 2795 buffer.append((byte) (b | 0x0C)); 2796 break; 2797 case 'd': 2798 case 'D': 2799 buffer.append((byte) (b | 0x0D)); 2800 break; 2801 case 'e': 2802 case 'E': 2803 buffer.append((byte) (b | 0x0E)); 2804 break; 2805 case 'f': 2806 case 'F': 2807 buffer.append((byte) (b | 0x0F)); 2808 break; 2809 default: 2810 throw new LDAPException(ResultCode.FILTER_ERROR, 2811 ERR_FILTER_INVALID_HEX_CHAR.get(filterString, 2812 filterString.charAt(startPos+1), (startPos+1))); 2813 } 2814 2815 return startPos+2; 2816 } 2817 2818 2819 2820 /** 2821 * Writes an ASN.1-encoded representation of this filter to the provided ASN.1 2822 * buffer. 2823 * 2824 * @param buffer The ASN.1 buffer to which the encoded representation should 2825 * be written. 2826 */ 2827 public void writeTo(@NotNull final ASN1Buffer buffer) 2828 { 2829 switch (filterType) 2830 { 2831 case FILTER_TYPE_AND: 2832 case FILTER_TYPE_OR: 2833 final ASN1BufferSet compSet = buffer.beginSet(filterType); 2834 for (final Filter f : filterComps) 2835 { 2836 f.writeTo(buffer); 2837 } 2838 compSet.end(); 2839 break; 2840 2841 case FILTER_TYPE_NOT: 2842 buffer.addElement( 2843 new ASN1Element(filterType, notComp.encode().encode())); 2844 break; 2845 2846 case FILTER_TYPE_EQUALITY: 2847 case FILTER_TYPE_GREATER_OR_EQUAL: 2848 case FILTER_TYPE_LESS_OR_EQUAL: 2849 case FILTER_TYPE_APPROXIMATE_MATCH: 2850 final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType); 2851 buffer.addOctetString(attrName); 2852 buffer.addElement(assertionValue); 2853 avaSequence.end(); 2854 break; 2855 2856 case FILTER_TYPE_SUBSTRING: 2857 final ASN1BufferSequence subFilterSequence = 2858 buffer.beginSequence(filterType); 2859 buffer.addOctetString(attrName); 2860 2861 final ASN1BufferSequence valueSequence = buffer.beginSequence(); 2862 if (subInitial != null) 2863 { 2864 buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL, 2865 subInitial.getValue()); 2866 } 2867 2868 for (final ASN1OctetString s : subAny) 2869 { 2870 buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue()); 2871 } 2872 2873 if (subFinal != null) 2874 { 2875 buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue()); 2876 } 2877 valueSequence.end(); 2878 subFilterSequence.end(); 2879 break; 2880 2881 case FILTER_TYPE_PRESENCE: 2882 buffer.addOctetString(filterType, attrName); 2883 break; 2884 2885 case FILTER_TYPE_EXTENSIBLE_MATCH: 2886 final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType); 2887 if (matchingRuleID != null) 2888 { 2889 buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID, 2890 matchingRuleID); 2891 } 2892 2893 if (attrName != null) 2894 { 2895 buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName); 2896 } 2897 2898 buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE, 2899 assertionValue.getValue()); 2900 2901 if (dnAttributes) 2902 { 2903 buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true); 2904 } 2905 mrSequence.end(); 2906 break; 2907 } 2908 } 2909 2910 2911 2912 /** 2913 * Encodes this search filter to an ASN.1 element suitable for inclusion in an 2914 * LDAP search request protocol op. 2915 * 2916 * @return An ASN.1 element containing the encoded search filter. 2917 */ 2918 @NotNull() 2919 public ASN1Element encode() 2920 { 2921 switch (filterType) 2922 { 2923 case FILTER_TYPE_AND: 2924 case FILTER_TYPE_OR: 2925 final ASN1Element[] filterElements = 2926 new ASN1Element[filterComps.length]; 2927 for (int i=0; i < filterComps.length; i++) 2928 { 2929 filterElements[i] = filterComps[i].encode(); 2930 } 2931 return new ASN1Set(filterType, filterElements); 2932 2933 2934 case FILTER_TYPE_NOT: 2935 return new ASN1Element(filterType, notComp.encode().encode()); 2936 2937 2938 case FILTER_TYPE_EQUALITY: 2939 case FILTER_TYPE_GREATER_OR_EQUAL: 2940 case FILTER_TYPE_LESS_OR_EQUAL: 2941 case FILTER_TYPE_APPROXIMATE_MATCH: 2942 final ASN1OctetString[] attrValueAssertionElements = 2943 { 2944 new ASN1OctetString(attrName), 2945 assertionValue 2946 }; 2947 return new ASN1Sequence(filterType, attrValueAssertionElements); 2948 2949 2950 case FILTER_TYPE_SUBSTRING: 2951 final ArrayList<ASN1OctetString> subList = 2952 new ArrayList<>(2 + subAny.length); 2953 if (subInitial != null) 2954 { 2955 subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL, 2956 subInitial.getValue())); 2957 } 2958 2959 for (final ASN1Element subAnyElement : subAny) 2960 { 2961 subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY, 2962 subAnyElement.getValue())); 2963 } 2964 2965 2966 if (subFinal != null) 2967 { 2968 subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL, 2969 subFinal.getValue())); 2970 } 2971 2972 final ASN1Element[] subFilterElements = 2973 { 2974 new ASN1OctetString(attrName), 2975 new ASN1Sequence(subList) 2976 }; 2977 return new ASN1Sequence(filterType, subFilterElements); 2978 2979 2980 case FILTER_TYPE_PRESENCE: 2981 return new ASN1OctetString(filterType, attrName); 2982 2983 2984 case FILTER_TYPE_EXTENSIBLE_MATCH: 2985 final ArrayList<ASN1Element> emElementList = new ArrayList<>(4); 2986 if (matchingRuleID != null) 2987 { 2988 emElementList.add(new ASN1OctetString( 2989 EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID)); 2990 } 2991 2992 if (attrName != null) 2993 { 2994 emElementList.add(new ASN1OctetString( 2995 EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName)); 2996 } 2997 2998 emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE, 2999 assertionValue.getValue())); 3000 3001 if (dnAttributes) 3002 { 3003 emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, 3004 true)); 3005 } 3006 3007 return new ASN1Sequence(filterType, emElementList); 3008 3009 3010 default: 3011 throw new AssertionError(ERR_FILTER_INVALID_TYPE.get( 3012 StaticUtils.toHex(filterType))); 3013 } 3014 } 3015 3016 3017 3018 /** 3019 * Reads and decodes a search filter from the provided ASN.1 stream reader. 3020 * 3021 * @param reader The ASN.1 stream reader from which to read the filter. 3022 * 3023 * @return The decoded search filter. 3024 * 3025 * @throws LDAPException If an error occurs while reading or parsing the 3026 * search filter. 3027 */ 3028 @NotNull() 3029 public static Filter readFrom(@NotNull final ASN1StreamReader reader) 3030 throws LDAPException 3031 { 3032 try 3033 { 3034 final Filter[] filterComps; 3035 final Filter notComp; 3036 final String attrName; 3037 final ASN1OctetString assertionValue; 3038 final ASN1OctetString subInitial; 3039 final ASN1OctetString[] subAny; 3040 final ASN1OctetString subFinal; 3041 final String matchingRuleID; 3042 final boolean dnAttributes; 3043 3044 final byte filterType = (byte) reader.peek(); 3045 3046 switch (filterType) 3047 { 3048 case FILTER_TYPE_AND: 3049 case FILTER_TYPE_OR: 3050 final ArrayList<Filter> comps = new ArrayList<>(5); 3051 final ASN1StreamReaderSet elementSet = reader.beginSet(); 3052 while (elementSet.hasMoreElements()) 3053 { 3054 comps.add(readFrom(reader)); 3055 } 3056 3057 filterComps = new Filter[comps.size()]; 3058 comps.toArray(filterComps); 3059 3060 notComp = null; 3061 attrName = null; 3062 assertionValue = null; 3063 subInitial = null; 3064 subAny = NO_SUB_ANY; 3065 subFinal = null; 3066 matchingRuleID = null; 3067 dnAttributes = false; 3068 break; 3069 3070 3071 case FILTER_TYPE_NOT: 3072 final ASN1Element notFilterElement; 3073 try 3074 { 3075 final ASN1Element e = reader.readElement(); 3076 notFilterElement = ASN1Element.decode(e.getValue()); 3077 } 3078 catch (final ASN1Exception ae) 3079 { 3080 Debug.debugException(ae); 3081 throw new LDAPException(ResultCode.DECODING_ERROR, 3082 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get( 3083 StaticUtils.getExceptionMessage(ae)), 3084 ae); 3085 } 3086 notComp = decode(notFilterElement); 3087 3088 filterComps = NO_FILTERS; 3089 attrName = null; 3090 assertionValue = null; 3091 subInitial = null; 3092 subAny = NO_SUB_ANY; 3093 subFinal = null; 3094 matchingRuleID = null; 3095 dnAttributes = false; 3096 break; 3097 3098 3099 case FILTER_TYPE_EQUALITY: 3100 case FILTER_TYPE_GREATER_OR_EQUAL: 3101 case FILTER_TYPE_LESS_OR_EQUAL: 3102 case FILTER_TYPE_APPROXIMATE_MATCH: 3103 reader.beginSequence(); 3104 attrName = reader.readString(); 3105 assertionValue = new ASN1OctetString(reader.readBytes()); 3106 3107 filterComps = NO_FILTERS; 3108 notComp = null; 3109 subInitial = null; 3110 subAny = NO_SUB_ANY; 3111 subFinal = null; 3112 matchingRuleID = null; 3113 dnAttributes = false; 3114 break; 3115 3116 3117 case FILTER_TYPE_SUBSTRING: 3118 reader.beginSequence(); 3119 attrName = reader.readString(); 3120 3121 ASN1OctetString tempSubInitial = null; 3122 ASN1OctetString tempSubFinal = null; 3123 final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1); 3124 final ASN1StreamReaderSequence subSequence = reader.beginSequence(); 3125 while (subSequence.hasMoreElements()) 3126 { 3127 final byte type = (byte) reader.peek(); 3128 final ASN1OctetString s = 3129 new ASN1OctetString(type, reader.readBytes()); 3130 switch (type) 3131 { 3132 case SUBSTRING_TYPE_SUBINITIAL: 3133 tempSubInitial = s; 3134 break; 3135 case SUBSTRING_TYPE_SUBANY: 3136 subAnyList.add(s); 3137 break; 3138 case SUBSTRING_TYPE_SUBFINAL: 3139 tempSubFinal = s; 3140 break; 3141 default: 3142 throw new LDAPException(ResultCode.DECODING_ERROR, 3143 ERR_FILTER_INVALID_SUBSTR_TYPE.get( 3144 StaticUtils.toHex(type))); 3145 } 3146 } 3147 3148 subInitial = tempSubInitial; 3149 subFinal = tempSubFinal; 3150 3151 subAny = new ASN1OctetString[subAnyList.size()]; 3152 subAnyList.toArray(subAny); 3153 3154 filterComps = NO_FILTERS; 3155 notComp = null; 3156 assertionValue = null; 3157 matchingRuleID = null; 3158 dnAttributes = false; 3159 break; 3160 3161 3162 case FILTER_TYPE_PRESENCE: 3163 attrName = reader.readString(); 3164 3165 filterComps = NO_FILTERS; 3166 notComp = null; 3167 assertionValue = null; 3168 subInitial = null; 3169 subAny = NO_SUB_ANY; 3170 subFinal = null; 3171 matchingRuleID = null; 3172 dnAttributes = false; 3173 break; 3174 3175 3176 case FILTER_TYPE_EXTENSIBLE_MATCH: 3177 String tempAttrName = null; 3178 ASN1OctetString tempAssertionValue = null; 3179 String tempMatchingRuleID = null; 3180 boolean tempDNAttributes = false; 3181 3182 final ASN1StreamReaderSequence emSequence = reader.beginSequence(); 3183 while (emSequence.hasMoreElements()) 3184 { 3185 final byte type = (byte) reader.peek(); 3186 switch (type) 3187 { 3188 case EXTENSIBLE_TYPE_ATTRIBUTE_NAME: 3189 tempAttrName = reader.readString(); 3190 break; 3191 case EXTENSIBLE_TYPE_MATCHING_RULE_ID: 3192 tempMatchingRuleID = reader.readString(); 3193 break; 3194 case EXTENSIBLE_TYPE_MATCH_VALUE: 3195 tempAssertionValue = 3196 new ASN1OctetString(type, reader.readBytes()); 3197 break; 3198 case EXTENSIBLE_TYPE_DN_ATTRIBUTES: 3199 tempDNAttributes = reader.readBoolean(); 3200 break; 3201 default: 3202 throw new LDAPException(ResultCode.DECODING_ERROR, 3203 ERR_FILTER_EXTMATCH_INVALID_TYPE.get( 3204 StaticUtils.toHex(type))); 3205 } 3206 } 3207 3208 if ((tempAttrName == null) && (tempMatchingRuleID == null)) 3209 { 3210 throw new LDAPException(ResultCode.DECODING_ERROR, 3211 ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get()); 3212 } 3213 3214 if (tempAssertionValue == null) 3215 { 3216 throw new LDAPException(ResultCode.DECODING_ERROR, 3217 ERR_FILTER_EXTMATCH_NO_VALUE.get()); 3218 } 3219 3220 attrName = tempAttrName; 3221 assertionValue = tempAssertionValue; 3222 matchingRuleID = tempMatchingRuleID; 3223 dnAttributes = tempDNAttributes; 3224 3225 filterComps = NO_FILTERS; 3226 notComp = null; 3227 subInitial = null; 3228 subAny = NO_SUB_ANY; 3229 subFinal = null; 3230 break; 3231 3232 3233 default: 3234 throw new LDAPException(ResultCode.DECODING_ERROR, 3235 ERR_FILTER_ELEMENT_INVALID_TYPE.get( 3236 StaticUtils.toHex(filterType))); 3237 } 3238 3239 return new Filter(null, filterType, filterComps, notComp, attrName, 3240 assertionValue, subInitial, subAny, subFinal, 3241 matchingRuleID, dnAttributes); 3242 } 3243 catch (final LDAPException le) 3244 { 3245 Debug.debugException(le); 3246 throw le; 3247 } 3248 catch (final Exception e) 3249 { 3250 Debug.debugException(e); 3251 throw new LDAPException(ResultCode.DECODING_ERROR, 3252 ERR_FILTER_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e); 3253 } 3254 } 3255 3256 3257 3258 /** 3259 * Decodes the provided ASN.1 element as a search filter. 3260 * 3261 * @param filterElement The ASN.1 element containing the encoded search 3262 * filter. 3263 * 3264 * @return The decoded search filter. 3265 * 3266 * @throws LDAPException If the provided ASN.1 element cannot be decoded as 3267 * a search filter. 3268 */ 3269 @NotNull() 3270 public static Filter decode(@NotNull final ASN1Element filterElement) 3271 throws LDAPException 3272 { 3273 final byte filterType = filterElement.getType(); 3274 final Filter[] filterComps; 3275 final Filter notComp; 3276 final String attrName; 3277 final ASN1OctetString assertionValue; 3278 final ASN1OctetString subInitial; 3279 final ASN1OctetString[] subAny; 3280 final ASN1OctetString subFinal; 3281 final String matchingRuleID; 3282 final boolean dnAttributes; 3283 3284 switch (filterType) 3285 { 3286 case FILTER_TYPE_AND: 3287 case FILTER_TYPE_OR: 3288 notComp = null; 3289 attrName = null; 3290 assertionValue = null; 3291 subInitial = null; 3292 subAny = NO_SUB_ANY; 3293 subFinal = null; 3294 matchingRuleID = null; 3295 dnAttributes = false; 3296 3297 final ASN1Set compSet; 3298 try 3299 { 3300 compSet = ASN1Set.decodeAsSet(filterElement); 3301 } 3302 catch (final ASN1Exception ae) 3303 { 3304 Debug.debugException(ae); 3305 throw new LDAPException(ResultCode.DECODING_ERROR, 3306 ERR_FILTER_CANNOT_DECODE_COMPS.get( 3307 StaticUtils.getExceptionMessage(ae)), 3308 ae); 3309 } 3310 3311 final ASN1Element[] compElements = compSet.elements(); 3312 filterComps = new Filter[compElements.length]; 3313 for (int i=0; i < compElements.length; i++) 3314 { 3315 filterComps[i] = decode(compElements[i]); 3316 } 3317 break; 3318 3319 3320 case FILTER_TYPE_NOT: 3321 filterComps = NO_FILTERS; 3322 attrName = null; 3323 assertionValue = null; 3324 subInitial = null; 3325 subAny = NO_SUB_ANY; 3326 subFinal = null; 3327 matchingRuleID = null; 3328 dnAttributes = false; 3329 3330 final ASN1Element notFilterElement; 3331 try 3332 { 3333 notFilterElement = ASN1Element.decode(filterElement.getValue()); 3334 } 3335 catch (final ASN1Exception ae) 3336 { 3337 Debug.debugException(ae); 3338 throw new LDAPException(ResultCode.DECODING_ERROR, 3339 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get( 3340 StaticUtils.getExceptionMessage(ae)), 3341 ae); 3342 } 3343 notComp = decode(notFilterElement); 3344 break; 3345 3346 3347 3348 case FILTER_TYPE_EQUALITY: 3349 case FILTER_TYPE_GREATER_OR_EQUAL: 3350 case FILTER_TYPE_LESS_OR_EQUAL: 3351 case FILTER_TYPE_APPROXIMATE_MATCH: 3352 filterComps = NO_FILTERS; 3353 notComp = null; 3354 subInitial = null; 3355 subAny = NO_SUB_ANY; 3356 subFinal = null; 3357 matchingRuleID = null; 3358 dnAttributes = false; 3359 3360 final ASN1Sequence avaSequence; 3361 try 3362 { 3363 avaSequence = ASN1Sequence.decodeAsSequence(filterElement); 3364 } 3365 catch (final ASN1Exception ae) 3366 { 3367 Debug.debugException(ae); 3368 throw new LDAPException(ResultCode.DECODING_ERROR, 3369 ERR_FILTER_CANNOT_DECODE_AVA.get( 3370 StaticUtils.getExceptionMessage(ae)), 3371 ae); 3372 } 3373 3374 final ASN1Element[] avaElements = avaSequence.elements(); 3375 if (avaElements.length != 2) 3376 { 3377 throw new LDAPException(ResultCode.DECODING_ERROR, 3378 ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get( 3379 avaElements.length)); 3380 } 3381 3382 attrName = 3383 ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue(); 3384 assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]); 3385 break; 3386 3387 3388 case FILTER_TYPE_SUBSTRING: 3389 filterComps = NO_FILTERS; 3390 notComp = null; 3391 assertionValue = null; 3392 matchingRuleID = null; 3393 dnAttributes = false; 3394 3395 final ASN1Sequence subFilterSequence; 3396 try 3397 { 3398 subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement); 3399 } 3400 catch (final ASN1Exception ae) 3401 { 3402 Debug.debugException(ae); 3403 throw new LDAPException(ResultCode.DECODING_ERROR, 3404 ERR_FILTER_CANNOT_DECODE_SUBSTRING.get( 3405 StaticUtils.getExceptionMessage(ae)), 3406 ae); 3407 } 3408 3409 final ASN1Element[] subFilterElements = subFilterSequence.elements(); 3410 if (subFilterElements.length != 2) 3411 { 3412 throw new LDAPException(ResultCode.DECODING_ERROR, 3413 ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get( 3414 subFilterElements.length)); 3415 } 3416 3417 attrName = ASN1OctetString.decodeAsOctetString( 3418 subFilterElements[0]).stringValue(); 3419 3420 final ASN1Sequence subSequence; 3421 try 3422 { 3423 subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]); 3424 } 3425 catch (final ASN1Exception ae) 3426 { 3427 Debug.debugException(ae); 3428 throw new LDAPException(ResultCode.DECODING_ERROR, 3429 ERR_FILTER_CANNOT_DECODE_SUBSTRING.get( 3430 StaticUtils.getExceptionMessage(ae)), 3431 ae); 3432 } 3433 3434 ASN1OctetString tempSubInitial = null; 3435 ASN1OctetString tempSubFinal = null; 3436 final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1); 3437 3438 final ASN1Element[] subElements = subSequence.elements(); 3439 for (final ASN1Element subElement : subElements) 3440 { 3441 switch (subElement.getType()) 3442 { 3443 case SUBSTRING_TYPE_SUBINITIAL: 3444 if (tempSubInitial == null) 3445 { 3446 tempSubInitial = 3447 ASN1OctetString.decodeAsOctetString(subElement); 3448 } 3449 else 3450 { 3451 throw new LDAPException(ResultCode.DECODING_ERROR, 3452 ERR_FILTER_MULTIPLE_SUBINITIAL.get()); 3453 } 3454 break; 3455 3456 case SUBSTRING_TYPE_SUBANY: 3457 subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement)); 3458 break; 3459 3460 case SUBSTRING_TYPE_SUBFINAL: 3461 if (tempSubFinal == null) 3462 { 3463 tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement); 3464 } 3465 else 3466 { 3467 throw new LDAPException(ResultCode.DECODING_ERROR, 3468 ERR_FILTER_MULTIPLE_SUBFINAL.get()); 3469 } 3470 break; 3471 3472 default: 3473 throw new LDAPException(ResultCode.DECODING_ERROR, 3474 ERR_FILTER_INVALID_SUBSTR_TYPE.get( 3475 StaticUtils.toHex(subElement.getType()))); 3476 } 3477 } 3478 3479 subInitial = tempSubInitial; 3480 subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]); 3481 subFinal = tempSubFinal; 3482 break; 3483 3484 3485 case FILTER_TYPE_PRESENCE: 3486 filterComps = NO_FILTERS; 3487 notComp = null; 3488 assertionValue = null; 3489 subInitial = null; 3490 subAny = NO_SUB_ANY; 3491 subFinal = null; 3492 matchingRuleID = null; 3493 dnAttributes = false; 3494 attrName = 3495 ASN1OctetString.decodeAsOctetString(filterElement).stringValue(); 3496 break; 3497 3498 3499 case FILTER_TYPE_EXTENSIBLE_MATCH: 3500 filterComps = NO_FILTERS; 3501 notComp = null; 3502 subInitial = null; 3503 subAny = NO_SUB_ANY; 3504 subFinal = null; 3505 3506 final ASN1Sequence emSequence; 3507 try 3508 { 3509 emSequence = ASN1Sequence.decodeAsSequence(filterElement); 3510 } 3511 catch (final ASN1Exception ae) 3512 { 3513 Debug.debugException(ae); 3514 throw new LDAPException(ResultCode.DECODING_ERROR, 3515 ERR_FILTER_CANNOT_DECODE_EXTMATCH.get( 3516 StaticUtils.getExceptionMessage(ae)), 3517 ae); 3518 } 3519 3520 String tempAttrName = null; 3521 ASN1OctetString tempAssertionValue = null; 3522 String tempMatchingRuleID = null; 3523 boolean tempDNAttributes = false; 3524 for (final ASN1Element e : emSequence.elements()) 3525 { 3526 switch (e.getType()) 3527 { 3528 case EXTENSIBLE_TYPE_ATTRIBUTE_NAME: 3529 if (tempAttrName == null) 3530 { 3531 tempAttrName = 3532 ASN1OctetString.decodeAsOctetString(e).stringValue(); 3533 } 3534 else 3535 { 3536 throw new LDAPException(ResultCode.DECODING_ERROR, 3537 ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get()); 3538 } 3539 break; 3540 3541 case EXTENSIBLE_TYPE_MATCHING_RULE_ID: 3542 if (tempMatchingRuleID == null) 3543 { 3544 tempMatchingRuleID = 3545 ASN1OctetString.decodeAsOctetString(e).stringValue(); 3546 } 3547 else 3548 { 3549 throw new LDAPException(ResultCode.DECODING_ERROR, 3550 ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get()); 3551 } 3552 break; 3553 3554 case EXTENSIBLE_TYPE_MATCH_VALUE: 3555 if (tempAssertionValue == null) 3556 { 3557 tempAssertionValue = ASN1OctetString.decodeAsOctetString(e); 3558 } 3559 else 3560 { 3561 throw new LDAPException(ResultCode.DECODING_ERROR, 3562 ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get()); 3563 } 3564 break; 3565 3566 case EXTENSIBLE_TYPE_DN_ATTRIBUTES: 3567 try 3568 { 3569 if (tempDNAttributes) 3570 { 3571 throw new LDAPException(ResultCode.DECODING_ERROR, 3572 ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get()); 3573 } 3574 else 3575 { 3576 tempDNAttributes = 3577 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 3578 } 3579 } 3580 catch (final ASN1Exception ae) 3581 { 3582 Debug.debugException(ae); 3583 throw new LDAPException(ResultCode.DECODING_ERROR, 3584 ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get( 3585 StaticUtils.getExceptionMessage(ae)), 3586 ae); 3587 } 3588 break; 3589 3590 default: 3591 throw new LDAPException(ResultCode.DECODING_ERROR, 3592 ERR_FILTER_EXTMATCH_INVALID_TYPE.get( 3593 StaticUtils.toHex(e.getType()))); 3594 } 3595 } 3596 3597 if ((tempAttrName == null) && (tempMatchingRuleID == null)) 3598 { 3599 throw new LDAPException(ResultCode.DECODING_ERROR, 3600 ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get()); 3601 } 3602 3603 if (tempAssertionValue == null) 3604 { 3605 throw new LDAPException(ResultCode.DECODING_ERROR, 3606 ERR_FILTER_EXTMATCH_NO_VALUE.get()); 3607 } 3608 3609 attrName = tempAttrName; 3610 assertionValue = tempAssertionValue; 3611 matchingRuleID = tempMatchingRuleID; 3612 dnAttributes = tempDNAttributes; 3613 break; 3614 3615 3616 default: 3617 throw new LDAPException(ResultCode.DECODING_ERROR, 3618 ERR_FILTER_ELEMENT_INVALID_TYPE.get( 3619 StaticUtils.toHex(filterElement.getType()))); 3620 } 3621 3622 3623 return new Filter(null, filterType, filterComps, notComp, attrName, 3624 assertionValue, subInitial, subAny, subFinal, 3625 matchingRuleID, dnAttributes); 3626 } 3627 3628 3629 3630 /** 3631 * Retrieves the filter type for this filter. 3632 * 3633 * @return The filter type for this filter. 3634 */ 3635 public byte getFilterType() 3636 { 3637 return filterType; 3638 } 3639 3640 3641 3642 /** 3643 * Retrieves the set of filter components used in this AND or OR filter. This 3644 * is not applicable for any other filter type. 3645 * 3646 * @return The set of filter components used in this AND or OR filter, or an 3647 * empty array if this is some other type of filter or if there are 3648 * no components (i.e., as in an LDAP TRUE or LDAP FALSE filter). 3649 */ 3650 @NotNull() 3651 public Filter[] getComponents() 3652 { 3653 return filterComps; 3654 } 3655 3656 3657 3658 /** 3659 * Retrieves the filter component used in this NOT filter. This is not 3660 * applicable for any other filter type. 3661 * 3662 * @return The filter component used in this NOT filter, or {@code null} if 3663 * this is some other type of filter. 3664 */ 3665 @Nullable() 3666 public Filter getNOTComponent() 3667 { 3668 return notComp; 3669 } 3670 3671 3672 3673 /** 3674 * Retrieves the name of the attribute type for this search filter. This is 3675 * applicable for the following types of filters: 3676 * <UL> 3677 * <LI>Equality</LI> 3678 * <LI>Substring</LI> 3679 * <LI>Greater or Equal</LI> 3680 * <LI>Less or Equal</LI> 3681 * <LI>Presence</LI> 3682 * <LI>Approximate Match</LI> 3683 * <LI>Extensible Match</LI> 3684 * </UL> 3685 * 3686 * @return The name of the attribute type for this search filter, or 3687 * {@code null} if it is not applicable for this type of filter. 3688 */ 3689 @Nullable() 3690 public String getAttributeName() 3691 { 3692 return attrName; 3693 } 3694 3695 3696 3697 /** 3698 * Retrieves the string representation of the assertion value for this search 3699 * filter. This is applicable for the following types of filters: 3700 * <UL> 3701 * <LI>Equality</LI> 3702 * <LI>Greater or Equal</LI> 3703 * <LI>Less or Equal</LI> 3704 * <LI>Approximate Match</LI> 3705 * <LI>Extensible Match</LI> 3706 * </UL> 3707 * 3708 * @return The string representation of the assertion value for this search 3709 * filter, or {@code null} if it is not applicable for this type of 3710 * filter. 3711 */ 3712 @Nullable() 3713 public String getAssertionValue() 3714 { 3715 if (assertionValue == null) 3716 { 3717 return null; 3718 } 3719 else 3720 { 3721 return assertionValue.stringValue(); 3722 } 3723 } 3724 3725 3726 3727 /** 3728 * Retrieves the binary representation of the assertion value for this search 3729 * filter. This is applicable for the following types of filters: 3730 * <UL> 3731 * <LI>Equality</LI> 3732 * <LI>Greater or Equal</LI> 3733 * <LI>Less or Equal</LI> 3734 * <LI>Approximate Match</LI> 3735 * <LI>Extensible Match</LI> 3736 * </UL> 3737 * 3738 * @return The binary representation of the assertion value for this search 3739 * filter, or {@code null} if it is not applicable for this type of 3740 * filter. 3741 */ 3742 @Nullable() 3743 public byte[] getAssertionValueBytes() 3744 { 3745 if (assertionValue == null) 3746 { 3747 return null; 3748 } 3749 else 3750 { 3751 return assertionValue.getValue(); 3752 } 3753 } 3754 3755 3756 3757 /** 3758 * Retrieves the raw assertion value for this search filter as an ASN.1 3759 * octet string. This is applicable for the following types of filters: 3760 * <UL> 3761 * <LI>Equality</LI> 3762 * <LI>Greater or Equal</LI> 3763 * <LI>Less or Equal</LI> 3764 * <LI>Approximate Match</LI> 3765 * <LI>Extensible Match</LI> 3766 * </UL> 3767 * 3768 * @return The raw assertion value for this search filter as an ASN.1 octet 3769 * string, or {@code null} if it is not applicable for this type of 3770 * filter. 3771 */ 3772 @Nullable() 3773 public ASN1OctetString getRawAssertionValue() 3774 { 3775 return assertionValue; 3776 } 3777 3778 3779 3780 /** 3781 * Retrieves the string representation of the subInitial element for this 3782 * substring filter. This is not applicable for any other filter type. 3783 * 3784 * @return The string representation of the subInitial element for this 3785 * substring filter, or {@code null} if this is some other type of 3786 * filter, or if it is a substring filter with no subInitial element. 3787 */ 3788 @Nullable() 3789 public String getSubInitialString() 3790 { 3791 if (subInitial == null) 3792 { 3793 return null; 3794 } 3795 else 3796 { 3797 return subInitial.stringValue(); 3798 } 3799 } 3800 3801 3802 3803 /** 3804 * Retrieves the binary representation of the subInitial element for this 3805 * substring filter. This is not applicable for any other filter type. 3806 * 3807 * @return The binary representation of the subInitial element for this 3808 * substring filter, or {@code null} if this is some other type of 3809 * filter, or if it is a substring filter with no subInitial element. 3810 */ 3811 @Nullable() 3812 public byte[] getSubInitialBytes() 3813 { 3814 if (subInitial == null) 3815 { 3816 return null; 3817 } 3818 else 3819 { 3820 return subInitial.getValue(); 3821 } 3822 } 3823 3824 3825 3826 /** 3827 * Retrieves the raw subInitial element for this filter as an ASN.1 octet 3828 * string. This is not applicable for any other filter type. 3829 * 3830 * @return The raw subInitial element for this filter as an ASN.1 octet 3831 * string, or {@code null} if this is not a substring filter, or if 3832 * it is a substring filter with no subInitial element. 3833 */ 3834 @Nullable() 3835 public ASN1OctetString getRawSubInitialValue() 3836 { 3837 return subInitial; 3838 } 3839 3840 3841 3842 /** 3843 * Retrieves the string representations of the subAny elements for this 3844 * substring filter. This is not applicable for any other filter type. 3845 * 3846 * @return The string representations of the subAny elements for this 3847 * substring filter, or an empty array if this is some other type of 3848 * filter, or if it is a substring filter with no subFinal element. 3849 */ 3850 @NotNull() 3851 public String[] getSubAnyStrings() 3852 { 3853 final String[] subAnyStrings = new String[subAny.length]; 3854 for (int i=0; i < subAny.length; i++) 3855 { 3856 subAnyStrings[i] = subAny[i].stringValue(); 3857 } 3858 3859 return subAnyStrings; 3860 } 3861 3862 3863 3864 /** 3865 * Retrieves the binary representations of the subAny elements for this 3866 * substring filter. This is not applicable for any other filter type. 3867 * 3868 * @return The binary representations of the subAny elements for this 3869 * substring filter, or an empty array if this is some other type of 3870 * filter, or if it is a substring filter with no subFinal element. 3871 */ 3872 @NotNull() 3873 public byte[][] getSubAnyBytes() 3874 { 3875 final byte[][] subAnyBytes = new byte[subAny.length][]; 3876 for (int i=0; i < subAny.length; i++) 3877 { 3878 subAnyBytes[i] = subAny[i].getValue(); 3879 } 3880 3881 return subAnyBytes; 3882 } 3883 3884 3885 3886 /** 3887 * Retrieves the raw subAny values for this substring filter. This is not 3888 * applicable for any other filter type. 3889 * 3890 * @return The raw subAny values for this substring filter, or an empty array 3891 * if this is some other type of filter, or if it is a substring 3892 * filter with no subFinal element. 3893 */ 3894 @NotNull() 3895 public ASN1OctetString[] getRawSubAnyValues() 3896 { 3897 return subAny; 3898 } 3899 3900 3901 3902 /** 3903 * Retrieves the string representation of the subFinal element for this 3904 * substring filter. This is not applicable for any other filter type. 3905 * 3906 * @return The string representation of the subFinal element for this 3907 * substring filter, or {@code null} if this is some other type of 3908 * filter, or if it is a substring filter with no subFinal element. 3909 */ 3910 @Nullable() 3911 public String getSubFinalString() 3912 { 3913 if (subFinal == null) 3914 { 3915 return null; 3916 } 3917 else 3918 { 3919 return subFinal.stringValue(); 3920 } 3921 } 3922 3923 3924 3925 /** 3926 * Retrieves the binary representation of the subFinal element for this 3927 * substring filter. This is not applicable for any other filter type. 3928 * 3929 * @return The binary representation of the subFinal element for this 3930 * substring filter, or {@code null} if this is some other type of 3931 * filter, or if it is a substring filter with no subFinal element. 3932 */ 3933 @Nullable() 3934 public byte[] getSubFinalBytes() 3935 { 3936 if (subFinal == null) 3937 { 3938 return null; 3939 } 3940 else 3941 { 3942 return subFinal.getValue(); 3943 } 3944 } 3945 3946 3947 3948 /** 3949 * Retrieves the raw subFinal element for this filter as an ASN.1 octet 3950 * string. This is not applicable for any other filter type. 3951 * 3952 * @return The raw subFinal element for this filter as an ASN.1 octet 3953 * string, or {@code null} if this is not a substring filter, or if 3954 * it is a substring filter with no subFinal element. 3955 */ 3956 @Nullable() 3957 public ASN1OctetString getRawSubFinalValue() 3958 { 3959 return subFinal; 3960 } 3961 3962 3963 3964 /** 3965 * Retrieves the matching rule ID for this extensible match filter. This is 3966 * not applicable for any other filter type. 3967 * 3968 * @return The matching rule ID for this extensible match filter, or 3969 * {@code null} if this is some other type of filter, or if this 3970 * extensible match filter does not have a matching rule ID. 3971 */ 3972 @Nullable() 3973 public String getMatchingRuleID() 3974 { 3975 return matchingRuleID; 3976 } 3977 3978 3979 3980 /** 3981 * Retrieves the dnAttributes flag for this extensible match filter. This is 3982 * not applicable for any other filter type. 3983 * 3984 * @return The dnAttributes flag for this extensible match filter. 3985 */ 3986 public boolean getDNAttributes() 3987 { 3988 return dnAttributes; 3989 } 3990 3991 3992 3993 /** 3994 * Indicates whether this filter matches the provided entry. Note that this 3995 * is a best-guess effort and may not be completely accurate in all cases. 3996 * All matching will be performed using case-ignore string matching, which may 3997 * yield an unexpected result for values that should not be treated as simple 3998 * strings. For example: 3999 * <UL> 4000 * <LI>Two DN values which are logically equivalent may not be considered 4001 * matches if they have different spacing.</LI> 4002 * <LI>Ordering comparisons against numeric values may yield unexpected 4003 * results (e.g., "2" will be considered greater than "10" because the 4004 * character "2" has a larger ASCII value than the character "1").</LI> 4005 * </UL> 4006 * <BR> 4007 * In addition to the above constraints, it should be noted that neither 4008 * approximate matching nor extensible matching are currently supported. 4009 * 4010 * @param entry The entry for which to make the determination. It must not 4011 * be {@code null}. 4012 * 4013 * @return {@code true} if this filter appears to match the provided entry, 4014 * or {@code false} if not. 4015 * 4016 * @throws LDAPException If a problem occurs while trying to make the 4017 * determination. 4018 */ 4019 public boolean matchesEntry(@NotNull final Entry entry) 4020 throws LDAPException 4021 { 4022 return matchesEntry(entry, entry.getSchema()); 4023 } 4024 4025 4026 4027 /** 4028 * Indicates whether this filter matches the provided entry. Note that this 4029 * is a best-guess effort and may not be completely accurate in all cases. 4030 * If provided, the given schema will be used in an attempt to determine the 4031 * appropriate matching rule for making the determinations, but some corner 4032 * cases may not be handled accurately. Neither approximate matching nor 4033 * extensible matching are currently supported. 4034 * 4035 * @param entry The entry for which to make the determination. It must not 4036 * be {@code null}. 4037 * @param schema The schema to use when making the determination. If this 4038 * is {@code null}, then all matching will be performed using 4039 * a case-ignore matching rule. 4040 * 4041 * @return {@code true} if this filter appears to match the provided entry, 4042 * or {@code false} if not. 4043 * 4044 * @throws LDAPException If a problem occurs while trying to make the 4045 * determination. 4046 */ 4047 public boolean matchesEntry(@NotNull final Entry entry, 4048 @Nullable final Schema schema) 4049 throws LDAPException 4050 { 4051 Validator.ensureNotNull(entry); 4052 4053 switch (filterType) 4054 { 4055 case FILTER_TYPE_AND: 4056 for (final Filter f : filterComps) 4057 { 4058 try 4059 { 4060 if (! f.matchesEntry(entry, schema)) 4061 { 4062 return false; 4063 } 4064 } 4065 catch (final Exception e) 4066 { 4067 Debug.debugException(e); 4068 return false; 4069 } 4070 } 4071 return true; 4072 4073 case FILTER_TYPE_OR: 4074 for (final Filter f : filterComps) 4075 { 4076 try 4077 { 4078 if (f.matchesEntry(entry, schema)) 4079 { 4080 return true; 4081 } 4082 } 4083 catch (final Exception e) 4084 { 4085 Debug.debugException(e); 4086 } 4087 } 4088 return false; 4089 4090 case FILTER_TYPE_NOT: 4091 return (! notComp.matchesEntry(entry, schema)); 4092 4093 case FILTER_TYPE_EQUALITY: 4094 Attribute a = entry.getAttribute(attrName, schema); 4095 if (a == null) 4096 { 4097 return false; 4098 } 4099 4100 MatchingRule matchingRule = 4101 MatchingRule.selectEqualityMatchingRule(attrName, schema); 4102 return matchingRule.matchesAnyValue(assertionValue, a.getRawValues()); 4103 4104 case FILTER_TYPE_SUBSTRING: 4105 a = entry.getAttribute(attrName, schema); 4106 if (a == null) 4107 { 4108 return false; 4109 } 4110 4111 matchingRule = 4112 MatchingRule.selectSubstringMatchingRule(attrName, schema); 4113 for (final ASN1OctetString v : a.getRawValues()) 4114 { 4115 if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal)) 4116 { 4117 return true; 4118 } 4119 } 4120 return false; 4121 4122 case FILTER_TYPE_GREATER_OR_EQUAL: 4123 a = entry.getAttribute(attrName, schema); 4124 if (a == null) 4125 { 4126 return false; 4127 } 4128 4129 matchingRule = 4130 MatchingRule.selectOrderingMatchingRule(attrName, schema); 4131 for (final ASN1OctetString v : a.getRawValues()) 4132 { 4133 if (matchingRule.compareValues(v, assertionValue) >= 0) 4134 { 4135 return true; 4136 } 4137 } 4138 return false; 4139 4140 case FILTER_TYPE_LESS_OR_EQUAL: 4141 a = entry.getAttribute(attrName, schema); 4142 if (a == null) 4143 { 4144 return false; 4145 } 4146 4147 matchingRule = 4148 MatchingRule.selectOrderingMatchingRule(attrName, schema); 4149 for (final ASN1OctetString v : a.getRawValues()) 4150 { 4151 if (matchingRule.compareValues(v, assertionValue) <= 0) 4152 { 4153 return true; 4154 } 4155 } 4156 return false; 4157 4158 case FILTER_TYPE_PRESENCE: 4159 return (entry.hasAttribute(attrName)); 4160 4161 case FILTER_TYPE_APPROXIMATE_MATCH: 4162 throw new LDAPException(ResultCode.NOT_SUPPORTED, 4163 ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get()); 4164 4165 case FILTER_TYPE_EXTENSIBLE_MATCH: 4166 return extensibleMatchFilterMatchesEntry(entry, schema); 4167 4168 default: 4169 throw new LDAPException(ResultCode.PARAM_ERROR, 4170 ERR_FILTER_INVALID_TYPE.get()); 4171 } 4172 } 4173 4174 4175 4176 /** 4177 * Indicates whether the provided extensible matching filter component matches 4178 * the provided entry. This method provides very limited support for 4179 * extensible matching It can only be used for filters that contain both an 4180 * attribute type and a matching rule ID, and when the matching rule ID is 4181 * one of the following: 4182 * <OL> 4183 * <LI>jsonObjectFilterExtensibleMatch (or 1.3.6.1.4.1.30221.2.4.13)</LI> 4184 * </OL> 4185 * 4186 * @param entry The entry for which to make the determination. It must not 4187 * be {@code null}. 4188 * @param schema The schema to use when making the determination. If this 4189 * is {@code null}, then all matching will be performed using 4190 * a case-ignore matching rule. 4191 * 4192 * @return {@code true} if this filter appears to match the provided entry, 4193 * or {@code false} if not. 4194 * 4195 * @throws LDAPException If a problem occurs while trying to make the 4196 * determination. 4197 */ 4198 private boolean extensibleMatchFilterMatchesEntry(@NotNull final Entry entry, 4199 @Nullable final Schema schema) 4200 throws LDAPException 4201 { 4202 if ((attrName != null) && (matchingRuleID != null) && (! dnAttributes)) 4203 { 4204 if (matchingRuleID.equalsIgnoreCase("jsonObjectFilterExtensibleMatch") || 4205 matchingRuleID.equals("1.3.6.1.4.1.30221.2.4.13")) 4206 { 4207 final JSONObjectFilter jsonObjectFilter; 4208 try 4209 { 4210 final JSONObject jsonObject = 4211 new JSONObject(assertionValue.stringValue()); 4212 jsonObjectFilter = JSONObjectFilter.decode(jsonObject); 4213 } 4214 catch (final Exception e) 4215 { 4216 Debug.debugException(e); 4217 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 4218 ERR_FILTER_EXTENSIBLE_MATCH_MALFORMED_JSON_OBJECT_FILTER.get( 4219 toString(), entry.getDN(), 4220 StaticUtils.getExceptionMessage(e)), 4221 e); 4222 } 4223 4224 final Attribute attr = entry.getAttribute(attrName, schema); 4225 if (attr != null) 4226 { 4227 for (final ASN1OctetString v : attr.getRawValues()) 4228 { 4229 try 4230 { 4231 final JSONObject jsonObject = new JSONObject(v.stringValue()); 4232 if (jsonObjectFilter.matchesJSONObject(jsonObject)) 4233 { 4234 return true; 4235 } 4236 } 4237 catch (final Exception e) 4238 { 4239 Debug.debugException(e); 4240 } 4241 } 4242 } 4243 4244 return false; 4245 } 4246 } 4247 4248 throw new LDAPException(ResultCode.NOT_SUPPORTED, 4249 ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get()); 4250 } 4251 4252 4253 4254 /** 4255 * Attempts to simplify the provided filter to allow it to be more efficiently 4256 * processed by the server. The simplifications it will make include: 4257 * <UL> 4258 * <LI>Any AND or OR filter that contains only a single filter component 4259 * will be converted to just that embedded filter component to eliminate 4260 * the unnecessary AND or OR wrapper. For example, the filter 4261 * "(&(uid=john.doe))" will be converted to just 4262 * "(uid=john.doe)".</LI> 4263 * <LI>Any AND components inside of an AND filter will be merged into the 4264 * outer AND filter. Any OR components inside of an OR filter will be 4265 * merged into the outer OR filter. For example, the filter 4266 * "(&(objectClass=person)(&(givenName=John)(sn=Doe)))" will be 4267 * converted to 4268 * "(&(objectClass=person)(givenName=John)(sn=Doe))".</LI> 4269 * <LI>Any AND filter that contains an LDAP false filter will be converted 4270 * to just an LDAP false filter.</LI> 4271 * <LI>Any OR filter that contains an LDAP true filter will be converted 4272 * to just an LDAP true filter.</LI> 4273 * <LI>If {@code reOrderElements} is true, then this method will attempt to 4274 * re-order the elements inside AND and OR filters in an attempt to 4275 * ensure that the components which are likely to be the most efficient 4276 * come earlier than those which are likely to be the least efficient. 4277 * This can speed up processing in servers that process filter 4278 * components in a left-to-right order.</LI> 4279 * </UL> 4280 * <BR><BR> 4281 * The simplification will happen recursively, in an attempt to generate a 4282 * filter that is as simple and efficient as possible. 4283 * 4284 * @param filter The filter to attempt to simplify. 4285 * @param reOrderElements Indicates whether this method may re-order the 4286 * elements in the filter so that, in a server that 4287 * evaluates the components in a left-to-right order, 4288 * the components which are likely to be more 4289 * efficient to process will be listed before those 4290 * which are likely to be less efficient. 4291 * 4292 * @return The simplified filter, or the original filter if the provided 4293 * filter is not one that can be simplified any further. 4294 */ 4295 @NotNull() 4296 public static Filter simplifyFilter(@NotNull final Filter filter, 4297 final boolean reOrderElements) 4298 { 4299 final byte filterType = filter.filterType; 4300 switch (filterType) 4301 { 4302 case FILTER_TYPE_AND: 4303 case FILTER_TYPE_OR: 4304 // These will be handled below. 4305 break; 4306 4307 case FILTER_TYPE_NOT: 4308 // We may be able to simplify the filter component contained inside the 4309 // NOT. 4310 return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements)); 4311 4312 default: 4313 // We can't simplify this filter, so just return what was provided. 4314 return filter; 4315 } 4316 4317 4318 // An AND filter with zero components is an LDAP true filter, and we can't 4319 // simplify that. An OR filter with zero components is an LDAP false 4320 // filter, and we can't simplify that either. The set of components 4321 // should never be null for an AND or OR filter, but if that happens to be 4322 // the case, then we'll return the original filter. 4323 final Filter[] components = filter.filterComps; 4324 if ((components == null) || (components.length == 0)) 4325 { 4326 return filter; 4327 } 4328 4329 4330 // For either an AND or an OR filter with just a single component, then just 4331 // return that embedded component. But simplify it first. 4332 if (components.length == 1) 4333 { 4334 return simplifyFilter(components[0], reOrderElements); 4335 } 4336 4337 4338 // If we've gotten here, then we have a filter with multiple components. 4339 // Simplify each of them to the extent possible, un-embed any ANDs 4340 // contained inside an AND or ORs contained inside an OR, and eliminate any 4341 // duplicate components in the resulting top-level filter. 4342 final LinkedHashSet<Filter> componentSet = 4343 new LinkedHashSet<>(StaticUtils.computeMapCapacity(10)); 4344 for (final Filter f : components) 4345 { 4346 final Filter simplifiedFilter = simplifyFilter(f, reOrderElements); 4347 if (simplifiedFilter.filterType == FILTER_TYPE_AND) 4348 { 4349 if (filterType == FILTER_TYPE_AND) 4350 { 4351 // This is an AND nested inside an AND. In that case, we'll just put 4352 // all the nested components inside the outer AND. 4353 componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps)); 4354 } 4355 else 4356 { 4357 componentSet.add(simplifiedFilter); 4358 } 4359 } 4360 else if (simplifiedFilter.filterType == FILTER_TYPE_OR) 4361 { 4362 if (filterType == FILTER_TYPE_OR) 4363 { 4364 // This is an OR nested inside an OR. In that case, we'll just put 4365 // all the nested components inside the outer OR. 4366 componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps)); 4367 } 4368 else 4369 { 4370 componentSet.add(simplifiedFilter); 4371 } 4372 } 4373 else 4374 { 4375 componentSet.add(simplifiedFilter); 4376 } 4377 } 4378 4379 4380 // It's possible at this point that we are down to just a single component. 4381 // That can happen if the filter was an AND or an OR with a duplicate 4382 // element, like "(&(a=b)(a=b))". In that case, just return that one 4383 // component. 4384 if (componentSet.size() == 1) 4385 { 4386 return componentSet.iterator().next(); 4387 } 4388 4389 4390 // If we have an AND filter that contains an embedded LDAP false filter, 4391 // then just return the LDAP false filter. If we have an OR filter that 4392 // contains an embedded LDAP true filter, then just return the LDAP true 4393 // filter. 4394 if (filterType == FILTER_TYPE_AND) 4395 { 4396 for (final Filter f : componentSet) 4397 { 4398 if ((f.filterType == FILTER_TYPE_OR) && (f.filterComps.length == 0)) 4399 { 4400 return f; 4401 } 4402 } 4403 } 4404 else if (filterType == FILTER_TYPE_OR) 4405 { 4406 for (final Filter f : componentSet) 4407 { 4408 if ((f.filterType == FILTER_TYPE_AND) && (f.filterComps.length == 0)) 4409 { 4410 return f; 4411 } 4412 } 4413 } 4414 4415 4416 // If we should re-order the components, then use the following priority 4417 // list: 4418 // 4419 // 1. Equality components that target an attribute other than objectClass. 4420 // These are most likely to require only a single database lookup to get 4421 // the candidate list, and that candidate list will frequently be small. 4422 // 2. Equality components that target the objectClass attribute. These are 4423 // likely to require only a single database lookup to get the candidate 4424 // list, but the candidate list is more likely to be larger. 4425 // 3. Approximate match components. These are also likely to require only 4426 // a single database lookup to get the candidate list, but that 4427 // candidate list is likely to have a larger number of candidates. 4428 // 4. Presence components that target an attribute other than objectClass. 4429 // These are also likely to require only a single database lookup to get 4430 // the candidate list, but are likely to have a large number of 4431 // candidates. 4432 // 5. Substring components that have a subInitial element. These are 4433 // generally the most efficient substring filters to process, requiring 4434 // access to fewer database keys than substring filters with only subAny 4435 // and/or subFinal components. 4436 // 6. Substring components that only have subAny and/or subFinal elements. 4437 // These will probably require a number of database lookups and will 4438 // probably result in large candidate lists. 4439 // 7. Greater-or-equal components and less-or-equal components. These 4440 // will probably require a number of database lookups and will probably 4441 // result in large candidate lists. 4442 // 8. Extensible match components. Even if these are indexed, there isn't 4443 // any good way to know how expensive they might be to process or how 4444 // big the candidate list might be. 4445 // 9. Presence components that target the objectClass attribute. This is 4446 // likely to require only a single database lookup to get the candidate 4447 // list, but the candidate list will also be extremely large (if it's 4448 // indexed at all) since it will match every entry. 4449 // 10. NOT components. These are generally not possible to index and 4450 // therefore cannot be used to create a candidate list. 4451 // 4452 // AND and OR components will be ordered according to the first of their 4453 // embedded components Since the filter has already been simplified, then 4454 // the first element in the list will be the one we think will be the most 4455 // efficient to process. 4456 if (reOrderElements) 4457 { 4458 final TreeMap<Integer,LinkedHashSet<Filter>> m = new TreeMap<>(); 4459 for (final Filter f : componentSet) 4460 { 4461 final Filter prioritizeComp; 4462 if ((f.filterType == FILTER_TYPE_AND) || 4463 (f.filterType == FILTER_TYPE_OR)) 4464 { 4465 if (f.filterComps.length > 0) 4466 { 4467 prioritizeComp = f.filterComps[0]; 4468 } 4469 else 4470 { 4471 prioritizeComp = f; 4472 } 4473 } 4474 else 4475 { 4476 prioritizeComp = f; 4477 } 4478 4479 final Integer slot; 4480 switch (prioritizeComp.filterType) 4481 { 4482 case FILTER_TYPE_EQUALITY: 4483 if (prioritizeComp.attrName.equalsIgnoreCase("objectClass")) 4484 { 4485 slot = 2; 4486 } 4487 else 4488 { 4489 slot = 1; 4490 } 4491 break; 4492 4493 case FILTER_TYPE_APPROXIMATE_MATCH: 4494 slot = 3; 4495 break; 4496 4497 case FILTER_TYPE_PRESENCE: 4498 if (prioritizeComp.attrName.equalsIgnoreCase("objectClass")) 4499 { 4500 slot = 9; 4501 } 4502 else 4503 { 4504 slot = 4; 4505 } 4506 break; 4507 4508 case FILTER_TYPE_SUBSTRING: 4509 if (prioritizeComp.subInitial == null) 4510 { 4511 slot = 6; 4512 } 4513 else 4514 { 4515 slot = 5; 4516 } 4517 break; 4518 4519 case FILTER_TYPE_GREATER_OR_EQUAL: 4520 case FILTER_TYPE_LESS_OR_EQUAL: 4521 slot = 7; 4522 break; 4523 4524 case FILTER_TYPE_EXTENSIBLE_MATCH: 4525 slot = 8; 4526 break; 4527 4528 case FILTER_TYPE_NOT: 4529 default: 4530 slot = 10; 4531 break; 4532 } 4533 4534 LinkedHashSet<Filter> filterSet = m.get(slot-1); 4535 if (filterSet == null) 4536 { 4537 filterSet = new LinkedHashSet<>(StaticUtils.computeMapCapacity(10)); 4538 m.put(slot-1, filterSet); 4539 } 4540 filterSet.add(f); 4541 } 4542 4543 componentSet.clear(); 4544 for (final LinkedHashSet<Filter> filterSet : m.values()) 4545 { 4546 componentSet.addAll(filterSet); 4547 } 4548 } 4549 4550 4551 // Return the new, possibly simplified filter. 4552 if (filterType == FILTER_TYPE_AND) 4553 { 4554 return createANDFilter(componentSet); 4555 } 4556 else 4557 { 4558 return createORFilter(componentSet); 4559 } 4560 } 4561 4562 4563 4564 /** 4565 * Generates a hash code for this search filter. 4566 * 4567 * @return The generated hash code for this search filter. 4568 */ 4569 @Override() 4570 public int hashCode() 4571 { 4572 final CaseIgnoreStringMatchingRule matchingRule = 4573 CaseIgnoreStringMatchingRule.getInstance(); 4574 int hashCode = filterType; 4575 4576 switch (filterType) 4577 { 4578 case FILTER_TYPE_AND: 4579 case FILTER_TYPE_OR: 4580 for (final Filter f : filterComps) 4581 { 4582 hashCode += f.hashCode(); 4583 } 4584 break; 4585 4586 case FILTER_TYPE_NOT: 4587 hashCode += notComp.hashCode(); 4588 break; 4589 4590 case FILTER_TYPE_EQUALITY: 4591 case FILTER_TYPE_GREATER_OR_EQUAL: 4592 case FILTER_TYPE_LESS_OR_EQUAL: 4593 case FILTER_TYPE_APPROXIMATE_MATCH: 4594 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 4595 hashCode += matchingRule.normalize(assertionValue).hashCode(); 4596 break; 4597 4598 case FILTER_TYPE_SUBSTRING: 4599 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 4600 if (subInitial != null) 4601 { 4602 hashCode += matchingRule.normalizeSubstring(subInitial, 4603 MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode(); 4604 } 4605 for (final ASN1OctetString s : subAny) 4606 { 4607 hashCode += matchingRule.normalizeSubstring(s, 4608 MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode(); 4609 } 4610 if (subFinal != null) 4611 { 4612 hashCode += matchingRule.normalizeSubstring(subFinal, 4613 MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode(); 4614 } 4615 break; 4616 4617 case FILTER_TYPE_PRESENCE: 4618 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 4619 break; 4620 4621 case FILTER_TYPE_EXTENSIBLE_MATCH: 4622 if (attrName != null) 4623 { 4624 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 4625 } 4626 4627 if (matchingRuleID != null) 4628 { 4629 hashCode += StaticUtils.toLowerCase(matchingRuleID).hashCode(); 4630 } 4631 4632 if (dnAttributes) 4633 { 4634 hashCode++; 4635 } 4636 4637 hashCode += matchingRule.normalize(assertionValue).hashCode(); 4638 break; 4639 } 4640 4641 return hashCode; 4642 } 4643 4644 4645 4646 /** 4647 * Indicates whether the provided object is equal to this search filter. 4648 * 4649 * @param o The object for which to make the determination. 4650 * 4651 * @return {@code true} if the provided object can be considered equal to 4652 * this search filter, or {@code false} if not. 4653 */ 4654 @Override() 4655 public boolean equals(@Nullable final Object o) 4656 { 4657 if (o == null) 4658 { 4659 return false; 4660 } 4661 4662 if (o == this) 4663 { 4664 return true; 4665 } 4666 4667 if (! (o instanceof Filter)) 4668 { 4669 return false; 4670 } 4671 4672 final Filter f = (Filter) o; 4673 if (filterType != f.filterType) 4674 { 4675 return false; 4676 } 4677 4678 final CaseIgnoreStringMatchingRule matchingRule = 4679 CaseIgnoreStringMatchingRule.getInstance(); 4680 4681 switch (filterType) 4682 { 4683 case FILTER_TYPE_AND: 4684 case FILTER_TYPE_OR: 4685 if (filterComps.length != f.filterComps.length) 4686 { 4687 return false; 4688 } 4689 4690 final HashSet<Filter> compSet = 4691 new HashSet<>(StaticUtils.computeMapCapacity(10)); 4692 compSet.addAll(Arrays.asList(filterComps)); 4693 4694 for (final Filter filterComp : f.filterComps) 4695 { 4696 if (! compSet.remove(filterComp)) 4697 { 4698 return false; 4699 } 4700 } 4701 4702 return true; 4703 4704 4705 case FILTER_TYPE_NOT: 4706 return notComp.equals(f.notComp); 4707 4708 4709 case FILTER_TYPE_EQUALITY: 4710 case FILTER_TYPE_GREATER_OR_EQUAL: 4711 case FILTER_TYPE_LESS_OR_EQUAL: 4712 case FILTER_TYPE_APPROXIMATE_MATCH: 4713 return (attrName.equalsIgnoreCase(f.attrName) && 4714 matchingRule.valuesMatch(assertionValue, f.assertionValue)); 4715 4716 4717 case FILTER_TYPE_SUBSTRING: 4718 if (! attrName.equalsIgnoreCase(f.attrName)) 4719 { 4720 return false; 4721 } 4722 4723 if (subAny.length != f.subAny.length) 4724 { 4725 return false; 4726 } 4727 4728 if (subInitial == null) 4729 { 4730 if (f.subInitial != null) 4731 { 4732 return false; 4733 } 4734 } 4735 else 4736 { 4737 if (f.subInitial == null) 4738 { 4739 return false; 4740 } 4741 4742 final ASN1OctetString si1 = matchingRule.normalizeSubstring( 4743 subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL); 4744 final ASN1OctetString si2 = matchingRule.normalizeSubstring( 4745 f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL); 4746 if (! si1.equals(si2)) 4747 { 4748 return false; 4749 } 4750 } 4751 4752 for (int i=0; i < subAny.length; i++) 4753 { 4754 final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i], 4755 MatchingRule.SUBSTRING_TYPE_SUBANY); 4756 final ASN1OctetString sa2 = matchingRule.normalizeSubstring( 4757 f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY); 4758 if (! sa1.equals(sa2)) 4759 { 4760 return false; 4761 } 4762 } 4763 4764 if (subFinal == null) 4765 { 4766 if (f.subFinal != null) 4767 { 4768 return false; 4769 } 4770 } 4771 else 4772 { 4773 if (f.subFinal == null) 4774 { 4775 return false; 4776 } 4777 4778 final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal, 4779 MatchingRule.SUBSTRING_TYPE_SUBFINAL); 4780 final ASN1OctetString sf2 = matchingRule.normalizeSubstring( 4781 f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL); 4782 if (! sf1.equals(sf2)) 4783 { 4784 return false; 4785 } 4786 } 4787 4788 return true; 4789 4790 4791 case FILTER_TYPE_PRESENCE: 4792 return (attrName.equalsIgnoreCase(f.attrName)); 4793 4794 4795 case FILTER_TYPE_EXTENSIBLE_MATCH: 4796 if (attrName == null) 4797 { 4798 if (f.attrName != null) 4799 { 4800 return false; 4801 } 4802 } 4803 else 4804 { 4805 if (f.attrName == null) 4806 { 4807 return false; 4808 } 4809 else 4810 { 4811 if (! attrName.equalsIgnoreCase(f.attrName)) 4812 { 4813 return false; 4814 } 4815 } 4816 } 4817 4818 if (matchingRuleID == null) 4819 { 4820 if (f.matchingRuleID != null) 4821 { 4822 return false; 4823 } 4824 } 4825 else 4826 { 4827 if (f.matchingRuleID == null) 4828 { 4829 return false; 4830 } 4831 else 4832 { 4833 if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID)) 4834 { 4835 return false; 4836 } 4837 } 4838 } 4839 4840 if (dnAttributes != f.dnAttributes) 4841 { 4842 return false; 4843 } 4844 4845 return matchingRule.valuesMatch(assertionValue, f.assertionValue); 4846 4847 4848 default: 4849 return false; 4850 } 4851 } 4852 4853 4854 4855 /** 4856 * Retrieves a string representation of this search filter. 4857 * 4858 * @return A string representation of this search filter. 4859 */ 4860 @Override() 4861 @NotNull() 4862 public String toString() 4863 { 4864 if (filterString == null) 4865 { 4866 final StringBuilder buffer = new StringBuilder(); 4867 toString(buffer); 4868 filterString = buffer.toString(); 4869 } 4870 4871 return filterString; 4872 } 4873 4874 4875 4876 /** 4877 * Appends a string representation of this search filter to the provided 4878 * buffer. 4879 * 4880 * @param buffer The buffer to which to append a string representation of 4881 * this search filter. 4882 */ 4883 public void toString(@NotNull final StringBuilder buffer) 4884 { 4885 switch (filterType) 4886 { 4887 case FILTER_TYPE_AND: 4888 buffer.append("(&"); 4889 for (final Filter f : filterComps) 4890 { 4891 f.toString(buffer); 4892 } 4893 buffer.append(')'); 4894 break; 4895 4896 case FILTER_TYPE_OR: 4897 buffer.append("(|"); 4898 for (final Filter f : filterComps) 4899 { 4900 f.toString(buffer); 4901 } 4902 buffer.append(')'); 4903 break; 4904 4905 case FILTER_TYPE_NOT: 4906 buffer.append("(!"); 4907 notComp.toString(buffer); 4908 buffer.append(')'); 4909 break; 4910 4911 case FILTER_TYPE_EQUALITY: 4912 buffer.append('('); 4913 buffer.append(attrName); 4914 buffer.append('='); 4915 encodeValue(assertionValue, buffer); 4916 buffer.append(')'); 4917 break; 4918 4919 case FILTER_TYPE_SUBSTRING: 4920 buffer.append('('); 4921 buffer.append(attrName); 4922 buffer.append('='); 4923 if (subInitial != null) 4924 { 4925 encodeValue(subInitial, buffer); 4926 } 4927 buffer.append('*'); 4928 for (final ASN1OctetString s : subAny) 4929 { 4930 encodeValue(s, buffer); 4931 buffer.append('*'); 4932 } 4933 if (subFinal != null) 4934 { 4935 encodeValue(subFinal, buffer); 4936 } 4937 buffer.append(')'); 4938 break; 4939 4940 case FILTER_TYPE_GREATER_OR_EQUAL: 4941 buffer.append('('); 4942 buffer.append(attrName); 4943 buffer.append(">="); 4944 encodeValue(assertionValue, buffer); 4945 buffer.append(')'); 4946 break; 4947 4948 case FILTER_TYPE_LESS_OR_EQUAL: 4949 buffer.append('('); 4950 buffer.append(attrName); 4951 buffer.append("<="); 4952 encodeValue(assertionValue, buffer); 4953 buffer.append(')'); 4954 break; 4955 4956 case FILTER_TYPE_PRESENCE: 4957 buffer.append('('); 4958 buffer.append(attrName); 4959 buffer.append("=*)"); 4960 break; 4961 4962 case FILTER_TYPE_APPROXIMATE_MATCH: 4963 buffer.append('('); 4964 buffer.append(attrName); 4965 buffer.append("~="); 4966 encodeValue(assertionValue, buffer); 4967 buffer.append(')'); 4968 break; 4969 4970 case FILTER_TYPE_EXTENSIBLE_MATCH: 4971 buffer.append('('); 4972 if (attrName != null) 4973 { 4974 buffer.append(attrName); 4975 } 4976 4977 if (dnAttributes) 4978 { 4979 buffer.append(":dn"); 4980 } 4981 4982 if (matchingRuleID != null) 4983 { 4984 buffer.append(':'); 4985 buffer.append(matchingRuleID); 4986 } 4987 4988 buffer.append(":="); 4989 encodeValue(assertionValue, buffer); 4990 buffer.append(')'); 4991 break; 4992 } 4993 } 4994 4995 4996 4997 /** 4998 * Retrieves a normalized string representation of this search filter. 4999 * 5000 * @return A normalized string representation of this search filter. 5001 */ 5002 @NotNull() 5003 public String toNormalizedString() 5004 { 5005 if (normalizedString == null) 5006 { 5007 final StringBuilder buffer = new StringBuilder(); 5008 toNormalizedString(buffer); 5009 normalizedString = buffer.toString(); 5010 } 5011 5012 return normalizedString; 5013 } 5014 5015 5016 5017 /** 5018 * Appends a normalized string representation of this search filter to the 5019 * provided buffer. 5020 * 5021 * @param buffer The buffer to which to append a normalized string 5022 * representation of this search filter. 5023 */ 5024 public void toNormalizedString(@NotNull final StringBuilder buffer) 5025 { 5026 final CaseIgnoreStringMatchingRule mr = 5027 CaseIgnoreStringMatchingRule.getInstance(); 5028 5029 switch (filterType) 5030 { 5031 case FILTER_TYPE_AND: 5032 buffer.append("(&"); 5033 for (final Filter f : filterComps) 5034 { 5035 f.toNormalizedString(buffer); 5036 } 5037 buffer.append(')'); 5038 break; 5039 5040 case FILTER_TYPE_OR: 5041 buffer.append("(|"); 5042 for (final Filter f : filterComps) 5043 { 5044 f.toNormalizedString(buffer); 5045 } 5046 buffer.append(')'); 5047 break; 5048 5049 case FILTER_TYPE_NOT: 5050 buffer.append("(!"); 5051 notComp.toNormalizedString(buffer); 5052 buffer.append(')'); 5053 break; 5054 5055 case FILTER_TYPE_EQUALITY: 5056 buffer.append('('); 5057 buffer.append(StaticUtils.toLowerCase(attrName)); 5058 buffer.append('='); 5059 encodeValue(mr.normalize(assertionValue), buffer); 5060 buffer.append(')'); 5061 break; 5062 5063 case FILTER_TYPE_SUBSTRING: 5064 buffer.append('('); 5065 buffer.append(StaticUtils.toLowerCase(attrName)); 5066 buffer.append('='); 5067 if (subInitial != null) 5068 { 5069 encodeValue(mr.normalizeSubstring(subInitial, 5070 MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer); 5071 } 5072 buffer.append('*'); 5073 for (final ASN1OctetString s : subAny) 5074 { 5075 encodeValue(mr.normalizeSubstring(s, 5076 MatchingRule.SUBSTRING_TYPE_SUBANY), buffer); 5077 buffer.append('*'); 5078 } 5079 if (subFinal != null) 5080 { 5081 encodeValue(mr.normalizeSubstring(subFinal, 5082 MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer); 5083 } 5084 buffer.append(')'); 5085 break; 5086 5087 case FILTER_TYPE_GREATER_OR_EQUAL: 5088 buffer.append('('); 5089 buffer.append(StaticUtils.toLowerCase(attrName)); 5090 buffer.append(">="); 5091 encodeValue(mr.normalize(assertionValue), buffer); 5092 buffer.append(')'); 5093 break; 5094 5095 case FILTER_TYPE_LESS_OR_EQUAL: 5096 buffer.append('('); 5097 buffer.append(StaticUtils.toLowerCase(attrName)); 5098 buffer.append("<="); 5099 encodeValue(mr.normalize(assertionValue), buffer); 5100 buffer.append(')'); 5101 break; 5102 5103 case FILTER_TYPE_PRESENCE: 5104 buffer.append('('); 5105 buffer.append(StaticUtils.toLowerCase(attrName)); 5106 buffer.append("=*)"); 5107 break; 5108 5109 case FILTER_TYPE_APPROXIMATE_MATCH: 5110 buffer.append('('); 5111 buffer.append(StaticUtils.toLowerCase(attrName)); 5112 buffer.append("~="); 5113 encodeValue(mr.normalize(assertionValue), buffer); 5114 buffer.append(')'); 5115 break; 5116 5117 case FILTER_TYPE_EXTENSIBLE_MATCH: 5118 buffer.append('('); 5119 if (attrName != null) 5120 { 5121 buffer.append(StaticUtils.toLowerCase(attrName)); 5122 } 5123 5124 if (dnAttributes) 5125 { 5126 buffer.append(":dn"); 5127 } 5128 5129 if (matchingRuleID != null) 5130 { 5131 buffer.append(':'); 5132 buffer.append(StaticUtils.toLowerCase(matchingRuleID)); 5133 } 5134 5135 buffer.append(":="); 5136 encodeValue(mr.normalize(assertionValue), buffer); 5137 buffer.append(')'); 5138 break; 5139 } 5140 } 5141 5142 5143 5144 /** 5145 * Encodes the provided value into a form suitable for use as the assertion 5146 * value in the string representation of a search filter. Parentheses, 5147 * asterisks, backslashes, null characters, and any non-ASCII characters will 5148 * be escaped using a backslash before the hexadecimal representation of each 5149 * byte in the character to escape. 5150 * 5151 * @param value The value to be encoded. It must not be {@code null}. 5152 * 5153 * @return The encoded representation of the provided string. 5154 */ 5155 @NotNull() 5156 public static String encodeValue(@NotNull final String value) 5157 { 5158 Validator.ensureNotNull(value); 5159 5160 final StringBuilder buffer = new StringBuilder(); 5161 encodeValue(new ASN1OctetString(value), buffer); 5162 return buffer.toString(); 5163 } 5164 5165 5166 5167 /** 5168 * Creates the string representation of a substring assertion with the 5169 * provided components. 5170 * 5171 * @param subInitial The subInitial component for this substring filter. It 5172 * may be {@code null} if there is no subInitial 5173 * component, but it must not be empty. 5174 * @param subAny The set of subAny components for this substring filter. 5175 * It may be {@code null} or empty if there are no subAny 5176 * components. 5177 * @param subFinal The subFinal component for this substring filter. It 5178 * may be {@code null} if there is no subFinal component, 5179 * but it must not be empty. 5180 * 5181 * @return The string representation of a substring assertion with the 5182 * provided components. 5183 */ 5184 @NotNull() 5185 public static String createSubstringAssertion( 5186 @Nullable final String subInitial, 5187 @Nullable final String[] subAny, 5188 @Nullable final String subFinal) 5189 { 5190 final ASN1OctetString subInitialOS; 5191 if (subInitial == null) 5192 { 5193 subInitialOS = null; 5194 } 5195 else 5196 { 5197 subInitialOS = new ASN1OctetString(subInitial); 5198 } 5199 5200 final ASN1OctetString[] subAnyOS; 5201 if (subAny == null) 5202 { 5203 subAnyOS = NO_SUB_ANY; 5204 } 5205 else 5206 { 5207 subAnyOS = new ASN1OctetString[subAny.length]; 5208 for (int i=0; i < subAny.length; i++) 5209 { 5210 subAnyOS[i] = new ASN1OctetString(subAny[i]); 5211 } 5212 } 5213 5214 final ASN1OctetString subFinalOS; 5215 if (subFinal == null) 5216 { 5217 subFinalOS = null; 5218 } 5219 else 5220 { 5221 subFinalOS = new ASN1OctetString(subFinal); 5222 } 5223 5224 return createSubstringAssertion(subInitialOS, subAnyOS, subFinalOS); 5225 } 5226 5227 5228 5229 /** 5230 * Creates the string representation of a substring assertion with the 5231 * provided components. 5232 * 5233 * @param subInitial The subInitial component for this substring filter. It 5234 * may be {@code null} if there is no subInitial 5235 * component, but it must not be empty. 5236 * @param subAny The set of subAny components for this substring filter. 5237 * It may be {@code null} or empty if there are no subAny 5238 * components. 5239 * @param subFinal The subFinal component for this substring filter. It 5240 * may be {@code null} if there is no subFinal component, 5241 * but it must not be empty. 5242 * 5243 * @return The string representation of a substring assertion with the 5244 * provided components. 5245 */ 5246 @NotNull() 5247 public static String createSubstringAssertion( 5248 @Nullable final byte[] subInitial, 5249 @Nullable final byte[][] subAny, 5250 @Nullable final byte[] subFinal) 5251 { 5252 final ASN1OctetString subInitialOS; 5253 if (subInitial == null) 5254 { 5255 subInitialOS = null; 5256 } 5257 else 5258 { 5259 subInitialOS = new ASN1OctetString(subInitial); 5260 } 5261 5262 final ASN1OctetString[] subAnyOS; 5263 if (subAny == null) 5264 { 5265 subAnyOS = NO_SUB_ANY; 5266 } 5267 else 5268 { 5269 subAnyOS = new ASN1OctetString[subAny.length]; 5270 for (int i=0; i < subAny.length; i++) 5271 { 5272 subAnyOS[i] = new ASN1OctetString(subAny[i]); 5273 } 5274 } 5275 5276 final ASN1OctetString subFinalOS; 5277 if (subFinal == null) 5278 { 5279 subFinalOS = null; 5280 } 5281 else 5282 { 5283 subFinalOS = new ASN1OctetString(subFinal); 5284 } 5285 5286 return createSubstringAssertion(subInitialOS, subAnyOS, subFinalOS); 5287 } 5288 5289 5290 5291 /** 5292 * Creates the string representation of a substring assertion with the 5293 * provided components. 5294 * 5295 * @param subInitial The subInitial component for this substring filter. It 5296 * may be {@code null} if there is no subInitial 5297 * component, but it must not be empty. 5298 * @param subAny The set of subAny components for this substring filter. 5299 * It must not be {@code null}, but may be empty if there 5300 * are no subAny components. 5301 * @param subFinal The subFinal component for this substring filter. It 5302 * may be {@code null} if there is no subFinal component, 5303 * but it must not be empty. 5304 * 5305 * @return The string representation of a substring assertion with the 5306 * provided components. 5307 */ 5308 @NotNull() 5309 private static String createSubstringAssertion( 5310 @Nullable final ASN1OctetString subInitial, 5311 @Nullable final ASN1OctetString[] subAny, 5312 @Nullable final ASN1OctetString subFinal) 5313 { 5314 Validator.ensureTrue( 5315 (((subInitial != null) && (subInitial.getValueLength() > 0)) || 5316 ((subAny != null) && (subAny.length > 0) && 5317 (subAny[0].getValueLength() > 0)) || 5318 ((subFinal != null) && (subFinal.getValueLength() > 0))), 5319 "At least one substring filter component must be non-null and " + 5320 "non-empty"); 5321 5322 final StringBuilder buffer = new StringBuilder(); 5323 if (subInitial != null) 5324 { 5325 encodeValue(subInitial, buffer); 5326 } 5327 buffer.append('*'); 5328 for (final ASN1OctetString s : subAny) 5329 { 5330 encodeValue(s, buffer); 5331 buffer.append('*'); 5332 } 5333 if (subFinal != null) 5334 { 5335 encodeValue(subFinal, buffer); 5336 } 5337 5338 return buffer.toString(); 5339 } 5340 5341 5342 5343 /** 5344 * Encodes the provided value into a form suitable for use as the assertion 5345 * value in the string representation of a search filter. Parentheses, 5346 * asterisks, backslashes, null characters, and any non-ASCII characters will 5347 * be escaped using a backslash before the hexadecimal representation of each 5348 * byte in the character to escape. 5349 * 5350 * @param value The value to be encoded. It must not be {@code null}. 5351 * 5352 * @return The encoded representation of the provided string. 5353 */ 5354 @NotNull() 5355 public static String encodeValue(@NotNull final byte[]value) 5356 { 5357 Validator.ensureNotNull(value); 5358 5359 final StringBuilder buffer = new StringBuilder(); 5360 encodeValue(new ASN1OctetString(value), buffer); 5361 return buffer.toString(); 5362 } 5363 5364 5365 5366 /** 5367 * Appends the assertion value for this filter to the provided buffer, 5368 * encoding any special characters as necessary. 5369 * 5370 * @param value The value to be encoded. 5371 * @param buffer The buffer to which the assertion value should be appended. 5372 */ 5373 public static void encodeValue(@NotNull final ASN1OctetString value, 5374 @NotNull final StringBuilder buffer) 5375 { 5376 final byte[] valueBytes = value.getValue(); 5377 for (int i=0; i < valueBytes.length; i++) 5378 { 5379 switch (StaticUtils.numBytesInUTF8CharacterWithFirstByte(valueBytes[i])) 5380 { 5381 case 1: 5382 // This character is ASCII, but might still need to be escaped. 5383 if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters. 5384 (valueBytes[i] == 0x28) || // Open parenthesis 5385 (valueBytes[i] == 0x29) || // Close parenthesis 5386 (valueBytes[i] == 0x2A) || // Asterisk 5387 (valueBytes[i] == 0x5C) || // Backslash 5388 (valueBytes[i] == 0x7F)) // DEL 5389 { 5390 buffer.append('\\'); 5391 StaticUtils.toHex(valueBytes[i], buffer); 5392 } 5393 else 5394 { 5395 buffer.append((char) valueBytes[i]); 5396 } 5397 break; 5398 5399 case 2: 5400 // If there are at least two bytes left, then we'll hex-encode the 5401 // next two bytes. Otherwise we'll hex-encode whatever is left. 5402 buffer.append('\\'); 5403 StaticUtils.toHex(valueBytes[i++], buffer); 5404 if (i < valueBytes.length) 5405 { 5406 buffer.append('\\'); 5407 StaticUtils.toHex(valueBytes[i], buffer); 5408 } 5409 break; 5410 5411 case 3: 5412 // If there are at least three bytes left, then we'll hex-encode the 5413 // next three bytes. Otherwise we'll hex-encode whatever is left. 5414 buffer.append('\\'); 5415 StaticUtils.toHex(valueBytes[i++], buffer); 5416 if (i < valueBytes.length) 5417 { 5418 buffer.append('\\'); 5419 StaticUtils.toHex(valueBytes[i++], buffer); 5420 } 5421 if (i < valueBytes.length) 5422 { 5423 buffer.append('\\'); 5424 StaticUtils.toHex(valueBytes[i], buffer); 5425 } 5426 break; 5427 5428 case 4: 5429 // If there are at least four bytes left, then we'll hex-encode the 5430 // next four bytes. Otherwise we'll hex-encode whatever is left. 5431 buffer.append('\\'); 5432 StaticUtils.toHex(valueBytes[i++], buffer); 5433 if (i < valueBytes.length) 5434 { 5435 buffer.append('\\'); 5436 StaticUtils.toHex(valueBytes[i++], buffer); 5437 } 5438 if (i < valueBytes.length) 5439 { 5440 buffer.append('\\'); 5441 StaticUtils.toHex(valueBytes[i++], buffer); 5442 } 5443 if (i < valueBytes.length) 5444 { 5445 buffer.append('\\'); 5446 StaticUtils.toHex(valueBytes[i], buffer); 5447 } 5448 break; 5449 5450 default: 5451 // We'll hex-encode whatever is left in the buffer. 5452 while (i < valueBytes.length) 5453 { 5454 buffer.append('\\'); 5455 StaticUtils.toHex(valueBytes[i++], buffer); 5456 } 5457 break; 5458 } 5459 } 5460 } 5461 5462 5463 5464 /** 5465 * Appends a number of lines comprising the Java source code that can be used 5466 * to recreate this filter to the given list. Note that unless a first line 5467 * prefix and/or last line suffix are provided, this will just include the 5468 * code for the static method used to create the filter, starting with 5469 * "Filter.createXFilter(" and ending with the closing parenthesis for that 5470 * method call. 5471 * 5472 * @param lineList The list to which the source code lines should be 5473 * added. 5474 * @param indentSpaces The number of spaces that should be used to indent 5475 * the generated code. It must not be negative. 5476 * @param firstLinePrefix An optional string that should precede the static 5477 * method call (e.g., it could be used for an 5478 * attribute assignment, like "Filter f = "). It may 5479 * be {@code null} or empty if there should be no 5480 * first line prefix. 5481 * @param lastLineSuffix An optional suffix that should follow the closing 5482 * parenthesis of the static method call (e.g., it 5483 * could be a semicolon to represent the end of a 5484 * Java statement). It may be {@code null} or empty 5485 * if there should be no last line suffix. 5486 */ 5487 public void toCode(@NotNull final List<String> lineList, 5488 final int indentSpaces, 5489 @Nullable final String firstLinePrefix, 5490 @Nullable final String lastLineSuffix) 5491 { 5492 // Generate a string with the appropriate indent. 5493 final StringBuilder buffer = new StringBuilder(); 5494 for (int i = 0; i < indentSpaces; i++) 5495 { 5496 buffer.append(' '); 5497 } 5498 final String indent = buffer.toString(); 5499 5500 5501 // Start the first line, including any appropriate prefix. 5502 buffer.setLength(0); 5503 buffer.append(indent); 5504 if (firstLinePrefix != null) 5505 { 5506 buffer.append(firstLinePrefix); 5507 } 5508 5509 5510 // Figure out what type of filter it is and create the appropriate code for 5511 // that type of filter. 5512 switch (filterType) 5513 { 5514 case FILTER_TYPE_AND: 5515 case FILTER_TYPE_OR: 5516 if (filterType == FILTER_TYPE_AND) 5517 { 5518 buffer.append("Filter.and("); 5519 } 5520 else 5521 { 5522 buffer.append("Filter.or("); 5523 } 5524 if (filterComps.length == 0) 5525 { 5526 buffer.append(')'); 5527 if (lastLineSuffix != null) 5528 { 5529 buffer.append(lastLineSuffix); 5530 } 5531 lineList.add(buffer.toString()); 5532 return; 5533 } 5534 5535 for (int i = 0; i < filterComps.length; i++) 5536 { 5537 String suffix; 5538 if (i == (filterComps.length - 1)) 5539 { 5540 suffix = ")"; 5541 if (lastLineSuffix != null) 5542 { 5543 suffix += lastLineSuffix; 5544 } 5545 } 5546 else 5547 { 5548 suffix = ","; 5549 } 5550 5551 filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix); 5552 } 5553 return; 5554 5555 5556 case FILTER_TYPE_NOT: 5557 buffer.append("Filter.not("); 5558 lineList.add(buffer.toString()); 5559 5560 final String suffix; 5561 if (lastLineSuffix == null) 5562 { 5563 suffix = ")"; 5564 } 5565 else 5566 { 5567 suffix = ')' + lastLineSuffix; 5568 } 5569 notComp.toCode(lineList, indentSpaces + 5, null, suffix); 5570 return; 5571 5572 case FILTER_TYPE_PRESENCE: 5573 buffer.append("Filter.present("); 5574 lineList.add(buffer.toString()); 5575 5576 buffer.setLength(0); 5577 buffer.append(indent); 5578 buffer.append(" \""); 5579 buffer.append(attrName); 5580 buffer.append("\")"); 5581 5582 if (lastLineSuffix != null) 5583 { 5584 buffer.append(lastLineSuffix); 5585 } 5586 5587 lineList.add(buffer.toString()); 5588 return; 5589 5590 5591 case FILTER_TYPE_EQUALITY: 5592 case FILTER_TYPE_GREATER_OR_EQUAL: 5593 case FILTER_TYPE_LESS_OR_EQUAL: 5594 case FILTER_TYPE_APPROXIMATE_MATCH: 5595 if (filterType == FILTER_TYPE_EQUALITY) 5596 { 5597 buffer.append("Filter.equals("); 5598 } 5599 else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL) 5600 { 5601 buffer.append("Filter.greaterOrEqual("); 5602 } 5603 else if (filterType == FILTER_TYPE_LESS_OR_EQUAL) 5604 { 5605 buffer.append("Filter.lessOrEqual("); 5606 } 5607 else 5608 { 5609 buffer.append("Filter.approximateMatch("); 5610 } 5611 lineList.add(buffer.toString()); 5612 5613 buffer.setLength(0); 5614 buffer.append(indent); 5615 buffer.append(" \""); 5616 buffer.append(attrName); 5617 buffer.append("\","); 5618 lineList.add(buffer.toString()); 5619 5620 buffer.setLength(0); 5621 buffer.append(indent); 5622 buffer.append(" "); 5623 if (StaticUtils.isSensitiveToCodeAttribute(attrName)) 5624 { 5625 buffer.append("\"---redacted-value---\""); 5626 } 5627 else if (StaticUtils.isPrintableString(assertionValue.getValue())) 5628 { 5629 buffer.append('"'); 5630 buffer.append(assertionValue.stringValue()); 5631 buffer.append('"'); 5632 } 5633 else 5634 { 5635 StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer); 5636 } 5637 5638 buffer.append(')'); 5639 5640 if (lastLineSuffix != null) 5641 { 5642 buffer.append(lastLineSuffix); 5643 } 5644 5645 lineList.add(buffer.toString()); 5646 return; 5647 5648 5649 case FILTER_TYPE_SUBSTRING: 5650 buffer.append("Filter.substring("); 5651 lineList.add(buffer.toString()); 5652 5653 buffer.setLength(0); 5654 buffer.append(indent); 5655 buffer.append(" \""); 5656 buffer.append(attrName); 5657 buffer.append("\","); 5658 lineList.add(buffer.toString()); 5659 5660 final boolean isRedacted = 5661 StaticUtils.isSensitiveToCodeAttribute(attrName); 5662 boolean isPrintable = true; 5663 if (subInitial != null) 5664 { 5665 isPrintable = StaticUtils.isPrintableString(subInitial.getValue()); 5666 } 5667 5668 if (isPrintable && (subAny != null)) 5669 { 5670 for (final ASN1OctetString s : subAny) 5671 { 5672 if (! StaticUtils.isPrintableString(s.getValue())) 5673 { 5674 isPrintable = false; 5675 break; 5676 } 5677 } 5678 } 5679 5680 if (isPrintable && (subFinal != null)) 5681 { 5682 isPrintable = StaticUtils.isPrintableString(subFinal.getValue()); 5683 } 5684 5685 buffer.setLength(0); 5686 buffer.append(indent); 5687 buffer.append(" "); 5688 if (subInitial == null) 5689 { 5690 buffer.append("null"); 5691 } 5692 else if (isRedacted) 5693 { 5694 buffer.append("\"---redacted-subInitial---\""); 5695 } 5696 else if (isPrintable) 5697 { 5698 buffer.append('"'); 5699 buffer.append(subInitial.stringValue()); 5700 buffer.append('"'); 5701 } 5702 else 5703 { 5704 StaticUtils.byteArrayToCode(subInitial.getValue(), buffer); 5705 } 5706 buffer.append(','); 5707 lineList.add(buffer.toString()); 5708 5709 buffer.setLength(0); 5710 buffer.append(indent); 5711 buffer.append(" "); 5712 if ((subAny == null) || (subAny.length == 0)) 5713 { 5714 buffer.append("null,"); 5715 lineList.add(buffer.toString()); 5716 } 5717 else if (isRedacted) 5718 { 5719 buffer.append("new String[]"); 5720 lineList.add(buffer.toString()); 5721 5722 lineList.add(indent + " {"); 5723 5724 for (int i=0; i < subAny.length; i++) 5725 { 5726 buffer.setLength(0); 5727 buffer.append(indent); 5728 buffer.append(" \"---redacted-subAny-"); 5729 buffer.append(i+1); 5730 buffer.append("---\""); 5731 if (i < (subAny.length-1)) 5732 { 5733 buffer.append(','); 5734 } 5735 lineList.add(buffer.toString()); 5736 } 5737 5738 lineList.add(indent + " },"); 5739 } 5740 else if (isPrintable) 5741 { 5742 buffer.append("new String[]"); 5743 lineList.add(buffer.toString()); 5744 5745 lineList.add(indent + " {"); 5746 5747 for (int i=0; i < subAny.length; i++) 5748 { 5749 buffer.setLength(0); 5750 buffer.append(indent); 5751 buffer.append(" \""); 5752 buffer.append(subAny[i].stringValue()); 5753 buffer.append('"'); 5754 if (i < (subAny.length-1)) 5755 { 5756 buffer.append(','); 5757 } 5758 lineList.add(buffer.toString()); 5759 } 5760 5761 lineList.add(indent + " },"); 5762 } 5763 else 5764 { 5765 buffer.append("new String[]"); 5766 lineList.add(buffer.toString()); 5767 5768 lineList.add(indent + " {"); 5769 5770 for (int i=0; i < subAny.length; i++) 5771 { 5772 buffer.setLength(0); 5773 buffer.append(indent); 5774 buffer.append(" "); 5775 StaticUtils.byteArrayToCode(subAny[i].getValue(), buffer); 5776 if (i < (subAny.length-1)) 5777 { 5778 buffer.append(','); 5779 } 5780 lineList.add(buffer.toString()); 5781 } 5782 5783 lineList.add(indent + " },"); 5784 } 5785 5786 buffer.setLength(0); 5787 buffer.append(indent); 5788 buffer.append(" "); 5789 if (subFinal == null) 5790 { 5791 buffer.append("null)"); 5792 } 5793 else if (isRedacted) 5794 { 5795 buffer.append("\"---redacted-subFinal---\")"); 5796 } 5797 else if (isPrintable) 5798 { 5799 buffer.append('"'); 5800 buffer.append(subFinal.stringValue()); 5801 buffer.append("\")"); 5802 } 5803 else 5804 { 5805 StaticUtils.byteArrayToCode(subFinal.getValue(), buffer); 5806 buffer.append(')'); 5807 } 5808 if (lastLineSuffix != null) 5809 { 5810 buffer.append(lastLineSuffix); 5811 } 5812 lineList.add(buffer.toString()); 5813 return; 5814 5815 5816 case FILTER_TYPE_EXTENSIBLE_MATCH: 5817 buffer.append("Filter.extensibleMatch("); 5818 lineList.add(buffer.toString()); 5819 5820 buffer.setLength(0); 5821 buffer.append(indent); 5822 buffer.append(" "); 5823 if (attrName == null) 5824 { 5825 buffer.append("null, // Attribute Description"); 5826 } 5827 else 5828 { 5829 buffer.append('"'); 5830 buffer.append(attrName); 5831 buffer.append("\","); 5832 } 5833 lineList.add(buffer.toString()); 5834 5835 buffer.setLength(0); 5836 buffer.append(indent); 5837 buffer.append(" "); 5838 if (matchingRuleID == null) 5839 { 5840 buffer.append("null, // Matching Rule ID"); 5841 } 5842 else 5843 { 5844 buffer.append('"'); 5845 buffer.append(matchingRuleID); 5846 buffer.append("\","); 5847 } 5848 lineList.add(buffer.toString()); 5849 5850 buffer.setLength(0); 5851 buffer.append(indent); 5852 buffer.append(" "); 5853 buffer.append(dnAttributes); 5854 buffer.append(", // DN Attributes"); 5855 lineList.add(buffer.toString()); 5856 5857 buffer.setLength(0); 5858 buffer.append(indent); 5859 buffer.append(" "); 5860 if ((attrName != null) && 5861 StaticUtils.isSensitiveToCodeAttribute(attrName)) 5862 { 5863 buffer.append("\"---redacted-value---\")"); 5864 } 5865 else 5866 { 5867 if (StaticUtils.isPrintableString(assertionValue.getValue())) 5868 { 5869 buffer.append('"'); 5870 buffer.append(assertionValue.stringValue()); 5871 buffer.append("\")"); 5872 } 5873 else 5874 { 5875 StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer); 5876 buffer.append(')'); 5877 } 5878 } 5879 5880 if (lastLineSuffix != null) 5881 { 5882 buffer.append(lastLineSuffix); 5883 } 5884 lineList.add(buffer.toString()); 5885 return; 5886 } 5887 } 5888}