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