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