001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.protocols.ldap;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashSet;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.StringTokenizer;
025
026import org.forgerock.i18n.LocalizableMessage;
027import org.forgerock.i18n.slf4j.LocalizedLogger;
028import org.forgerock.opendj.ldap.ByteString;
029import org.forgerock.opendj.ldap.ByteStringBuilder;
030import org.forgerock.opendj.ldap.ResultCode;
031import org.forgerock.opendj.ldap.schema.AttributeType;
032import org.forgerock.opendj.ldap.schema.MatchingRule;
033import org.opends.server.core.DirectoryServer;
034import org.opends.server.types.DirectoryException;
035import org.opends.server.types.FilterType;
036import org.opends.server.types.LDAPException;
037import org.opends.server.types.RawFilter;
038import org.opends.server.types.SearchFilter;
039
040import static org.opends.messages.ProtocolMessages.*;
041import static org.opends.server.util.StaticUtils.*;
042
043/**
044 * This class defines the data structures and methods to use when interacting
045 * with an LDAP search filter, which defines a set of criteria for locating
046 * entries in a search request.
047 */
048public class LDAPFilter
049       extends RawFilter
050{
051  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
052
053  private static LDAPFilter objectClassPresent;
054
055  /** The set of subAny elements for substring filters. */
056  private ArrayList<ByteString> subAnyElements;
057
058  /** The set of filter components for AND and OR filters. */
059  private ArrayList<RawFilter> filterComponents;
060
061  /** Indicates whether to match on DN attributes for extensible match filters. */
062  private boolean dnAttributes;
063
064  /** The assertion value for several filter types. */
065  private ByteString assertionValue;
066
067  /** The subFinal element for substring filters. */
068  private ByteString subFinalElement;
069
070  /** The subInitial element for substring filters. */
071  private ByteString subInitialElement;
072
073  /** The filter type for this filter. */
074  private FilterType filterType;
075
076  /** The filter component for NOT filters. */
077  private RawFilter notComponent;
078
079  /** The attribute type for several filter types. */
080  private String attributeType;
081
082  /** The matching rule ID for extensible matching filters. */
083  private String matchingRuleID;
084
085
086
087  /**
088   * Creates a new LDAP filter with the provided information.  Note that this
089   * constructor is only intended for use by the {@code RawFilter} class and any
090   * use of this constructor outside of that class must be very careful to
091   * ensure that all of the appropriate element types have been provided for the
092   * associated filter type.
093   *
094   * @param  filterType         The filter type for this filter.
095   * @param  filterComponents   The filter components for AND and OR filters.
096   * @param  notComponent       The filter component for NOT filters.
097   * @param  attributeType      The attribute type for this filter.
098   * @param  assertionValue     The assertion value for this filter.
099   * @param  subInitialElement  The subInitial element for substring filters.
100   * @param  subAnyElements     The subAny elements for substring filters.
101   * @param  subFinalElement    The subFinal element for substring filters.
102   * @param  matchingRuleID     The matching rule ID for extensible filters.
103   * @param  dnAttributes       The dnAttributes flag for extensible filters.
104   */
105  public LDAPFilter(FilterType filterType,
106                    ArrayList<RawFilter> filterComponents,
107                    RawFilter notComponent, String attributeType,
108                    ByteString assertionValue, ByteString subInitialElement,
109                    ArrayList<ByteString> subAnyElements,
110                    ByteString subFinalElement, String matchingRuleID,
111                    boolean dnAttributes)
112  {
113    this.filterType        = filterType;
114    this.filterComponents  = filterComponents;
115    this.notComponent      = notComponent;
116    this.attributeType     = attributeType;
117    this.assertionValue    = assertionValue;
118    this.subInitialElement = subInitialElement;
119    this.subAnyElements    = subAnyElements;
120    this.subFinalElement   = subFinalElement;
121    this.matchingRuleID    = matchingRuleID;
122    this.dnAttributes      = dnAttributes;
123  }
124
125
126
127  /**
128   * Creates a new LDAP filter from the provided search filter.
129   *
130   * @param  filter  The search filter to use to create this LDAP filter.
131   */
132  public LDAPFilter(SearchFilter filter)
133  {
134    this.filterType = filter.getFilterType();
135
136    switch (filterType)
137    {
138      case AND:
139      case OR:
140        Collection<SearchFilter> comps = filter.getFilterComponents();
141        filterComponents = new ArrayList<>(comps.size());
142        for (SearchFilter f : comps)
143        {
144          filterComponents.add(new LDAPFilter(f));
145        }
146
147        notComponent      = null;
148        attributeType     = null;
149        assertionValue    = null;
150        subInitialElement = null;
151        subAnyElements    = null;
152        subFinalElement   = null;
153        matchingRuleID    = null;
154        dnAttributes      = false;
155        break;
156      case NOT:
157        notComponent = new LDAPFilter(filter.getNotComponent());
158
159        filterComponents  = null;
160        attributeType     = null;
161        assertionValue    = null;
162        subInitialElement = null;
163        subAnyElements    = null;
164        subFinalElement   = null;
165        matchingRuleID    = null;
166        dnAttributes      = false;
167        break;
168      case EQUALITY:
169      case GREATER_OR_EQUAL:
170      case LESS_OR_EQUAL:
171      case APPROXIMATE_MATCH:
172        attributeType  = filter.getAttributeType().getNameOrOID();
173        assertionValue = filter.getAssertionValue();
174
175        filterComponents  = null;
176        notComponent      = null;
177        subInitialElement = null;
178        subAnyElements    = null;
179        subFinalElement   = null;
180        matchingRuleID    = null;
181        dnAttributes      = false;
182        break;
183      case SUBSTRING:
184        attributeType  = filter.getAttributeType().getNameOrOID();
185
186        ByteString bs = filter.getSubInitialElement();
187        if (bs == null)
188        {
189          subInitialElement = null;
190        }
191        else
192        {
193          subInitialElement = bs;
194        }
195
196        bs = filter.getSubFinalElement();
197        if (bs == null)
198        {
199          subFinalElement = null;
200        }
201        else
202        {
203          subFinalElement = bs;
204        }
205
206        List<ByteString> subAnyStrings = filter.getSubAnyElements();
207        if (subAnyStrings == null)
208        {
209          subAnyElements = null;
210        }
211        else
212        {
213          subAnyElements = new ArrayList<>(subAnyStrings);
214        }
215
216        filterComponents  = null;
217        notComponent      = null;
218        assertionValue    = null;
219        matchingRuleID    = null;
220        dnAttributes      = false;
221        break;
222      case PRESENT:
223        attributeType  = filter.getAttributeType().getNameOrOID();
224
225        filterComponents  = null;
226        notComponent      = null;
227        assertionValue    = null;
228        subInitialElement = null;
229        subAnyElements    = null;
230        subFinalElement   = null;
231        matchingRuleID    = null;
232        dnAttributes      = false;
233        break;
234      case EXTENSIBLE_MATCH:
235        dnAttributes   = filter.getDNAttributes();
236        matchingRuleID = filter.getMatchingRuleID();
237
238        AttributeType attrType = filter.getAttributeType();
239        if (attrType == null)
240        {
241          attributeType = null;
242        }
243        else
244        {
245          attributeType = attrType.getNameOrOID();
246        }
247
248        assertionValue    = filter.getAssertionValue();
249        filterComponents  = null;
250        notComponent      = null;
251        subInitialElement = null;
252        subAnyElements    = null;
253        subFinalElement   = null;
254        break;
255    }
256  }
257
258
259
260  /**
261   * Decodes the provided string into an LDAP search filter.
262   *
263   * @param  filterString  The string representation of the search filter to
264   *                       decode.
265   *
266   * @return  The decoded LDAP search filter.
267   *
268   * @throws  LDAPException  If the provided string does not represent a valid
269   *                         LDAP search filter.
270   */
271  public static LDAPFilter decode(String filterString)
272         throws LDAPException
273  {
274    if (filterString == null)
275    {
276      LocalizableMessage message = ERR_LDAP_FILTER_STRING_NULL.get();
277      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
278    }
279
280
281    try
282    {
283      return decode(filterString, 0, filterString.length());
284    }
285    catch (LDAPException le)
286    {
287      logger.traceException(le);
288
289      throw le;
290    }
291    catch (Exception e)
292    {
293      logger.traceException(e);
294
295      LocalizableMessage message = ERR_LDAP_FILTER_UNCAUGHT_EXCEPTION.get(filterString, e);
296      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message, e);
297    }
298  }
299
300
301
302  /**
303   * Decodes the provided string into an LDAP search filter.
304   *
305   * @param  filterString  The string representation of the search filter to
306   *                       decode.
307   * @param  startPos      The position of the first character in the filter
308   *                       to parse.
309   * @param  endPos        The position of the first character after the end of
310   *                       the filter to parse.
311   *
312   * @return  The decoded LDAP search filter.
313   *
314   * @throws  LDAPException  If the provided string does not represent a valid
315   *                         LDAP search filter.
316   */
317  private static LDAPFilter decode(String filterString, int startPos,
318                                   int endPos)
319          throws LDAPException
320  {
321    // Make sure that the length is sufficient for a valid search filter.
322    int length = endPos - startPos;
323    if (length <= 0)
324    {
325      LocalizableMessage message = ERR_LDAP_FILTER_STRING_NULL.get();
326      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
327    }
328
329    // If the filter is enclosed in a pair of apostrophes ("single-quotes") it
330    // is invalid (issue #1024).
331    if (1 < filterString.length()
332         && filterString.startsWith("'") && filterString.endsWith("'"))
333    {
334      LocalizableMessage message =
335          ERR_LDAP_FILTER_ENCLOSED_IN_APOSTROPHES.get(filterString);
336      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
337    }
338
339    // If the filter is surrounded by parentheses (which it should be), then
340    // strip them off.
341    if (filterString.charAt(startPos) == '(')
342    {
343      if (filterString.charAt(endPos-1) == ')')
344      {
345        startPos++;
346        endPos--;
347      }
348      else
349      {
350        LocalizableMessage message = ERR_LDAP_FILTER_MISMATCHED_PARENTHESES.get(
351            filterString, startPos, endPos);
352        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
353      }
354    }
355
356
357    // Look at the first character.  If it is a '&' then it is an AND search.
358    // If it is a '|' then it is an OR search.  If it is a '!' then it is a NOT
359    // search.
360    char c = filterString.charAt(startPos);
361    if (c == '&')
362    {
363      return decodeCompoundFilter(FilterType.AND, filterString, startPos+1,
364                                  endPos);
365    }
366    else if (c == '|')
367    {
368      return decodeCompoundFilter(FilterType.OR, filterString, startPos+1,
369                                  endPos);
370    }
371    else if (c == '!')
372    {
373      return decodeCompoundFilter(FilterType.NOT, filterString, startPos+1,
374                                  endPos);
375    }
376
377
378    // If we've gotten here, then it must be a simple filter.  It must have an
379    // equal sign at some point, so find it.
380    int equalPos = -1;
381    for (int i=startPos; i < endPos; i++)
382    {
383      if (filterString.charAt(i) == '=')
384      {
385        equalPos = i;
386        break;
387      }
388    }
389
390    if (equalPos <= startPos)
391    {
392      LocalizableMessage message =
393          ERR_LDAP_FILTER_NO_EQUAL_SIGN.get(filterString, startPos, endPos);
394      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
395    }
396
397
398    // Look at the character immediately before the equal sign, because it may
399    // help determine the filter type.
400    int attrEndPos;
401    FilterType filterType;
402    switch (filterString.charAt(equalPos-1))
403    {
404      case '~':
405        filterType = FilterType.APPROXIMATE_MATCH;
406        attrEndPos = equalPos-1;
407        break;
408      case '>':
409        filterType = FilterType.GREATER_OR_EQUAL;
410        attrEndPos = equalPos-1;
411        break;
412      case '<':
413        filterType = FilterType.LESS_OR_EQUAL;
414        attrEndPos = equalPos-1;
415        break;
416      case ':':
417        return decodeExtensibleMatchFilter(filterString, startPos, equalPos,
418                                           endPos);
419      default:
420        filterType = FilterType.EQUALITY;
421        attrEndPos = equalPos;
422        break;
423    }
424
425
426    // The part of the filter string before the equal sign should be the
427    // attribute type.  Make sure that the characters it contains are acceptable
428    // for attribute types, including those allowed by attribute name
429    // exceptions (ASCII letters and digits, the dash, and the underscore).  We
430    // also need to allow attribute options, which includes the semicolon and
431    // the equal sign.
432    String attrType = filterString.substring(startPos, attrEndPos);
433    for (int i=0; i < attrType.length(); i++)
434    {
435      switch (attrType.charAt(i))
436      {
437        case '-':
438        case '0':
439        case '1':
440        case '2':
441        case '3':
442        case '4':
443        case '5':
444        case '6':
445        case '7':
446        case '8':
447        case '9':
448        case ';':
449        case '=':
450        case 'A':
451        case 'B':
452        case 'C':
453        case 'D':
454        case 'E':
455        case 'F':
456        case 'G':
457        case 'H':
458        case 'I':
459        case 'J':
460        case 'K':
461        case 'L':
462        case 'M':
463        case 'N':
464        case 'O':
465        case 'P':
466        case 'Q':
467        case 'R':
468        case 'S':
469        case 'T':
470        case 'U':
471        case 'V':
472        case 'W':
473        case 'X':
474        case 'Y':
475        case 'Z':
476        case '_':
477        case 'a':
478        case 'b':
479        case 'c':
480        case 'd':
481        case 'e':
482        case 'f':
483        case 'g':
484        case 'h':
485        case 'i':
486        case 'j':
487        case 'k':
488        case 'l':
489        case 'm':
490        case 'n':
491        case 'o':
492        case 'p':
493        case 'q':
494        case 'r':
495        case 's':
496        case 't':
497        case 'u':
498        case 'v':
499        case 'w':
500        case 'x':
501        case 'y':
502        case 'z':
503          // These are all OK.
504          break;
505
506        case '.':
507        case '/':
508        case ':':
509        case '<':
510        case '>':
511        case '?':
512        case '@':
513        case '[':
514        case '\\':
515        case ']':
516        case '^':
517        case '`':
518          // These are not allowed, but they are explicitly called out because
519          // they are included in the range of values between '-' and 'z', and
520          // making sure all possible characters are included can help make the
521          // switch statement more efficient.  We'll fall through to the default
522          // clause to reject them.
523        default:
524          LocalizableMessage message = ERR_LDAP_FILTER_INVALID_CHAR_IN_ATTR_TYPE.get(
525              attrType, attrType.charAt(i), i);
526          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
527      }
528    }
529
530
531    // Get the attribute value.
532    String valueStr = filterString.substring(equalPos+1, endPos);
533    if (valueStr.length() == 0)
534    {
535      return new LDAPFilter(filterType, null, null, attrType,
536                            ByteString.empty(), null, null, null, null,
537                            false);
538    }
539    else if (valueStr.equals("*"))
540    {
541      return new LDAPFilter(FilterType.PRESENT, null, null, attrType, null,
542                            null, null, null, null, false);
543    }
544    else if (valueStr.indexOf('*') >= 0)
545    {
546      return decodeSubstringFilter(filterString, attrType, equalPos, endPos);
547    }
548    else
549    {
550      boolean hasEscape = false;
551      byte[] valueBytes = getBytes(valueStr);
552      for (byte valueByte : valueBytes)
553      {
554        if (valueByte == 0x5C) // The backslash character
555        {
556          hasEscape = true;
557          break;
558        }
559      }
560
561      ByteString value;
562      if (hasEscape)
563      {
564        ByteStringBuilder valueBuffer =
565            new ByteStringBuilder(valueStr.length());
566        for (int i=0; i < valueBytes.length; i++)
567        {
568          if (valueBytes[i] == 0x5C) // The backslash character
569          {
570            // The next two bytes must be the hex characters that comprise the
571            // binary value.
572            if (i + 2 >= valueBytes.length)
573            {
574              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
575                  filterString, equalPos+i+1);
576              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
577            }
578
579            byte byteValue = 0;
580            switch (valueBytes[++i])
581            {
582              case 0x30: // '0'
583                break;
584              case 0x31: // '1'
585                byteValue = (byte) 0x10;
586                break;
587              case 0x32: // '2'
588                byteValue = (byte) 0x20;
589                break;
590              case 0x33: // '3'
591                byteValue = (byte) 0x30;
592                break;
593              case 0x34: // '4'
594                byteValue = (byte) 0x40;
595                break;
596              case 0x35: // '5'
597                byteValue = (byte) 0x50;
598                break;
599              case 0x36: // '6'
600                byteValue = (byte) 0x60;
601                break;
602              case 0x37: // '7'
603                byteValue = (byte) 0x70;
604                break;
605              case 0x38: // '8'
606                byteValue = (byte) 0x80;
607                break;
608              case 0x39: // '9'
609                byteValue = (byte) 0x90;
610                break;
611              case 0x41: // 'A'
612              case 0x61: // 'a'
613                byteValue = (byte) 0xA0;
614                break;
615              case 0x42: // 'B'
616              case 0x62: // 'b'
617                byteValue = (byte) 0xB0;
618                break;
619              case 0x43: // 'C'
620              case 0x63: // 'c'
621                byteValue = (byte) 0xC0;
622                break;
623              case 0x44: // 'D'
624              case 0x64: // 'd'
625                byteValue = (byte) 0xD0;
626                break;
627              case 0x45: // 'E'
628              case 0x65: // 'e'
629                byteValue = (byte) 0xE0;
630                break;
631              case 0x46: // 'F'
632              case 0x66: // 'f'
633                byteValue = (byte) 0xF0;
634                break;
635              default:
636                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
637                    filterString, equalPos+i+1);
638                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
639            }
640
641            switch (valueBytes[++i])
642            {
643              case 0x30: // '0'
644                break;
645              case 0x31: // '1'
646                byteValue |= (byte) 0x01;
647                break;
648              case 0x32: // '2'
649                byteValue |= (byte) 0x02;
650                break;
651              case 0x33: // '3'
652                byteValue |= (byte) 0x03;
653                break;
654              case 0x34: // '4'
655                byteValue |= (byte) 0x04;
656                break;
657              case 0x35: // '5'
658                byteValue |= (byte) 0x05;
659                break;
660              case 0x36: // '6'
661                byteValue |= (byte) 0x06;
662                break;
663              case 0x37: // '7'
664                byteValue |= (byte) 0x07;
665                break;
666              case 0x38: // '8'
667                byteValue |= (byte) 0x08;
668                break;
669              case 0x39: // '9'
670                byteValue |= (byte) 0x09;
671                break;
672              case 0x41: // 'A'
673              case 0x61: // 'a'
674                byteValue |= (byte) 0x0A;
675                break;
676              case 0x42: // 'B'
677              case 0x62: // 'b'
678                byteValue |= (byte) 0x0B;
679                break;
680              case 0x43: // 'C'
681              case 0x63: // 'c'
682                byteValue |= (byte) 0x0C;
683                break;
684              case 0x44: // 'D'
685              case 0x64: // 'd'
686                byteValue |= (byte) 0x0D;
687                break;
688              case 0x45: // 'E'
689              case 0x65: // 'e'
690                byteValue |= (byte) 0x0E;
691                break;
692              case 0x46: // 'F'
693              case 0x66: // 'f'
694                byteValue |= (byte) 0x0F;
695                break;
696              default:
697                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
698                    filterString, equalPos+i+1);
699                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
700            }
701
702            valueBuffer.appendByte(byteValue);
703          }
704          else
705          {
706            valueBuffer.appendByte(valueBytes[i]);
707          }
708        }
709
710        value = valueBuffer.toByteString();
711      }
712      else
713      {
714        value = ByteString.wrap(valueBytes);
715      }
716
717      return new LDAPFilter(filterType, null, null, attrType, value, null, null,
718                            null, null, false);
719    }
720  }
721
722
723
724  /**
725   * Decodes a set of filters from the provided filter string within the
726   * indicated range.
727   *
728   * @param  filterType    The filter type for this compound filter.  It must be
729   *                       an AND, OR or NOT filter.
730   * @param  filterString  The string containing the filter information to
731   *                       decode.
732   * @param  startPos      The position of the first character in the set of
733   *                       filters to decode.
734   * @param  endPos        The position of the first character after the end of
735   *                       the set of filters to decode.
736   *
737   * @return  The decoded LDAP filter.
738   *
739   * @throws  LDAPException  If a problem occurs while attempting to decode the
740   *                         compound filter.
741   */
742  private static LDAPFilter decodeCompoundFilter(FilterType filterType,
743                                                 String filterString,
744                                                 int startPos, int endPos)
745          throws LDAPException
746  {
747    // Create a list to hold the returned components.
748    ArrayList<RawFilter> filterComponents = new ArrayList<>();
749
750
751    // If the end pos is equal to the start pos, then there are no components.
752    if (startPos == endPos)
753    {
754      if (filterType == FilterType.NOT)
755      {
756        LocalizableMessage message =
757            ERR_LDAP_FILTER_NOT_EXACTLY_ONE.get(filterString, startPos, endPos);
758        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
759      }
760      else
761      {
762        // This is valid and will be treated as a TRUE/FALSE filter.
763        return new LDAPFilter(filterType, filterComponents, null, null, null,
764                              null, null, null, null, false);
765      }
766    }
767
768
769    // The first and last characters must be parentheses.  If not, then that's
770    // an error.
771    if (filterString.charAt(startPos) != '(' ||
772        filterString.charAt(endPos-1) != ')')
773    {
774      LocalizableMessage message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES.get(
775          filterString, startPos, endPos);
776      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
777    }
778
779
780    // Iterate through the characters in the value.  Whenever an open
781    // parenthesis is found, locate the corresponding close parenthesis by
782    // counting the number of intermediate open/close parentheses.
783    int pendingOpens = 0;
784    int openPos = -1;
785    for (int i=startPos; i < endPos; i++)
786    {
787      char c = filterString.charAt(i);
788      if (c == '(')
789      {
790        if (openPos < 0)
791        {
792          openPos = i;
793        }
794
795        pendingOpens++;
796      }
797      else if (c == ')')
798      {
799        pendingOpens--;
800        if (pendingOpens == 0)
801        {
802          filterComponents.add(decode(filterString, openPos, i+1));
803          openPos = -1;
804        }
805        else if (pendingOpens < 0)
806        {
807          LocalizableMessage message = ERR_LDAP_FILTER_NO_CORRESPONDING_OPEN_PARENTHESIS.
808              get(filterString, i);
809          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
810        }
811      }
812      else if (pendingOpens <= 0)
813      {
814        LocalizableMessage message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES.get(
815            filterString, startPos, endPos);
816        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
817      }
818    }
819
820
821    // At this point, we have parsed the entire set of filter components.  The
822    // list of open parenthesis positions must be empty.
823    if (pendingOpens != 0)
824    {
825      LocalizableMessage message = ERR_LDAP_FILTER_NO_CORRESPONDING_CLOSE_PARENTHESIS.get(
826          filterString, openPos);
827      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
828    }
829
830
831    // We should have everything we need, so return the list.
832    if (filterType == FilterType.NOT)
833    {
834      if (filterComponents.size() != 1)
835      {
836        LocalizableMessage message =
837            ERR_LDAP_FILTER_NOT_EXACTLY_ONE.get(filterString, startPos, endPos);
838        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
839      }
840      RawFilter notComponent = filterComponents.get(0);
841      return new LDAPFilter(filterType, null, notComponent, null, null,
842                            null, null, null, null, false);
843    }
844    else
845    {
846      return new LDAPFilter(filterType, filterComponents, null, null, null,
847                            null, null, null, null, false);
848    }
849  }
850
851
852
853  /**
854   * Decodes a substring search filter component based on the provided
855   * information.
856   *
857   * @param  filterString  The filter string containing the information to
858   *                       decode.
859   * @param  attrType      The attribute type for this substring filter
860   *                       component.
861   * @param  equalPos      The location of the equal sign separating the
862   *                       attribute type from the value.
863   * @param  endPos        The position of the first character after the end of
864   *                       the substring value.
865   *
866   * @return  The decoded LDAP filter.
867   *
868   * @throws  LDAPException  If a problem occurs while attempting to decode the
869   *                         substring filter.
870   */
871  private static LDAPFilter decodeSubstringFilter(String filterString,
872                                                  String attrType, int equalPos,
873                                                  int endPos)
874          throws LDAPException
875  {
876    // Get a binary representation of the value.
877    byte[] valueBytes = getBytes(filterString.substring(equalPos+1, endPos));
878
879
880    // Find the locations of all the asterisks in the value.  Also, check to
881    // see if there are any escaped values, since they will need special
882    // treatment.
883    boolean hasEscape = false;
884    LinkedList<Integer> asteriskPositions = new LinkedList<>();
885    for (int i=0; i < valueBytes.length; i++)
886    {
887      if (valueBytes[i] == 0x2A) // The asterisk.
888      {
889        asteriskPositions.add(i);
890      }
891      else if (valueBytes[i] == 0x5C) // The backslash.
892      {
893        hasEscape = true;
894      }
895    }
896
897
898    // If there were no asterisks, then this isn't a substring filter.
899    if (asteriskPositions.isEmpty())
900    {
901      LocalizableMessage message = ERR_LDAP_FILTER_SUBSTRING_NO_ASTERISKS.get(
902          filterString, equalPos+1, endPos);
903      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
904    }
905
906
907    // If the value starts with an asterisk, then there is no subInitial
908    // component.  Otherwise, parse out the subInitial.
909    ByteString subInitial;
910    int firstPos = asteriskPositions.removeFirst();
911    if (firstPos == 0)
912    {
913      subInitial = null;
914    }
915    else
916    {
917      if (hasEscape)
918      {
919        ByteStringBuilder buffer = new ByteStringBuilder(firstPos);
920        for (int i=0; i < firstPos; i++)
921        {
922          if (valueBytes[i] == 0x5C)
923          {
924            // The next two bytes must be the hex characters that comprise the
925            // binary value.
926            if (i + 2 >= valueBytes.length)
927            {
928              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
929                  filterString, equalPos+i+1);
930              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
931            }
932
933            byte byteValue = 0;
934            switch (valueBytes[++i])
935            {
936              case 0x30: // '0'
937                break;
938              case 0x31: // '1'
939                byteValue = (byte) 0x10;
940                break;
941              case 0x32: // '2'
942                byteValue = (byte) 0x20;
943                break;
944              case 0x33: // '3'
945                byteValue = (byte) 0x30;
946                break;
947              case 0x34: // '4'
948                byteValue = (byte) 0x40;
949                break;
950              case 0x35: // '5'
951                byteValue = (byte) 0x50;
952                break;
953              case 0x36: // '6'
954                byteValue = (byte) 0x60;
955                break;
956              case 0x37: // '7'
957                byteValue = (byte) 0x70;
958                break;
959              case 0x38: // '8'
960                byteValue = (byte) 0x80;
961                break;
962              case 0x39: // '9'
963                byteValue = (byte) 0x90;
964                break;
965              case 0x41: // 'A'
966              case 0x61: // 'a'
967                byteValue = (byte) 0xA0;
968                break;
969              case 0x42: // 'B'
970              case 0x62: // 'b'
971                byteValue = (byte) 0xB0;
972                break;
973              case 0x43: // 'C'
974              case 0x63: // 'c'
975                byteValue = (byte) 0xC0;
976                break;
977              case 0x44: // 'D'
978              case 0x64: // 'd'
979                byteValue = (byte) 0xD0;
980                break;
981              case 0x45: // 'E'
982              case 0x65: // 'e'
983                byteValue = (byte) 0xE0;
984                break;
985              case 0x46: // 'F'
986              case 0x66: // 'f'
987                byteValue = (byte) 0xF0;
988                break;
989              default:
990                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
991                    filterString, equalPos+i+1);
992                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
993            }
994
995            switch (valueBytes[++i])
996            {
997              case 0x30: // '0'
998                break;
999              case 0x31: // '1'
1000                byteValue |= (byte) 0x01;
1001                break;
1002              case 0x32: // '2'
1003                byteValue |= (byte) 0x02;
1004                break;
1005              case 0x33: // '3'
1006                byteValue |= (byte) 0x03;
1007                break;
1008              case 0x34: // '4'
1009                byteValue |= (byte) 0x04;
1010                break;
1011              case 0x35: // '5'
1012                byteValue |= (byte) 0x05;
1013                break;
1014              case 0x36: // '6'
1015                byteValue |= (byte) 0x06;
1016                break;
1017              case 0x37: // '7'
1018                byteValue |= (byte) 0x07;
1019                break;
1020              case 0x38: // '8'
1021                byteValue |= (byte) 0x08;
1022                break;
1023              case 0x39: // '9'
1024                byteValue |= (byte) 0x09;
1025                break;
1026              case 0x41: // 'A'
1027              case 0x61: // 'a'
1028                byteValue |= (byte) 0x0A;
1029                break;
1030              case 0x42: // 'B'
1031              case 0x62: // 'b'
1032                byteValue |= (byte) 0x0B;
1033                break;
1034              case 0x43: // 'C'
1035              case 0x63: // 'c'
1036                byteValue |= (byte) 0x0C;
1037                break;
1038              case 0x44: // 'D'
1039              case 0x64: // 'd'
1040                byteValue |= (byte) 0x0D;
1041                break;
1042              case 0x45: // 'E'
1043              case 0x65: // 'e'
1044                byteValue |= (byte) 0x0E;
1045                break;
1046              case 0x46: // 'F'
1047              case 0x66: // 'f'
1048                byteValue |= (byte) 0x0F;
1049                break;
1050              default:
1051                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1052                    filterString, equalPos+i+1);
1053                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1054            }
1055
1056            buffer.appendByte(byteValue);
1057          }
1058          else
1059          {
1060            buffer.appendByte(valueBytes[i]);
1061          }
1062        }
1063
1064        subInitial = buffer.toByteString();
1065      }
1066      else
1067      {
1068        subInitial = ByteString.wrap(valueBytes, 0, firstPos);
1069      }
1070    }
1071
1072
1073    // Next, process through the rest of the asterisks to get the subAny values.
1074    ArrayList<ByteString> subAny = new ArrayList<>();
1075    for (int asteriskPos : asteriskPositions)
1076    {
1077      int length = asteriskPos - firstPos - 1;
1078
1079      if (hasEscape)
1080      {
1081        ByteStringBuilder buffer = new ByteStringBuilder(length);
1082        for (int i=firstPos+1; i < asteriskPos; i++)
1083        {
1084          if (valueBytes[i] == 0x5C)
1085          {
1086            // The next two bytes must be the hex characters that comprise the
1087            // binary value.
1088            if (i + 2 >= valueBytes.length)
1089            {
1090              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1091                  filterString, equalPos+i+1);
1092              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1093            }
1094
1095            byte byteValue = 0;
1096            switch (valueBytes[++i])
1097            {
1098              case 0x30: // '0'
1099                break;
1100              case 0x31: // '1'
1101                byteValue = (byte) 0x10;
1102                break;
1103              case 0x32: // '2'
1104                byteValue = (byte) 0x20;
1105                break;
1106              case 0x33: // '3'
1107                byteValue = (byte) 0x30;
1108                break;
1109              case 0x34: // '4'
1110                byteValue = (byte) 0x40;
1111                break;
1112              case 0x35: // '5'
1113                byteValue = (byte) 0x50;
1114                break;
1115              case 0x36: // '6'
1116                byteValue = (byte) 0x60;
1117                break;
1118              case 0x37: // '7'
1119                byteValue = (byte) 0x70;
1120                break;
1121              case 0x38: // '8'
1122                byteValue = (byte) 0x80;
1123                break;
1124              case 0x39: // '9'
1125                byteValue = (byte) 0x90;
1126                break;
1127              case 0x41: // 'A'
1128              case 0x61: // 'a'
1129                byteValue = (byte) 0xA0;
1130                break;
1131              case 0x42: // 'B'
1132              case 0x62: // 'b'
1133                byteValue = (byte) 0xB0;
1134                break;
1135              case 0x43: // 'C'
1136              case 0x63: // 'c'
1137                byteValue = (byte) 0xC0;
1138                break;
1139              case 0x44: // 'D'
1140              case 0x64: // 'd'
1141                byteValue = (byte) 0xD0;
1142                break;
1143              case 0x45: // 'E'
1144              case 0x65: // 'e'
1145                byteValue = (byte) 0xE0;
1146                break;
1147              case 0x46: // 'F'
1148              case 0x66: // 'f'
1149                byteValue = (byte) 0xF0;
1150                break;
1151              default:
1152                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1153                    filterString, equalPos+i+1);
1154                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1155            }
1156
1157            switch (valueBytes[++i])
1158            {
1159              case 0x30: // '0'
1160                break;
1161              case 0x31: // '1'
1162                byteValue |= (byte) 0x01;
1163                break;
1164              case 0x32: // '2'
1165                byteValue |= (byte) 0x02;
1166                break;
1167              case 0x33: // '3'
1168                byteValue |= (byte) 0x03;
1169                break;
1170              case 0x34: // '4'
1171                byteValue |= (byte) 0x04;
1172                break;
1173              case 0x35: // '5'
1174                byteValue |= (byte) 0x05;
1175                break;
1176              case 0x36: // '6'
1177                byteValue |= (byte) 0x06;
1178                break;
1179              case 0x37: // '7'
1180                byteValue |= (byte) 0x07;
1181                break;
1182              case 0x38: // '8'
1183                byteValue |= (byte) 0x08;
1184                break;
1185              case 0x39: // '9'
1186                byteValue |= (byte) 0x09;
1187                break;
1188              case 0x41: // 'A'
1189              case 0x61: // 'a'
1190                byteValue |= (byte) 0x0A;
1191                break;
1192              case 0x42: // 'B'
1193              case 0x62: // 'b'
1194                byteValue |= (byte) 0x0B;
1195                break;
1196              case 0x43: // 'C'
1197              case 0x63: // 'c'
1198                byteValue |= (byte) 0x0C;
1199                break;
1200              case 0x44: // 'D'
1201              case 0x64: // 'd'
1202                byteValue |= (byte) 0x0D;
1203                break;
1204              case 0x45: // 'E'
1205              case 0x65: // 'e'
1206                byteValue |= (byte) 0x0E;
1207                break;
1208              case 0x46: // 'F'
1209              case 0x66: // 'f'
1210                byteValue |= (byte) 0x0F;
1211                break;
1212              default:
1213                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1214                    filterString, equalPos+i+1);
1215                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1216            }
1217
1218            buffer.appendByte(byteValue);
1219          }
1220          else
1221          {
1222            buffer.appendByte(valueBytes[i]);
1223          }
1224        }
1225
1226        subAny.add(buffer.toByteString());
1227        buffer.clear();
1228      }
1229      else
1230      {
1231        subAny.add(ByteString.wrap(valueBytes, firstPos+1, length));
1232      }
1233
1234
1235      firstPos = asteriskPos;
1236    }
1237
1238
1239    // Finally, see if there is anything after the last asterisk, which would be
1240    // the subFinal value.
1241    ByteString subFinal;
1242    if (firstPos == (valueBytes.length-1))
1243    {
1244      subFinal = null;
1245    }
1246    else
1247    {
1248      int length = valueBytes.length - firstPos - 1;
1249
1250      if (hasEscape)
1251      {
1252        ByteStringBuilder buffer = new ByteStringBuilder(length);
1253        for (int i=firstPos+1; i < valueBytes.length; i++)
1254        {
1255          if (valueBytes[i] == 0x5C)
1256          {
1257            // The next two bytes must be the hex characters that comprise the
1258            // binary value.
1259            if (i + 2 >= valueBytes.length)
1260            {
1261              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1262                  filterString, equalPos+i+1);
1263              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1264            }
1265
1266            byte byteValue = 0;
1267            switch (valueBytes[++i])
1268            {
1269              case 0x30: // '0'
1270                break;
1271              case 0x31: // '1'
1272                byteValue = (byte) 0x10;
1273                break;
1274              case 0x32: // '2'
1275                byteValue = (byte) 0x20;
1276                break;
1277              case 0x33: // '3'
1278                byteValue = (byte) 0x30;
1279                break;
1280              case 0x34: // '4'
1281                byteValue = (byte) 0x40;
1282                break;
1283              case 0x35: // '5'
1284                byteValue = (byte) 0x50;
1285                break;
1286              case 0x36: // '6'
1287                byteValue = (byte) 0x60;
1288                break;
1289              case 0x37: // '7'
1290                byteValue = (byte) 0x70;
1291                break;
1292              case 0x38: // '8'
1293                byteValue = (byte) 0x80;
1294                break;
1295              case 0x39: // '9'
1296                byteValue = (byte) 0x90;
1297                break;
1298              case 0x41: // 'A'
1299              case 0x61: // 'a'
1300                byteValue = (byte) 0xA0;
1301                break;
1302              case 0x42: // 'B'
1303              case 0x62: // 'b'
1304                byteValue = (byte) 0xB0;
1305                break;
1306              case 0x43: // 'C'
1307              case 0x63: // 'c'
1308                byteValue = (byte) 0xC0;
1309                break;
1310              case 0x44: // 'D'
1311              case 0x64: // 'd'
1312                byteValue = (byte) 0xD0;
1313                break;
1314              case 0x45: // 'E'
1315              case 0x65: // 'e'
1316                byteValue = (byte) 0xE0;
1317                break;
1318              case 0x46: // 'F'
1319              case 0x66: // 'f'
1320                byteValue = (byte) 0xF0;
1321                break;
1322              default:
1323                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1324                    filterString, equalPos+i+1);
1325                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1326            }
1327
1328            switch (valueBytes[++i])
1329            {
1330              case 0x30: // '0'
1331                break;
1332              case 0x31: // '1'
1333                byteValue |= (byte) 0x01;
1334                break;
1335              case 0x32: // '2'
1336                byteValue |= (byte) 0x02;
1337                break;
1338              case 0x33: // '3'
1339                byteValue |= (byte) 0x03;
1340                break;
1341              case 0x34: // '4'
1342                byteValue |= (byte) 0x04;
1343                break;
1344              case 0x35: // '5'
1345                byteValue |= (byte) 0x05;
1346                break;
1347              case 0x36: // '6'
1348                byteValue |= (byte) 0x06;
1349                break;
1350              case 0x37: // '7'
1351                byteValue |= (byte) 0x07;
1352                break;
1353              case 0x38: // '8'
1354                byteValue |= (byte) 0x08;
1355                break;
1356              case 0x39: // '9'
1357                byteValue |= (byte) 0x09;
1358                break;
1359              case 0x41: // 'A'
1360              case 0x61: // 'a'
1361                byteValue |= (byte) 0x0A;
1362                break;
1363              case 0x42: // 'B'
1364              case 0x62: // 'b'
1365                byteValue |= (byte) 0x0B;
1366                break;
1367              case 0x43: // 'C'
1368              case 0x63: // 'c'
1369                byteValue |= (byte) 0x0C;
1370                break;
1371              case 0x44: // 'D'
1372              case 0x64: // 'd'
1373                byteValue |= (byte) 0x0D;
1374                break;
1375              case 0x45: // 'E'
1376              case 0x65: // 'e'
1377                byteValue |= (byte) 0x0E;
1378                break;
1379              case 0x46: // 'F'
1380              case 0x66: // 'f'
1381                byteValue |= (byte) 0x0F;
1382                break;
1383              default:
1384                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1385                    filterString, equalPos+i+1);
1386                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1387            }
1388
1389            buffer.appendByte(byteValue);
1390          }
1391          else
1392          {
1393            buffer.appendByte(valueBytes[i]);
1394          }
1395        }
1396
1397        subFinal = buffer.toByteString();
1398      }
1399      else
1400      {
1401        subFinal = ByteString.wrap(valueBytes, firstPos+1, length);
1402      }
1403    }
1404
1405
1406    return new LDAPFilter(FilterType.SUBSTRING, null, null, attrType, null,
1407                          subInitial, subAny, subFinal, null, false);
1408  }
1409
1410
1411
1412  /**
1413   * Decodes an extensible match filter component based on the provided
1414   * information.
1415   *
1416   * @param  filterString  The filter string containing the information to
1417   *                       decode.
1418   * @param  startPos      The position in the filter string of the first
1419   *                       character in the extensible match filter.
1420   * @param  equalPos      The position of the equal sign in the extensible
1421   *                       match filter.
1422   * @param  endPos        The position of the first character after the end of
1423   *                       the extensible match filter.
1424   *
1425   * @return  The decoded LDAP filter.
1426   *
1427   * @throws  LDAPException  If a problem occurs while attempting to decode the
1428   *                         extensible match filter.
1429   */
1430  private static LDAPFilter decodeExtensibleMatchFilter(String filterString,
1431                                                        int startPos,
1432                                                        int equalPos,
1433                                                        int endPos)
1434          throws LDAPException
1435  {
1436    String  attributeType  = null;
1437    boolean dnAttributes   = false;
1438    String  matchingRuleID = null;
1439
1440
1441    // Look at the first character.  If it is a colon, then it must be followed
1442    // by either the string "dn" or the matching rule ID.  If it is not, then
1443    // must be the attribute type.
1444    String lowerLeftStr =
1445         toLowerCase(filterString.substring(startPos, equalPos));
1446    if (filterString.charAt(startPos) == ':')
1447    {
1448      // See if it starts with ":dn".  Otherwise, it much be the matching rule
1449      // ID.
1450      if (lowerLeftStr.startsWith(":dn:"))
1451      {
1452        dnAttributes = true;
1453
1454        if(startPos+4 < equalPos-1)
1455        {
1456          matchingRuleID = filterString.substring(startPos+4, equalPos-1);
1457        }
1458      }
1459      else
1460      {
1461        matchingRuleID = filterString.substring(startPos+1, equalPos-1);
1462      }
1463    }
1464    else
1465    {
1466      int colonPos = filterString.indexOf(':',startPos);
1467      if (colonPos < 0)
1468      {
1469        LocalizableMessage message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_COLON.get(
1470            filterString, startPos);
1471        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1472      }
1473
1474
1475      attributeType = filterString.substring(startPos, colonPos);
1476
1477
1478      // If there is anything left, then it should be ":dn" and/or ":" followed
1479      // by the matching rule ID.
1480      if (colonPos < equalPos-1)
1481      {
1482        if (lowerLeftStr.startsWith(":dn:", colonPos - startPos))
1483        {
1484          dnAttributes = true;
1485
1486          if (colonPos+4 < equalPos-1)
1487          {
1488            matchingRuleID = filterString.substring(colonPos+4, equalPos-1);
1489          }
1490        }
1491        else
1492        {
1493          matchingRuleID = filterString.substring(colonPos+1, equalPos-1);
1494        }
1495      }
1496    }
1497
1498
1499    // Parse out the attribute value.
1500    byte[] valueBytes = getBytes(filterString.substring(equalPos+1, endPos));
1501    boolean hasEscape = false;
1502    for (byte valueByte : valueBytes)
1503    {
1504      if (valueByte == 0x5C)
1505      {
1506        hasEscape = true;
1507        break;
1508      }
1509    }
1510
1511    ByteString value;
1512    if (hasEscape)
1513    {
1514      ByteStringBuilder valueBuffer = new ByteStringBuilder(valueBytes.length);
1515      for (int i=0; i < valueBytes.length; i++)
1516      {
1517        if (valueBytes[i] == 0x5C) // The backslash character
1518        {
1519          // The next two bytes must be the hex characters that comprise the
1520          // binary value.
1521          if (i + 2 >= valueBytes.length)
1522          {
1523            LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1524                filterString, equalPos+i+1);
1525            throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1526          }
1527
1528          byte byteValue = 0;
1529          switch (valueBytes[++i])
1530          {
1531            case 0x30: // '0'
1532              break;
1533            case 0x31: // '1'
1534              byteValue = (byte) 0x10;
1535              break;
1536            case 0x32: // '2'
1537              byteValue = (byte) 0x20;
1538              break;
1539            case 0x33: // '3'
1540              byteValue = (byte) 0x30;
1541              break;
1542            case 0x34: // '4'
1543              byteValue = (byte) 0x40;
1544              break;
1545            case 0x35: // '5'
1546              byteValue = (byte) 0x50;
1547              break;
1548            case 0x36: // '6'
1549              byteValue = (byte) 0x60;
1550              break;
1551            case 0x37: // '7'
1552              byteValue = (byte) 0x70;
1553              break;
1554            case 0x38: // '8'
1555              byteValue = (byte) 0x80;
1556              break;
1557            case 0x39: // '9'
1558              byteValue = (byte) 0x90;
1559              break;
1560            case 0x41: // 'A'
1561            case 0x61: // 'a'
1562              byteValue = (byte) 0xA0;
1563              break;
1564            case 0x42: // 'B'
1565            case 0x62: // 'b'
1566              byteValue = (byte) 0xB0;
1567              break;
1568            case 0x43: // 'C'
1569            case 0x63: // 'c'
1570              byteValue = (byte) 0xC0;
1571              break;
1572            case 0x44: // 'D'
1573            case 0x64: // 'd'
1574              byteValue = (byte) 0xD0;
1575              break;
1576            case 0x45: // 'E'
1577            case 0x65: // 'e'
1578              byteValue = (byte) 0xE0;
1579              break;
1580            case 0x46: // 'F'
1581            case 0x66: // 'f'
1582              byteValue = (byte) 0xF0;
1583              break;
1584            default:
1585              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1586                  filterString, equalPos+i+1);
1587              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1588          }
1589
1590          switch (valueBytes[++i])
1591          {
1592            case 0x30: // '0'
1593              break;
1594            case 0x31: // '1'
1595              byteValue |= (byte) 0x01;
1596              break;
1597            case 0x32: // '2'
1598              byteValue |= (byte) 0x02;
1599              break;
1600            case 0x33: // '3'
1601              byteValue |= (byte) 0x03;
1602              break;
1603            case 0x34: // '4'
1604              byteValue |= (byte) 0x04;
1605              break;
1606            case 0x35: // '5'
1607              byteValue |= (byte) 0x05;
1608              break;
1609            case 0x36: // '6'
1610              byteValue |= (byte) 0x06;
1611              break;
1612            case 0x37: // '7'
1613              byteValue |= (byte) 0x07;
1614              break;
1615            case 0x38: // '8'
1616              byteValue |= (byte) 0x08;
1617              break;
1618            case 0x39: // '9'
1619              byteValue |= (byte) 0x09;
1620              break;
1621            case 0x41: // 'A'
1622            case 0x61: // 'a'
1623              byteValue |= (byte) 0x0A;
1624              break;
1625            case 0x42: // 'B'
1626            case 0x62: // 'b'
1627              byteValue |= (byte) 0x0B;
1628              break;
1629            case 0x43: // 'C'
1630            case 0x63: // 'c'
1631              byteValue |= (byte) 0x0C;
1632              break;
1633            case 0x44: // 'D'
1634            case 0x64: // 'd'
1635              byteValue |= (byte) 0x0D;
1636              break;
1637            case 0x45: // 'E'
1638            case 0x65: // 'e'
1639              byteValue |= (byte) 0x0E;
1640              break;
1641            case 0x46: // 'F'
1642            case 0x66: // 'f'
1643              byteValue |= (byte) 0x0F;
1644              break;
1645            default:
1646              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1647                  filterString, equalPos+i+1);
1648              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1649          }
1650
1651          valueBuffer.appendByte(byteValue);
1652        }
1653        else
1654        {
1655          valueBuffer.appendByte(valueBytes[i]);
1656        }
1657      }
1658
1659      value = valueBuffer.toByteString();
1660    }
1661    else
1662    {
1663      value = ByteString.wrap(valueBytes);
1664    }
1665
1666
1667    // Make sure that the filter has at least one of an attribute description
1668    // and/or a matching rule ID.
1669    if (attributeType == null && matchingRuleID == null)
1670    {
1671      LocalizableMessage message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR.get(
1672          filterString, startPos);
1673      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1674    }
1675
1676
1677    return new LDAPFilter(FilterType.EXTENSIBLE_MATCH, null, null,
1678                          attributeType, value, null, null, null,
1679                          matchingRuleID, dnAttributes);
1680  }
1681
1682
1683
1684  /**
1685   * Retrieves the filter type for this search filter.
1686   *
1687   * @return  The filter type for this search filter.
1688   */
1689  @Override
1690  public FilterType getFilterType()
1691  {
1692    return filterType;
1693  }
1694
1695
1696
1697  /**
1698   * Retrieves the set of subordinate filter components for AND or OR searches.
1699   * The contents of the returned list may be altered by the caller.
1700   *
1701   * @return  The set of subordinate filter components for AND and OR searches,
1702   *          or <CODE>null</CODE> if this is not an AND or OR search.
1703   */
1704  @Override
1705  public ArrayList<RawFilter> getFilterComponents()
1706  {
1707    return filterComponents;
1708  }
1709
1710
1711
1712  /**
1713   * Retrieves the subordinate filter component for NOT searches.
1714   *
1715   * @return  The subordinate filter component for NOT searches, or
1716   *          <CODE>null</CODE> if this is not a NOT search.
1717   */
1718  @Override
1719  public RawFilter getNOTComponent()
1720  {
1721    return notComponent;
1722  }
1723
1724
1725
1726  /**
1727   * Retrieves the attribute type for this search filter.  This will not be
1728   * applicable for AND, OR, or NOT filters.
1729   *
1730   * @return  The attribute type for this search filter, or <CODE>null</CODE> if
1731   *          there is none.
1732   */
1733  @Override
1734  public String getAttributeType()
1735  {
1736    return attributeType;
1737  }
1738
1739
1740
1741  /**
1742   * Retrieves the assertion value for this search filter.  This will only be
1743   * applicable for equality, greater or equal, less or equal, approximate, or
1744   * extensible matching filters.
1745   *
1746   * @return  The assertion value for this search filter, or <CODE>null</CODE>
1747   *          if there is none.
1748   */
1749  @Override
1750  public ByteString getAssertionValue()
1751  {
1752    return assertionValue;
1753  }
1754
1755
1756
1757  /**
1758   * Retrieves the subInitial component for this substring filter.  This is only
1759   * applicable for substring search filters, but even substring filters might
1760   * not have a value for this component.
1761   *
1762   * @return  The subInitial component for this substring filter, or
1763   *          <CODE>null</CODE> if there is none.
1764   */
1765  @Override
1766  public ByteString getSubInitialElement()
1767  {
1768    return subInitialElement;
1769  }
1770
1771
1772
1773  /**
1774   * Specifies the subInitial element for this substring filter.  This will be
1775   * ignored for all other types of filters.
1776   *
1777   * @param  subInitialElement  The subInitial element for this substring
1778   *                            filter.
1779   */
1780  public void setSubInitialElement(ByteString subInitialElement)
1781  {
1782    this.subInitialElement = subInitialElement;
1783  }
1784
1785
1786
1787  /**
1788   * Retrieves the set of subAny elements for this substring filter.  This is
1789   * only applicable for substring search filters, and even then may be null or
1790   * empty for some substring filters.
1791   *
1792   * @return  The set of subAny elements for this substring filter, or
1793   *          <CODE>null</CODE> if there are none.
1794   */
1795  @Override
1796  public ArrayList<ByteString> getSubAnyElements()
1797  {
1798    return subAnyElements;
1799  }
1800
1801
1802
1803  /**
1804   * Retrieves the subFinal element for this substring filter.  This is not
1805   * applicable for any other filter type, and may not be provided even for some
1806   * substring filters.
1807   *
1808   * @return  The subFinal element for this substring filter, or
1809   *          <CODE>null</CODE> if there is none.
1810   */
1811  @Override
1812  public ByteString getSubFinalElement()
1813  {
1814    return subFinalElement;
1815  }
1816
1817
1818
1819  /**
1820   * Retrieves the matching rule ID for this extensible match filter.  This is
1821   * not applicable for any other type of filter and may not be included in
1822   * some extensible matching filters.
1823   *
1824   * @return  The matching rule ID for this extensible match filter, or
1825   *          <CODE>null</CODE> if there is none.
1826   */
1827  @Override
1828  public String getMatchingRuleID()
1829  {
1830    return matchingRuleID;
1831  }
1832
1833
1834
1835  /**
1836   * Retrieves the value of the DN attributes flag for this extensible match
1837   * filter, which indicates whether to perform matching on the components of
1838   * the DN.  This does not apply for any other type of filter.
1839   *
1840   * @return  The value of the DN attributes flag for this extensibleMatch
1841   *          filter.
1842   */
1843  @Override
1844  public boolean getDNAttributes()
1845  {
1846    return dnAttributes;
1847  }
1848
1849
1850
1851  /**
1852   * Converts this LDAP filter to a search filter that may be used by the
1853   * Directory Server's core processing.
1854   *
1855   * @return  The generated search filter.
1856   *
1857   * @throws  DirectoryException  If a problem occurs while attempting to
1858   *                              construct the search filter.
1859   */
1860  @Override
1861  public SearchFilter toSearchFilter()
1862         throws DirectoryException
1863  {
1864    ArrayList<SearchFilter> subComps;
1865    if (filterComponents == null)
1866    {
1867      subComps = null;
1868    }
1869    else
1870    {
1871      subComps = new ArrayList<>(filterComponents.size());
1872      for (RawFilter f : filterComponents)
1873      {
1874        subComps.add(f.toSearchFilter());
1875      }
1876    }
1877
1878
1879    SearchFilter notComp;
1880    if (notComponent == null)
1881    {
1882      notComp = null;
1883    }
1884    else
1885    {
1886      notComp = notComponent.toSearchFilter();
1887    }
1888
1889
1890    AttributeType attrType;
1891    HashSet<String> options;
1892    if (attributeType == null)
1893    {
1894      attrType = null;
1895      options  = null;
1896    }
1897    else
1898    {
1899      int semicolonPos = attributeType.indexOf(';');
1900      if (semicolonPos > 0)
1901      {
1902        String baseName = attributeType.substring(0, semicolonPos);
1903        attrType = DirectoryServer.getAttributeType(baseName);
1904        options = new HashSet<>();
1905        StringTokenizer tokenizer =
1906             new StringTokenizer(attributeType.substring(semicolonPos+1), ";");
1907        while (tokenizer.hasMoreTokens())
1908        {
1909          options.add(tokenizer.nextToken());
1910        }
1911      }
1912      else
1913      {
1914        options = null;
1915        attrType = DirectoryServer.getAttributeType(attributeType);
1916      }
1917    }
1918
1919
1920    if (assertionValue != null && attrType == null)
1921    {
1922      if (matchingRuleID == null)
1923      {
1924        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1925            ERR_LDAP_FILTER_VALUE_WITH_NO_ATTR_OR_MR.get());
1926      }
1927
1928      MatchingRule mr = DirectoryServer.getMatchingRule(toLowerCase(matchingRuleID));
1929      if (mr == null)
1930      {
1931        throw new DirectoryException(ResultCode.INAPPROPRIATE_MATCHING,
1932            ERR_LDAP_FILTER_UNKNOWN_MATCHING_RULE.get(matchingRuleID));
1933      }
1934    }
1935
1936    ArrayList<ByteString> subAnyComps =
1937        subAnyElements != null ? new ArrayList<ByteString>(subAnyElements) : null;
1938
1939    return new SearchFilter(filterType, subComps, notComp, attrType,
1940                            options, assertionValue, subInitialElement, subAnyComps,
1941                            subFinalElement, matchingRuleID, dnAttributes);
1942  }
1943
1944
1945
1946  /**
1947   * Appends a string representation of this search filter to the provided
1948   * buffer.
1949   *
1950   * @param  buffer  The buffer to which the information should be appended.
1951   */
1952  @Override
1953  public void toString(StringBuilder buffer)
1954  {
1955    switch (filterType)
1956    {
1957      case AND:
1958        buffer.append("(&");
1959        for (RawFilter f : filterComponents)
1960        {
1961          f.toString(buffer);
1962        }
1963        buffer.append(")");
1964        break;
1965      case OR:
1966        buffer.append("(|");
1967        for (RawFilter f : filterComponents)
1968        {
1969          f.toString(buffer);
1970        }
1971        buffer.append(")");
1972        break;
1973      case NOT:
1974        buffer.append("(!");
1975        notComponent.toString(buffer);
1976        buffer.append(")");
1977        break;
1978      case EQUALITY:
1979        buffer.append("(");
1980        buffer.append(attributeType);
1981        buffer.append("=");
1982        valueToFilterString(buffer, assertionValue);
1983        buffer.append(")");
1984        break;
1985      case SUBSTRING:
1986        buffer.append("(");
1987        buffer.append(attributeType);
1988        buffer.append("=");
1989
1990        if (subInitialElement != null)
1991        {
1992          valueToFilterString(buffer, subInitialElement);
1993        }
1994
1995        if (subAnyElements != null && !subAnyElements.isEmpty())
1996        {
1997          for (ByteString s : subAnyElements)
1998          {
1999            buffer.append("*");
2000            valueToFilterString(buffer, s);
2001          }
2002        }
2003
2004        buffer.append("*");
2005
2006        if (subFinalElement != null)
2007        {
2008          valueToFilterString(buffer, subFinalElement);
2009        }
2010
2011        buffer.append(")");
2012        break;
2013      case GREATER_OR_EQUAL:
2014        buffer.append("(");
2015        buffer.append(attributeType);
2016        buffer.append(">=");
2017        valueToFilterString(buffer, assertionValue);
2018        buffer.append(")");
2019        break;
2020      case LESS_OR_EQUAL:
2021        buffer.append("(");
2022        buffer.append(attributeType);
2023        buffer.append("<=");
2024        valueToFilterString(buffer, assertionValue);
2025        buffer.append(")");
2026        break;
2027      case PRESENT:
2028        buffer.append("(");
2029        buffer.append(attributeType);
2030        buffer.append("=*)");
2031        break;
2032      case APPROXIMATE_MATCH:
2033        buffer.append("(");
2034        buffer.append(attributeType);
2035        buffer.append("~=");
2036        valueToFilterString(buffer, assertionValue);
2037        buffer.append(")");
2038        break;
2039      case EXTENSIBLE_MATCH:
2040        buffer.append("(");
2041
2042        if (attributeType != null)
2043        {
2044          buffer.append(attributeType);
2045        }
2046
2047        if (dnAttributes)
2048        {
2049          buffer.append(":dn");
2050        }
2051
2052        if (matchingRuleID != null)
2053        {
2054          buffer.append(":");
2055          buffer.append(matchingRuleID);
2056        }
2057
2058        buffer.append(":=");
2059        valueToFilterString(buffer, assertionValue);
2060        buffer.append(")");
2061        break;
2062    }
2063  }
2064
2065  /**
2066   * Returns the {@code objectClass} presence filter {@code (objectClass=*)}.
2067   *
2068   * @return The {@code objectClass} presence filter {@code (objectClass=*)}.
2069   */
2070  public static LDAPFilter objectClassPresent()
2071  {
2072    if (objectClassPresent == null)
2073    {
2074      try
2075      {
2076        objectClassPresent = LDAPFilter.decode("(objectclass=*)");
2077      }
2078      catch (LDAPException canNeverHappen)
2079      {
2080        logger.traceException(canNeverHappen);
2081      }
2082    }
2083    return objectClassPresent;
2084  }
2085}