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