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.LinkedList;
022import java.util.List;
023
024import org.forgerock.i18n.LocalizableMessage;
025import org.forgerock.i18n.LocalizedIllegalArgumentException;
026import org.forgerock.i18n.slf4j.LocalizedLogger;
027import org.forgerock.opendj.ldap.AttributeDescription;
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.UnknownSchemaElementException;
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 description for several filter types. */
080  private String attributeDescription;
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  attributeDescription The attribute description 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 attributeDescription,
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.attributeDescription = attributeDescription;
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        attributeDescription     = 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        attributeDescription     = 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        attributeDescription  = 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        attributeDescription  = 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        attributeDescription  = 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          attributeDescription = null;
242        }
243        else
244        {
245          attributeDescription = 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 attribute description.
427    // Make sure that the characters it contains are acceptable for attribute descriptions,
428    // including those allowed by attribute name exceptions
429    // (ASCII letters and digits, the dash, and the underscore).
430    // We also need to allow attribute options, which includes the semicolon and the equal sign.
431    String attrType = filterString.substring(startPos, attrEndPos);
432    for (int i=0; i < attrType.length(); i++)
433    {
434      switch (attrType.charAt(i))
435      {
436        case '-':
437        case '0':
438        case '1':
439        case '2':
440        case '3':
441        case '4':
442        case '5':
443        case '6':
444        case '7':
445        case '8':
446        case '9':
447        case ';':
448        case '=':
449        case 'A':
450        case 'B':
451        case 'C':
452        case 'D':
453        case 'E':
454        case 'F':
455        case 'G':
456        case 'H':
457        case 'I':
458        case 'J':
459        case 'K':
460        case 'L':
461        case 'M':
462        case 'N':
463        case 'O':
464        case 'P':
465        case 'Q':
466        case 'R':
467        case 'S':
468        case 'T':
469        case 'U':
470        case 'V':
471        case 'W':
472        case 'X':
473        case 'Y':
474        case 'Z':
475        case '_':
476        case 'a':
477        case 'b':
478        case 'c':
479        case 'd':
480        case 'e':
481        case 'f':
482        case 'g':
483        case 'h':
484        case 'i':
485        case 'j':
486        case 'k':
487        case 'l':
488        case 'm':
489        case 'n':
490        case 'o':
491        case 'p':
492        case 'q':
493        case 'r':
494        case 's':
495        case 't':
496        case 'u':
497        case 'v':
498        case 'w':
499        case 'x':
500        case 'y':
501        case 'z':
502          // These are all OK.
503          break;
504
505        case '.':
506        case '/':
507        case ':':
508        case '<':
509        case '>':
510        case '?':
511        case '@':
512        case '[':
513        case '\\':
514        case ']':
515        case '^':
516        case '`':
517          // These are not allowed, but they are explicitly called out because
518          // they are included in the range of values between '-' and 'z', and
519          // making sure all possible characters are included can help make the
520          // switch statement more efficient.  We'll fall through to the default
521          // clause to reject them.
522        default:
523          LocalizableMessage message = ERR_LDAP_FILTER_INVALID_CHAR_IN_ATTR_TYPE.get(
524              attrType, attrType.charAt(i), i);
525          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
526      }
527    }
528
529
530    // Get the attribute value.
531    String valueStr = filterString.substring(equalPos+1, endPos);
532    if (valueStr.length() == 0)
533    {
534      return new LDAPFilter(filterType, null, null, attrType,
535                            ByteString.empty(), null, null, null, null,
536                            false);
537    }
538    else if (valueStr.equals("*"))
539    {
540      return new LDAPFilter(FilterType.PRESENT, null, null, attrType, null,
541                            null, null, null, null, false);
542    }
543    else if (valueStr.indexOf('*') >= 0)
544    {
545      return decodeSubstringFilter(filterString, attrType, equalPos, endPos);
546    }
547    else
548    {
549      boolean hasEscape = false;
550      byte[] valueBytes = getBytes(valueStr);
551      for (byte valueByte : valueBytes)
552      {
553        if (valueByte == 0x5C) // The backslash character
554        {
555          hasEscape = true;
556          break;
557        }
558      }
559
560      ByteString value;
561      if (hasEscape)
562      {
563        ByteStringBuilder valueBuffer =
564            new ByteStringBuilder(valueStr.length());
565        for (int i=0; i < valueBytes.length; i++)
566        {
567          if (valueBytes[i] == 0x5C) // The backslash character
568          {
569            // The next two bytes must be the hex characters that comprise the
570            // binary value.
571            if (i + 2 >= valueBytes.length)
572            {
573              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
574                  filterString, equalPos+i+1);
575              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
576            }
577
578            byte byteValue = 0;
579            switch (valueBytes[++i])
580            {
581              case 0x30: // '0'
582                break;
583              case 0x31: // '1'
584                byteValue = (byte) 0x10;
585                break;
586              case 0x32: // '2'
587                byteValue = (byte) 0x20;
588                break;
589              case 0x33: // '3'
590                byteValue = (byte) 0x30;
591                break;
592              case 0x34: // '4'
593                byteValue = (byte) 0x40;
594                break;
595              case 0x35: // '5'
596                byteValue = (byte) 0x50;
597                break;
598              case 0x36: // '6'
599                byteValue = (byte) 0x60;
600                break;
601              case 0x37: // '7'
602                byteValue = (byte) 0x70;
603                break;
604              case 0x38: // '8'
605                byteValue = (byte) 0x80;
606                break;
607              case 0x39: // '9'
608                byteValue = (byte) 0x90;
609                break;
610              case 0x41: // 'A'
611              case 0x61: // 'a'
612                byteValue = (byte) 0xA0;
613                break;
614              case 0x42: // 'B'
615              case 0x62: // 'b'
616                byteValue = (byte) 0xB0;
617                break;
618              case 0x43: // 'C'
619              case 0x63: // 'c'
620                byteValue = (byte) 0xC0;
621                break;
622              case 0x44: // 'D'
623              case 0x64: // 'd'
624                byteValue = (byte) 0xD0;
625                break;
626              case 0x45: // 'E'
627              case 0x65: // 'e'
628                byteValue = (byte) 0xE0;
629                break;
630              case 0x46: // 'F'
631              case 0x66: // 'f'
632                byteValue = (byte) 0xF0;
633                break;
634              default:
635                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
636                    filterString, equalPos+i+1);
637                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
638            }
639
640            switch (valueBytes[++i])
641            {
642              case 0x30: // '0'
643                break;
644              case 0x31: // '1'
645                byteValue |= (byte) 0x01;
646                break;
647              case 0x32: // '2'
648                byteValue |= (byte) 0x02;
649                break;
650              case 0x33: // '3'
651                byteValue |= (byte) 0x03;
652                break;
653              case 0x34: // '4'
654                byteValue |= (byte) 0x04;
655                break;
656              case 0x35: // '5'
657                byteValue |= (byte) 0x05;
658                break;
659              case 0x36: // '6'
660                byteValue |= (byte) 0x06;
661                break;
662              case 0x37: // '7'
663                byteValue |= (byte) 0x07;
664                break;
665              case 0x38: // '8'
666                byteValue |= (byte) 0x08;
667                break;
668              case 0x39: // '9'
669                byteValue |= (byte) 0x09;
670                break;
671              case 0x41: // 'A'
672              case 0x61: // 'a'
673                byteValue |= (byte) 0x0A;
674                break;
675              case 0x42: // 'B'
676              case 0x62: // 'b'
677                byteValue |= (byte) 0x0B;
678                break;
679              case 0x43: // 'C'
680              case 0x63: // 'c'
681                byteValue |= (byte) 0x0C;
682                break;
683              case 0x44: // 'D'
684              case 0x64: // 'd'
685                byteValue |= (byte) 0x0D;
686                break;
687              case 0x45: // 'E'
688              case 0x65: // 'e'
689                byteValue |= (byte) 0x0E;
690                break;
691              case 0x46: // 'F'
692              case 0x66: // 'f'
693                byteValue |= (byte) 0x0F;
694                break;
695              default:
696                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
697                    filterString, equalPos+i+1);
698                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
699            }
700
701            valueBuffer.appendByte(byteValue);
702          }
703          else
704          {
705            valueBuffer.appendByte(valueBytes[i]);
706          }
707        }
708
709        value = valueBuffer.toByteString();
710      }
711      else
712      {
713        value = ByteString.wrap(valueBytes);
714      }
715
716      return new LDAPFilter(filterType, null, null, attrType, value, null, null,
717                            null, null, false);
718    }
719  }
720
721
722
723  /**
724   * Decodes a set of filters from the provided filter string within the
725   * indicated range.
726   *
727   * @param  filterType    The filter type for this compound filter.  It must be
728   *                       an AND, OR or NOT filter.
729   * @param  filterString  The string containing the filter information to
730   *                       decode.
731   * @param  startPos      The position of the first character in the set of
732   *                       filters to decode.
733   * @param  endPos        The position of the first character after the end of
734   *                       the set of filters to decode.
735   *
736   * @return  The decoded LDAP filter.
737   *
738   * @throws  LDAPException  If a problem occurs while attempting to decode the
739   *                         compound filter.
740   */
741  private static LDAPFilter decodeCompoundFilter(FilterType filterType,
742                                                 String filterString,
743                                                 int startPos, int endPos)
744          throws LDAPException
745  {
746    // Create a list to hold the returned components.
747    ArrayList<RawFilter> filterComponents = new ArrayList<>();
748
749
750    // If the end pos is equal to the start pos, then there are no components.
751    if (startPos == endPos)
752    {
753      if (filterType == FilterType.NOT)
754      {
755        LocalizableMessage message =
756            ERR_LDAP_FILTER_NOT_EXACTLY_ONE.get(filterString, startPos, endPos);
757        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
758      }
759      else
760      {
761        // This is valid and will be treated as a TRUE/FALSE filter.
762        return new LDAPFilter(filterType, filterComponents, null, null, null,
763                              null, null, null, null, false);
764      }
765    }
766
767
768    // The first and last characters must be parentheses.  If not, then that's
769    // an error.
770    if (filterString.charAt(startPos) != '(' ||
771        filterString.charAt(endPos-1) != ')')
772    {
773      LocalizableMessage message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES.get(
774          filterString, startPos, endPos);
775      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
776    }
777
778
779    // Iterate through the characters in the value.  Whenever an open
780    // parenthesis is found, locate the corresponding close parenthesis by
781    // counting the number of intermediate open/close parentheses.
782    int pendingOpens = 0;
783    int openPos = -1;
784    for (int i=startPos; i < endPos; i++)
785    {
786      char c = filterString.charAt(i);
787      if (c == '(')
788      {
789        if (openPos < 0)
790        {
791          openPos = i;
792        }
793
794        pendingOpens++;
795      }
796      else if (c == ')')
797      {
798        pendingOpens--;
799        if (pendingOpens == 0)
800        {
801          filterComponents.add(decode(filterString, openPos, i+1));
802          openPos = -1;
803        }
804        else if (pendingOpens < 0)
805        {
806          LocalizableMessage message = ERR_LDAP_FILTER_NO_CORRESPONDING_OPEN_PARENTHESIS.
807              get(filterString, i);
808          throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
809        }
810      }
811      else if (pendingOpens <= 0)
812      {
813        LocalizableMessage message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES.get(
814            filterString, startPos, endPos);
815        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
816      }
817    }
818
819
820    // At this point, we have parsed the entire set of filter components.  The
821    // list of open parenthesis positions must be empty.
822    if (pendingOpens != 0)
823    {
824      LocalizableMessage message = ERR_LDAP_FILTER_NO_CORRESPONDING_CLOSE_PARENTHESIS.get(
825          filterString, openPos);
826      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
827    }
828
829
830    // We should have everything we need, so return the list.
831    if (filterType == FilterType.NOT)
832    {
833      if (filterComponents.size() != 1)
834      {
835        LocalizableMessage message =
836            ERR_LDAP_FILTER_NOT_EXACTLY_ONE.get(filterString, startPos, endPos);
837        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
838      }
839      RawFilter notComponent = filterComponents.get(0);
840      return new LDAPFilter(filterType, null, notComponent, null, null,
841                            null, null, null, null, false);
842    }
843    else
844    {
845      return new LDAPFilter(filterType, filterComponents, null, null, null,
846                            null, null, null, null, false);
847    }
848  }
849
850
851
852  /**
853   * Decodes a substring search filter component based on the provided
854   * information.
855   *
856   * @param  filterString  The filter string containing the information to
857   *                       decode.
858   * @param  attrDesc      The attribute description for this substring filter
859   *                       component.
860   * @param  equalPos      The location of the equal sign separating the
861   *                       attribute description from the value.
862   * @param  endPos        The position of the first character after the end of
863   *                       the substring value.
864   *
865   * @return  The decoded LDAP filter.
866   *
867   * @throws  LDAPException  If a problem occurs while attempting to decode the
868   *                         substring filter.
869   */
870  private static LDAPFilter decodeSubstringFilter(String filterString,
871                                                  String attrDesc, int equalPos,
872                                                  int endPos)
873          throws LDAPException
874  {
875    // Get a binary representation of the value.
876    byte[] valueBytes = getBytes(filterString.substring(equalPos+1, endPos));
877
878
879    // Find the locations of all the asterisks in the value.  Also, check to
880    // see if there are any escaped values, since they will need special
881    // treatment.
882    boolean hasEscape = false;
883    LinkedList<Integer> asteriskPositions = new LinkedList<>();
884    for (int i=0; i < valueBytes.length; i++)
885    {
886      if (valueBytes[i] == 0x2A) // The asterisk.
887      {
888        asteriskPositions.add(i);
889      }
890      else if (valueBytes[i] == 0x5C) // The backslash.
891      {
892        hasEscape = true;
893      }
894    }
895
896
897    // If there were no asterisks, then this isn't a substring filter.
898    if (asteriskPositions.isEmpty())
899    {
900      LocalizableMessage message = ERR_LDAP_FILTER_SUBSTRING_NO_ASTERISKS.get(
901          filterString, equalPos+1, endPos);
902      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
903    }
904
905
906    // If the value starts with an asterisk, then there is no subInitial
907    // component.  Otherwise, parse out the subInitial.
908    ByteString subInitial;
909    int firstPos = asteriskPositions.removeFirst();
910    if (firstPos == 0)
911    {
912      subInitial = null;
913    }
914    else
915    {
916      if (hasEscape)
917      {
918        ByteStringBuilder buffer = new ByteStringBuilder(firstPos);
919        for (int i=0; i < firstPos; i++)
920        {
921          if (valueBytes[i] == 0x5C)
922          {
923            // The next two bytes must be the hex characters that comprise the
924            // binary value.
925            if (i + 2 >= valueBytes.length)
926            {
927              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
928                  filterString, equalPos+i+1);
929              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
930            }
931
932            byte byteValue = 0;
933            switch (valueBytes[++i])
934            {
935              case 0x30: // '0'
936                break;
937              case 0x31: // '1'
938                byteValue = (byte) 0x10;
939                break;
940              case 0x32: // '2'
941                byteValue = (byte) 0x20;
942                break;
943              case 0x33: // '3'
944                byteValue = (byte) 0x30;
945                break;
946              case 0x34: // '4'
947                byteValue = (byte) 0x40;
948                break;
949              case 0x35: // '5'
950                byteValue = (byte) 0x50;
951                break;
952              case 0x36: // '6'
953                byteValue = (byte) 0x60;
954                break;
955              case 0x37: // '7'
956                byteValue = (byte) 0x70;
957                break;
958              case 0x38: // '8'
959                byteValue = (byte) 0x80;
960                break;
961              case 0x39: // '9'
962                byteValue = (byte) 0x90;
963                break;
964              case 0x41: // 'A'
965              case 0x61: // 'a'
966                byteValue = (byte) 0xA0;
967                break;
968              case 0x42: // 'B'
969              case 0x62: // 'b'
970                byteValue = (byte) 0xB0;
971                break;
972              case 0x43: // 'C'
973              case 0x63: // 'c'
974                byteValue = (byte) 0xC0;
975                break;
976              case 0x44: // 'D'
977              case 0x64: // 'd'
978                byteValue = (byte) 0xD0;
979                break;
980              case 0x45: // 'E'
981              case 0x65: // 'e'
982                byteValue = (byte) 0xE0;
983                break;
984              case 0x46: // 'F'
985              case 0x66: // 'f'
986                byteValue = (byte) 0xF0;
987                break;
988              default:
989                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
990                    filterString, equalPos+i+1);
991                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
992            }
993
994            switch (valueBytes[++i])
995            {
996              case 0x30: // '0'
997                break;
998              case 0x31: // '1'
999                byteValue |= (byte) 0x01;
1000                break;
1001              case 0x32: // '2'
1002                byteValue |= (byte) 0x02;
1003                break;
1004              case 0x33: // '3'
1005                byteValue |= (byte) 0x03;
1006                break;
1007              case 0x34: // '4'
1008                byteValue |= (byte) 0x04;
1009                break;
1010              case 0x35: // '5'
1011                byteValue |= (byte) 0x05;
1012                break;
1013              case 0x36: // '6'
1014                byteValue |= (byte) 0x06;
1015                break;
1016              case 0x37: // '7'
1017                byteValue |= (byte) 0x07;
1018                break;
1019              case 0x38: // '8'
1020                byteValue |= (byte) 0x08;
1021                break;
1022              case 0x39: // '9'
1023                byteValue |= (byte) 0x09;
1024                break;
1025              case 0x41: // 'A'
1026              case 0x61: // 'a'
1027                byteValue |= (byte) 0x0A;
1028                break;
1029              case 0x42: // 'B'
1030              case 0x62: // 'b'
1031                byteValue |= (byte) 0x0B;
1032                break;
1033              case 0x43: // 'C'
1034              case 0x63: // 'c'
1035                byteValue |= (byte) 0x0C;
1036                break;
1037              case 0x44: // 'D'
1038              case 0x64: // 'd'
1039                byteValue |= (byte) 0x0D;
1040                break;
1041              case 0x45: // 'E'
1042              case 0x65: // 'e'
1043                byteValue |= (byte) 0x0E;
1044                break;
1045              case 0x46: // 'F'
1046              case 0x66: // 'f'
1047                byteValue |= (byte) 0x0F;
1048                break;
1049              default:
1050                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1051                    filterString, equalPos+i+1);
1052                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1053            }
1054
1055            buffer.appendByte(byteValue);
1056          }
1057          else
1058          {
1059            buffer.appendByte(valueBytes[i]);
1060          }
1061        }
1062
1063        subInitial = buffer.toByteString();
1064      }
1065      else
1066      {
1067        subInitial = ByteString.wrap(valueBytes, 0, firstPos);
1068      }
1069    }
1070
1071
1072    // Next, process through the rest of the asterisks to get the subAny values.
1073    ArrayList<ByteString> subAny = new ArrayList<>();
1074    for (int asteriskPos : asteriskPositions)
1075    {
1076      int length = asteriskPos - firstPos - 1;
1077
1078      if (hasEscape)
1079      {
1080        ByteStringBuilder buffer = new ByteStringBuilder(length);
1081        for (int i=firstPos+1; i < asteriskPos; i++)
1082        {
1083          if (valueBytes[i] == 0x5C)
1084          {
1085            // The next two bytes must be the hex characters that comprise the
1086            // binary value.
1087            if (i + 2 >= valueBytes.length)
1088            {
1089              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1090                  filterString, equalPos+i+1);
1091              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1092            }
1093
1094            byte byteValue = 0;
1095            switch (valueBytes[++i])
1096            {
1097              case 0x30: // '0'
1098                break;
1099              case 0x31: // '1'
1100                byteValue = (byte) 0x10;
1101                break;
1102              case 0x32: // '2'
1103                byteValue = (byte) 0x20;
1104                break;
1105              case 0x33: // '3'
1106                byteValue = (byte) 0x30;
1107                break;
1108              case 0x34: // '4'
1109                byteValue = (byte) 0x40;
1110                break;
1111              case 0x35: // '5'
1112                byteValue = (byte) 0x50;
1113                break;
1114              case 0x36: // '6'
1115                byteValue = (byte) 0x60;
1116                break;
1117              case 0x37: // '7'
1118                byteValue = (byte) 0x70;
1119                break;
1120              case 0x38: // '8'
1121                byteValue = (byte) 0x80;
1122                break;
1123              case 0x39: // '9'
1124                byteValue = (byte) 0x90;
1125                break;
1126              case 0x41: // 'A'
1127              case 0x61: // 'a'
1128                byteValue = (byte) 0xA0;
1129                break;
1130              case 0x42: // 'B'
1131              case 0x62: // 'b'
1132                byteValue = (byte) 0xB0;
1133                break;
1134              case 0x43: // 'C'
1135              case 0x63: // 'c'
1136                byteValue = (byte) 0xC0;
1137                break;
1138              case 0x44: // 'D'
1139              case 0x64: // 'd'
1140                byteValue = (byte) 0xD0;
1141                break;
1142              case 0x45: // 'E'
1143              case 0x65: // 'e'
1144                byteValue = (byte) 0xE0;
1145                break;
1146              case 0x46: // 'F'
1147              case 0x66: // 'f'
1148                byteValue = (byte) 0xF0;
1149                break;
1150              default:
1151                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1152                    filterString, equalPos+i+1);
1153                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1154            }
1155
1156            switch (valueBytes[++i])
1157            {
1158              case 0x30: // '0'
1159                break;
1160              case 0x31: // '1'
1161                byteValue |= (byte) 0x01;
1162                break;
1163              case 0x32: // '2'
1164                byteValue |= (byte) 0x02;
1165                break;
1166              case 0x33: // '3'
1167                byteValue |= (byte) 0x03;
1168                break;
1169              case 0x34: // '4'
1170                byteValue |= (byte) 0x04;
1171                break;
1172              case 0x35: // '5'
1173                byteValue |= (byte) 0x05;
1174                break;
1175              case 0x36: // '6'
1176                byteValue |= (byte) 0x06;
1177                break;
1178              case 0x37: // '7'
1179                byteValue |= (byte) 0x07;
1180                break;
1181              case 0x38: // '8'
1182                byteValue |= (byte) 0x08;
1183                break;
1184              case 0x39: // '9'
1185                byteValue |= (byte) 0x09;
1186                break;
1187              case 0x41: // 'A'
1188              case 0x61: // 'a'
1189                byteValue |= (byte) 0x0A;
1190                break;
1191              case 0x42: // 'B'
1192              case 0x62: // 'b'
1193                byteValue |= (byte) 0x0B;
1194                break;
1195              case 0x43: // 'C'
1196              case 0x63: // 'c'
1197                byteValue |= (byte) 0x0C;
1198                break;
1199              case 0x44: // 'D'
1200              case 0x64: // 'd'
1201                byteValue |= (byte) 0x0D;
1202                break;
1203              case 0x45: // 'E'
1204              case 0x65: // 'e'
1205                byteValue |= (byte) 0x0E;
1206                break;
1207              case 0x46: // 'F'
1208              case 0x66: // 'f'
1209                byteValue |= (byte) 0x0F;
1210                break;
1211              default:
1212                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1213                    filterString, equalPos+i+1);
1214                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1215            }
1216
1217            buffer.appendByte(byteValue);
1218          }
1219          else
1220          {
1221            buffer.appendByte(valueBytes[i]);
1222          }
1223        }
1224
1225        subAny.add(buffer.toByteString());
1226        buffer.clear();
1227      }
1228      else
1229      {
1230        subAny.add(ByteString.wrap(valueBytes, firstPos+1, length));
1231      }
1232
1233
1234      firstPos = asteriskPos;
1235    }
1236
1237
1238    // Finally, see if there is anything after the last asterisk, which would be
1239    // the subFinal value.
1240    ByteString subFinal;
1241    if (firstPos == (valueBytes.length-1))
1242    {
1243      subFinal = null;
1244    }
1245    else
1246    {
1247      int length = valueBytes.length - firstPos - 1;
1248
1249      if (hasEscape)
1250      {
1251        ByteStringBuilder buffer = new ByteStringBuilder(length);
1252        for (int i=firstPos+1; i < valueBytes.length; i++)
1253        {
1254          if (valueBytes[i] == 0x5C)
1255          {
1256            // The next two bytes must be the hex characters that comprise the
1257            // binary value.
1258            if (i + 2 >= valueBytes.length)
1259            {
1260              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1261                  filterString, equalPos+i+1);
1262              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1263            }
1264
1265            byte byteValue = 0;
1266            switch (valueBytes[++i])
1267            {
1268              case 0x30: // '0'
1269                break;
1270              case 0x31: // '1'
1271                byteValue = (byte) 0x10;
1272                break;
1273              case 0x32: // '2'
1274                byteValue = (byte) 0x20;
1275                break;
1276              case 0x33: // '3'
1277                byteValue = (byte) 0x30;
1278                break;
1279              case 0x34: // '4'
1280                byteValue = (byte) 0x40;
1281                break;
1282              case 0x35: // '5'
1283                byteValue = (byte) 0x50;
1284                break;
1285              case 0x36: // '6'
1286                byteValue = (byte) 0x60;
1287                break;
1288              case 0x37: // '7'
1289                byteValue = (byte) 0x70;
1290                break;
1291              case 0x38: // '8'
1292                byteValue = (byte) 0x80;
1293                break;
1294              case 0x39: // '9'
1295                byteValue = (byte) 0x90;
1296                break;
1297              case 0x41: // 'A'
1298              case 0x61: // 'a'
1299                byteValue = (byte) 0xA0;
1300                break;
1301              case 0x42: // 'B'
1302              case 0x62: // 'b'
1303                byteValue = (byte) 0xB0;
1304                break;
1305              case 0x43: // 'C'
1306              case 0x63: // 'c'
1307                byteValue = (byte) 0xC0;
1308                break;
1309              case 0x44: // 'D'
1310              case 0x64: // 'd'
1311                byteValue = (byte) 0xD0;
1312                break;
1313              case 0x45: // 'E'
1314              case 0x65: // 'e'
1315                byteValue = (byte) 0xE0;
1316                break;
1317              case 0x46: // 'F'
1318              case 0x66: // 'f'
1319                byteValue = (byte) 0xF0;
1320                break;
1321              default:
1322                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1323                    filterString, equalPos+i+1);
1324                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1325            }
1326
1327            switch (valueBytes[++i])
1328            {
1329              case 0x30: // '0'
1330                break;
1331              case 0x31: // '1'
1332                byteValue |= (byte) 0x01;
1333                break;
1334              case 0x32: // '2'
1335                byteValue |= (byte) 0x02;
1336                break;
1337              case 0x33: // '3'
1338                byteValue |= (byte) 0x03;
1339                break;
1340              case 0x34: // '4'
1341                byteValue |= (byte) 0x04;
1342                break;
1343              case 0x35: // '5'
1344                byteValue |= (byte) 0x05;
1345                break;
1346              case 0x36: // '6'
1347                byteValue |= (byte) 0x06;
1348                break;
1349              case 0x37: // '7'
1350                byteValue |= (byte) 0x07;
1351                break;
1352              case 0x38: // '8'
1353                byteValue |= (byte) 0x08;
1354                break;
1355              case 0x39: // '9'
1356                byteValue |= (byte) 0x09;
1357                break;
1358              case 0x41: // 'A'
1359              case 0x61: // 'a'
1360                byteValue |= (byte) 0x0A;
1361                break;
1362              case 0x42: // 'B'
1363              case 0x62: // 'b'
1364                byteValue |= (byte) 0x0B;
1365                break;
1366              case 0x43: // 'C'
1367              case 0x63: // 'c'
1368                byteValue |= (byte) 0x0C;
1369                break;
1370              case 0x44: // 'D'
1371              case 0x64: // 'd'
1372                byteValue |= (byte) 0x0D;
1373                break;
1374              case 0x45: // 'E'
1375              case 0x65: // 'e'
1376                byteValue |= (byte) 0x0E;
1377                break;
1378              case 0x46: // 'F'
1379              case 0x66: // 'f'
1380                byteValue |= (byte) 0x0F;
1381                break;
1382              default:
1383                LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1384                    filterString, equalPos+i+1);
1385                throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1386            }
1387
1388            buffer.appendByte(byteValue);
1389          }
1390          else
1391          {
1392            buffer.appendByte(valueBytes[i]);
1393          }
1394        }
1395
1396        subFinal = buffer.toByteString();
1397      }
1398      else
1399      {
1400        subFinal = ByteString.wrap(valueBytes, firstPos+1, length);
1401      }
1402    }
1403
1404
1405    return new LDAPFilter(FilterType.SUBSTRING, null, null, attrDesc, null,
1406                          subInitial, subAny, subFinal, null, false);
1407  }
1408
1409
1410
1411  /**
1412   * Decodes an extensible match filter component based on the provided
1413   * information.
1414   *
1415   * @param  filterString  The filter string containing the information to
1416   *                       decode.
1417   * @param  startPos      The position in the filter string of the first
1418   *                       character in the extensible match filter.
1419   * @param  equalPos      The position of the equal sign in the extensible
1420   *                       match filter.
1421   * @param  endPos        The position of the first character after the end of
1422   *                       the extensible match filter.
1423   *
1424   * @return  The decoded LDAP filter.
1425   *
1426   * @throws  LDAPException  If a problem occurs while attempting to decode the
1427   *                         extensible match filter.
1428   */
1429  private static LDAPFilter decodeExtensibleMatchFilter(String filterString,
1430                                                        int startPos,
1431                                                        int equalPos,
1432                                                        int endPos)
1433          throws LDAPException
1434  {
1435    String  attributeType  = null;
1436    boolean dnAttributes   = false;
1437    String  matchingRuleID = null;
1438
1439
1440    // Look at the first character.  If it is a colon, then it must be followed
1441    // by either the string "dn" or the matching rule ID.  If it is not, then
1442    // must be the attribute description.
1443    String lowerLeftStr =
1444         toLowerCase(filterString.substring(startPos, equalPos));
1445    if (filterString.charAt(startPos) == ':')
1446    {
1447      // See if it starts with ":dn".  Otherwise, it much be the matching rule ID.
1448      if (lowerLeftStr.startsWith(":dn:"))
1449      {
1450        dnAttributes = true;
1451
1452        if(startPos+4 < equalPos-1)
1453        {
1454          matchingRuleID = filterString.substring(startPos+4, equalPos-1);
1455        }
1456      }
1457      else
1458      {
1459        matchingRuleID = filterString.substring(startPos+1, equalPos-1);
1460      }
1461    }
1462    else
1463    {
1464      int colonPos = filterString.indexOf(':',startPos);
1465      if (colonPos < 0)
1466      {
1467        LocalizableMessage message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_COLON.get(
1468            filterString, startPos);
1469        throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1470      }
1471
1472
1473      attributeType = filterString.substring(startPos, colonPos);
1474
1475
1476      // If there is anything left, then it should be ":dn" and/or ":" followed
1477      // by the matching rule ID.
1478      if (colonPos < equalPos-1)
1479      {
1480        if (lowerLeftStr.startsWith(":dn:", colonPos - startPos))
1481        {
1482          dnAttributes = true;
1483
1484          if (colonPos+4 < equalPos-1)
1485          {
1486            matchingRuleID = filterString.substring(colonPos+4, equalPos-1);
1487          }
1488        }
1489        else
1490        {
1491          matchingRuleID = filterString.substring(colonPos+1, equalPos-1);
1492        }
1493      }
1494    }
1495
1496
1497    // Parse out the attribute value.
1498    byte[] valueBytes = getBytes(filterString.substring(equalPos+1, endPos));
1499    boolean hasEscape = false;
1500    for (byte valueByte : valueBytes)
1501    {
1502      if (valueByte == 0x5C)
1503      {
1504        hasEscape = true;
1505        break;
1506      }
1507    }
1508
1509    ByteString value;
1510    if (hasEscape)
1511    {
1512      ByteStringBuilder valueBuffer = new ByteStringBuilder(valueBytes.length);
1513      for (int i=0; i < valueBytes.length; i++)
1514      {
1515        if (valueBytes[i] == 0x5C) // The backslash character
1516        {
1517          // The next two bytes must be the hex characters that comprise the
1518          // binary value.
1519          if (i + 2 >= valueBytes.length)
1520          {
1521            LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1522                filterString, equalPos+i+1);
1523            throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1524          }
1525
1526          byte byteValue = 0;
1527          switch (valueBytes[++i])
1528          {
1529            case 0x30: // '0'
1530              break;
1531            case 0x31: // '1'
1532              byteValue = (byte) 0x10;
1533              break;
1534            case 0x32: // '2'
1535              byteValue = (byte) 0x20;
1536              break;
1537            case 0x33: // '3'
1538              byteValue = (byte) 0x30;
1539              break;
1540            case 0x34: // '4'
1541              byteValue = (byte) 0x40;
1542              break;
1543            case 0x35: // '5'
1544              byteValue = (byte) 0x50;
1545              break;
1546            case 0x36: // '6'
1547              byteValue = (byte) 0x60;
1548              break;
1549            case 0x37: // '7'
1550              byteValue = (byte) 0x70;
1551              break;
1552            case 0x38: // '8'
1553              byteValue = (byte) 0x80;
1554              break;
1555            case 0x39: // '9'
1556              byteValue = (byte) 0x90;
1557              break;
1558            case 0x41: // 'A'
1559            case 0x61: // 'a'
1560              byteValue = (byte) 0xA0;
1561              break;
1562            case 0x42: // 'B'
1563            case 0x62: // 'b'
1564              byteValue = (byte) 0xB0;
1565              break;
1566            case 0x43: // 'C'
1567            case 0x63: // 'c'
1568              byteValue = (byte) 0xC0;
1569              break;
1570            case 0x44: // 'D'
1571            case 0x64: // 'd'
1572              byteValue = (byte) 0xD0;
1573              break;
1574            case 0x45: // 'E'
1575            case 0x65: // 'e'
1576              byteValue = (byte) 0xE0;
1577              break;
1578            case 0x46: // 'F'
1579            case 0x66: // 'f'
1580              byteValue = (byte) 0xF0;
1581              break;
1582            default:
1583              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1584                  filterString, equalPos+i+1);
1585              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1586          }
1587
1588          switch (valueBytes[++i])
1589          {
1590            case 0x30: // '0'
1591              break;
1592            case 0x31: // '1'
1593              byteValue |= (byte) 0x01;
1594              break;
1595            case 0x32: // '2'
1596              byteValue |= (byte) 0x02;
1597              break;
1598            case 0x33: // '3'
1599              byteValue |= (byte) 0x03;
1600              break;
1601            case 0x34: // '4'
1602              byteValue |= (byte) 0x04;
1603              break;
1604            case 0x35: // '5'
1605              byteValue |= (byte) 0x05;
1606              break;
1607            case 0x36: // '6'
1608              byteValue |= (byte) 0x06;
1609              break;
1610            case 0x37: // '7'
1611              byteValue |= (byte) 0x07;
1612              break;
1613            case 0x38: // '8'
1614              byteValue |= (byte) 0x08;
1615              break;
1616            case 0x39: // '9'
1617              byteValue |= (byte) 0x09;
1618              break;
1619            case 0x41: // 'A'
1620            case 0x61: // 'a'
1621              byteValue |= (byte) 0x0A;
1622              break;
1623            case 0x42: // 'B'
1624            case 0x62: // 'b'
1625              byteValue |= (byte) 0x0B;
1626              break;
1627            case 0x43: // 'C'
1628            case 0x63: // 'c'
1629              byteValue |= (byte) 0x0C;
1630              break;
1631            case 0x44: // 'D'
1632            case 0x64: // 'd'
1633              byteValue |= (byte) 0x0D;
1634              break;
1635            case 0x45: // 'E'
1636            case 0x65: // 'e'
1637              byteValue |= (byte) 0x0E;
1638              break;
1639            case 0x46: // 'F'
1640            case 0x66: // 'f'
1641              byteValue |= (byte) 0x0F;
1642              break;
1643            default:
1644              LocalizableMessage message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
1645                  filterString, equalPos+i+1);
1646              throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1647          }
1648
1649          valueBuffer.appendByte(byteValue);
1650        }
1651        else
1652        {
1653          valueBuffer.appendByte(valueBytes[i]);
1654        }
1655      }
1656
1657      value = valueBuffer.toByteString();
1658    }
1659    else
1660    {
1661      value = ByteString.wrap(valueBytes);
1662    }
1663
1664
1665    // Make sure that the filter has at least one of an attribute description
1666    // and/or a matching rule ID.
1667    if (attributeType == null && matchingRuleID == null)
1668    {
1669      LocalizableMessage message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR.get(
1670          filterString, startPos);
1671      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, message);
1672    }
1673
1674
1675    return new LDAPFilter(FilterType.EXTENSIBLE_MATCH, null, null,
1676                          attributeType, value, null, null, null,
1677                          matchingRuleID, dnAttributes);
1678  }
1679
1680
1681
1682  /**
1683   * Retrieves the filter type for this search filter.
1684   *
1685   * @return  The filter type for this search filter.
1686   */
1687  @Override
1688  public FilterType getFilterType()
1689  {
1690    return filterType;
1691  }
1692
1693
1694
1695  /**
1696   * Retrieves the set of subordinate filter components for AND or OR searches.
1697   * The contents of the returned list may be altered by the caller.
1698   *
1699   * @return  The set of subordinate filter components for AND and OR searches,
1700   *          or <CODE>null</CODE> if this is not an AND or OR search.
1701   */
1702  @Override
1703  public ArrayList<RawFilter> getFilterComponents()
1704  {
1705    return filterComponents;
1706  }
1707
1708
1709
1710  /**
1711   * Retrieves the subordinate filter component for NOT searches.
1712   *
1713   * @return  The subordinate filter component for NOT searches, or
1714   *          <CODE>null</CODE> if this is not a NOT search.
1715   */
1716  @Override
1717  public RawFilter getNOTComponent()
1718  {
1719    return notComponent;
1720  }
1721
1722
1723
1724  /**
1725   * Retrieves the attribute description for this search filter.  This will not be
1726   * applicable for AND, OR, or NOT filters.
1727   *
1728   * @return  The attribute description for this search filter, or <CODE>null</CODE> if
1729   *          there is none.
1730   */
1731  @Override
1732  public String getAttributeType()
1733  {
1734    return attributeDescription;
1735  }
1736
1737
1738
1739  /**
1740   * Retrieves the assertion value for this search filter.  This will only be
1741   * applicable for equality, greater or equal, less or equal, approximate, or
1742   * extensible matching filters.
1743   *
1744   * @return  The assertion value for this search filter, or <CODE>null</CODE>
1745   *          if there is none.
1746   */
1747  @Override
1748  public ByteString getAssertionValue()
1749  {
1750    return assertionValue;
1751  }
1752
1753
1754
1755  /**
1756   * Retrieves the subInitial component for this substring filter.  This is only
1757   * applicable for substring search filters, but even substring filters might
1758   * not have a value for this component.
1759   *
1760   * @return  The subInitial component for this substring filter, or
1761   *          <CODE>null</CODE> if there is none.
1762   */
1763  @Override
1764  public ByteString getSubInitialElement()
1765  {
1766    return subInitialElement;
1767  }
1768
1769
1770
1771  /**
1772   * Specifies the subInitial element for this substring filter.  This will be
1773   * ignored for all other types of filters.
1774   *
1775   * @param  subInitialElement  The subInitial element for this substring
1776   *                            filter.
1777   */
1778  public void setSubInitialElement(ByteString subInitialElement)
1779  {
1780    this.subInitialElement = subInitialElement;
1781  }
1782
1783
1784
1785  /**
1786   * Retrieves the set of subAny elements for this substring filter.  This is
1787   * only applicable for substring search filters, and even then may be null or
1788   * empty for some substring filters.
1789   *
1790   * @return  The set of subAny elements for this substring filter, or
1791   *          <CODE>null</CODE> if there are none.
1792   */
1793  @Override
1794  public ArrayList<ByteString> getSubAnyElements()
1795  {
1796    return subAnyElements;
1797  }
1798
1799
1800
1801  /**
1802   * Retrieves the subFinal element for this substring filter.  This is not
1803   * applicable for any other filter type, and may not be provided even for some
1804   * substring filters.
1805   *
1806   * @return  The subFinal element for this substring filter, or
1807   *          <CODE>null</CODE> if there is none.
1808   */
1809  @Override
1810  public ByteString getSubFinalElement()
1811  {
1812    return subFinalElement;
1813  }
1814
1815
1816
1817  /**
1818   * Retrieves the matching rule ID for this extensible match filter.  This is
1819   * not applicable for any other type of filter and may not be included in
1820   * some extensible matching filters.
1821   *
1822   * @return  The matching rule ID for this extensible match filter, or
1823   *          <CODE>null</CODE> if there is none.
1824   */
1825  @Override
1826  public String getMatchingRuleID()
1827  {
1828    return matchingRuleID;
1829  }
1830
1831
1832
1833  /**
1834   * Retrieves the value of the DN attributes flag for this extensible match
1835   * filter, which indicates whether to perform matching on the components of
1836   * the DN.  This does not apply for any other type of filter.
1837   *
1838   * @return  The value of the DN attributes flag for this extensibleMatch
1839   *          filter.
1840   */
1841  @Override
1842  public boolean getDNAttributes()
1843  {
1844    return dnAttributes;
1845  }
1846
1847
1848
1849  /**
1850   * Converts this LDAP filter to a search filter that may be used by the
1851   * Directory Server's core processing.
1852   *
1853   * @return  The generated search filter.
1854   *
1855   * @throws  DirectoryException  If a problem occurs while attempting to
1856   *                              construct the search filter.
1857   */
1858  @Override
1859  public SearchFilter toSearchFilter()
1860         throws DirectoryException
1861  {
1862    ArrayList<SearchFilter> subComps;
1863    if (filterComponents == null)
1864    {
1865      subComps = null;
1866    }
1867    else
1868    {
1869      int compSize = filterComponents.size();
1870      if (compSize == 1)
1871      {
1872        // the filter can be simplified to the single component
1873        return filterComponents.get(0).toSearchFilter();
1874      }
1875      subComps = new ArrayList<>(compSize);
1876      for (RawFilter f : filterComponents)
1877      {
1878        subComps.add(f.toSearchFilter());
1879      }
1880    }
1881
1882
1883    SearchFilter notComp;
1884    if (notComponent == null)
1885    {
1886      notComp = null;
1887    }
1888    else
1889    {
1890      notComp = notComponent.toSearchFilter();
1891    }
1892
1893    AttributeDescription attrDesc = null;
1894    if (attributeDescription != null)
1895    {
1896      try
1897      {
1898        attrDesc = AttributeDescription.valueOf(attributeDescription);
1899      }
1900      catch (LocalizedIllegalArgumentException e)
1901      {
1902        throw new DirectoryException(ResultCode.PROTOCOL_ERROR, e.getMessageObject(), e);
1903      }
1904    }
1905    if (assertionValue != null && attrDesc == null)
1906    {
1907      if (matchingRuleID == null)
1908      {
1909        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
1910            ERR_LDAP_FILTER_VALUE_WITH_NO_ATTR_OR_MR.get());
1911      }
1912
1913      try
1914      {
1915        DirectoryServer.getSchema().getMatchingRule(matchingRuleID);
1916      }
1917      catch (UnknownSchemaElementException e)
1918      {
1919        throw new DirectoryException(ResultCode.INAPPROPRIATE_MATCHING,
1920            ERR_LDAP_FILTER_UNKNOWN_MATCHING_RULE.get(matchingRuleID));
1921      }
1922    }
1923
1924    ArrayList<ByteString> subAnyComps =
1925        subAnyElements != null ? new ArrayList<ByteString>(subAnyElements) : null;
1926    return new SearchFilter(filterType, subComps, notComp, attrDesc,
1927                            assertionValue, subInitialElement, subAnyComps,
1928                            subFinalElement, matchingRuleID, dnAttributes);
1929  }
1930
1931  /**
1932   * Appends a string representation of this search filter to the provided
1933   * buffer.
1934   *
1935   * @param  buffer  The buffer to which the information should be appended.
1936   */
1937  @Override
1938  public void toString(StringBuilder buffer)
1939  {
1940    switch (filterType)
1941    {
1942      case AND:
1943        buffer.append("(&");
1944        for (RawFilter f : filterComponents)
1945        {
1946          f.toString(buffer);
1947        }
1948        buffer.append(")");
1949        break;
1950      case OR:
1951        buffer.append("(|");
1952        for (RawFilter f : filterComponents)
1953        {
1954          f.toString(buffer);
1955        }
1956        buffer.append(")");
1957        break;
1958      case NOT:
1959        buffer.append("(!");
1960        notComponent.toString(buffer);
1961        buffer.append(")");
1962        break;
1963      case EQUALITY:
1964        buffer.append("(");
1965        buffer.append(attributeDescription);
1966        buffer.append("=");
1967        valueToFilterString(buffer, assertionValue);
1968        buffer.append(")");
1969        break;
1970      case SUBSTRING:
1971        buffer.append("(");
1972        buffer.append(attributeDescription);
1973        buffer.append("=");
1974
1975        if (subInitialElement != null)
1976        {
1977          valueToFilterString(buffer, subInitialElement);
1978        }
1979
1980        if (subAnyElements != null && !subAnyElements.isEmpty())
1981        {
1982          for (ByteString s : subAnyElements)
1983          {
1984            buffer.append("*");
1985            valueToFilterString(buffer, s);
1986          }
1987        }
1988
1989        buffer.append("*");
1990
1991        if (subFinalElement != null)
1992        {
1993          valueToFilterString(buffer, subFinalElement);
1994        }
1995
1996        buffer.append(")");
1997        break;
1998      case GREATER_OR_EQUAL:
1999        buffer.append("(");
2000        buffer.append(attributeDescription);
2001        buffer.append(">=");
2002        valueToFilterString(buffer, assertionValue);
2003        buffer.append(")");
2004        break;
2005      case LESS_OR_EQUAL:
2006        buffer.append("(");
2007        buffer.append(attributeDescription);
2008        buffer.append("<=");
2009        valueToFilterString(buffer, assertionValue);
2010        buffer.append(")");
2011        break;
2012      case PRESENT:
2013        buffer.append("(");
2014        buffer.append(attributeDescription);
2015        buffer.append("=*)");
2016        break;
2017      case APPROXIMATE_MATCH:
2018        buffer.append("(");
2019        buffer.append(attributeDescription);
2020        buffer.append("~=");
2021        valueToFilterString(buffer, assertionValue);
2022        buffer.append(")");
2023        break;
2024      case EXTENSIBLE_MATCH:
2025        buffer.append("(");
2026
2027        if (attributeDescription != null)
2028        {
2029          buffer.append(attributeDescription);
2030        }
2031
2032        if (dnAttributes)
2033        {
2034          buffer.append(":dn");
2035        }
2036
2037        if (matchingRuleID != null)
2038        {
2039          buffer.append(":");
2040          buffer.append(matchingRuleID);
2041        }
2042
2043        buffer.append(":=");
2044        valueToFilterString(buffer, assertionValue);
2045        buffer.append(")");
2046        break;
2047    }
2048  }
2049
2050  /**
2051   * Returns the {@code objectClass} presence filter {@code (objectClass=*)}.
2052   *
2053   * @return The {@code objectClass} presence filter {@code (objectClass=*)}.
2054   */
2055  public static LDAPFilter objectClassPresent()
2056  {
2057    if (objectClassPresent == null)
2058    {
2059      try
2060      {
2061        objectClassPresent = LDAPFilter.decode("(objectclass=*)");
2062      }
2063      catch (LDAPException canNeverHappen)
2064      {
2065        logger.traceException(canNeverHappen);
2066      }
2067    }
2068    return objectClassPresent;
2069  }
2070}