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