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