001/* 002 * Copyright 2007-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2022 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-2022 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.createPresenceFilter("objectClass"); 204 * Filter f2 = Filter.createEqualityFilter("uid", "john.doe"); 205 * Filter f3 = Filter.createORFilter( 206 * Filter.createEqualityFilter("givenName", "John"), 207 * Filter.createEqualityFilter("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 * 445 * @param andComponents The set of filter components to include in the AND 446 * filter. It must not be {@code null}. 447 * 448 * @return The created AND search filter. 449 */ 450 @NotNull() 451 public static Filter createANDFilter(@NotNull final Filter... andComponents) 452 { 453 Validator.ensureNotNull(andComponents); 454 455 return new Filter(null, FILTER_TYPE_AND, andComponents, null, null, null, 456 null, NO_SUB_ANY, null, null, false); 457 } 458 459 460 461 /** 462 * Creates a new AND search filter with the provided components. 463 * 464 * @param andComponents The set of filter components to include in the AND 465 * filter. It must not be {@code null}. 466 * 467 * @return The created AND search filter. 468 */ 469 @NotNull() 470 public static Filter createANDFilter( 471 @NotNull final List<Filter> andComponents) 472 { 473 Validator.ensureNotNull(andComponents); 474 475 return new Filter(null, FILTER_TYPE_AND, 476 andComponents.toArray(new Filter[andComponents.size()]), 477 null, null, null, null, NO_SUB_ANY, null, null, false); 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( 492 @NotNull final Collection<Filter> andComponents) 493 { 494 Validator.ensureNotNull(andComponents); 495 496 return new Filter(null, FILTER_TYPE_AND, 497 andComponents.toArray(new Filter[andComponents.size()]), 498 null, null, null, null, NO_SUB_ANY, null, null, false); 499 } 500 501 502 503 /** 504 * Creates a new OR search filter with the provided components. 505 * 506 * @param orComponents The set of filter components to include in the OR 507 * filter. It must not be {@code null}. 508 * 509 * @return The created OR search filter. 510 */ 511 @NotNull() 512 public static Filter createORFilter(@NotNull final Filter... orComponents) 513 { 514 Validator.ensureNotNull(orComponents); 515 516 return new Filter(null, FILTER_TYPE_OR, orComponents, null, null, null, 517 null, NO_SUB_ANY, null, null, false); 518 } 519 520 521 522 /** 523 * Creates a new OR search filter with the provided components. 524 * 525 * @param orComponents The set of filter components to include in the OR 526 * filter. It must not be {@code null}. 527 * 528 * @return The created OR search filter. 529 */ 530 @NotNull() 531 public static Filter createORFilter(@NotNull final List<Filter> orComponents) 532 { 533 Validator.ensureNotNull(orComponents); 534 535 return new Filter(null, FILTER_TYPE_OR, 536 orComponents.toArray(new Filter[orComponents.size()]), 537 null, null, null, null, NO_SUB_ANY, null, null, false); 538 } 539 540 541 542 /** 543 * Creates a new OR search filter with the provided components. 544 * 545 * @param orComponents The set of filter components to include in the OR 546 * filter. It must not be {@code null}. 547 * 548 * @return The created OR search filter. 549 */ 550 @NotNull() 551 public static Filter createORFilter( 552 @NotNull final Collection<Filter> orComponents) 553 { 554 Validator.ensureNotNull(orComponents); 555 556 return new Filter(null, FILTER_TYPE_OR, 557 orComponents.toArray(new Filter[orComponents.size()]), 558 null, null, null, null, NO_SUB_ANY, null, null, false); 559 } 560 561 562 563 /** 564 * Creates a new NOT search filter with the provided component. 565 * 566 * @param notComponent The filter component to include in this NOT filter. 567 * It must not be {@code null}. 568 * 569 * @return The created NOT search filter. 570 */ 571 @NotNull() 572 public static Filter createNOTFilter(@NotNull final Filter notComponent) 573 { 574 Validator.ensureNotNull(notComponent); 575 576 return new Filter(null, FILTER_TYPE_NOT, NO_FILTERS, notComponent, null, 577 null, null, NO_SUB_ANY, null, null, false); 578 } 579 580 581 582 /** 583 * Creates a new equality search filter with the provided information. 584 * 585 * @param attributeName The attribute name for this equality filter. It 586 * must not be {@code null}. 587 * @param assertionValue The assertion value for this equality filter. It 588 * must not be {@code null}. 589 * 590 * @return The created equality search filter. 591 */ 592 @NotNull() 593 public static Filter createEqualityFilter(@NotNull final String attributeName, 594 @NotNull final String assertionValue) 595 { 596 Validator.ensureNotNull(attributeName, assertionValue); 597 598 return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, 599 attributeName, new ASN1OctetString(assertionValue), null, 600 NO_SUB_ANY, null, null, false); 601 } 602 603 604 605 /** 606 * Creates a new equality search filter with the provided information. 607 * 608 * @param attributeName The attribute name for this equality filter. It 609 * must not be {@code null}. 610 * @param assertionValue The assertion value for this equality filter. It 611 * must not be {@code null}. 612 * 613 * @return The created equality search filter. 614 */ 615 @NotNull() 616 public static Filter createEqualityFilter(@NotNull final String attributeName, 617 @NotNull final byte[] assertionValue) 618 { 619 Validator.ensureNotNull(attributeName, assertionValue); 620 621 return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, 622 attributeName, new ASN1OctetString(assertionValue), null, 623 NO_SUB_ANY, null, null, false); 624 } 625 626 627 628 /** 629 * Creates a new equality search filter with the provided information. 630 * 631 * @param attributeName The attribute name for this equality filter. It 632 * must not be {@code null}. 633 * @param assertionValue The assertion value for this equality filter. It 634 * must not be {@code null}. 635 * 636 * @return The created equality search filter. 637 */ 638 @NotNull() 639 static Filter createEqualityFilter(@NotNull final String attributeName, 640 @NotNull final ASN1OctetString assertionValue) 641 { 642 Validator.ensureNotNull(attributeName, assertionValue); 643 644 return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, 645 attributeName, assertionValue, null, NO_SUB_ANY, null, 646 null, false); 647 } 648 649 650 651 /** 652 * Creates a new substring search filter with the provided information. At 653 * least one of the subInitial, subAny, and subFinal components must not be 654 * {@code null}. 655 * 656 * @param attributeName The attribute name for this substring filter. It 657 * must not be {@code null}. 658 * @param subInitial The subInitial component for this substring filter. 659 * @param subAny The set of subAny components for this substring 660 * filter. 661 * @param subFinal The subFinal component for this substring filter. 662 * 663 * @return The created substring search filter. 664 */ 665 @NotNull() 666 public static Filter createSubstringFilter( 667 @NotNull final String attributeName, 668 @Nullable final String subInitial, 669 @Nullable final String[] subAny, 670 @Nullable final String subFinal) 671 { 672 Validator.ensureNotNull(attributeName); 673 Validator.ensureTrue((subInitial != null) || 674 ((subAny != null) && (subAny.length > 0)) || 675 (subFinal != null)); 676 677 final ASN1OctetString subInitialOS; 678 if (subInitial == null) 679 { 680 subInitialOS = null; 681 } 682 else 683 { 684 subInitialOS = new ASN1OctetString(subInitial); 685 } 686 687 final ASN1OctetString[] subAnyArray; 688 if (subAny == null) 689 { 690 subAnyArray = NO_SUB_ANY; 691 } 692 else 693 { 694 subAnyArray = new ASN1OctetString[subAny.length]; 695 for (int i=0; i < subAny.length; i++) 696 { 697 subAnyArray[i] = new ASN1OctetString(subAny[i]); 698 } 699 } 700 701 final ASN1OctetString subFinalOS; 702 if (subFinal == null) 703 { 704 subFinalOS = null; 705 } 706 else 707 { 708 subFinalOS = new ASN1OctetString(subFinal); 709 } 710 711 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 712 attributeName, null, subInitialOS, subAnyArray, 713 subFinalOS, null, false); 714 } 715 716 717 718 /** 719 * Creates a new substring search filter with the provided information. At 720 * least one of the subInitial, subAny, and subFinal components must not be 721 * {@code null}. 722 * 723 * @param attributeName The attribute name for this substring filter. It 724 * must not be {@code null}. 725 * @param subInitial The subInitial component for this substring filter. 726 * @param subAny The set of subAny components for this substring 727 * filter. 728 * @param subFinal The subFinal component for this substring filter. 729 * 730 * @return The created substring search filter. 731 */ 732 @NotNull() 733 public static Filter createSubstringFilter( 734 @NotNull final String attributeName, 735 @Nullable final byte[] subInitial, 736 @Nullable final byte[][] subAny, 737 @Nullable final byte[] subFinal) 738 { 739 Validator.ensureNotNull(attributeName); 740 Validator.ensureTrue((subInitial != null) || 741 ((subAny != null) && (subAny.length > 0)) || 742 (subFinal != null)); 743 744 final ASN1OctetString subInitialOS; 745 if (subInitial == null) 746 { 747 subInitialOS = null; 748 } 749 else 750 { 751 subInitialOS = new ASN1OctetString(subInitial); 752 } 753 754 final ASN1OctetString[] subAnyArray; 755 if (subAny == null) 756 { 757 subAnyArray = NO_SUB_ANY; 758 } 759 else 760 { 761 subAnyArray = new ASN1OctetString[subAny.length]; 762 for (int i=0; i < subAny.length; i++) 763 { 764 subAnyArray[i] = new ASN1OctetString(subAny[i]); 765 } 766 } 767 768 final ASN1OctetString subFinalOS; 769 if (subFinal == null) 770 { 771 subFinalOS = null; 772 } 773 else 774 { 775 subFinalOS = new ASN1OctetString(subFinal); 776 } 777 778 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 779 attributeName, null, subInitialOS, subAnyArray, 780 subFinalOS, null, false); 781 } 782 783 784 785 /** 786 * Creates a new substring search filter with the provided information. At 787 * least one of the subInitial, subAny, and subFinal components must not be 788 * {@code null}. 789 * 790 * @param attributeName The attribute name for this substring filter. It 791 * must not be {@code null}. 792 * @param subInitial The subInitial component for this substring filter. 793 * @param subAny The set of subAny components for this substring 794 * filter. 795 * @param subFinal The subFinal component for this substring filter. 796 * 797 * @return The created substring search filter. 798 */ 799 @NotNull() 800 static Filter createSubstringFilter(@NotNull final String attributeName, 801 @Nullable final ASN1OctetString subInitial, 802 @Nullable final ASN1OctetString[] subAny, 803 @Nullable final ASN1OctetString subFinal) 804 { 805 Validator.ensureNotNull(attributeName); 806 Validator.ensureTrue((subInitial != null) || 807 ((subAny != null) && (subAny.length > 0)) || 808 (subFinal != null)); 809 810 if (subAny == null) 811 { 812 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 813 attributeName, null, subInitial, NO_SUB_ANY, subFinal, 814 null, false); 815 } 816 else 817 { 818 return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, 819 attributeName, null, subInitial, subAny, subFinal, null, 820 false); 821 } 822 } 823 824 825 826 /** 827 * Creates a new substring search filter with only a subInitial (starts with) 828 * component. 829 * 830 * @param attributeName The attribute name for this substring filter. It 831 * must not be {@code null}. 832 * @param subInitial The subInitial component for this substring filter. 833 * It must not be {@code null}. 834 * 835 * @return The created substring search filter. 836 */ 837 @NotNull() 838 public static Filter createSubInitialFilter( 839 @NotNull final String attributeName, 840 @NotNull final String subInitial) 841 { 842 return createSubstringFilter(attributeName, subInitial, null, null); 843 } 844 845 846 847 /** 848 * Creates a new substring search filter with only a subInitial (starts with) 849 * component. 850 * 851 * @param attributeName The attribute name for this substring filter. It 852 * must not be {@code null}. 853 * @param subInitial The subInitial component for this substring filter. 854 * It must not be {@code null}. 855 * 856 * @return The created substring search filter. 857 */ 858 @NotNull() 859 public static Filter createSubInitialFilter( 860 @NotNull final String attributeName, 861 @NotNull final byte[] subInitial) 862 { 863 return createSubstringFilter(attributeName, subInitial, null, null); 864 } 865 866 867 868 /** 869 * Creates a new substring search filter with only a subAny (contains) 870 * component. 871 * 872 * @param attributeName The attribute name for this substring filter. It 873 * must not be {@code null}. 874 * @param subAny The subAny values for this substring filter. It 875 * must not be {@code null} or empty. 876 * 877 * @return The created substring search filter. 878 */ 879 @NotNull() 880 public static Filter createSubAnyFilter(@NotNull final String attributeName, 881 @NotNull final String... subAny) 882 { 883 return createSubstringFilter(attributeName, null, subAny, null); 884 } 885 886 887 888 /** 889 * Creates a new substring search filter with only a subAny (contains) 890 * component. 891 * 892 * @param attributeName The attribute name for this substring filter. It 893 * must not be {@code null}. 894 * @param subAny The subAny values for this substring filter. It 895 * must not be {@code null} or empty. 896 * 897 * @return The created substring search filter. 898 */ 899 @NotNull() 900 public static Filter createSubAnyFilter(@NotNull final String attributeName, 901 @NotNull final byte[]... subAny) 902 { 903 return createSubstringFilter(attributeName, null, subAny, null); 904 } 905 906 907 908 /** 909 * Creates a new substring search filter with only a subFinal (ends with) 910 * component. 911 * 912 * @param attributeName The attribute name for this substring filter. It 913 * must not be {@code null}. 914 * @param subFinal The subFinal component for this substring filter. 915 * It must not be {@code null}. 916 * 917 * @return The created substring search filter. 918 */ 919 @NotNull() 920 public static Filter createSubFinalFilter(@NotNull final String attributeName, 921 @NotNull final String subFinal) 922 { 923 return createSubstringFilter(attributeName, null, null, subFinal); 924 } 925 926 927 928 /** 929 * Creates a new substring search filter with only a subFinal (ends with) 930 * component. 931 * 932 * @param attributeName The attribute name for this substring filter. It 933 * must not be {@code null}. 934 * @param subFinal The subFinal component for this substring filter. 935 * It must not be {@code null}. 936 * 937 * @return The created substring search filter. 938 */ 939 @NotNull() 940 public static Filter createSubFinalFilter(@NotNull final String attributeName, 941 @NotNull final byte[] subFinal) 942 { 943 return createSubstringFilter(attributeName, null, null, subFinal); 944 } 945 946 947 948 /** 949 * Creates a new greater-or-equal search filter with the provided information. 950 * 951 * @param attributeName The attribute name for this greater-or-equal 952 * filter. It must not be {@code null}. 953 * @param assertionValue The assertion value for this greater-or-equal 954 * filter. It must not be {@code null}. 955 * 956 * @return The created greater-or-equal search filter. 957 */ 958 @NotNull() 959 public static Filter createGreaterOrEqualFilter( 960 @NotNull final String attributeName, 961 @NotNull final String assertionValue) 962 { 963 Validator.ensureNotNull(attributeName, assertionValue); 964 965 return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, 966 attributeName, new ASN1OctetString(assertionValue), null, 967 NO_SUB_ANY, null, null, false); 968 } 969 970 971 972 /** 973 * Creates a new greater-or-equal search filter with the provided information. 974 * 975 * @param attributeName The attribute name for this greater-or-equal 976 * filter. It must not be {@code null}. 977 * @param assertionValue The assertion value for this greater-or-equal 978 * filter. It must not be {@code null}. 979 * 980 * @return The created greater-or-equal search filter. 981 */ 982 @NotNull() 983 public static Filter createGreaterOrEqualFilter( 984 @NotNull final String attributeName, 985 @NotNull final byte[] assertionValue) 986 { 987 Validator.ensureNotNull(attributeName, assertionValue); 988 989 return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, 990 attributeName, new ASN1OctetString(assertionValue), null, 991 NO_SUB_ANY, null, null, false); 992 } 993 994 995 996 /** 997 * Creates a new greater-or-equal search filter with the provided information. 998 * 999 * @param attributeName The attribute name for this greater-or-equal 1000 * filter. It must not be {@code null}. 1001 * @param assertionValue The assertion value for this greater-or-equal 1002 * filter. It must not be {@code null}. 1003 * 1004 * @return The created greater-or-equal search filter. 1005 */ 1006 @NotNull() 1007 static Filter createGreaterOrEqualFilter( 1008 @NotNull final String attributeName, 1009 @NotNull final ASN1OctetString assertionValue) 1010 { 1011 Validator.ensureNotNull(attributeName, assertionValue); 1012 1013 return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, 1014 attributeName, assertionValue, null, NO_SUB_ANY, null, 1015 null, false); 1016 } 1017 1018 1019 1020 /** 1021 * Creates a new less-or-equal search filter with the provided information. 1022 * 1023 * @param attributeName The attribute name for this less-or-equal 1024 * filter. It must not be {@code null}. 1025 * @param assertionValue The assertion value for this less-or-equal 1026 * filter. It must not be {@code null}. 1027 * 1028 * @return The created less-or-equal search filter. 1029 */ 1030 @NotNull() 1031 public static Filter createLessOrEqualFilter( 1032 @NotNull final String attributeName, 1033 @NotNull final String assertionValue) 1034 { 1035 Validator.ensureNotNull(attributeName, assertionValue); 1036 1037 return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, 1038 attributeName, new ASN1OctetString(assertionValue), null, 1039 NO_SUB_ANY, null, null, false); 1040 } 1041 1042 1043 1044 /** 1045 * Creates a new less-or-equal search filter with the provided information. 1046 * 1047 * @param attributeName The attribute name for this less-or-equal 1048 * filter. It must not be {@code null}. 1049 * @param assertionValue The assertion value for this less-or-equal 1050 * filter. It must not be {@code null}. 1051 * 1052 * @return The created less-or-equal search filter. 1053 */ 1054 @NotNull() 1055 public static Filter createLessOrEqualFilter( 1056 @NotNull final String attributeName, 1057 @NotNull final byte[] assertionValue) 1058 { 1059 Validator.ensureNotNull(attributeName, assertionValue); 1060 1061 return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, 1062 attributeName, new ASN1OctetString(assertionValue), null, 1063 NO_SUB_ANY, null, null, false); 1064 } 1065 1066 1067 1068 /** 1069 * Creates a new less-or-equal search filter with the provided information. 1070 * 1071 * @param attributeName The attribute name for this less-or-equal 1072 * filter. It must not be {@code null}. 1073 * @param assertionValue The assertion value for this less-or-equal 1074 * filter. It must not be {@code null}. 1075 * 1076 * @return The created less-or-equal search filter. 1077 */ 1078 @NotNull() 1079 static Filter createLessOrEqualFilter( 1080 @NotNull final String attributeName, 1081 @NotNull final ASN1OctetString assertionValue) 1082 { 1083 Validator.ensureNotNull(attributeName, assertionValue); 1084 1085 return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, 1086 attributeName, assertionValue, null, NO_SUB_ANY, null, 1087 null, false); 1088 } 1089 1090 1091 1092 /** 1093 * Creates a new presence search filter with the provided information. 1094 * 1095 * @param attributeName The attribute name for this presence filter. It 1096 * must not be {@code null}. 1097 * 1098 * @return The created presence search filter. 1099 */ 1100 @NotNull() 1101 public static Filter createPresenceFilter(@NotNull final String attributeName) 1102 { 1103 Validator.ensureNotNull(attributeName); 1104 1105 return new Filter(null, FILTER_TYPE_PRESENCE, NO_FILTERS, null, 1106 attributeName, null, null, NO_SUB_ANY, null, null, false); 1107 } 1108 1109 1110 1111 /** 1112 * Creates a new approximate match search filter with the provided 1113 * information. 1114 * 1115 * @param attributeName The attribute name for this approximate match 1116 * filter. It must not be {@code null}. 1117 * @param assertionValue The assertion value for this approximate match 1118 * filter. It must not be {@code null}. 1119 * 1120 * @return The created approximate match search filter. 1121 */ 1122 @NotNull() 1123 public static Filter createApproximateMatchFilter( 1124 @NotNull final String attributeName, 1125 @NotNull final String assertionValue) 1126 { 1127 Validator.ensureNotNull(attributeName, assertionValue); 1128 1129 return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, 1130 attributeName, new ASN1OctetString(assertionValue), null, 1131 NO_SUB_ANY, null, null, false); 1132 } 1133 1134 1135 1136 /** 1137 * Creates a new approximate match search filter with the provided 1138 * information. 1139 * 1140 * @param attributeName The attribute name for this approximate match 1141 * filter. It must not be {@code null}. 1142 * @param assertionValue The assertion value for this approximate match 1143 * filter. It must not be {@code null}. 1144 * 1145 * @return The created approximate match search filter. 1146 */ 1147 @NotNull() 1148 public static Filter createApproximateMatchFilter( 1149 @NotNull final String attributeName, 1150 @NotNull final byte[] assertionValue) 1151 { 1152 Validator.ensureNotNull(attributeName, assertionValue); 1153 1154 return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, 1155 attributeName, new ASN1OctetString(assertionValue), null, 1156 NO_SUB_ANY, null, null, false); 1157 } 1158 1159 1160 1161 /** 1162 * Creates a new approximate match search filter with the provided 1163 * information. 1164 * 1165 * @param attributeName The attribute name for this approximate match 1166 * filter. It must not be {@code null}. 1167 * @param assertionValue The assertion value for this approximate match 1168 * filter. It must not be {@code null}. 1169 * 1170 * @return The created approximate match search filter. 1171 */ 1172 @NotNull() 1173 static Filter createApproximateMatchFilter( 1174 @NotNull final String attributeName, 1175 @NotNull final ASN1OctetString assertionValue) 1176 { 1177 Validator.ensureNotNull(attributeName, assertionValue); 1178 1179 return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, 1180 attributeName, assertionValue, null, NO_SUB_ANY, null, 1181 null, false); 1182 } 1183 1184 1185 1186 /** 1187 * Creates a new extensible match search filter with the provided 1188 * information. At least one of the attribute name and matching rule ID must 1189 * be specified, and the assertion value must always be present. 1190 * 1191 * @param attributeName The attribute name for this extensible match 1192 * filter. 1193 * @param matchingRuleID The matching rule ID for this extensible match 1194 * filter. 1195 * @param dnAttributes Indicates whether the match should be performed 1196 * against attributes in the target entry's DN. 1197 * @param assertionValue The assertion value for this extensible match 1198 * filter. It must not be {@code null}. 1199 * 1200 * @return The created extensible match search filter. 1201 */ 1202 @NotNull() 1203 public static Filter createExtensibleMatchFilter( 1204 @Nullable final String attributeName, 1205 @Nullable final String matchingRuleID, 1206 final boolean dnAttributes, 1207 @NotNull final String assertionValue) 1208 { 1209 Validator.ensureNotNull(assertionValue); 1210 Validator.ensureFalse((attributeName == null) && (matchingRuleID == null)); 1211 1212 return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, 1213 attributeName, new ASN1OctetString(assertionValue), null, 1214 NO_SUB_ANY, null, matchingRuleID, dnAttributes); 1215 } 1216 1217 1218 1219 /** 1220 * Creates a new extensible match search filter with the provided 1221 * information. At least one of the attribute name and matching rule ID must 1222 * be specified, and the assertion value must always be present. 1223 * 1224 * @param attributeName The attribute name for this extensible match 1225 * filter. 1226 * @param matchingRuleID The matching rule ID for this extensible match 1227 * filter. 1228 * @param dnAttributes Indicates whether the match should be performed 1229 * against attributes in the target entry's DN. 1230 * @param assertionValue The assertion value for this extensible match 1231 * filter. It must not be {@code null}. 1232 * 1233 * @return The created extensible match search filter. 1234 */ 1235 @NotNull() 1236 public static Filter createExtensibleMatchFilter( 1237 @Nullable final String attributeName, 1238 @Nullable final String matchingRuleID, 1239 final boolean dnAttributes, 1240 @NotNull final byte[] assertionValue) 1241 { 1242 Validator.ensureNotNull(assertionValue); 1243 Validator.ensureFalse((attributeName == null) && (matchingRuleID == null)); 1244 1245 return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, 1246 attributeName, new ASN1OctetString(assertionValue), null, 1247 NO_SUB_ANY, null, matchingRuleID, dnAttributes); 1248 } 1249 1250 1251 1252 /** 1253 * Creates a new extensible match search filter with the provided 1254 * information. At least one of the attribute name and matching rule ID must 1255 * be specified, and the assertion value must always be present. 1256 * 1257 * @param attributeName The attribute name for this extensible match 1258 * filter. 1259 * @param matchingRuleID The matching rule ID for this extensible match 1260 * filter. 1261 * @param dnAttributes Indicates whether the match should be performed 1262 * against attributes in the target entry's DN. 1263 * @param assertionValue The assertion value for this extensible match 1264 * filter. It must not be {@code null}. 1265 * 1266 * @return The created approximate match search filter. 1267 */ 1268 @NotNull() 1269 static Filter createExtensibleMatchFilter( 1270 @Nullable final String attributeName, 1271 @Nullable final String matchingRuleID, 1272 final boolean dnAttributes, 1273 @NotNull final ASN1OctetString assertionValue) 1274 { 1275 Validator.ensureNotNull(assertionValue); 1276 Validator.ensureFalse((attributeName == null) && (matchingRuleID == null)); 1277 1278 return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, 1279 attributeName, assertionValue, null, NO_SUB_ANY, null, 1280 matchingRuleID, dnAttributes); 1281 } 1282 1283 1284 1285 /** 1286 * Creates a new search filter from the provided string representation. 1287 * 1288 * @param filterString The string representation of the filter to create. 1289 * It must not be {@code null}. 1290 * 1291 * @return The search filter decoded from the provided filter string. 1292 * 1293 * @throws LDAPException If the provided string cannot be decoded as a valid 1294 * LDAP search filter. 1295 */ 1296 @NotNull() 1297 public static Filter create(@NotNull final String filterString) 1298 throws LDAPException 1299 { 1300 Validator.ensureNotNull(filterString); 1301 1302 return create(filterString, 0, (filterString.length() - 1), 0); 1303 } 1304 1305 1306 1307 /** 1308 * Creates a new search filter from the specified portion of the provided 1309 * string representation. 1310 * 1311 * @param filterString The string representation of the filter to create. 1312 * @param startPos The position of the first character to consider as 1313 * part of the filter. 1314 * @param endPos The position of the last character to consider as 1315 * part of the filter. 1316 * @param depth The current nesting depth for this filter. It should 1317 * be increased by one for each AND, OR, or NOT filter 1318 * encountered, in order to prevent stack overflow 1319 * errors from excessive recursion. 1320 * 1321 * @return The decoded search filter. 1322 * 1323 * @throws LDAPException If the provided string cannot be decoded as a valid 1324 * LDAP search filter. 1325 */ 1326 @NotNull() 1327 private static Filter create(@NotNull final String filterString, 1328 final int startPos, final int endPos, 1329 final int depth) 1330 throws LDAPException 1331 { 1332 if (depth > 100) 1333 { 1334 throw new LDAPException(ResultCode.FILTER_ERROR, 1335 ERR_FILTER_TOO_DEEP.get(filterString)); 1336 } 1337 1338 final byte filterType; 1339 final Filter[] filterComps; 1340 final Filter notComp; 1341 final String attrName; 1342 final ASN1OctetString assertionValue; 1343 final ASN1OctetString subInitial; 1344 final ASN1OctetString[] subAny; 1345 final ASN1OctetString subFinal; 1346 final String matchingRuleID; 1347 final boolean dnAttributes; 1348 1349 if (startPos >= endPos) 1350 { 1351 throw new LDAPException(ResultCode.FILTER_ERROR, 1352 ERR_FILTER_TOO_SHORT.get(filterString)); 1353 } 1354 1355 int l = startPos; 1356 int r = endPos; 1357 1358 // First, see if the provided filter string is enclosed in parentheses, like 1359 // it should be. If so, then strip off the outer parentheses. 1360 if (filterString.charAt(l) == '(') 1361 { 1362 if (filterString.charAt(r) == ')') 1363 { 1364 l++; 1365 r--; 1366 } 1367 else 1368 { 1369 throw new LDAPException(ResultCode.FILTER_ERROR, 1370 ERR_FILTER_OPEN_WITHOUT_CLOSE.get(filterString, l, r)); 1371 } 1372 } 1373 else 1374 { 1375 // This is technically an error, and it's a bad practice. If we're 1376 // working on the complete filter string then we'll let it slide, but 1377 // otherwise we'll raise an error. 1378 if (l != 0) 1379 { 1380 throw new LDAPException(ResultCode.FILTER_ERROR, 1381 ERR_FILTER_MISSING_PARENTHESES.get(filterString, 1382 filterString.substring(l, r+1))); 1383 } 1384 } 1385 1386 1387 // Look at the first character of the filter to see if it's an '&', '|', or 1388 // '!'. If we find a parenthesis, then that's an error. 1389 switch (filterString.charAt(l)) 1390 { 1391 case '&': 1392 filterType = FILTER_TYPE_AND; 1393 filterComps = parseFilterComps(filterString, l+1, r, depth+1); 1394 notComp = null; 1395 attrName = null; 1396 assertionValue = null; 1397 subInitial = null; 1398 subAny = NO_SUB_ANY; 1399 subFinal = null; 1400 matchingRuleID = null; 1401 dnAttributes = false; 1402 break; 1403 1404 case '|': 1405 filterType = FILTER_TYPE_OR; 1406 filterComps = parseFilterComps(filterString, l+1, r, depth+1); 1407 notComp = null; 1408 attrName = null; 1409 assertionValue = null; 1410 subInitial = null; 1411 subAny = NO_SUB_ANY; 1412 subFinal = null; 1413 matchingRuleID = null; 1414 dnAttributes = false; 1415 break; 1416 1417 case '!': 1418 filterType = FILTER_TYPE_NOT; 1419 filterComps = NO_FILTERS; 1420 notComp = create(filterString, l+1, r, depth+1); 1421 attrName = null; 1422 assertionValue = null; 1423 subInitial = null; 1424 subAny = NO_SUB_ANY; 1425 subFinal = null; 1426 matchingRuleID = null; 1427 dnAttributes = false; 1428 break; 1429 1430 case '(': 1431 throw new LDAPException(ResultCode.FILTER_ERROR, 1432 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l)); 1433 1434 case ':': 1435 // This must be an extensible matching filter that starts with a 1436 // dnAttributes flag and/or matching rule ID, and we should parse it 1437 // accordingly. 1438 filterType = FILTER_TYPE_EXTENSIBLE_MATCH; 1439 filterComps = NO_FILTERS; 1440 notComp = null; 1441 attrName = null; 1442 subInitial = null; 1443 subAny = NO_SUB_ANY; 1444 subFinal = null; 1445 1446 // The next element must be either the "dn:{matchingruleid}" or just 1447 // "{matchingruleid}", and it must be followed by a colon. 1448 final int dnMRIDStart = ++l; 1449 while ((l <= r) && (filterString.charAt(l) != ':')) 1450 { 1451 l++; 1452 } 1453 1454 if (l > r) 1455 { 1456 throw new LDAPException(ResultCode.FILTER_ERROR, 1457 ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos)); 1458 } 1459 else if (l == dnMRIDStart) 1460 { 1461 throw new LDAPException(ResultCode.FILTER_ERROR, 1462 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 1463 } 1464 final String s = filterString.substring(dnMRIDStart, l++); 1465 if (s.equalsIgnoreCase("dn")) 1466 { 1467 dnAttributes = true; 1468 1469 // The colon must be followed by the matching rule ID and another 1470 // colon. 1471 final int mrIDStart = l; 1472 while ((l < r) && (filterString.charAt(l) != ':')) 1473 { 1474 l++; 1475 } 1476 1477 if (l >= r) 1478 { 1479 throw new LDAPException(ResultCode.FILTER_ERROR, 1480 ERR_FILTER_NO_COLON_AFTER_MRID.get(filterString, startPos)); 1481 } 1482 1483 matchingRuleID = filterString.substring(mrIDStart, l); 1484 if (matchingRuleID.isEmpty()) 1485 { 1486 throw new LDAPException(ResultCode.FILTER_ERROR, 1487 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 1488 } 1489 1490 if ((++l > r) || (filterString.charAt(l) != '=')) 1491 { 1492 throw new LDAPException(ResultCode.FILTER_ERROR, 1493 ERR_FILTER_UNEXPECTED_CHAR_AFTER_MRID.get(filterString, 1494 startPos, filterString.charAt(l))); 1495 } 1496 } 1497 else 1498 { 1499 matchingRuleID = s; 1500 dnAttributes = false; 1501 1502 // The colon must be followed by an equal sign. 1503 if ((l > r) || (filterString.charAt(l) != '=')) 1504 { 1505 throw new LDAPException(ResultCode.FILTER_ERROR, 1506 ERR_FILTER_NO_EQUAL_AFTER_MRID.get(filterString, startPos)); 1507 } 1508 } 1509 1510 // Now we should be able to read the value, handling any escape 1511 // characters as we go. 1512 l++; 1513 final ByteStringBuffer valueBuffer = new ByteStringBuffer(r - l + 1); 1514 while (l <= r) 1515 { 1516 final char c = filterString.charAt(l); 1517 if (c == '\\') 1518 { 1519 l = readEscapedHexString(filterString, ++l, valueBuffer); 1520 } 1521 else if (c == '(') 1522 { 1523 throw new LDAPException(ResultCode.FILTER_ERROR, 1524 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l)); 1525 } 1526 else if (c == ')') 1527 { 1528 throw new LDAPException(ResultCode.FILTER_ERROR, 1529 ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l)); 1530 } 1531 else 1532 { 1533 valueBuffer.append(c); 1534 l++; 1535 } 1536 } 1537 assertionValue = new ASN1OctetString(valueBuffer.toByteArray()); 1538 break; 1539 1540 1541 default: 1542 // We know that it's not an AND, OR, or NOT filter, so we can eliminate 1543 // the variables used only for them. 1544 filterComps = NO_FILTERS; 1545 notComp = null; 1546 1547 1548 // We should now be able to read a non-empty attribute name. 1549 final int attrStartPos = l; 1550 int attrEndPos = -1; 1551 byte tempFilterType = 0x00; 1552 boolean filterTypeKnown = false; 1553 boolean equalFound = false; 1554attrNameLoop: 1555 while (l <= r) 1556 { 1557 final char c = filterString.charAt(l++); 1558 switch (c) 1559 { 1560 case ':': 1561 tempFilterType = FILTER_TYPE_EXTENSIBLE_MATCH; 1562 filterTypeKnown = true; 1563 attrEndPos = l - 1; 1564 break attrNameLoop; 1565 1566 case '>': 1567 tempFilterType = FILTER_TYPE_GREATER_OR_EQUAL; 1568 filterTypeKnown = true; 1569 attrEndPos = l - 1; 1570 1571 if (l <= r) 1572 { 1573 if (filterString.charAt(l++) != '=') 1574 { 1575 throw new LDAPException(ResultCode.FILTER_ERROR, 1576 ERR_FILTER_UNEXPECTED_CHAR_AFTER_GT.get(filterString, 1577 startPos, filterString.charAt(l-1))); 1578 } 1579 } 1580 else 1581 { 1582 throw new LDAPException(ResultCode.FILTER_ERROR, 1583 ERR_FILTER_END_AFTER_GT.get(filterString, startPos)); 1584 } 1585 break attrNameLoop; 1586 1587 case '<': 1588 tempFilterType = FILTER_TYPE_LESS_OR_EQUAL; 1589 filterTypeKnown = true; 1590 attrEndPos = l - 1; 1591 1592 if (l <= r) 1593 { 1594 if (filterString.charAt(l++) != '=') 1595 { 1596 throw new LDAPException(ResultCode.FILTER_ERROR, 1597 ERR_FILTER_UNEXPECTED_CHAR_AFTER_LT.get(filterString, 1598 startPos, filterString.charAt(l-1))); 1599 } 1600 } 1601 else 1602 { 1603 throw new LDAPException(ResultCode.FILTER_ERROR, 1604 ERR_FILTER_END_AFTER_LT.get(filterString, startPos)); 1605 } 1606 break attrNameLoop; 1607 1608 case '~': 1609 tempFilterType = FILTER_TYPE_APPROXIMATE_MATCH; 1610 filterTypeKnown = true; 1611 attrEndPos = l - 1; 1612 1613 if (l <= r) 1614 { 1615 if (filterString.charAt(l++) != '=') 1616 { 1617 throw new LDAPException(ResultCode.FILTER_ERROR, 1618 ERR_FILTER_UNEXPECTED_CHAR_AFTER_TILDE.get(filterString, 1619 startPos, filterString.charAt(l-1))); 1620 } 1621 } 1622 else 1623 { 1624 throw new LDAPException(ResultCode.FILTER_ERROR, 1625 ERR_FILTER_END_AFTER_TILDE.get(filterString, startPos)); 1626 } 1627 break attrNameLoop; 1628 1629 case '=': 1630 // It could be either an equality, presence, or substring filter. 1631 // We'll need to look at the value to determine that. 1632 attrEndPos = l - 1; 1633 equalFound = true; 1634 break attrNameLoop; 1635 } 1636 } 1637 1638 if (attrEndPos <= attrStartPos) 1639 { 1640 if (equalFound) 1641 { 1642 throw new LDAPException(ResultCode.FILTER_ERROR, 1643 ERR_FILTER_EMPTY_ATTR_NAME.get(filterString, startPos)); 1644 } 1645 else 1646 { 1647 throw new LDAPException(ResultCode.FILTER_ERROR, 1648 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos)); 1649 } 1650 } 1651 attrName = filterString.substring(attrStartPos, attrEndPos); 1652 1653 1654 // See if we're dealing with an extensible match filter. If so, then 1655 // we may still need to do additional parsing to get the matching rule 1656 // ID and/or the dnAttributes flag. Otherwise, we can rule out any 1657 // variables that are specific to extensible matching filters. 1658 if (filterTypeKnown && (tempFilterType == FILTER_TYPE_EXTENSIBLE_MATCH)) 1659 { 1660 if (l > r) 1661 { 1662 throw new LDAPException(ResultCode.FILTER_ERROR, 1663 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos)); 1664 } 1665 1666 final char c = filterString.charAt(l++); 1667 if (c == '=') 1668 { 1669 matchingRuleID = null; 1670 dnAttributes = false; 1671 } 1672 else 1673 { 1674 // We have either a matching rule ID or a dnAttributes flag, or 1675 // both. Iterate through the filter until we find the equal sign, 1676 // and then figure out what we have from that. 1677 equalFound = false; 1678 final int substrStartPos = l - 1; 1679 while (l <= r) 1680 { 1681 if (filterString.charAt(l++) == '=') 1682 { 1683 equalFound = true; 1684 break; 1685 } 1686 } 1687 1688 if (! equalFound) 1689 { 1690 throw new LDAPException(ResultCode.FILTER_ERROR, 1691 ERR_FILTER_NO_EQUAL_SIGN.get(filterString, startPos)); 1692 } 1693 1694 final String substr = filterString.substring(substrStartPos, l-1); 1695 final String lowerSubstr = StaticUtils.toLowerCase(substr); 1696 if (! substr.endsWith(":")) 1697 { 1698 throw new LDAPException(ResultCode.FILTER_ERROR, 1699 ERR_FILTER_CANNOT_PARSE_MRID.get(filterString, startPos)); 1700 } 1701 1702 if (lowerSubstr.equals("dn:")) 1703 { 1704 matchingRuleID = null; 1705 dnAttributes = true; 1706 } 1707 else if (lowerSubstr.startsWith("dn:")) 1708 { 1709 matchingRuleID = substr.substring(3, substr.length() - 1); 1710 if (matchingRuleID.isEmpty()) 1711 { 1712 throw new LDAPException(ResultCode.FILTER_ERROR, 1713 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 1714 } 1715 1716 dnAttributes = true; 1717 } 1718 else 1719 { 1720 matchingRuleID = substr.substring(0, substr.length() - 1); 1721 dnAttributes = false; 1722 1723 if (matchingRuleID.isEmpty()) 1724 { 1725 throw new LDAPException(ResultCode.FILTER_ERROR, 1726 ERR_FILTER_EMPTY_MRID.get(filterString, startPos)); 1727 } 1728 } 1729 } 1730 } 1731 else 1732 { 1733 matchingRuleID = null; 1734 dnAttributes = false; 1735 } 1736 1737 1738 // At this point, we're ready to read the value. If we still don't 1739 // know what type of filter we're dealing with, then we can tell that 1740 // based on asterisks in the value. 1741 if (l > r) 1742 { 1743 assertionValue = new ASN1OctetString(); 1744 if (! filterTypeKnown) 1745 { 1746 tempFilterType = FILTER_TYPE_EQUALITY; 1747 } 1748 1749 subInitial = null; 1750 subAny = NO_SUB_ANY; 1751 subFinal = null; 1752 } 1753 else if (l == r) 1754 { 1755 if (filterTypeKnown) 1756 { 1757 switch (filterString.charAt(l)) 1758 { 1759 case '*': 1760 case '(': 1761 case ')': 1762 case '\\': 1763 throw new LDAPException(ResultCode.FILTER_ERROR, 1764 ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString, 1765 startPos, filterString.charAt(l))); 1766 } 1767 1768 assertionValue = 1769 new ASN1OctetString(filterString.substring(l, l+1)); 1770 } 1771 else 1772 { 1773 final char c = filterString.charAt(l); 1774 switch (c) 1775 { 1776 case '*': 1777 tempFilterType = FILTER_TYPE_PRESENCE; 1778 assertionValue = null; 1779 break; 1780 1781 case '\\': 1782 case '(': 1783 case ')': 1784 throw new LDAPException(ResultCode.FILTER_ERROR, 1785 ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get(filterString, 1786 startPos, filterString.charAt(l))); 1787 1788 default: 1789 tempFilterType = FILTER_TYPE_EQUALITY; 1790 assertionValue = 1791 new ASN1OctetString(filterString.substring(l, l+1)); 1792 break; 1793 } 1794 } 1795 1796 subInitial = null; 1797 subAny = NO_SUB_ANY; 1798 subFinal = null; 1799 } 1800 else 1801 { 1802 if (! filterTypeKnown) 1803 { 1804 tempFilterType = FILTER_TYPE_EQUALITY; 1805 } 1806 1807 final int valueStartPos = l; 1808 ASN1OctetString tempSubInitial = null; 1809 ASN1OctetString tempSubFinal = null; 1810 final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1); 1811 ByteStringBuffer buffer = new ByteStringBuffer(r - l + 1); 1812 while (l <= r) 1813 { 1814 final char c = filterString.charAt(l++); 1815 switch (c) 1816 { 1817 case '*': 1818 if (filterTypeKnown) 1819 { 1820 throw new LDAPException(ResultCode.FILTER_ERROR, 1821 ERR_FILTER_UNEXPECTED_ASTERISK.get(filterString, 1822 startPos)); 1823 } 1824 else 1825 { 1826 if ((l-1) == valueStartPos) 1827 { 1828 // The first character is an asterisk, so there is no 1829 // subInitial. 1830 } 1831 else 1832 { 1833 if (tempFilterType == FILTER_TYPE_SUBSTRING) 1834 { 1835 // We already know that it's a substring filter, so this 1836 // must be a subAny portion. However, if the buffer is 1837 // empty, then that means that there were two asterisks 1838 // right next to each other, which is invalid. 1839 if (buffer.length() == 0) 1840 { 1841 throw new LDAPException(ResultCode.FILTER_ERROR, 1842 ERR_FILTER_UNEXPECTED_DOUBLE_ASTERISK.get( 1843 filterString, startPos)); 1844 } 1845 else 1846 { 1847 subAnyList.add( 1848 new ASN1OctetString(buffer.toByteArray())); 1849 buffer = new ByteStringBuffer(r - l + 1); 1850 } 1851 } 1852 else 1853 { 1854 // We haven't yet set the filter type, so the buffer must 1855 // contain the subInitial portion. We also know it's not 1856 // empty because of an earlier check. 1857 tempSubInitial = 1858 new ASN1OctetString(buffer.toByteArray()); 1859 buffer = new ByteStringBuffer(r - l + 1); 1860 } 1861 } 1862 1863 tempFilterType = FILTER_TYPE_SUBSTRING; 1864 } 1865 break; 1866 1867 case '\\': 1868 l = readEscapedHexString(filterString, l, buffer); 1869 break; 1870 1871 case '(': 1872 throw new LDAPException(ResultCode.FILTER_ERROR, 1873 ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(filterString, l)); 1874 1875 case ')': 1876 throw new LDAPException(ResultCode.FILTER_ERROR, 1877 ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(filterString, l)); 1878 1879 default: 1880 if (Character.isHighSurrogate(c)) 1881 { 1882 if (l <= r) 1883 { 1884 final char c2 = filterString.charAt(l); 1885 if (Character.isLowSurrogate(c2)) 1886 { 1887 l++; 1888 final int codePoint = Character.toCodePoint(c, c2); 1889 buffer.append(new String(new int[] { codePoint }, 0, 1)); 1890 break; 1891 } 1892 } 1893 } 1894 1895 buffer.append(c); 1896 break; 1897 } 1898 } 1899 1900 if ((tempFilterType == FILTER_TYPE_SUBSTRING) && 1901 (! buffer.isEmpty())) 1902 { 1903 // The buffer must contain the subFinal portion. 1904 tempSubFinal = new ASN1OctetString(buffer.toByteArray()); 1905 } 1906 1907 subInitial = tempSubInitial; 1908 subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]); 1909 subFinal = tempSubFinal; 1910 1911 if (tempFilterType == FILTER_TYPE_SUBSTRING) 1912 { 1913 assertionValue = null; 1914 } 1915 else 1916 { 1917 assertionValue = new ASN1OctetString(buffer.toByteArray()); 1918 } 1919 } 1920 1921 filterType = tempFilterType; 1922 break; 1923 } 1924 1925 1926 if (startPos == 0) 1927 { 1928 return new Filter(filterString, filterType, filterComps, notComp, 1929 attrName, assertionValue, subInitial, subAny, subFinal, 1930 matchingRuleID, dnAttributes); 1931 } 1932 else 1933 { 1934 return new Filter(filterString.substring(startPos, endPos+1), filterType, 1935 filterComps, notComp, attrName, assertionValue, 1936 subInitial, subAny, subFinal, matchingRuleID, 1937 dnAttributes); 1938 } 1939 } 1940 1941 1942 1943 /** 1944 * Parses the specified portion of the provided filter string to obtain a set 1945 * of filter components for use in an AND or OR filter. 1946 * 1947 * @param filterString The string representation for the set of filters. 1948 * @param startPos The position of the first character to consider as 1949 * part of the first filter. 1950 * @param endPos The position of the last character to consider as 1951 * part of the last filter. 1952 * @param depth The current nesting depth for this filter. It should 1953 * be increased by one for each AND, OR, or NOT filter 1954 * encountered, in order to prevent stack overflow 1955 * errors from excessive recursion. 1956 * 1957 * @return The decoded set of search filters. 1958 * 1959 * @throws LDAPException If the provided string cannot be decoded as a set 1960 * of LDAP search filters. 1961 */ 1962 @NotNull() 1963 private static Filter[] parseFilterComps(@NotNull final String filterString, 1964 final int startPos, final int endPos, 1965 final int depth) 1966 throws LDAPException 1967 { 1968 if (startPos > endPos) 1969 { 1970 // This is acceptable, since it can represent an LDAP TRUE or FALSE filter 1971 // as described in RFC 4526. 1972 return NO_FILTERS; 1973 } 1974 1975 1976 // The set of filters must start with an opening parenthesis, and end with a 1977 // closing parenthesis. 1978 if (filterString.charAt(startPos) != '(') 1979 { 1980 throw new LDAPException(ResultCode.FILTER_ERROR, 1981 ERR_FILTER_EXPECTED_OPEN_PAREN.get(filterString, startPos)); 1982 } 1983 if (filterString.charAt(endPos) != ')') 1984 { 1985 throw new LDAPException(ResultCode.FILTER_ERROR, 1986 ERR_FILTER_EXPECTED_CLOSE_PAREN.get(filterString, startPos)); 1987 } 1988 1989 1990 // Iterate through the specified portion of the filter string and count 1991 // opening and closing parentheses to figure out where one filter ends and 1992 // another begins. 1993 final ArrayList<Filter> filterList = new ArrayList<>(5); 1994 int filterStartPos = startPos; 1995 int pos = startPos; 1996 int numOpen = 0; 1997 while (pos <= endPos) 1998 { 1999 final char c = filterString.charAt(pos++); 2000 if (c == '(') 2001 { 2002 numOpen++; 2003 } 2004 else if (c == ')') 2005 { 2006 numOpen--; 2007 if (numOpen == 0) 2008 { 2009 filterList.add(create(filterString, filterStartPos, pos-1, depth)); 2010 filterStartPos = pos; 2011 } 2012 } 2013 } 2014 2015 if (numOpen != 0) 2016 { 2017 throw new LDAPException(ResultCode.FILTER_ERROR, 2018 ERR_FILTER_MISMATCHED_PARENS.get(filterString, startPos, endPos)); 2019 } 2020 2021 return filterList.toArray(new Filter[filterList.size()]); 2022 } 2023 2024 2025 2026 /** 2027 * Reads one or more hex-encoded bytes from the specified portion of the 2028 * filter string. 2029 * 2030 * @param filterString The string from which the data is to be read. 2031 * @param startPos The position at which to start reading. This should 2032 * be the position of first hex character immediately 2033 * after the initial backslash. 2034 * @param buffer The buffer to which the decoded string portion should 2035 * be appended. 2036 * 2037 * @return The position at which the caller may resume parsing. 2038 * 2039 * @throws LDAPException If a problem occurs while reading hex-encoded 2040 * bytes. 2041 */ 2042 private static int readEscapedHexString(@NotNull final String filterString, 2043 final int startPos, 2044 @NotNull final ByteStringBuffer buffer) 2045 throws LDAPException 2046 { 2047 final byte b; 2048 switch (filterString.charAt(startPos)) 2049 { 2050 case '0': 2051 b = 0x00; 2052 break; 2053 case '1': 2054 b = 0x10; 2055 break; 2056 case '2': 2057 b = 0x20; 2058 break; 2059 case '3': 2060 b = 0x30; 2061 break; 2062 case '4': 2063 b = 0x40; 2064 break; 2065 case '5': 2066 b = 0x50; 2067 break; 2068 case '6': 2069 b = 0x60; 2070 break; 2071 case '7': 2072 b = 0x70; 2073 break; 2074 case '8': 2075 b = (byte) 0x80; 2076 break; 2077 case '9': 2078 b = (byte) 0x90; 2079 break; 2080 case 'a': 2081 case 'A': 2082 b = (byte) 0xA0; 2083 break; 2084 case 'b': 2085 case 'B': 2086 b = (byte) 0xB0; 2087 break; 2088 case 'c': 2089 case 'C': 2090 b = (byte) 0xC0; 2091 break; 2092 case 'd': 2093 case 'D': 2094 b = (byte) 0xD0; 2095 break; 2096 case 'e': 2097 case 'E': 2098 b = (byte) 0xE0; 2099 break; 2100 case 'f': 2101 case 'F': 2102 b = (byte) 0xF0; 2103 break; 2104 default: 2105 throw new LDAPException(ResultCode.FILTER_ERROR, 2106 ERR_FILTER_INVALID_HEX_CHAR.get(filterString, 2107 filterString.charAt(startPos), startPos)); 2108 } 2109 2110 switch (filterString.charAt(startPos+1)) 2111 { 2112 case '0': 2113 buffer.append(b); 2114 break; 2115 case '1': 2116 buffer.append((byte) (b | 0x01)); 2117 break; 2118 case '2': 2119 buffer.append((byte) (b | 0x02)); 2120 break; 2121 case '3': 2122 buffer.append((byte) (b | 0x03)); 2123 break; 2124 case '4': 2125 buffer.append((byte) (b | 0x04)); 2126 break; 2127 case '5': 2128 buffer.append((byte) (b | 0x05)); 2129 break; 2130 case '6': 2131 buffer.append((byte) (b | 0x06)); 2132 break; 2133 case '7': 2134 buffer.append((byte) (b | 0x07)); 2135 break; 2136 case '8': 2137 buffer.append((byte) (b | 0x08)); 2138 break; 2139 case '9': 2140 buffer.append((byte) (b | 0x09)); 2141 break; 2142 case 'a': 2143 case 'A': 2144 buffer.append((byte) (b | 0x0A)); 2145 break; 2146 case 'b': 2147 case 'B': 2148 buffer.append((byte) (b | 0x0B)); 2149 break; 2150 case 'c': 2151 case 'C': 2152 buffer.append((byte) (b | 0x0C)); 2153 break; 2154 case 'd': 2155 case 'D': 2156 buffer.append((byte) (b | 0x0D)); 2157 break; 2158 case 'e': 2159 case 'E': 2160 buffer.append((byte) (b | 0x0E)); 2161 break; 2162 case 'f': 2163 case 'F': 2164 buffer.append((byte) (b | 0x0F)); 2165 break; 2166 default: 2167 throw new LDAPException(ResultCode.FILTER_ERROR, 2168 ERR_FILTER_INVALID_HEX_CHAR.get(filterString, 2169 filterString.charAt(startPos+1), (startPos+1))); 2170 } 2171 2172 return startPos+2; 2173 } 2174 2175 2176 2177 /** 2178 * Writes an ASN.1-encoded representation of this filter to the provided ASN.1 2179 * buffer. 2180 * 2181 * @param buffer The ASN.1 buffer to which the encoded representation should 2182 * be written. 2183 */ 2184 public void writeTo(@NotNull final ASN1Buffer buffer) 2185 { 2186 switch (filterType) 2187 { 2188 case FILTER_TYPE_AND: 2189 case FILTER_TYPE_OR: 2190 final ASN1BufferSet compSet = buffer.beginSet(filterType); 2191 for (final Filter f : filterComps) 2192 { 2193 f.writeTo(buffer); 2194 } 2195 compSet.end(); 2196 break; 2197 2198 case FILTER_TYPE_NOT: 2199 buffer.addElement( 2200 new ASN1Element(filterType, notComp.encode().encode())); 2201 break; 2202 2203 case FILTER_TYPE_EQUALITY: 2204 case FILTER_TYPE_GREATER_OR_EQUAL: 2205 case FILTER_TYPE_LESS_OR_EQUAL: 2206 case FILTER_TYPE_APPROXIMATE_MATCH: 2207 final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType); 2208 buffer.addOctetString(attrName); 2209 buffer.addElement(assertionValue); 2210 avaSequence.end(); 2211 break; 2212 2213 case FILTER_TYPE_SUBSTRING: 2214 final ASN1BufferSequence subFilterSequence = 2215 buffer.beginSequence(filterType); 2216 buffer.addOctetString(attrName); 2217 2218 final ASN1BufferSequence valueSequence = buffer.beginSequence(); 2219 if (subInitial != null) 2220 { 2221 buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL, 2222 subInitial.getValue()); 2223 } 2224 2225 for (final ASN1OctetString s : subAny) 2226 { 2227 buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue()); 2228 } 2229 2230 if (subFinal != null) 2231 { 2232 buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue()); 2233 } 2234 valueSequence.end(); 2235 subFilterSequence.end(); 2236 break; 2237 2238 case FILTER_TYPE_PRESENCE: 2239 buffer.addOctetString(filterType, attrName); 2240 break; 2241 2242 case FILTER_TYPE_EXTENSIBLE_MATCH: 2243 final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType); 2244 if (matchingRuleID != null) 2245 { 2246 buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID, 2247 matchingRuleID); 2248 } 2249 2250 if (attrName != null) 2251 { 2252 buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName); 2253 } 2254 2255 buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE, 2256 assertionValue.getValue()); 2257 2258 if (dnAttributes) 2259 { 2260 buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true); 2261 } 2262 mrSequence.end(); 2263 break; 2264 } 2265 } 2266 2267 2268 2269 /** 2270 * Encodes this search filter to an ASN.1 element suitable for inclusion in an 2271 * LDAP search request protocol op. 2272 * 2273 * @return An ASN.1 element containing the encoded search filter. 2274 */ 2275 @NotNull() 2276 public ASN1Element encode() 2277 { 2278 switch (filterType) 2279 { 2280 case FILTER_TYPE_AND: 2281 case FILTER_TYPE_OR: 2282 final ASN1Element[] filterElements = 2283 new ASN1Element[filterComps.length]; 2284 for (int i=0; i < filterComps.length; i++) 2285 { 2286 filterElements[i] = filterComps[i].encode(); 2287 } 2288 return new ASN1Set(filterType, filterElements); 2289 2290 2291 case FILTER_TYPE_NOT: 2292 return new ASN1Element(filterType, notComp.encode().encode()); 2293 2294 2295 case FILTER_TYPE_EQUALITY: 2296 case FILTER_TYPE_GREATER_OR_EQUAL: 2297 case FILTER_TYPE_LESS_OR_EQUAL: 2298 case FILTER_TYPE_APPROXIMATE_MATCH: 2299 final ASN1OctetString[] attrValueAssertionElements = 2300 { 2301 new ASN1OctetString(attrName), 2302 assertionValue 2303 }; 2304 return new ASN1Sequence(filterType, attrValueAssertionElements); 2305 2306 2307 case FILTER_TYPE_SUBSTRING: 2308 final ArrayList<ASN1OctetString> subList = 2309 new ArrayList<>(2 + subAny.length); 2310 if (subInitial != null) 2311 { 2312 subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL, 2313 subInitial.getValue())); 2314 } 2315 2316 for (final ASN1Element subAnyElement : subAny) 2317 { 2318 subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY, 2319 subAnyElement.getValue())); 2320 } 2321 2322 2323 if (subFinal != null) 2324 { 2325 subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL, 2326 subFinal.getValue())); 2327 } 2328 2329 final ASN1Element[] subFilterElements = 2330 { 2331 new ASN1OctetString(attrName), 2332 new ASN1Sequence(subList) 2333 }; 2334 return new ASN1Sequence(filterType, subFilterElements); 2335 2336 2337 case FILTER_TYPE_PRESENCE: 2338 return new ASN1OctetString(filterType, attrName); 2339 2340 2341 case FILTER_TYPE_EXTENSIBLE_MATCH: 2342 final ArrayList<ASN1Element> emElementList = new ArrayList<>(4); 2343 if (matchingRuleID != null) 2344 { 2345 emElementList.add(new ASN1OctetString( 2346 EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID)); 2347 } 2348 2349 if (attrName != null) 2350 { 2351 emElementList.add(new ASN1OctetString( 2352 EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName)); 2353 } 2354 2355 emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE, 2356 assertionValue.getValue())); 2357 2358 if (dnAttributes) 2359 { 2360 emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, 2361 true)); 2362 } 2363 2364 return new ASN1Sequence(filterType, emElementList); 2365 2366 2367 default: 2368 throw new AssertionError(ERR_FILTER_INVALID_TYPE.get( 2369 StaticUtils.toHex(filterType))); 2370 } 2371 } 2372 2373 2374 2375 /** 2376 * Reads and decodes a search filter from the provided ASN.1 stream reader. 2377 * 2378 * @param reader The ASN.1 stream reader from which to read the filter. 2379 * 2380 * @return The decoded search filter. 2381 * 2382 * @throws LDAPException If an error occurs while reading or parsing the 2383 * search filter. 2384 */ 2385 @NotNull() 2386 public static Filter readFrom(@NotNull final ASN1StreamReader reader) 2387 throws LDAPException 2388 { 2389 try 2390 { 2391 final Filter[] filterComps; 2392 final Filter notComp; 2393 final String attrName; 2394 final ASN1OctetString assertionValue; 2395 final ASN1OctetString subInitial; 2396 final ASN1OctetString[] subAny; 2397 final ASN1OctetString subFinal; 2398 final String matchingRuleID; 2399 final boolean dnAttributes; 2400 2401 final byte filterType = (byte) reader.peek(); 2402 2403 switch (filterType) 2404 { 2405 case FILTER_TYPE_AND: 2406 case FILTER_TYPE_OR: 2407 final ArrayList<Filter> comps = new ArrayList<>(5); 2408 final ASN1StreamReaderSet elementSet = reader.beginSet(); 2409 while (elementSet.hasMoreElements()) 2410 { 2411 comps.add(readFrom(reader)); 2412 } 2413 2414 filterComps = new Filter[comps.size()]; 2415 comps.toArray(filterComps); 2416 2417 notComp = null; 2418 attrName = null; 2419 assertionValue = null; 2420 subInitial = null; 2421 subAny = NO_SUB_ANY; 2422 subFinal = null; 2423 matchingRuleID = null; 2424 dnAttributes = false; 2425 break; 2426 2427 2428 case FILTER_TYPE_NOT: 2429 final ASN1Element notFilterElement; 2430 try 2431 { 2432 final ASN1Element e = reader.readElement(); 2433 notFilterElement = ASN1Element.decode(e.getValue()); 2434 } 2435 catch (final ASN1Exception ae) 2436 { 2437 Debug.debugException(ae); 2438 throw new LDAPException(ResultCode.DECODING_ERROR, 2439 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get( 2440 StaticUtils.getExceptionMessage(ae)), 2441 ae); 2442 } 2443 notComp = decode(notFilterElement); 2444 2445 filterComps = NO_FILTERS; 2446 attrName = null; 2447 assertionValue = null; 2448 subInitial = null; 2449 subAny = NO_SUB_ANY; 2450 subFinal = null; 2451 matchingRuleID = null; 2452 dnAttributes = false; 2453 break; 2454 2455 2456 case FILTER_TYPE_EQUALITY: 2457 case FILTER_TYPE_GREATER_OR_EQUAL: 2458 case FILTER_TYPE_LESS_OR_EQUAL: 2459 case FILTER_TYPE_APPROXIMATE_MATCH: 2460 reader.beginSequence(); 2461 attrName = reader.readString(); 2462 assertionValue = new ASN1OctetString(reader.readBytes()); 2463 2464 filterComps = NO_FILTERS; 2465 notComp = null; 2466 subInitial = null; 2467 subAny = NO_SUB_ANY; 2468 subFinal = null; 2469 matchingRuleID = null; 2470 dnAttributes = false; 2471 break; 2472 2473 2474 case FILTER_TYPE_SUBSTRING: 2475 reader.beginSequence(); 2476 attrName = reader.readString(); 2477 2478 ASN1OctetString tempSubInitial = null; 2479 ASN1OctetString tempSubFinal = null; 2480 final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1); 2481 final ASN1StreamReaderSequence subSequence = reader.beginSequence(); 2482 while (subSequence.hasMoreElements()) 2483 { 2484 final byte type = (byte) reader.peek(); 2485 final ASN1OctetString s = 2486 new ASN1OctetString(type, reader.readBytes()); 2487 switch (type) 2488 { 2489 case SUBSTRING_TYPE_SUBINITIAL: 2490 tempSubInitial = s; 2491 break; 2492 case SUBSTRING_TYPE_SUBANY: 2493 subAnyList.add(s); 2494 break; 2495 case SUBSTRING_TYPE_SUBFINAL: 2496 tempSubFinal = s; 2497 break; 2498 default: 2499 throw new LDAPException(ResultCode.DECODING_ERROR, 2500 ERR_FILTER_INVALID_SUBSTR_TYPE.get( 2501 StaticUtils.toHex(type))); 2502 } 2503 } 2504 2505 subInitial = tempSubInitial; 2506 subFinal = tempSubFinal; 2507 2508 subAny = new ASN1OctetString[subAnyList.size()]; 2509 subAnyList.toArray(subAny); 2510 2511 filterComps = NO_FILTERS; 2512 notComp = null; 2513 assertionValue = null; 2514 matchingRuleID = null; 2515 dnAttributes = false; 2516 break; 2517 2518 2519 case FILTER_TYPE_PRESENCE: 2520 attrName = reader.readString(); 2521 2522 filterComps = NO_FILTERS; 2523 notComp = null; 2524 assertionValue = null; 2525 subInitial = null; 2526 subAny = NO_SUB_ANY; 2527 subFinal = null; 2528 matchingRuleID = null; 2529 dnAttributes = false; 2530 break; 2531 2532 2533 case FILTER_TYPE_EXTENSIBLE_MATCH: 2534 String tempAttrName = null; 2535 ASN1OctetString tempAssertionValue = null; 2536 String tempMatchingRuleID = null; 2537 boolean tempDNAttributes = false; 2538 2539 final ASN1StreamReaderSequence emSequence = reader.beginSequence(); 2540 while (emSequence.hasMoreElements()) 2541 { 2542 final byte type = (byte) reader.peek(); 2543 switch (type) 2544 { 2545 case EXTENSIBLE_TYPE_ATTRIBUTE_NAME: 2546 tempAttrName = reader.readString(); 2547 break; 2548 case EXTENSIBLE_TYPE_MATCHING_RULE_ID: 2549 tempMatchingRuleID = reader.readString(); 2550 break; 2551 case EXTENSIBLE_TYPE_MATCH_VALUE: 2552 tempAssertionValue = 2553 new ASN1OctetString(type, reader.readBytes()); 2554 break; 2555 case EXTENSIBLE_TYPE_DN_ATTRIBUTES: 2556 tempDNAttributes = reader.readBoolean(); 2557 break; 2558 default: 2559 throw new LDAPException(ResultCode.DECODING_ERROR, 2560 ERR_FILTER_EXTMATCH_INVALID_TYPE.get( 2561 StaticUtils.toHex(type))); 2562 } 2563 } 2564 2565 if ((tempAttrName == null) && (tempMatchingRuleID == null)) 2566 { 2567 throw new LDAPException(ResultCode.DECODING_ERROR, 2568 ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get()); 2569 } 2570 2571 if (tempAssertionValue == null) 2572 { 2573 throw new LDAPException(ResultCode.DECODING_ERROR, 2574 ERR_FILTER_EXTMATCH_NO_VALUE.get()); 2575 } 2576 2577 attrName = tempAttrName; 2578 assertionValue = tempAssertionValue; 2579 matchingRuleID = tempMatchingRuleID; 2580 dnAttributes = tempDNAttributes; 2581 2582 filterComps = NO_FILTERS; 2583 notComp = null; 2584 subInitial = null; 2585 subAny = NO_SUB_ANY; 2586 subFinal = null; 2587 break; 2588 2589 2590 default: 2591 throw new LDAPException(ResultCode.DECODING_ERROR, 2592 ERR_FILTER_ELEMENT_INVALID_TYPE.get( 2593 StaticUtils.toHex(filterType))); 2594 } 2595 2596 return new Filter(null, filterType, filterComps, notComp, attrName, 2597 assertionValue, subInitial, subAny, subFinal, 2598 matchingRuleID, dnAttributes); 2599 } 2600 catch (final LDAPException le) 2601 { 2602 Debug.debugException(le); 2603 throw le; 2604 } 2605 catch (final Exception e) 2606 { 2607 Debug.debugException(e); 2608 throw new LDAPException(ResultCode.DECODING_ERROR, 2609 ERR_FILTER_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), e); 2610 } 2611 } 2612 2613 2614 2615 /** 2616 * Decodes the provided ASN.1 element as a search filter. 2617 * 2618 * @param filterElement The ASN.1 element containing the encoded search 2619 * filter. 2620 * 2621 * @return The decoded search filter. 2622 * 2623 * @throws LDAPException If the provided ASN.1 element cannot be decoded as 2624 * a search filter. 2625 */ 2626 @NotNull() 2627 public static Filter decode(@NotNull final ASN1Element filterElement) 2628 throws LDAPException 2629 { 2630 final byte filterType = filterElement.getType(); 2631 final Filter[] filterComps; 2632 final Filter notComp; 2633 final String attrName; 2634 final ASN1OctetString assertionValue; 2635 final ASN1OctetString subInitial; 2636 final ASN1OctetString[] subAny; 2637 final ASN1OctetString subFinal; 2638 final String matchingRuleID; 2639 final boolean dnAttributes; 2640 2641 switch (filterType) 2642 { 2643 case FILTER_TYPE_AND: 2644 case FILTER_TYPE_OR: 2645 notComp = null; 2646 attrName = null; 2647 assertionValue = null; 2648 subInitial = null; 2649 subAny = NO_SUB_ANY; 2650 subFinal = null; 2651 matchingRuleID = null; 2652 dnAttributes = false; 2653 2654 final ASN1Set compSet; 2655 try 2656 { 2657 compSet = ASN1Set.decodeAsSet(filterElement); 2658 } 2659 catch (final ASN1Exception ae) 2660 { 2661 Debug.debugException(ae); 2662 throw new LDAPException(ResultCode.DECODING_ERROR, 2663 ERR_FILTER_CANNOT_DECODE_COMPS.get( 2664 StaticUtils.getExceptionMessage(ae)), 2665 ae); 2666 } 2667 2668 final ASN1Element[] compElements = compSet.elements(); 2669 filterComps = new Filter[compElements.length]; 2670 for (int i=0; i < compElements.length; i++) 2671 { 2672 filterComps[i] = decode(compElements[i]); 2673 } 2674 break; 2675 2676 2677 case FILTER_TYPE_NOT: 2678 filterComps = NO_FILTERS; 2679 attrName = null; 2680 assertionValue = null; 2681 subInitial = null; 2682 subAny = NO_SUB_ANY; 2683 subFinal = null; 2684 matchingRuleID = null; 2685 dnAttributes = false; 2686 2687 final ASN1Element notFilterElement; 2688 try 2689 { 2690 notFilterElement = ASN1Element.decode(filterElement.getValue()); 2691 } 2692 catch (final ASN1Exception ae) 2693 { 2694 Debug.debugException(ae); 2695 throw new LDAPException(ResultCode.DECODING_ERROR, 2696 ERR_FILTER_CANNOT_DECODE_NOT_COMP.get( 2697 StaticUtils.getExceptionMessage(ae)), 2698 ae); 2699 } 2700 notComp = decode(notFilterElement); 2701 break; 2702 2703 2704 2705 case FILTER_TYPE_EQUALITY: 2706 case FILTER_TYPE_GREATER_OR_EQUAL: 2707 case FILTER_TYPE_LESS_OR_EQUAL: 2708 case FILTER_TYPE_APPROXIMATE_MATCH: 2709 filterComps = NO_FILTERS; 2710 notComp = null; 2711 subInitial = null; 2712 subAny = NO_SUB_ANY; 2713 subFinal = null; 2714 matchingRuleID = null; 2715 dnAttributes = false; 2716 2717 final ASN1Sequence avaSequence; 2718 try 2719 { 2720 avaSequence = ASN1Sequence.decodeAsSequence(filterElement); 2721 } 2722 catch (final ASN1Exception ae) 2723 { 2724 Debug.debugException(ae); 2725 throw new LDAPException(ResultCode.DECODING_ERROR, 2726 ERR_FILTER_CANNOT_DECODE_AVA.get( 2727 StaticUtils.getExceptionMessage(ae)), 2728 ae); 2729 } 2730 2731 final ASN1Element[] avaElements = avaSequence.elements(); 2732 if (avaElements.length != 2) 2733 { 2734 throw new LDAPException(ResultCode.DECODING_ERROR, 2735 ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get( 2736 avaElements.length)); 2737 } 2738 2739 attrName = 2740 ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue(); 2741 assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]); 2742 break; 2743 2744 2745 case FILTER_TYPE_SUBSTRING: 2746 filterComps = NO_FILTERS; 2747 notComp = null; 2748 assertionValue = null; 2749 matchingRuleID = null; 2750 dnAttributes = false; 2751 2752 final ASN1Sequence subFilterSequence; 2753 try 2754 { 2755 subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement); 2756 } 2757 catch (final ASN1Exception ae) 2758 { 2759 Debug.debugException(ae); 2760 throw new LDAPException(ResultCode.DECODING_ERROR, 2761 ERR_FILTER_CANNOT_DECODE_SUBSTRING.get( 2762 StaticUtils.getExceptionMessage(ae)), 2763 ae); 2764 } 2765 2766 final ASN1Element[] subFilterElements = subFilterSequence.elements(); 2767 if (subFilterElements.length != 2) 2768 { 2769 throw new LDAPException(ResultCode.DECODING_ERROR, 2770 ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get( 2771 subFilterElements.length)); 2772 } 2773 2774 attrName = ASN1OctetString.decodeAsOctetString( 2775 subFilterElements[0]).stringValue(); 2776 2777 final ASN1Sequence subSequence; 2778 try 2779 { 2780 subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]); 2781 } 2782 catch (final ASN1Exception ae) 2783 { 2784 Debug.debugException(ae); 2785 throw new LDAPException(ResultCode.DECODING_ERROR, 2786 ERR_FILTER_CANNOT_DECODE_SUBSTRING.get( 2787 StaticUtils.getExceptionMessage(ae)), 2788 ae); 2789 } 2790 2791 ASN1OctetString tempSubInitial = null; 2792 ASN1OctetString tempSubFinal = null; 2793 final ArrayList<ASN1OctetString> subAnyList = new ArrayList<>(1); 2794 2795 final ASN1Element[] subElements = subSequence.elements(); 2796 for (final ASN1Element subElement : subElements) 2797 { 2798 switch (subElement.getType()) 2799 { 2800 case SUBSTRING_TYPE_SUBINITIAL: 2801 if (tempSubInitial == null) 2802 { 2803 tempSubInitial = 2804 ASN1OctetString.decodeAsOctetString(subElement); 2805 } 2806 else 2807 { 2808 throw new LDAPException(ResultCode.DECODING_ERROR, 2809 ERR_FILTER_MULTIPLE_SUBINITIAL.get()); 2810 } 2811 break; 2812 2813 case SUBSTRING_TYPE_SUBANY: 2814 subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement)); 2815 break; 2816 2817 case SUBSTRING_TYPE_SUBFINAL: 2818 if (tempSubFinal == null) 2819 { 2820 tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement); 2821 } 2822 else 2823 { 2824 throw new LDAPException(ResultCode.DECODING_ERROR, 2825 ERR_FILTER_MULTIPLE_SUBFINAL.get()); 2826 } 2827 break; 2828 2829 default: 2830 throw new LDAPException(ResultCode.DECODING_ERROR, 2831 ERR_FILTER_INVALID_SUBSTR_TYPE.get( 2832 StaticUtils.toHex(subElement.getType()))); 2833 } 2834 } 2835 2836 subInitial = tempSubInitial; 2837 subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]); 2838 subFinal = tempSubFinal; 2839 break; 2840 2841 2842 case FILTER_TYPE_PRESENCE: 2843 filterComps = NO_FILTERS; 2844 notComp = null; 2845 assertionValue = null; 2846 subInitial = null; 2847 subAny = NO_SUB_ANY; 2848 subFinal = null; 2849 matchingRuleID = null; 2850 dnAttributes = false; 2851 attrName = 2852 ASN1OctetString.decodeAsOctetString(filterElement).stringValue(); 2853 break; 2854 2855 2856 case FILTER_TYPE_EXTENSIBLE_MATCH: 2857 filterComps = NO_FILTERS; 2858 notComp = null; 2859 subInitial = null; 2860 subAny = NO_SUB_ANY; 2861 subFinal = null; 2862 2863 final ASN1Sequence emSequence; 2864 try 2865 { 2866 emSequence = ASN1Sequence.decodeAsSequence(filterElement); 2867 } 2868 catch (final ASN1Exception ae) 2869 { 2870 Debug.debugException(ae); 2871 throw new LDAPException(ResultCode.DECODING_ERROR, 2872 ERR_FILTER_CANNOT_DECODE_EXTMATCH.get( 2873 StaticUtils.getExceptionMessage(ae)), 2874 ae); 2875 } 2876 2877 String tempAttrName = null; 2878 ASN1OctetString tempAssertionValue = null; 2879 String tempMatchingRuleID = null; 2880 boolean tempDNAttributes = false; 2881 for (final ASN1Element e : emSequence.elements()) 2882 { 2883 switch (e.getType()) 2884 { 2885 case EXTENSIBLE_TYPE_ATTRIBUTE_NAME: 2886 if (tempAttrName == null) 2887 { 2888 tempAttrName = 2889 ASN1OctetString.decodeAsOctetString(e).stringValue(); 2890 } 2891 else 2892 { 2893 throw new LDAPException(ResultCode.DECODING_ERROR, 2894 ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get()); 2895 } 2896 break; 2897 2898 case EXTENSIBLE_TYPE_MATCHING_RULE_ID: 2899 if (tempMatchingRuleID == null) 2900 { 2901 tempMatchingRuleID = 2902 ASN1OctetString.decodeAsOctetString(e).stringValue(); 2903 } 2904 else 2905 { 2906 throw new LDAPException(ResultCode.DECODING_ERROR, 2907 ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get()); 2908 } 2909 break; 2910 2911 case EXTENSIBLE_TYPE_MATCH_VALUE: 2912 if (tempAssertionValue == null) 2913 { 2914 tempAssertionValue = ASN1OctetString.decodeAsOctetString(e); 2915 } 2916 else 2917 { 2918 throw new LDAPException(ResultCode.DECODING_ERROR, 2919 ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get()); 2920 } 2921 break; 2922 2923 case EXTENSIBLE_TYPE_DN_ATTRIBUTES: 2924 try 2925 { 2926 if (tempDNAttributes) 2927 { 2928 throw new LDAPException(ResultCode.DECODING_ERROR, 2929 ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get()); 2930 } 2931 else 2932 { 2933 tempDNAttributes = 2934 ASN1Boolean.decodeAsBoolean(e).booleanValue(); 2935 } 2936 } 2937 catch (final ASN1Exception ae) 2938 { 2939 Debug.debugException(ae); 2940 throw new LDAPException(ResultCode.DECODING_ERROR, 2941 ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get( 2942 StaticUtils.getExceptionMessage(ae)), 2943 ae); 2944 } 2945 break; 2946 2947 default: 2948 throw new LDAPException(ResultCode.DECODING_ERROR, 2949 ERR_FILTER_EXTMATCH_INVALID_TYPE.get( 2950 StaticUtils.toHex(e.getType()))); 2951 } 2952 } 2953 2954 if ((tempAttrName == null) && (tempMatchingRuleID == null)) 2955 { 2956 throw new LDAPException(ResultCode.DECODING_ERROR, 2957 ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get()); 2958 } 2959 2960 if (tempAssertionValue == null) 2961 { 2962 throw new LDAPException(ResultCode.DECODING_ERROR, 2963 ERR_FILTER_EXTMATCH_NO_VALUE.get()); 2964 } 2965 2966 attrName = tempAttrName; 2967 assertionValue = tempAssertionValue; 2968 matchingRuleID = tempMatchingRuleID; 2969 dnAttributes = tempDNAttributes; 2970 break; 2971 2972 2973 default: 2974 throw new LDAPException(ResultCode.DECODING_ERROR, 2975 ERR_FILTER_ELEMENT_INVALID_TYPE.get( 2976 StaticUtils.toHex(filterElement.getType()))); 2977 } 2978 2979 2980 return new Filter(null, filterType, filterComps, notComp, attrName, 2981 assertionValue, subInitial, subAny, subFinal, 2982 matchingRuleID, dnAttributes); 2983 } 2984 2985 2986 2987 /** 2988 * Retrieves the filter type for this filter. 2989 * 2990 * @return The filter type for this filter. 2991 */ 2992 public byte getFilterType() 2993 { 2994 return filterType; 2995 } 2996 2997 2998 2999 /** 3000 * Retrieves the set of filter components used in this AND or OR filter. This 3001 * is not applicable for any other filter type. 3002 * 3003 * @return The set of filter components used in this AND or OR filter, or an 3004 * empty array if this is some other type of filter or if there are 3005 * no components (i.e., as in an LDAP TRUE or LDAP FALSE filter). 3006 */ 3007 @NotNull() 3008 public Filter[] getComponents() 3009 { 3010 return filterComps; 3011 } 3012 3013 3014 3015 /** 3016 * Retrieves the filter component used in this NOT filter. This is not 3017 * applicable for any other filter type. 3018 * 3019 * @return The filter component used in this NOT filter, or {@code null} if 3020 * this is some other type of filter. 3021 */ 3022 @Nullable() 3023 public Filter getNOTComponent() 3024 { 3025 return notComp; 3026 } 3027 3028 3029 3030 /** 3031 * Retrieves the name of the attribute type for this search filter. This is 3032 * applicable for the following types of filters: 3033 * <UL> 3034 * <LI>Equality</LI> 3035 * <LI>Substring</LI> 3036 * <LI>Greater or Equal</LI> 3037 * <LI>Less or Equal</LI> 3038 * <LI>Presence</LI> 3039 * <LI>Approximate Match</LI> 3040 * <LI>Extensible Match</LI> 3041 * </UL> 3042 * 3043 * @return The name of the attribute type for this search filter, or 3044 * {@code null} if it is not applicable for this type of filter. 3045 */ 3046 @Nullable() 3047 public String getAttributeName() 3048 { 3049 return attrName; 3050 } 3051 3052 3053 3054 /** 3055 * Retrieves the string representation of the assertion value for this search 3056 * filter. This is applicable for the following types of filters: 3057 * <UL> 3058 * <LI>Equality</LI> 3059 * <LI>Greater or Equal</LI> 3060 * <LI>Less or Equal</LI> 3061 * <LI>Approximate Match</LI> 3062 * <LI>Extensible Match</LI> 3063 * </UL> 3064 * 3065 * @return The string representation of the assertion value for this search 3066 * filter, or {@code null} if it is not applicable for this type of 3067 * filter. 3068 */ 3069 @Nullable() 3070 public String getAssertionValue() 3071 { 3072 if (assertionValue == null) 3073 { 3074 return null; 3075 } 3076 else 3077 { 3078 return assertionValue.stringValue(); 3079 } 3080 } 3081 3082 3083 3084 /** 3085 * Retrieves the binary representation of the assertion value for this search 3086 * filter. This is applicable for the following types of filters: 3087 * <UL> 3088 * <LI>Equality</LI> 3089 * <LI>Greater or Equal</LI> 3090 * <LI>Less or Equal</LI> 3091 * <LI>Approximate Match</LI> 3092 * <LI>Extensible Match</LI> 3093 * </UL> 3094 * 3095 * @return The binary representation of the assertion value for this search 3096 * filter, or {@code null} if it is not applicable for this type of 3097 * filter. 3098 */ 3099 @Nullable() 3100 public byte[] getAssertionValueBytes() 3101 { 3102 if (assertionValue == null) 3103 { 3104 return null; 3105 } 3106 else 3107 { 3108 return assertionValue.getValue(); 3109 } 3110 } 3111 3112 3113 3114 /** 3115 * Retrieves the raw assertion value for this search filter as an ASN.1 3116 * octet string. This is applicable for the following types of filters: 3117 * <UL> 3118 * <LI>Equality</LI> 3119 * <LI>Greater or Equal</LI> 3120 * <LI>Less or Equal</LI> 3121 * <LI>Approximate Match</LI> 3122 * <LI>Extensible Match</LI> 3123 * </UL> 3124 * 3125 * @return The raw assertion value for this search filter as an ASN.1 octet 3126 * string, or {@code null} if it is not applicable for this type of 3127 * filter. 3128 */ 3129 @Nullable() 3130 public ASN1OctetString getRawAssertionValue() 3131 { 3132 return assertionValue; 3133 } 3134 3135 3136 3137 /** 3138 * Retrieves the string representation of the subInitial element for this 3139 * substring filter. This is not applicable for any other filter type. 3140 * 3141 * @return The string representation of the subInitial element for this 3142 * substring filter, or {@code null} if this is some other type of 3143 * filter, or if it is a substring filter with no subInitial element. 3144 */ 3145 @Nullable() 3146 public String getSubInitialString() 3147 { 3148 if (subInitial == null) 3149 { 3150 return null; 3151 } 3152 else 3153 { 3154 return subInitial.stringValue(); 3155 } 3156 } 3157 3158 3159 3160 /** 3161 * Retrieves the binary representation of the subInitial element for this 3162 * substring filter. This is not applicable for any other filter type. 3163 * 3164 * @return The binary representation of the subInitial element for this 3165 * substring filter, or {@code null} if this is some other type of 3166 * filter, or if it is a substring filter with no subInitial element. 3167 */ 3168 @Nullable() 3169 public byte[] getSubInitialBytes() 3170 { 3171 if (subInitial == null) 3172 { 3173 return null; 3174 } 3175 else 3176 { 3177 return subInitial.getValue(); 3178 } 3179 } 3180 3181 3182 3183 /** 3184 * Retrieves the raw subInitial element for this filter as an ASN.1 octet 3185 * string. This is not applicable for any other filter type. 3186 * 3187 * @return The raw subInitial element for this filter as an ASN.1 octet 3188 * string, or {@code null} if this is not a substring filter, or if 3189 * it is a substring filter with no subInitial element. 3190 */ 3191 @Nullable() 3192 public ASN1OctetString getRawSubInitialValue() 3193 { 3194 return subInitial; 3195 } 3196 3197 3198 3199 /** 3200 * Retrieves the string representations of the subAny elements for this 3201 * substring filter. This is not applicable for any other filter type. 3202 * 3203 * @return The string representations of the subAny elements for this 3204 * substring filter, or an empty array if this is some other type of 3205 * filter, or if it is a substring filter with no subFinal element. 3206 */ 3207 @NotNull() 3208 public String[] getSubAnyStrings() 3209 { 3210 final String[] subAnyStrings = new String[subAny.length]; 3211 for (int i=0; i < subAny.length; i++) 3212 { 3213 subAnyStrings[i] = subAny[i].stringValue(); 3214 } 3215 3216 return subAnyStrings; 3217 } 3218 3219 3220 3221 /** 3222 * Retrieves the binary representations of the subAny elements for this 3223 * substring filter. This is not applicable for any other filter type. 3224 * 3225 * @return The binary representations of the subAny elements for this 3226 * substring filter, or an empty array if this is some other type of 3227 * filter, or if it is a substring filter with no subFinal element. 3228 */ 3229 @NotNull() 3230 public byte[][] getSubAnyBytes() 3231 { 3232 final byte[][] subAnyBytes = new byte[subAny.length][]; 3233 for (int i=0; i < subAny.length; i++) 3234 { 3235 subAnyBytes[i] = subAny[i].getValue(); 3236 } 3237 3238 return subAnyBytes; 3239 } 3240 3241 3242 3243 /** 3244 * Retrieves the raw subAny values for this substring filter. This is not 3245 * applicable for any other filter type. 3246 * 3247 * @return The raw subAny values for this substring filter, or an empty array 3248 * if this is some other type of filter, or if it is a substring 3249 * filter with no subFinal element. 3250 */ 3251 @NotNull() 3252 public ASN1OctetString[] getRawSubAnyValues() 3253 { 3254 return subAny; 3255 } 3256 3257 3258 3259 /** 3260 * Retrieves the string representation of the subFinal element for this 3261 * substring filter. This is not applicable for any other filter type. 3262 * 3263 * @return The string representation of the subFinal element for this 3264 * substring filter, or {@code null} if this is some other type of 3265 * filter, or if it is a substring filter with no subFinal element. 3266 */ 3267 @Nullable() 3268 public String getSubFinalString() 3269 { 3270 if (subFinal == null) 3271 { 3272 return null; 3273 } 3274 else 3275 { 3276 return subFinal.stringValue(); 3277 } 3278 } 3279 3280 3281 3282 /** 3283 * Retrieves the binary representation of the subFinal element for this 3284 * substring filter. This is not applicable for any other filter type. 3285 * 3286 * @return The binary representation of the subFinal element for this 3287 * substring filter, or {@code null} if this is some other type of 3288 * filter, or if it is a substring filter with no subFinal element. 3289 */ 3290 @Nullable() 3291 public byte[] getSubFinalBytes() 3292 { 3293 if (subFinal == null) 3294 { 3295 return null; 3296 } 3297 else 3298 { 3299 return subFinal.getValue(); 3300 } 3301 } 3302 3303 3304 3305 /** 3306 * Retrieves the raw subFinal element for this filter as an ASN.1 octet 3307 * string. This is not applicable for any other filter type. 3308 * 3309 * @return The raw subFinal element for this filter as an ASN.1 octet 3310 * string, or {@code null} if this is not a substring filter, or if 3311 * it is a substring filter with no subFinal element. 3312 */ 3313 @Nullable() 3314 public ASN1OctetString getRawSubFinalValue() 3315 { 3316 return subFinal; 3317 } 3318 3319 3320 3321 /** 3322 * Retrieves the matching rule ID for this extensible match filter. This is 3323 * not applicable for any other filter type. 3324 * 3325 * @return The matching rule ID for this extensible match filter, or 3326 * {@code null} if this is some other type of filter, or if this 3327 * extensible match filter does not have a matching rule ID. 3328 */ 3329 @Nullable() 3330 public String getMatchingRuleID() 3331 { 3332 return matchingRuleID; 3333 } 3334 3335 3336 3337 /** 3338 * Retrieves the dnAttributes flag for this extensible match filter. This is 3339 * not applicable for any other filter type. 3340 * 3341 * @return The dnAttributes flag for this extensible match filter. 3342 */ 3343 public boolean getDNAttributes() 3344 { 3345 return dnAttributes; 3346 } 3347 3348 3349 3350 /** 3351 * Indicates whether this filter matches the provided entry. Note that this 3352 * is a best-guess effort and may not be completely accurate in all cases. 3353 * All matching will be performed using case-ignore string matching, which may 3354 * yield an unexpected result for values that should not be treated as simple 3355 * strings. For example: 3356 * <UL> 3357 * <LI>Two DN values which are logically equivalent may not be considered 3358 * matches if they have different spacing.</LI> 3359 * <LI>Ordering comparisons against numeric values may yield unexpected 3360 * results (e.g., "2" will be considered greater than "10" because the 3361 * character "2" has a larger ASCII value than the character "1").</LI> 3362 * </UL> 3363 * <BR> 3364 * In addition to the above constraints, it should be noted that neither 3365 * approximate matching nor extensible matching are currently supported. 3366 * 3367 * @param entry The entry for which to make the determination. It must not 3368 * be {@code null}. 3369 * 3370 * @return {@code true} if this filter appears to match the provided entry, 3371 * or {@code false} if not. 3372 * 3373 * @throws LDAPException If a problem occurs while trying to make the 3374 * determination. 3375 */ 3376 public boolean matchesEntry(@NotNull final Entry entry) 3377 throws LDAPException 3378 { 3379 return matchesEntry(entry, entry.getSchema()); 3380 } 3381 3382 3383 3384 /** 3385 * Indicates whether this filter matches the provided entry. Note that this 3386 * is a best-guess effort and may not be completely accurate in all cases. 3387 * If provided, the given schema will be used in an attempt to determine the 3388 * appropriate matching rule for making the determinations, but some corner 3389 * cases may not be handled accurately. Neither approximate matching nor 3390 * extensible matching are currently supported. 3391 * 3392 * @param entry The entry for which to make the determination. It must not 3393 * be {@code null}. 3394 * @param schema The schema to use when making the determination. If this 3395 * is {@code null}, then all matching will be performed using 3396 * a case-ignore matching rule. 3397 * 3398 * @return {@code true} if this filter appears to match the provided entry, 3399 * or {@code false} if not. 3400 * 3401 * @throws LDAPException If a problem occurs while trying to make the 3402 * determination. 3403 */ 3404 public boolean matchesEntry(@NotNull final Entry entry, 3405 @Nullable final Schema schema) 3406 throws LDAPException 3407 { 3408 Validator.ensureNotNull(entry); 3409 3410 switch (filterType) 3411 { 3412 case FILTER_TYPE_AND: 3413 for (final Filter f : filterComps) 3414 { 3415 try 3416 { 3417 if (! f.matchesEntry(entry, schema)) 3418 { 3419 return false; 3420 } 3421 } 3422 catch (final Exception e) 3423 { 3424 Debug.debugException(e); 3425 return false; 3426 } 3427 } 3428 return true; 3429 3430 case FILTER_TYPE_OR: 3431 for (final Filter f : filterComps) 3432 { 3433 try 3434 { 3435 if (f.matchesEntry(entry, schema)) 3436 { 3437 return true; 3438 } 3439 } 3440 catch (final Exception e) 3441 { 3442 Debug.debugException(e); 3443 } 3444 } 3445 return false; 3446 3447 case FILTER_TYPE_NOT: 3448 return (! notComp.matchesEntry(entry, schema)); 3449 3450 case FILTER_TYPE_EQUALITY: 3451 Attribute a = entry.getAttribute(attrName, schema); 3452 if (a == null) 3453 { 3454 return false; 3455 } 3456 3457 MatchingRule matchingRule = 3458 MatchingRule.selectEqualityMatchingRule(attrName, schema); 3459 return matchingRule.matchesAnyValue(assertionValue, a.getRawValues()); 3460 3461 case FILTER_TYPE_SUBSTRING: 3462 a = entry.getAttribute(attrName, schema); 3463 if (a == null) 3464 { 3465 return false; 3466 } 3467 3468 matchingRule = 3469 MatchingRule.selectSubstringMatchingRule(attrName, schema); 3470 for (final ASN1OctetString v : a.getRawValues()) 3471 { 3472 if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal)) 3473 { 3474 return true; 3475 } 3476 } 3477 return false; 3478 3479 case FILTER_TYPE_GREATER_OR_EQUAL: 3480 a = entry.getAttribute(attrName, schema); 3481 if (a == null) 3482 { 3483 return false; 3484 } 3485 3486 matchingRule = 3487 MatchingRule.selectOrderingMatchingRule(attrName, schema); 3488 for (final ASN1OctetString v : a.getRawValues()) 3489 { 3490 if (matchingRule.compareValues(v, assertionValue) >= 0) 3491 { 3492 return true; 3493 } 3494 } 3495 return false; 3496 3497 case FILTER_TYPE_LESS_OR_EQUAL: 3498 a = entry.getAttribute(attrName, schema); 3499 if (a == null) 3500 { 3501 return false; 3502 } 3503 3504 matchingRule = 3505 MatchingRule.selectOrderingMatchingRule(attrName, schema); 3506 for (final ASN1OctetString v : a.getRawValues()) 3507 { 3508 if (matchingRule.compareValues(v, assertionValue) <= 0) 3509 { 3510 return true; 3511 } 3512 } 3513 return false; 3514 3515 case FILTER_TYPE_PRESENCE: 3516 return (entry.hasAttribute(attrName)); 3517 3518 case FILTER_TYPE_APPROXIMATE_MATCH: 3519 throw new LDAPException(ResultCode.NOT_SUPPORTED, 3520 ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get()); 3521 3522 case FILTER_TYPE_EXTENSIBLE_MATCH: 3523 return extensibleMatchFilterMatchesEntry(entry, schema); 3524 3525 default: 3526 throw new LDAPException(ResultCode.PARAM_ERROR, 3527 ERR_FILTER_INVALID_TYPE.get()); 3528 } 3529 } 3530 3531 3532 3533 /** 3534 * Indicates whether the provided extensible matching filter component matches 3535 * the provided entry. This method provides very limited support for 3536 * extensible matching It can only be used for filters that contain both an 3537 * attribute type and a matching rule ID, and when the matching rule ID is 3538 * one of the following: 3539 * <OL> 3540 * <LI>jsonObjectFilterExtensibleMatch (or 1.3.6.1.4.1.30221.2.4.13)</LI> 3541 * </OL> 3542 * 3543 * @param entry The entry for which to make the determination. It must not 3544 * be {@code null}. 3545 * @param schema The schema to use when making the determination. If this 3546 * is {@code null}, then all matching will be performed using 3547 * a case-ignore matching rule. 3548 * 3549 * @return {@code true} if this filter appears to match the provided entry, 3550 * or {@code false} if not. 3551 * 3552 * @throws LDAPException If a problem occurs while trying to make the 3553 * determination. 3554 */ 3555 private boolean extensibleMatchFilterMatchesEntry(@NotNull final Entry entry, 3556 @Nullable final Schema schema) 3557 throws LDAPException 3558 { 3559 if ((attrName != null) && (matchingRuleID != null) && (! dnAttributes)) 3560 { 3561 if (matchingRuleID.equalsIgnoreCase("jsonObjectFilterExtensibleMatch") || 3562 matchingRuleID.equals("1.3.6.1.4.1.30221.2.4.13")) 3563 { 3564 final JSONObjectFilter jsonObjectFilter; 3565 try 3566 { 3567 final JSONObject jsonObject = 3568 new JSONObject(assertionValue.stringValue()); 3569 jsonObjectFilter = JSONObjectFilter.decode(jsonObject); 3570 } 3571 catch (final Exception e) 3572 { 3573 Debug.debugException(e); 3574 throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, 3575 ERR_FILTER_EXTENSIBLE_MATCH_MALFORMED_JSON_OBJECT_FILTER.get( 3576 toString(), entry.getDN(), 3577 StaticUtils.getExceptionMessage(e)), 3578 e); 3579 } 3580 3581 final Attribute attr = entry.getAttribute(attrName, schema); 3582 if (attr != null) 3583 { 3584 for (final ASN1OctetString v : attr.getRawValues()) 3585 { 3586 try 3587 { 3588 final JSONObject jsonObject = new JSONObject(v.stringValue()); 3589 if (jsonObjectFilter.matchesJSONObject(jsonObject)) 3590 { 3591 return true; 3592 } 3593 } 3594 catch (final Exception e) 3595 { 3596 Debug.debugException(e); 3597 } 3598 } 3599 } 3600 3601 return false; 3602 } 3603 } 3604 3605 throw new LDAPException(ResultCode.NOT_SUPPORTED, 3606 ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get()); 3607 } 3608 3609 3610 3611 /** 3612 * Attempts to simplify the provided filter to allow it to be more efficiently 3613 * processed by the server. The simplifications it will make include: 3614 * <UL> 3615 * <LI>Any AND or OR filter that contains only a single filter component 3616 * will be converted to just that embedded filter component to eliminate 3617 * the unnecessary AND or OR wrapper. For example, the filter 3618 * "(&(uid=john.doe))" will be converted to just 3619 * "(uid=john.doe)".</LI> 3620 * <LI>Any AND components inside of an AND filter will be merged into the 3621 * outer AND filter. Any OR components inside of an OR filter will be 3622 * merged into the outer OR filter. For example, the filter 3623 * "(&(objectClass=person)(&(givenName=John)(sn=Doe)))" will be 3624 * converted to 3625 * "(&(objectClass=person)(givenName=John)(sn=Doe))".</LI> 3626 * <LI>Any AND filter that contains an LDAP false filter will be converted 3627 * to just an LDAP false filter.</LI> 3628 * <LI>Any OR filter that contains an LDAP true filter will be converted 3629 * to just an LDAP true filter.</LI> 3630 * <LI>If {@code reOrderElements} is true, then this method will attempt to 3631 * re-order the elements inside AND and OR filters in an attempt to 3632 * ensure that the components which are likely to be the most efficient 3633 * come earlier than those which are likely to be the least efficient. 3634 * This can speed up processing in servers that process filter 3635 * components in a left-to-right order.</LI> 3636 * </UL> 3637 * <BR><BR> 3638 * The simplification will happen recursively, in an attempt to generate a 3639 * filter that is as simple and efficient as possible. 3640 * 3641 * @param filter The filter to attempt to simplify. 3642 * @param reOrderElements Indicates whether this method may re-order the 3643 * elements in the filter so that, in a server that 3644 * evaluates the components in a left-to-right order, 3645 * the components which are likely to be more 3646 * efficient to process will be listed before those 3647 * which are likely to be less efficient. 3648 * 3649 * @return The simplified filter, or the original filter if the provided 3650 * filter is not one that can be simplified any further. 3651 */ 3652 @NotNull() 3653 public static Filter simplifyFilter(@NotNull final Filter filter, 3654 final boolean reOrderElements) 3655 { 3656 final byte filterType = filter.filterType; 3657 switch (filterType) 3658 { 3659 case FILTER_TYPE_AND: 3660 case FILTER_TYPE_OR: 3661 // These will be handled below. 3662 break; 3663 3664 case FILTER_TYPE_NOT: 3665 // We may be able to simplify the filter component contained inside the 3666 // NOT. 3667 return createNOTFilter(simplifyFilter(filter.notComp, reOrderElements)); 3668 3669 default: 3670 // We can't simplify this filter, so just return what was provided. 3671 return filter; 3672 } 3673 3674 3675 // An AND filter with zero components is an LDAP true filter, and we can't 3676 // simplify that. An OR filter with zero components is an LDAP false 3677 // filter, and we can't simplify that either. The set of components 3678 // should never be null for an AND or OR filter, but if that happens to be 3679 // the case, then we'll return the original filter. 3680 final Filter[] components = filter.filterComps; 3681 if ((components == null) || (components.length == 0)) 3682 { 3683 return filter; 3684 } 3685 3686 3687 // For either an AND or an OR filter with just a single component, then just 3688 // return that embedded component. But simplify it first. 3689 if (components.length == 1) 3690 { 3691 return simplifyFilter(components[0], reOrderElements); 3692 } 3693 3694 3695 // If we've gotten here, then we have a filter with multiple components. 3696 // Simplify each of them to the extent possible, un-embed any ANDs 3697 // contained inside an AND or ORs contained inside an OR, and eliminate any 3698 // duplicate components in the resulting top-level filter. 3699 final LinkedHashSet<Filter> componentSet = 3700 new LinkedHashSet<>(StaticUtils.computeMapCapacity(10)); 3701 for (final Filter f : components) 3702 { 3703 final Filter simplifiedFilter = simplifyFilter(f, reOrderElements); 3704 if (simplifiedFilter.filterType == FILTER_TYPE_AND) 3705 { 3706 if (filterType == FILTER_TYPE_AND) 3707 { 3708 // This is an AND nested inside an AND. In that case, we'll just put 3709 // all the nested components inside the outer AND. 3710 componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps)); 3711 } 3712 else 3713 { 3714 componentSet.add(simplifiedFilter); 3715 } 3716 } 3717 else if (simplifiedFilter.filterType == FILTER_TYPE_OR) 3718 { 3719 if (filterType == FILTER_TYPE_OR) 3720 { 3721 // This is an OR nested inside an OR. In that case, we'll just put 3722 // all the nested components inside the outer OR. 3723 componentSet.addAll(Arrays.asList(simplifiedFilter.filterComps)); 3724 } 3725 else 3726 { 3727 componentSet.add(simplifiedFilter); 3728 } 3729 } 3730 else 3731 { 3732 componentSet.add(simplifiedFilter); 3733 } 3734 } 3735 3736 3737 // It's possible at this point that we are down to just a single component. 3738 // That can happen if the filter was an AND or an OR with a duplicate 3739 // element, like "(&(a=b)(a=b))". In that case, just return that one 3740 // component. 3741 if (componentSet.size() == 1) 3742 { 3743 return componentSet.iterator().next(); 3744 } 3745 3746 3747 // If we have an AND filter that contains an embedded LDAP false filter, 3748 // then just return the LDAP false filter. If we have an OR filter that 3749 // contains an embedded LDAP true filter, then just return the LDAP true 3750 // filter. 3751 if (filterType == FILTER_TYPE_AND) 3752 { 3753 for (final Filter f : componentSet) 3754 { 3755 if ((f.filterType == FILTER_TYPE_OR) && (f.filterComps.length == 0)) 3756 { 3757 return f; 3758 } 3759 } 3760 } 3761 else if (filterType == FILTER_TYPE_OR) 3762 { 3763 for (final Filter f : componentSet) 3764 { 3765 if ((f.filterType == FILTER_TYPE_AND) && (f.filterComps.length == 0)) 3766 { 3767 return f; 3768 } 3769 } 3770 } 3771 3772 3773 // If we should re-order the components, then use the following priority 3774 // list: 3775 // 3776 // 1. Equality components that target an attribute other than objectClass. 3777 // These are most likely to require only a single database lookup to get 3778 // the candidate list, and that candidate list will frequently be small. 3779 // 2. Equality components that target the objectClass attribute. These are 3780 // likely to require only a single database lookup to get the candidate 3781 // list, but the candidate list is more likely to be larger. 3782 // 3. Approximate match components. These are also likely to require only 3783 // a single database lookup to get the candidate list, but that 3784 // candidate list is likely to have a larger number of candidates. 3785 // 4. Presence components that target an attribute other than objectClass. 3786 // These are also likely to require only a single database lookup to get 3787 // the candidate list, but are likely to have a large number of 3788 // candidates. 3789 // 5. Substring components that have a subInitial element. These are 3790 // generally the most efficient substring filters to process, requiring 3791 // access to fewer database keys than substring filters with only subAny 3792 // and/or subFinal components. 3793 // 6. Substring components that only have subAny and/or subFinal elements. 3794 // These will probably require a number of database lookups and will 3795 // probably result in large candidate lists. 3796 // 7. Greater-or-equal components and less-or-equal components. These 3797 // will probably require a number of database lookups and will probably 3798 // result in large candidate lists. 3799 // 8. Extensible match components. Even if these are indexed, there isn't 3800 // any good way to know how expensive they might be to process or how 3801 // big the candidate list might be. 3802 // 9. Presence components that target the objectClass attribute. This is 3803 // likely to require only a single database lookup to get the candidate 3804 // list, but the candidate list will also be extremely large (if it's 3805 // indexed at all) since it will match every entry. 3806 // 10. NOT components. These are generally not possible to index and 3807 // therefore cannot be used to create a candidate list. 3808 // 3809 // AND and OR components will be ordered according to the first of their 3810 // embedded components Since the filter has already been simplified, then 3811 // the first element in the list will be the one we think will be the most 3812 // efficient to process. 3813 if (reOrderElements) 3814 { 3815 final TreeMap<Integer,LinkedHashSet<Filter>> m = new TreeMap<>(); 3816 for (final Filter f : componentSet) 3817 { 3818 final Filter prioritizeComp; 3819 if ((f.filterType == FILTER_TYPE_AND) || 3820 (f.filterType == FILTER_TYPE_OR)) 3821 { 3822 if (f.filterComps.length > 0) 3823 { 3824 prioritizeComp = f.filterComps[0]; 3825 } 3826 else 3827 { 3828 prioritizeComp = f; 3829 } 3830 } 3831 else 3832 { 3833 prioritizeComp = f; 3834 } 3835 3836 final Integer slot; 3837 switch (prioritizeComp.filterType) 3838 { 3839 case FILTER_TYPE_EQUALITY: 3840 if (prioritizeComp.attrName.equalsIgnoreCase("objectClass")) 3841 { 3842 slot = 2; 3843 } 3844 else 3845 { 3846 slot = 1; 3847 } 3848 break; 3849 3850 case FILTER_TYPE_APPROXIMATE_MATCH: 3851 slot = 3; 3852 break; 3853 3854 case FILTER_TYPE_PRESENCE: 3855 if (prioritizeComp.attrName.equalsIgnoreCase("objectClass")) 3856 { 3857 slot = 9; 3858 } 3859 else 3860 { 3861 slot = 4; 3862 } 3863 break; 3864 3865 case FILTER_TYPE_SUBSTRING: 3866 if (prioritizeComp.subInitial == null) 3867 { 3868 slot = 6; 3869 } 3870 else 3871 { 3872 slot = 5; 3873 } 3874 break; 3875 3876 case FILTER_TYPE_GREATER_OR_EQUAL: 3877 case FILTER_TYPE_LESS_OR_EQUAL: 3878 slot = 7; 3879 break; 3880 3881 case FILTER_TYPE_EXTENSIBLE_MATCH: 3882 slot = 8; 3883 break; 3884 3885 case FILTER_TYPE_NOT: 3886 default: 3887 slot = 10; 3888 break; 3889 } 3890 3891 LinkedHashSet<Filter> filterSet = m.get(slot-1); 3892 if (filterSet == null) 3893 { 3894 filterSet = new LinkedHashSet<>(StaticUtils.computeMapCapacity(10)); 3895 m.put(slot-1, filterSet); 3896 } 3897 filterSet.add(f); 3898 } 3899 3900 componentSet.clear(); 3901 for (final LinkedHashSet<Filter> filterSet : m.values()) 3902 { 3903 componentSet.addAll(filterSet); 3904 } 3905 } 3906 3907 3908 // Return the new, possibly simplified filter. 3909 if (filterType == FILTER_TYPE_AND) 3910 { 3911 return createANDFilter(componentSet); 3912 } 3913 else 3914 { 3915 return createORFilter(componentSet); 3916 } 3917 } 3918 3919 3920 3921 /** 3922 * Generates a hash code for this search filter. 3923 * 3924 * @return The generated hash code for this search filter. 3925 */ 3926 @Override() 3927 public int hashCode() 3928 { 3929 final CaseIgnoreStringMatchingRule matchingRule = 3930 CaseIgnoreStringMatchingRule.getInstance(); 3931 int hashCode = filterType; 3932 3933 switch (filterType) 3934 { 3935 case FILTER_TYPE_AND: 3936 case FILTER_TYPE_OR: 3937 for (final Filter f : filterComps) 3938 { 3939 hashCode += f.hashCode(); 3940 } 3941 break; 3942 3943 case FILTER_TYPE_NOT: 3944 hashCode += notComp.hashCode(); 3945 break; 3946 3947 case FILTER_TYPE_EQUALITY: 3948 case FILTER_TYPE_GREATER_OR_EQUAL: 3949 case FILTER_TYPE_LESS_OR_EQUAL: 3950 case FILTER_TYPE_APPROXIMATE_MATCH: 3951 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 3952 hashCode += matchingRule.normalize(assertionValue).hashCode(); 3953 break; 3954 3955 case FILTER_TYPE_SUBSTRING: 3956 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 3957 if (subInitial != null) 3958 { 3959 hashCode += matchingRule.normalizeSubstring(subInitial, 3960 MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode(); 3961 } 3962 for (final ASN1OctetString s : subAny) 3963 { 3964 hashCode += matchingRule.normalizeSubstring(s, 3965 MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode(); 3966 } 3967 if (subFinal != null) 3968 { 3969 hashCode += matchingRule.normalizeSubstring(subFinal, 3970 MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode(); 3971 } 3972 break; 3973 3974 case FILTER_TYPE_PRESENCE: 3975 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 3976 break; 3977 3978 case FILTER_TYPE_EXTENSIBLE_MATCH: 3979 if (attrName != null) 3980 { 3981 hashCode += StaticUtils.toLowerCase(attrName).hashCode(); 3982 } 3983 3984 if (matchingRuleID != null) 3985 { 3986 hashCode += StaticUtils.toLowerCase(matchingRuleID).hashCode(); 3987 } 3988 3989 if (dnAttributes) 3990 { 3991 hashCode++; 3992 } 3993 3994 hashCode += matchingRule.normalize(assertionValue).hashCode(); 3995 break; 3996 } 3997 3998 return hashCode; 3999 } 4000 4001 4002 4003 /** 4004 * Indicates whether the provided object is equal to this search filter. 4005 * 4006 * @param o The object for which to make the determination. 4007 * 4008 * @return {@code true} if the provided object can be considered equal to 4009 * this search filter, or {@code false} if not. 4010 */ 4011 @Override() 4012 public boolean equals(@Nullable final Object o) 4013 { 4014 if (o == null) 4015 { 4016 return false; 4017 } 4018 4019 if (o == this) 4020 { 4021 return true; 4022 } 4023 4024 if (! (o instanceof Filter)) 4025 { 4026 return false; 4027 } 4028 4029 final Filter f = (Filter) o; 4030 if (filterType != f.filterType) 4031 { 4032 return false; 4033 } 4034 4035 final CaseIgnoreStringMatchingRule matchingRule = 4036 CaseIgnoreStringMatchingRule.getInstance(); 4037 4038 switch (filterType) 4039 { 4040 case FILTER_TYPE_AND: 4041 case FILTER_TYPE_OR: 4042 if (filterComps.length != f.filterComps.length) 4043 { 4044 return false; 4045 } 4046 4047 final HashSet<Filter> compSet = 4048 new HashSet<>(StaticUtils.computeMapCapacity(10)); 4049 compSet.addAll(Arrays.asList(filterComps)); 4050 4051 for (final Filter filterComp : f.filterComps) 4052 { 4053 if (! compSet.remove(filterComp)) 4054 { 4055 return false; 4056 } 4057 } 4058 4059 return true; 4060 4061 4062 case FILTER_TYPE_NOT: 4063 return notComp.equals(f.notComp); 4064 4065 4066 case FILTER_TYPE_EQUALITY: 4067 case FILTER_TYPE_GREATER_OR_EQUAL: 4068 case FILTER_TYPE_LESS_OR_EQUAL: 4069 case FILTER_TYPE_APPROXIMATE_MATCH: 4070 return (attrName.equalsIgnoreCase(f.attrName) && 4071 matchingRule.valuesMatch(assertionValue, f.assertionValue)); 4072 4073 4074 case FILTER_TYPE_SUBSTRING: 4075 if (! attrName.equalsIgnoreCase(f.attrName)) 4076 { 4077 return false; 4078 } 4079 4080 if (subAny.length != f.subAny.length) 4081 { 4082 return false; 4083 } 4084 4085 if (subInitial == null) 4086 { 4087 if (f.subInitial != null) 4088 { 4089 return false; 4090 } 4091 } 4092 else 4093 { 4094 if (f.subInitial == null) 4095 { 4096 return false; 4097 } 4098 4099 final ASN1OctetString si1 = matchingRule.normalizeSubstring( 4100 subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL); 4101 final ASN1OctetString si2 = matchingRule.normalizeSubstring( 4102 f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL); 4103 if (! si1.equals(si2)) 4104 { 4105 return false; 4106 } 4107 } 4108 4109 for (int i=0; i < subAny.length; i++) 4110 { 4111 final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i], 4112 MatchingRule.SUBSTRING_TYPE_SUBANY); 4113 final ASN1OctetString sa2 = matchingRule.normalizeSubstring( 4114 f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY); 4115 if (! sa1.equals(sa2)) 4116 { 4117 return false; 4118 } 4119 } 4120 4121 if (subFinal == null) 4122 { 4123 if (f.subFinal != null) 4124 { 4125 return false; 4126 } 4127 } 4128 else 4129 { 4130 if (f.subFinal == null) 4131 { 4132 return false; 4133 } 4134 4135 final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal, 4136 MatchingRule.SUBSTRING_TYPE_SUBFINAL); 4137 final ASN1OctetString sf2 = matchingRule.normalizeSubstring( 4138 f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL); 4139 if (! sf1.equals(sf2)) 4140 { 4141 return false; 4142 } 4143 } 4144 4145 return true; 4146 4147 4148 case FILTER_TYPE_PRESENCE: 4149 return (attrName.equalsIgnoreCase(f.attrName)); 4150 4151 4152 case FILTER_TYPE_EXTENSIBLE_MATCH: 4153 if (attrName == null) 4154 { 4155 if (f.attrName != null) 4156 { 4157 return false; 4158 } 4159 } 4160 else 4161 { 4162 if (f.attrName == null) 4163 { 4164 return false; 4165 } 4166 else 4167 { 4168 if (! attrName.equalsIgnoreCase(f.attrName)) 4169 { 4170 return false; 4171 } 4172 } 4173 } 4174 4175 if (matchingRuleID == null) 4176 { 4177 if (f.matchingRuleID != null) 4178 { 4179 return false; 4180 } 4181 } 4182 else 4183 { 4184 if (f.matchingRuleID == null) 4185 { 4186 return false; 4187 } 4188 else 4189 { 4190 if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID)) 4191 { 4192 return false; 4193 } 4194 } 4195 } 4196 4197 if (dnAttributes != f.dnAttributes) 4198 { 4199 return false; 4200 } 4201 4202 return matchingRule.valuesMatch(assertionValue, f.assertionValue); 4203 4204 4205 default: 4206 return false; 4207 } 4208 } 4209 4210 4211 4212 /** 4213 * Retrieves a string representation of this search filter. 4214 * 4215 * @return A string representation of this search filter. 4216 */ 4217 @Override() 4218 @NotNull() 4219 public String toString() 4220 { 4221 if (filterString == null) 4222 { 4223 final StringBuilder buffer = new StringBuilder(); 4224 toString(buffer); 4225 filterString = buffer.toString(); 4226 } 4227 4228 return filterString; 4229 } 4230 4231 4232 4233 /** 4234 * Appends a string representation of this search filter to the provided 4235 * buffer. 4236 * 4237 * @param buffer The buffer to which to append a string representation of 4238 * this search filter. 4239 */ 4240 public void toString(@NotNull final StringBuilder buffer) 4241 { 4242 switch (filterType) 4243 { 4244 case FILTER_TYPE_AND: 4245 buffer.append("(&"); 4246 for (final Filter f : filterComps) 4247 { 4248 f.toString(buffer); 4249 } 4250 buffer.append(')'); 4251 break; 4252 4253 case FILTER_TYPE_OR: 4254 buffer.append("(|"); 4255 for (final Filter f : filterComps) 4256 { 4257 f.toString(buffer); 4258 } 4259 buffer.append(')'); 4260 break; 4261 4262 case FILTER_TYPE_NOT: 4263 buffer.append("(!"); 4264 notComp.toString(buffer); 4265 buffer.append(')'); 4266 break; 4267 4268 case FILTER_TYPE_EQUALITY: 4269 buffer.append('('); 4270 buffer.append(attrName); 4271 buffer.append('='); 4272 encodeValue(assertionValue, buffer); 4273 buffer.append(')'); 4274 break; 4275 4276 case FILTER_TYPE_SUBSTRING: 4277 buffer.append('('); 4278 buffer.append(attrName); 4279 buffer.append('='); 4280 if (subInitial != null) 4281 { 4282 encodeValue(subInitial, buffer); 4283 } 4284 buffer.append('*'); 4285 for (final ASN1OctetString s : subAny) 4286 { 4287 encodeValue(s, buffer); 4288 buffer.append('*'); 4289 } 4290 if (subFinal != null) 4291 { 4292 encodeValue(subFinal, buffer); 4293 } 4294 buffer.append(')'); 4295 break; 4296 4297 case FILTER_TYPE_GREATER_OR_EQUAL: 4298 buffer.append('('); 4299 buffer.append(attrName); 4300 buffer.append(">="); 4301 encodeValue(assertionValue, buffer); 4302 buffer.append(')'); 4303 break; 4304 4305 case FILTER_TYPE_LESS_OR_EQUAL: 4306 buffer.append('('); 4307 buffer.append(attrName); 4308 buffer.append("<="); 4309 encodeValue(assertionValue, buffer); 4310 buffer.append(')'); 4311 break; 4312 4313 case FILTER_TYPE_PRESENCE: 4314 buffer.append('('); 4315 buffer.append(attrName); 4316 buffer.append("=*)"); 4317 break; 4318 4319 case FILTER_TYPE_APPROXIMATE_MATCH: 4320 buffer.append('('); 4321 buffer.append(attrName); 4322 buffer.append("~="); 4323 encodeValue(assertionValue, buffer); 4324 buffer.append(')'); 4325 break; 4326 4327 case FILTER_TYPE_EXTENSIBLE_MATCH: 4328 buffer.append('('); 4329 if (attrName != null) 4330 { 4331 buffer.append(attrName); 4332 } 4333 4334 if (dnAttributes) 4335 { 4336 buffer.append(":dn"); 4337 } 4338 4339 if (matchingRuleID != null) 4340 { 4341 buffer.append(':'); 4342 buffer.append(matchingRuleID); 4343 } 4344 4345 buffer.append(":="); 4346 encodeValue(assertionValue, buffer); 4347 buffer.append(')'); 4348 break; 4349 } 4350 } 4351 4352 4353 4354 /** 4355 * Retrieves a normalized string representation of this search filter. 4356 * 4357 * @return A normalized string representation of this search filter. 4358 */ 4359 @NotNull() 4360 public String toNormalizedString() 4361 { 4362 if (normalizedString == null) 4363 { 4364 final StringBuilder buffer = new StringBuilder(); 4365 toNormalizedString(buffer); 4366 normalizedString = buffer.toString(); 4367 } 4368 4369 return normalizedString; 4370 } 4371 4372 4373 4374 /** 4375 * Appends a normalized string representation of this search filter to the 4376 * provided buffer. 4377 * 4378 * @param buffer The buffer to which to append a normalized string 4379 * representation of this search filter. 4380 */ 4381 public void toNormalizedString(@NotNull final StringBuilder buffer) 4382 { 4383 final CaseIgnoreStringMatchingRule mr = 4384 CaseIgnoreStringMatchingRule.getInstance(); 4385 4386 switch (filterType) 4387 { 4388 case FILTER_TYPE_AND: 4389 buffer.append("(&"); 4390 for (final Filter f : filterComps) 4391 { 4392 f.toNormalizedString(buffer); 4393 } 4394 buffer.append(')'); 4395 break; 4396 4397 case FILTER_TYPE_OR: 4398 buffer.append("(|"); 4399 for (final Filter f : filterComps) 4400 { 4401 f.toNormalizedString(buffer); 4402 } 4403 buffer.append(')'); 4404 break; 4405 4406 case FILTER_TYPE_NOT: 4407 buffer.append("(!"); 4408 notComp.toNormalizedString(buffer); 4409 buffer.append(')'); 4410 break; 4411 4412 case FILTER_TYPE_EQUALITY: 4413 buffer.append('('); 4414 buffer.append(StaticUtils.toLowerCase(attrName)); 4415 buffer.append('='); 4416 encodeValue(mr.normalize(assertionValue), buffer); 4417 buffer.append(')'); 4418 break; 4419 4420 case FILTER_TYPE_SUBSTRING: 4421 buffer.append('('); 4422 buffer.append(StaticUtils.toLowerCase(attrName)); 4423 buffer.append('='); 4424 if (subInitial != null) 4425 { 4426 encodeValue(mr.normalizeSubstring(subInitial, 4427 MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer); 4428 } 4429 buffer.append('*'); 4430 for (final ASN1OctetString s : subAny) 4431 { 4432 encodeValue(mr.normalizeSubstring(s, 4433 MatchingRule.SUBSTRING_TYPE_SUBANY), buffer); 4434 buffer.append('*'); 4435 } 4436 if (subFinal != null) 4437 { 4438 encodeValue(mr.normalizeSubstring(subFinal, 4439 MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer); 4440 } 4441 buffer.append(')'); 4442 break; 4443 4444 case FILTER_TYPE_GREATER_OR_EQUAL: 4445 buffer.append('('); 4446 buffer.append(StaticUtils.toLowerCase(attrName)); 4447 buffer.append(">="); 4448 encodeValue(mr.normalize(assertionValue), buffer); 4449 buffer.append(')'); 4450 break; 4451 4452 case FILTER_TYPE_LESS_OR_EQUAL: 4453 buffer.append('('); 4454 buffer.append(StaticUtils.toLowerCase(attrName)); 4455 buffer.append("<="); 4456 encodeValue(mr.normalize(assertionValue), buffer); 4457 buffer.append(')'); 4458 break; 4459 4460 case FILTER_TYPE_PRESENCE: 4461 buffer.append('('); 4462 buffer.append(StaticUtils.toLowerCase(attrName)); 4463 buffer.append("=*)"); 4464 break; 4465 4466 case FILTER_TYPE_APPROXIMATE_MATCH: 4467 buffer.append('('); 4468 buffer.append(StaticUtils.toLowerCase(attrName)); 4469 buffer.append("~="); 4470 encodeValue(mr.normalize(assertionValue), buffer); 4471 buffer.append(')'); 4472 break; 4473 4474 case FILTER_TYPE_EXTENSIBLE_MATCH: 4475 buffer.append('('); 4476 if (attrName != null) 4477 { 4478 buffer.append(StaticUtils.toLowerCase(attrName)); 4479 } 4480 4481 if (dnAttributes) 4482 { 4483 buffer.append(":dn"); 4484 } 4485 4486 if (matchingRuleID != null) 4487 { 4488 buffer.append(':'); 4489 buffer.append(StaticUtils.toLowerCase(matchingRuleID)); 4490 } 4491 4492 buffer.append(":="); 4493 encodeValue(mr.normalize(assertionValue), buffer); 4494 buffer.append(')'); 4495 break; 4496 } 4497 } 4498 4499 4500 4501 /** 4502 * Encodes the provided value into a form suitable for use as the assertion 4503 * value in the string representation of a search filter. Parentheses, 4504 * asterisks, backslashes, null characters, and any non-ASCII characters will 4505 * be escaped using a backslash before the hexadecimal representation of each 4506 * byte in the character to escape. 4507 * 4508 * @param value The value to be encoded. It must not be {@code null}. 4509 * 4510 * @return The encoded representation of the provided string. 4511 */ 4512 @NotNull() 4513 public static String encodeValue(@NotNull final String value) 4514 { 4515 Validator.ensureNotNull(value); 4516 4517 final StringBuilder buffer = new StringBuilder(); 4518 encodeValue(new ASN1OctetString(value), buffer); 4519 return buffer.toString(); 4520 } 4521 4522 4523 4524 /** 4525 * Encodes the provided value into a form suitable for use as the assertion 4526 * value in the string representation of a search filter. Parentheses, 4527 * asterisks, backslashes, null characters, and any non-ASCII characters will 4528 * be escaped using a backslash before the hexadecimal representation of each 4529 * byte in the character to escape. 4530 * 4531 * @param value The value to be encoded. It must not be {@code null}. 4532 * 4533 * @return The encoded representation of the provided string. 4534 */ 4535 @NotNull() 4536 public static String encodeValue(@NotNull final byte[]value) 4537 { 4538 Validator.ensureNotNull(value); 4539 4540 final StringBuilder buffer = new StringBuilder(); 4541 encodeValue(new ASN1OctetString(value), buffer); 4542 return buffer.toString(); 4543 } 4544 4545 4546 4547 /** 4548 * Appends the assertion value for this filter to the provided buffer, 4549 * encoding any special characters as necessary. 4550 * 4551 * @param value The value to be encoded. 4552 * @param buffer The buffer to which the assertion value should be appended. 4553 */ 4554 public static void encodeValue(@NotNull final ASN1OctetString value, 4555 @NotNull final StringBuilder buffer) 4556 { 4557 final byte[] valueBytes = value.getValue(); 4558 for (int i=0; i < valueBytes.length; i++) 4559 { 4560 switch (StaticUtils.numBytesInUTF8CharacterWithFirstByte(valueBytes[i])) 4561 { 4562 case 1: 4563 // This character is ASCII, but might still need to be escaped. 4564 if ((valueBytes[i] <= 0x1F) || // Non-printable ASCII characters. 4565 (valueBytes[i] == 0x28) || // Open parenthesis 4566 (valueBytes[i] == 0x29) || // Close parenthesis 4567 (valueBytes[i] == 0x2A) || // Asterisk 4568 (valueBytes[i] == 0x5C) || // Backslash 4569 (valueBytes[i] == 0x7F)) // DEL 4570 { 4571 buffer.append('\\'); 4572 StaticUtils.toHex(valueBytes[i], buffer); 4573 } 4574 else 4575 { 4576 buffer.append((char) valueBytes[i]); 4577 } 4578 break; 4579 4580 case 2: 4581 // If there are at least two bytes left, then we'll hex-encode the 4582 // next two bytes. Otherwise we'll hex-encode whatever is left. 4583 buffer.append('\\'); 4584 StaticUtils.toHex(valueBytes[i++], buffer); 4585 if (i < valueBytes.length) 4586 { 4587 buffer.append('\\'); 4588 StaticUtils.toHex(valueBytes[i], buffer); 4589 } 4590 break; 4591 4592 case 3: 4593 // If there are at least three bytes left, then we'll hex-encode the 4594 // next three bytes. Otherwise we'll hex-encode whatever is left. 4595 buffer.append('\\'); 4596 StaticUtils.toHex(valueBytes[i++], buffer); 4597 if (i < valueBytes.length) 4598 { 4599 buffer.append('\\'); 4600 StaticUtils.toHex(valueBytes[i++], buffer); 4601 } 4602 if (i < valueBytes.length) 4603 { 4604 buffer.append('\\'); 4605 StaticUtils.toHex(valueBytes[i], buffer); 4606 } 4607 break; 4608 4609 case 4: 4610 // If there are at least four bytes left, then we'll hex-encode the 4611 // next four bytes. Otherwise we'll hex-encode whatever is left. 4612 buffer.append('\\'); 4613 StaticUtils.toHex(valueBytes[i++], buffer); 4614 if (i < valueBytes.length) 4615 { 4616 buffer.append('\\'); 4617 StaticUtils.toHex(valueBytes[i++], buffer); 4618 } 4619 if (i < valueBytes.length) 4620 { 4621 buffer.append('\\'); 4622 StaticUtils.toHex(valueBytes[i++], buffer); 4623 } 4624 if (i < valueBytes.length) 4625 { 4626 buffer.append('\\'); 4627 StaticUtils.toHex(valueBytes[i], buffer); 4628 } 4629 break; 4630 4631 default: 4632 // We'll hex-encode whatever is left in the buffer. 4633 while (i < valueBytes.length) 4634 { 4635 buffer.append('\\'); 4636 StaticUtils.toHex(valueBytes[i++], buffer); 4637 } 4638 break; 4639 } 4640 } 4641 } 4642 4643 4644 4645 /** 4646 * Appends a number of lines comprising the Java source code that can be used 4647 * to recreate this filter to the given list. Note that unless a first line 4648 * prefix and/or last line suffix are provided, this will just include the 4649 * code for the static method used to create the filter, starting with 4650 * "Filter.createXFilter(" and ending with the closing parenthesis for that 4651 * method call. 4652 * 4653 * @param lineList The list to which the source code lines should be 4654 * added. 4655 * @param indentSpaces The number of spaces that should be used to indent 4656 * the generated code. It must not be negative. 4657 * @param firstLinePrefix An optional string that should precede the static 4658 * method call (e.g., it could be used for an 4659 * attribute assignment, like "Filter f = "). It may 4660 * be {@code null} or empty if there should be no 4661 * first line prefix. 4662 * @param lastLineSuffix An optional suffix that should follow the closing 4663 * parenthesis of the static method call (e.g., it 4664 * could be a semicolon to represent the end of a 4665 * Java statement). It may be {@code null} or empty 4666 * if there should be no last line suffix. 4667 */ 4668 public void toCode(@NotNull final List<String> lineList, 4669 final int indentSpaces, 4670 @Nullable final String firstLinePrefix, 4671 @Nullable final String lastLineSuffix) 4672 { 4673 // Generate a string with the appropriate indent. 4674 final StringBuilder buffer = new StringBuilder(); 4675 for (int i = 0; i < indentSpaces; i++) 4676 { 4677 buffer.append(' '); 4678 } 4679 final String indent = buffer.toString(); 4680 4681 4682 // Start the first line, including any appropriate prefix. 4683 buffer.setLength(0); 4684 buffer.append(indent); 4685 if (firstLinePrefix != null) 4686 { 4687 buffer.append(firstLinePrefix); 4688 } 4689 4690 4691 // Figure out what type of filter it is and create the appropriate code for 4692 // that type of filter. 4693 switch (filterType) 4694 { 4695 case FILTER_TYPE_AND: 4696 case FILTER_TYPE_OR: 4697 if (filterType == FILTER_TYPE_AND) 4698 { 4699 buffer.append("Filter.createANDFilter("); 4700 } 4701 else 4702 { 4703 buffer.append("Filter.createORFilter("); 4704 } 4705 if (filterComps.length == 0) 4706 { 4707 buffer.append(')'); 4708 if (lastLineSuffix != null) 4709 { 4710 buffer.append(lastLineSuffix); 4711 } 4712 lineList.add(buffer.toString()); 4713 return; 4714 } 4715 4716 for (int i = 0; i < filterComps.length; i++) 4717 { 4718 String suffix; 4719 if (i == (filterComps.length - 1)) 4720 { 4721 suffix = ")"; 4722 if (lastLineSuffix != null) 4723 { 4724 suffix += lastLineSuffix; 4725 } 4726 } 4727 else 4728 { 4729 suffix = ","; 4730 } 4731 4732 filterComps[i].toCode(lineList, indentSpaces + 5, null, suffix); 4733 } 4734 return; 4735 4736 4737 case FILTER_TYPE_NOT: 4738 buffer.append("Filter.createNOTFilter("); 4739 lineList.add(buffer.toString()); 4740 4741 final String suffix; 4742 if (lastLineSuffix == null) 4743 { 4744 suffix = ")"; 4745 } 4746 else 4747 { 4748 suffix = ')' + lastLineSuffix; 4749 } 4750 notComp.toCode(lineList, indentSpaces + 5, null, suffix); 4751 return; 4752 4753 case FILTER_TYPE_PRESENCE: 4754 buffer.append("Filter.createPresenceFilter("); 4755 lineList.add(buffer.toString()); 4756 4757 buffer.setLength(0); 4758 buffer.append(indent); 4759 buffer.append(" \""); 4760 buffer.append(attrName); 4761 buffer.append("\")"); 4762 4763 if (lastLineSuffix != null) 4764 { 4765 buffer.append(lastLineSuffix); 4766 } 4767 4768 lineList.add(buffer.toString()); 4769 return; 4770 4771 4772 case FILTER_TYPE_EQUALITY: 4773 case FILTER_TYPE_GREATER_OR_EQUAL: 4774 case FILTER_TYPE_LESS_OR_EQUAL: 4775 case FILTER_TYPE_APPROXIMATE_MATCH: 4776 if (filterType == FILTER_TYPE_EQUALITY) 4777 { 4778 buffer.append("Filter.createEqualityFilter("); 4779 } 4780 else if (filterType == FILTER_TYPE_GREATER_OR_EQUAL) 4781 { 4782 buffer.append("Filter.createGreaterOrEqualFilter("); 4783 } 4784 else if (filterType == FILTER_TYPE_LESS_OR_EQUAL) 4785 { 4786 buffer.append("Filter.createLessOrEqualFilter("); 4787 } 4788 else 4789 { 4790 buffer.append("Filter.createApproximateMatchFilter("); 4791 } 4792 lineList.add(buffer.toString()); 4793 4794 buffer.setLength(0); 4795 buffer.append(indent); 4796 buffer.append(" \""); 4797 buffer.append(attrName); 4798 buffer.append("\","); 4799 lineList.add(buffer.toString()); 4800 4801 buffer.setLength(0); 4802 buffer.append(indent); 4803 buffer.append(" "); 4804 if (StaticUtils.isSensitiveToCodeAttribute(attrName)) 4805 { 4806 buffer.append("\"---redacted-value---\""); 4807 } 4808 else if (StaticUtils.isPrintableString(assertionValue.getValue())) 4809 { 4810 buffer.append('"'); 4811 buffer.append(assertionValue.stringValue()); 4812 buffer.append('"'); 4813 } 4814 else 4815 { 4816 StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer); 4817 } 4818 4819 buffer.append(')'); 4820 4821 if (lastLineSuffix != null) 4822 { 4823 buffer.append(lastLineSuffix); 4824 } 4825 4826 lineList.add(buffer.toString()); 4827 return; 4828 4829 4830 case FILTER_TYPE_SUBSTRING: 4831 buffer.append("Filter.createSubstringFilter("); 4832 lineList.add(buffer.toString()); 4833 4834 buffer.setLength(0); 4835 buffer.append(indent); 4836 buffer.append(" \""); 4837 buffer.append(attrName); 4838 buffer.append("\","); 4839 lineList.add(buffer.toString()); 4840 4841 final boolean isRedacted = 4842 StaticUtils.isSensitiveToCodeAttribute(attrName); 4843 boolean isPrintable = true; 4844 if (subInitial != null) 4845 { 4846 isPrintable = StaticUtils.isPrintableString(subInitial.getValue()); 4847 } 4848 4849 if (isPrintable && (subAny != null)) 4850 { 4851 for (final ASN1OctetString s : subAny) 4852 { 4853 if (! StaticUtils.isPrintableString(s.getValue())) 4854 { 4855 isPrintable = false; 4856 break; 4857 } 4858 } 4859 } 4860 4861 if (isPrintable && (subFinal != null)) 4862 { 4863 isPrintable = StaticUtils.isPrintableString(subFinal.getValue()); 4864 } 4865 4866 buffer.setLength(0); 4867 buffer.append(indent); 4868 buffer.append(" "); 4869 if (subInitial == null) 4870 { 4871 buffer.append("null"); 4872 } 4873 else if (isRedacted) 4874 { 4875 buffer.append("\"---redacted-subInitial---\""); 4876 } 4877 else if (isPrintable) 4878 { 4879 buffer.append('"'); 4880 buffer.append(subInitial.stringValue()); 4881 buffer.append('"'); 4882 } 4883 else 4884 { 4885 StaticUtils.byteArrayToCode(subInitial.getValue(), buffer); 4886 } 4887 buffer.append(','); 4888 lineList.add(buffer.toString()); 4889 4890 buffer.setLength(0); 4891 buffer.append(indent); 4892 buffer.append(" "); 4893 if ((subAny == null) || (subAny.length == 0)) 4894 { 4895 buffer.append("null,"); 4896 lineList.add(buffer.toString()); 4897 } 4898 else if (isRedacted) 4899 { 4900 buffer.append("new String[]"); 4901 lineList.add(buffer.toString()); 4902 4903 lineList.add(indent + " {"); 4904 4905 for (int i=0; i < subAny.length; i++) 4906 { 4907 buffer.setLength(0); 4908 buffer.append(indent); 4909 buffer.append(" \"---redacted-subAny-"); 4910 buffer.append(i+1); 4911 buffer.append("---\""); 4912 if (i < (subAny.length-1)) 4913 { 4914 buffer.append(','); 4915 } 4916 lineList.add(buffer.toString()); 4917 } 4918 4919 lineList.add(indent + " },"); 4920 } 4921 else if (isPrintable) 4922 { 4923 buffer.append("new String[]"); 4924 lineList.add(buffer.toString()); 4925 4926 lineList.add(indent + " {"); 4927 4928 for (int i=0; i < subAny.length; i++) 4929 { 4930 buffer.setLength(0); 4931 buffer.append(indent); 4932 buffer.append(" \""); 4933 buffer.append(subAny[i].stringValue()); 4934 buffer.append('"'); 4935 if (i < (subAny.length-1)) 4936 { 4937 buffer.append(','); 4938 } 4939 lineList.add(buffer.toString()); 4940 } 4941 4942 lineList.add(indent + " },"); 4943 } 4944 else 4945 { 4946 buffer.append("new String[]"); 4947 lineList.add(buffer.toString()); 4948 4949 lineList.add(indent + " {"); 4950 4951 for (int i=0; i < subAny.length; i++) 4952 { 4953 buffer.setLength(0); 4954 buffer.append(indent); 4955 buffer.append(" "); 4956 StaticUtils.byteArrayToCode(subAny[i].getValue(), buffer); 4957 if (i < (subAny.length-1)) 4958 { 4959 buffer.append(','); 4960 } 4961 lineList.add(buffer.toString()); 4962 } 4963 4964 lineList.add(indent + " },"); 4965 } 4966 4967 buffer.setLength(0); 4968 buffer.append(indent); 4969 buffer.append(" "); 4970 if (subFinal == null) 4971 { 4972 buffer.append("null)"); 4973 } 4974 else if (isRedacted) 4975 { 4976 buffer.append("\"---redacted-subFinal---\")"); 4977 } 4978 else if (isPrintable) 4979 { 4980 buffer.append('"'); 4981 buffer.append(subFinal.stringValue()); 4982 buffer.append("\")"); 4983 } 4984 else 4985 { 4986 StaticUtils.byteArrayToCode(subFinal.getValue(), buffer); 4987 buffer.append(')'); 4988 } 4989 if (lastLineSuffix != null) 4990 { 4991 buffer.append(lastLineSuffix); 4992 } 4993 lineList.add(buffer.toString()); 4994 return; 4995 4996 4997 case FILTER_TYPE_EXTENSIBLE_MATCH: 4998 buffer.append("Filter.createExtensibleMatchFilter("); 4999 lineList.add(buffer.toString()); 5000 5001 buffer.setLength(0); 5002 buffer.append(indent); 5003 buffer.append(" "); 5004 if (attrName == null) 5005 { 5006 buffer.append("null, // Attribute Description"); 5007 } 5008 else 5009 { 5010 buffer.append('"'); 5011 buffer.append(attrName); 5012 buffer.append("\","); 5013 } 5014 lineList.add(buffer.toString()); 5015 5016 buffer.setLength(0); 5017 buffer.append(indent); 5018 buffer.append(" "); 5019 if (matchingRuleID == null) 5020 { 5021 buffer.append("null, // Matching Rule ID"); 5022 } 5023 else 5024 { 5025 buffer.append('"'); 5026 buffer.append(matchingRuleID); 5027 buffer.append("\","); 5028 } 5029 lineList.add(buffer.toString()); 5030 5031 buffer.setLength(0); 5032 buffer.append(indent); 5033 buffer.append(" "); 5034 buffer.append(dnAttributes); 5035 buffer.append(", // DN Attributes"); 5036 lineList.add(buffer.toString()); 5037 5038 buffer.setLength(0); 5039 buffer.append(indent); 5040 buffer.append(" "); 5041 if ((attrName != null) && 5042 StaticUtils.isSensitiveToCodeAttribute(attrName)) 5043 { 5044 buffer.append("\"---redacted-value---\")"); 5045 } 5046 else 5047 { 5048 if (StaticUtils.isPrintableString(assertionValue.getValue())) 5049 { 5050 buffer.append('"'); 5051 buffer.append(assertionValue.stringValue()); 5052 buffer.append("\")"); 5053 } 5054 else 5055 { 5056 StaticUtils.byteArrayToCode(assertionValue.getValue(), buffer); 5057 buffer.append(')'); 5058 } 5059 } 5060 5061 if (lastLineSuffix != null) 5062 { 5063 buffer.append(lastLineSuffix); 5064 } 5065 lineList.add(buffer.toString()); 5066 return; 5067 } 5068 } 5069}