View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.api.ldap.model.filter;
21  
22  
23  import java.text.ParseException;
24  
25  import org.apache.directory.api.i18n.I18n;
26  import org.apache.directory.api.ldap.model.entry.AttributeUtils;
27  import org.apache.directory.api.ldap.model.entry.BinaryValue;
28  import org.apache.directory.api.ldap.model.entry.StringValue;
29  import org.apache.directory.api.ldap.model.entry.Value;
30  import org.apache.directory.api.ldap.model.exception.LdapException;
31  import org.apache.directory.api.ldap.model.schema.AttributeType;
32  import org.apache.directory.api.ldap.model.schema.SchemaManager;
33  import org.apache.directory.api.util.Chars;
34  import org.apache.directory.api.util.Hex;
35  import org.apache.directory.api.util.Position;
36  import org.apache.directory.api.util.Strings;
37  import org.apache.directory.api.util.Unicode;
38  
39  
40  /**
41   * This class parse a Ldap filter. The grammar is given in RFC 4515
42   *
43   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
44   */
45  public class FilterParser
46  {
47      /**
48       * Creates a filter parser implementation.
49       */
50      public FilterParser()
51      {
52      }
53  
54  
55      /**
56       * Parse an extensible
57       *
58       * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
59       *                  / ( [":dn"] ':' oid ":=" assertionvalue )
60       * matchingrule   = ":" oid
61       */
62      private static ExprNode parseExtensible( SchemaManager schemaManager, String attribute, byte[] filter,
63          Position pos, boolean relaxed ) throws LdapException, ParseException
64      {
65          ExtensibleNode node = null;
66  
67          if ( schemaManager != null )
68          {
69              AttributeType attributeType = schemaManager.getAttributeType( attribute );
70  
71              if ( attributeType != null )
72              {
73                  node = new ExtensibleNode( attributeType );
74              }
75              else
76              {
77                  return UndefinedNode.UNDEFINED_NODE;
78              }
79          }
80          else
81          {
82              node = new ExtensibleNode( attribute );
83          }
84  
85          if ( attribute != null )
86          {
87              // First check if we have a ":dn"
88              if ( Strings.areEquals( filter, pos.start, "dn" ) >= 0 )
89              {
90                  // Set the dnAttributes flag and move forward in the string
91                  node.setDnAttributes( true );
92                  pos.start += 2;
93              }
94              else
95              {
96                  // Push back the ':'
97                  pos.start--;
98              }
99  
100             // Do we have a MatchingRule ?
101             if ( Strings.byteAt( filter, pos.start ) == ':' )
102             {
103                 pos.start++;
104 
105                 if ( Strings.byteAt( filter, pos.start ) == '=' )
106                 {
107                     pos.start++;
108 
109                     // Get the assertionValue
110                     node.setValue( parseAssertionValue( schemaManager, attribute, filter, pos ) );
111 
112                     return node;
113                 }
114                 else
115                 {
116                     String matchingRuleId = AttributeUtils.parseAttribute( filter, pos, false, relaxed );
117 
118                     node.setMatchingRuleId( matchingRuleId );
119 
120                     if ( Strings.areEquals( filter, pos.start, ":=" ) >= 0 )
121                     {
122                         pos.start += 2;
123 
124                         // Get the assertionValue
125                         node.setValue( parseAssertionValue( schemaManager, attribute, filter, pos ) );
126 
127                         return node;
128                     }
129                     else
130                     {
131                         throw new ParseException( I18n.err( I18n.ERR_04146 ), pos.start );
132                     }
133                 }
134             }
135             else
136             {
137                 throw new ParseException( I18n.err( I18n.ERR_04147 ), pos.start );
138             }
139         }
140         else
141         {
142             boolean oidRequested = false;
143 
144             // First check if we have a ":dn"
145             if ( Strings.areEquals( filter, pos.start, ":dn" ) >= 0 )
146             {
147                 // Set the dnAttributes flag and move forward in the string
148                 node.setDnAttributes( true );
149                 pos.start += 3;
150             }
151             else
152             {
153                 oidRequested = true;
154             }
155 
156             // Do we have a MatchingRule ?
157             if ( Strings.byteAt( filter, pos.start ) == ':' )
158             {
159                 pos.start++;
160 
161                 if ( Strings.byteAt( filter, pos.start ) == '=' )
162                 {
163                     if ( oidRequested )
164                     {
165                         throw new ParseException( I18n.err( I18n.ERR_04148 ), pos.start );
166                     }
167 
168                     pos.start++;
169 
170                     // Get the assertionValue
171                     node.setValue( parseAssertionValue( schemaManager, attribute, filter, pos ) );
172 
173                     return node;
174                 }
175                 else
176                 {
177                     String matchingRuleId = AttributeUtils.parseAttribute( filter, pos, false, relaxed );
178 
179                     node.setMatchingRuleId( matchingRuleId );
180 
181                     if ( Strings.areEquals( filter, pos.start, ":=" ) >= 0 )
182                     {
183                         pos.start += 2;
184 
185                         // Get the assertionValue
186                         node.setValue( parseAssertionValue( schemaManager, attribute, filter, pos ) );
187 
188                         return node;
189                     }
190                     else
191                     {
192                         throw new ParseException( I18n.err( I18n.ERR_04146 ), pos.start );
193                     }
194                 }
195             }
196             else
197             {
198                 throw new ParseException( I18n.err( I18n.ERR_04147 ), pos.start );
199             }
200         }
201     }
202 
203 
204     /**
205      * An assertion value :
206      * assertionvalue = valueencoding
207      * valueencoding  = 0*(normal / escaped)
208      * normal         = UTF1SUBSET / UTFMB
209      * escaped        = '\' HEX HEX
210      * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
211      * UTF1SUBSET     = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\')
212      * UTFMB          = UTF2 / UTF3 / UTF4
213      * UTF0           = %x80-BF
214      * UTF2           = %xC2-DF UTF0
215      * UTF3           = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0
216      * UTF4           = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0
217      *
218      * With the specific constraints (RFC 4515):
219      *    "The <valueencoding> rule ensures that the entire filter string is a"
220      *    "valid UTF-8 string and provides that the octets that represent the"
221      *    "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII"
222      *    "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a"
223      *    "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits"
224      *    "representing the value of the encoded octet."
225      *
226      * The incoming String is already transformed from UTF-8 to unicode, so we must assume that the
227      * grammar we have to check is the following :
228      *
229      * assertionvalue = valueencoding
230      * valueencoding  = 0*(normal / escaped)
231      * normal         = unicodeSubset
232      * escaped        = '\' HEX HEX
233      * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
234      * unicodeSubset     = %x01-27 / %x2B-5B / %x5D-FFFF
235      */
236     private static Value<?> parseAssertionValue( SchemaManager schemaManager, String attribute, byte[] filter,
237         Position pos ) throws ParseException
238     {
239         byte b = Strings.byteAt( filter, pos.start );
240 
241         // Create a buffer big enough to contain the value once converted
242         byte[] value = new byte[filter.length - pos.start];
243         int current = 0;
244 
245         do
246         {
247             if ( Unicode.isUnicodeSubset( b ) )
248             {
249                 value[current++] = b;
250                 pos.start++;
251             }
252             else if ( Strings.isCharASCII( filter, pos.start, '\\' ) )
253             {
254                 // Maybe an escaped
255                 pos.start++;
256 
257                 // First hex
258                 if ( Chars.isHex( filter, pos.start ) )
259                 {
260                     pos.start++;
261                 }
262                 else
263                 {
264                     throw new ParseException( I18n.err( I18n.ERR_04149 ), pos.start );
265                 }
266 
267                 // second hex
268                 if ( Chars.isHex( filter, pos.start ) )
269                 {
270                     value[current++] = Hex.getHexValue( filter[pos.start - 1], filter[pos.start] );
271                     pos.start++;
272                 }
273                 else
274                 {
275                     throw new ParseException( I18n.err( I18n.ERR_04149 ), pos.start );
276                 }
277             }
278             else
279             {
280                 // not a valid char, so let's get out
281                 break;
282             }
283         }
284         while ( ( b = Strings.byteAt( filter, pos.start ) ) != '\0' );
285 
286         if ( current != 0 )
287         {
288             byte[] result = new byte[current];
289             System.arraycopy( value, 0, result, 0, current );
290 
291             if ( schemaManager != null )
292             {
293                 AttributeType attributeType = schemaManager.getAttributeType( attribute );
294 
295                 if ( attributeType == null )
296                 {
297                     return new BinaryValue( result );
298                 }
299 
300                 if ( attributeType.getSyntax().isHumanReadable() )
301                 {
302                     return new StringValue( Strings.utf8ToString( result ) );
303                 }
304                 else
305                 {
306                     return new BinaryValue( result );
307                 }
308             }
309             else
310             {
311                 return new BinaryValue( result );
312             }
313         }
314         else
315         {
316             if ( schemaManager != null )
317             {
318                 AttributeType attributeType = schemaManager.getAttributeType( attribute );
319 
320                 if ( attributeType.getEquality().getSyntax().isHumanReadable() )
321                 {
322                     return new StringValue( ( String ) null );
323                 }
324                 else
325                 {
326                     return new BinaryValue( null );
327                 }
328             }
329             else
330             {
331                 return new BinaryValue( ( byte[] ) null );
332             }
333         }
334     }
335 
336 
337     /**
338      * Parse a substring
339      */
340     private static ExprNode parseSubstring( SchemaManager schemaManager, String attribute, Value<?> initial,
341         byte[] filter, Position pos )
342         throws ParseException, LdapException
343     {
344         if ( Strings.isCharASCII( filter, pos.start, '*' ) )
345         {
346             // We have found a '*' : this is a substring
347             SubstringNode node = null;
348 
349             if ( schemaManager != null )
350             {
351                 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute );
352 
353                 if ( attributeType != null )
354                 {
355                     node = new SubstringNode( schemaManager.lookupAttributeTypeRegistry( attribute ) );
356                 }
357                 else
358                 {
359                     return null;
360                 }
361             }
362             else
363             {
364                 node = new SubstringNode( attribute );
365             }
366 
367             if ( ( initial != null ) && !initial.isNull() )
368             {
369                 // We have a substring starting with a value : val*...
370                 // Set the initial value. It must be a String
371                 String initialStr = initial.getString();
372                 node.setInitial( initialStr );
373             }
374 
375             pos.start++;
376 
377             //
378             while ( true )
379             {
380                 Value<?> assertionValue = parseAssertionValue( schemaManager, attribute, filter, pos );
381 
382                 // Is there anything else but a ')' after the value ?
383                 if ( Strings.isCharASCII( filter, pos.start, ')' ) )
384                 {
385                     // Nope : as we have had [initial] '*' (any '*' ) *,
386                     // this is the final
387                     if ( !assertionValue.isNull() )
388                     {
389                         String finalStr = assertionValue.getString();
390                         node.setFinal( finalStr );
391                     }
392 
393                     return node;
394                 }
395                 else if ( Strings.isCharASCII( filter, pos.start, '*' ) )
396                 {
397                     // We have a '*' : it's an any
398                     // If the value is empty, that means we have more than
399                     // one consecutive '*' : do nothing in this case.
400                     if ( !assertionValue.isNull() )
401                     {
402                         String anyStr = assertionValue.getString();
403                         node.addAny( anyStr );
404                     }
405 
406                     pos.start++;
407                 }
408                 else
409                 {
410                     // This is an error
411                     throw new ParseException( I18n.err( I18n.ERR_04150 ), pos.start );
412                 }
413             }
414         }
415         else
416         {
417             // This is an error
418             throw new ParseException( I18n.err( I18n.ERR_04150 ), pos.start );
419         }
420     }
421 
422 
423     /**
424      * Here is the grammar to parse :
425      *
426      * simple    ::= '=' assertionValue
427      * present   ::= '=' '*'
428      * substring ::= '=' [initial] any [final]
429      * initial   ::= assertionValue
430      * any       ::= '*' ( assertionValue '*')*
431      *
432      * As we can see, there is an ambiguity in the grammar : attr=* can be
433      * seen as a present or as a substring. As stated in the RFC :
434      *
435      * "Note that although both the <substring> and <present> productions in"
436      * "the grammar above can produce the "attr=*" construct, this construct"
437      * "is used only to denote a presence filter." (RFC 4515, 3)
438      *
439      * We have also to consider the difference between a substring and the
440      * equality node : this last node does not contain a '*'
441      *
442      * @param attributeType
443      * @param filter
444      * @param pos
445      * @return
446      */
447     @SuppressWarnings(
448         { "rawtypes", "unchecked" })
449     private static ExprNode parsePresenceEqOrSubstring( SchemaManager schemaManager, String attribute, byte[] filter,
450         Position pos )
451         throws ParseException, LdapException
452     {
453         if ( Strings.isCharASCII( filter, pos.start, '*' ) )
454         {
455             // To be a present node, the next char should be a ')'
456             pos.start++;
457 
458             if ( Strings.isCharASCII( filter, pos.start, ')' ) )
459             {
460                 // This is a present node
461                 if ( schemaManager != null )
462                 {
463                     AttributeType attributeType = schemaManager.getAttributeType( attribute );
464 
465                     if ( attributeType != null )
466                     {
467                         return new PresenceNode( attributeType );
468                     }
469                     else
470                     {
471                         return null;
472                     }
473                 }
474                 else
475                 {
476                     return new PresenceNode( attribute );
477                 }
478             }
479             else
480             {
481                 // Definitively a substring with no initial or an error
482                 // Push back the '*' on the string
483                 pos.start--;
484                 return parseSubstring( schemaManager, attribute, null, filter, pos );
485             }
486         }
487         else if ( Strings.isCharASCII( filter, pos.start, ')' ) )
488         {
489             // An empty equality Node
490             if ( schemaManager != null )
491             {
492                 AttributeType attributeType = schemaManager.getAttributeType( attribute );
493 
494                 if ( attributeType != null )
495                 {
496                     return new EqualityNode( attributeType, new BinaryValue( ( byte[] ) null ) );
497                 }
498 
499                 else
500                 {
501                     return null;
502                 }
503             }
504             else
505             {
506                 return new EqualityNode( attribute, new BinaryValue( ( byte[] ) null ) );
507             }
508         }
509         else
510         {
511             // A substring or an equality node
512             Value<?> value = parseAssertionValue( schemaManager, attribute, filter, pos );
513 
514             // Is there anything else but a ')' after the value ?
515             if ( Strings.isCharASCII( filter, pos.start, ')' ) )
516             {
517                 // This is an equality node
518                 if ( schemaManager != null )
519                 {
520                     AttributeType attributeType = schemaManager.getAttributeType( attribute );
521 
522                     if ( attributeType != null )
523                     {
524                         return new EqualityNode( attributeType, value );
525                     }
526                     else
527                     {
528                         return null;
529                     }
530                 }
531                 else
532                 {
533                     return new EqualityNode( attribute, value );
534                 }
535             }
536 
537             return parseSubstring( schemaManager, attribute, value, filter, pos );
538         }
539     }
540 
541 
542     /**
543      * Parse the following grammar :
544      * item           = simple / present / substring / extensible
545      * simple         = attr filtertype assertionvalue
546      * filtertype     = '=' / '~=' / '>=' / '<='
547      * present        = attr '=' '*'
548      * substring      = attr '=' [initial] any [final]
549      * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
550      *                  / ( [":dn"] ':' oid ":=" assertionvalue )
551      * matchingrule   = ":" oid
552      *
553      * An item starts with an attribute or a colon.
554      */
555     @SuppressWarnings(
556         { "rawtypes", "unchecked" })
557     private static ExprNode parseItem( SchemaManager schemaManager, byte[] filter, Position pos, byte b,
558         boolean relaxed ) throws ParseException, LdapException
559     {
560         String attribute = null;
561 
562         if ( b == '\0' )
563         {
564             throw new ParseException( I18n.err( I18n.ERR_04151 ), pos.start );
565         }
566 
567         if ( b == ':' )
568         {
569             // If we have a colon, then the item is an extensible one
570             return parseExtensible( schemaManager, null, filter, pos, relaxed );
571         }
572         else
573         {
574             // We must have an attribute
575             attribute = AttributeUtils.parseAttribute( filter, pos, true, relaxed );
576 
577             // Now, we may have a present, substring, simple or an extensible
578             b = Strings.byteAt( filter, pos.start );
579 
580             switch ( b )
581             {
582                 case '=':
583                     // It can be a presence, an equal or a substring
584                     pos.start++;
585                     return parsePresenceEqOrSubstring( schemaManager, attribute, filter, pos );
586 
587                 case '~':
588                     // Approximate node
589                     pos.start++;
590 
591                     // Check that we have a '='
592                     if ( !Strings.isCharASCII( filter, pos.start, '=' ) )
593                     {
594                         throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start );
595                     }
596 
597                     pos.start++;
598 
599                     // Parse the value and create the node
600                     if ( schemaManager == null )
601                     {
602                         return new ApproximateNode( attribute, parseAssertionValue( schemaManager, attribute, filter,
603                             pos ) );
604                     }
605                     else
606                     {
607                         AttributeType attributeType = schemaManager.getAttributeType( attribute );
608 
609                         if ( attributeType != null )
610                         {
611                             return new ApproximateNode( attributeType, parseAssertionValue( schemaManager, attribute,
612                                 filter, pos ) );
613                         }
614                         else
615                         {
616                             return UndefinedNode.UNDEFINED_NODE;
617                         }
618                     }
619 
620                 case '>':
621                     // Greater or equal node
622                     pos.start++;
623 
624                     // Check that we have a '='
625                     if ( !Strings.isCharASCII( filter, pos.start, '=' ) )
626                     {
627                         throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start );
628                     }
629 
630                     pos.start++;
631 
632                     // Parse the value and create the node
633                     if ( schemaManager == null )
634                     {
635                         return new GreaterEqNode( attribute,
636                             parseAssertionValue( schemaManager, attribute, filter, pos ) );
637                     }
638                     else
639                     {
640                         AttributeType attributeType = schemaManager.getAttributeType( attribute );
641 
642                         if ( attributeType != null )
643                         {
644                             return new GreaterEqNode( attributeType, parseAssertionValue( schemaManager, attribute,
645                                 filter, pos ) );
646                         }
647                         else
648                         {
649                             return UndefinedNode.UNDEFINED_NODE;
650                         }
651                     }
652 
653                 case '<':
654                     // Less or equal node
655                     pos.start++;
656 
657                     // Check that we have a '='
658                     if ( !Strings.isCharASCII( filter, pos.start, '=' ) )
659                     {
660                         throw new ParseException( I18n.err( I18n.ERR_04152 ), pos.start );
661                     }
662 
663                     pos.start++;
664 
665                     // Parse the value and create the node
666                     if ( schemaManager == null )
667                     {
668                         return new LessEqNode( attribute, parseAssertionValue( schemaManager, attribute, filter, pos ) );
669                     }
670                     else
671                     {
672                         AttributeType attributeType = schemaManager.getAttributeType( attribute );
673 
674                         if ( attributeType != null )
675                         {
676                             return new LessEqNode( attributeType, parseAssertionValue( schemaManager, attribute,
677                                 filter, pos ) );
678                         }
679                         else
680                         {
681                             return UndefinedNode.UNDEFINED_NODE;
682                         }
683                     }
684 
685                 case ':':
686                     // An extensible node
687                     pos.start++;
688                     return parseExtensible( schemaManager, attribute, filter, pos, relaxed );
689 
690                 default:
691                     // This is an error
692                     throw new ParseException( I18n.err( I18n.ERR_04153 ), pos.start );
693             }
694         }
695     }
696 
697 
698     /**
699      * Parse AND, OR and NOT nodes :
700      *
701      * and            = '&' filterlist
702      * or             = '|' filterlist
703      * not            = '!' filter
704      * filterlist     = 1*filter
705      *
706      * @return
707      */
708     private static ExprNode parseBranchNode( SchemaManager schemaManager, ExprNode node, byte[] filter, Position pos,
709         boolean relaxed ) throws ParseException, LdapException
710     {
711         BranchNode branchNode = ( BranchNode ) node;
712         int nbChildren = 0;
713 
714         // We must have at least one filter
715         ExprNode child = parseFilterInternal( schemaManager, filter, pos, relaxed );
716 
717         if ( child != UndefinedNode.UNDEFINED_NODE )
718         {
719             // Add the child to the node children
720             branchNode.addNode( child );
721 
722             if ( branchNode instanceof NotNode )
723             {
724                 return node;
725             }
726 
727             nbChildren++;
728         }
729         else if ( node instanceof AndNode )
730         {
731             return UndefinedNode.UNDEFINED_NODE;
732         }
733 
734         // Now, iterate recusively though all the remaining filters, if any
735         while ( ( child = parseFilterInternal( schemaManager, filter, pos, relaxed ) ) != UndefinedNode.UNDEFINED_NODE )
736         {
737             // Add the child to the node children if not null
738             if ( child != null )
739             {
740                 branchNode.addNode( child );
741                 nbChildren++;
742             }
743             else if ( node instanceof AndNode )
744             {
745                 return UndefinedNode.UNDEFINED_NODE;
746             }
747         }
748 
749         if ( nbChildren > 0 )
750         {
751             return node;
752         }
753         else
754         {
755             return UndefinedNode.UNDEFINED_NODE;
756         }
757     }
758 
759 
760     /**
761      * filtercomp     = and / or / not / item
762      * and            = '&' filterlist
763      * or             = '|' filterlist
764      * not            = '!' filter
765      * item           = simple / present / substring / extensible
766      * simple         = attr filtertype assertionvalue
767      * present        = attr EQUALS ASTERISK
768      * substring      = attr EQUALS [initial] any [final]
769      * extensible     = ( attr [dnattrs]
770      *                    [matchingrule] COLON EQUALS assertionvalue )
771      *                    / ( [dnattrs]
772      *                         matchingrule COLON EQUALS assertionvalue )
773      */
774     private static ExprNode parseFilterComp( SchemaManager schemaManager, byte[] filter, Position pos,
775         boolean relaxed ) throws ParseException, LdapException
776     {
777         ExprNode node = null;
778 
779         if ( pos.start == pos.length )
780         {
781             throw new ParseException( I18n.err( I18n.ERR_04154 ), pos.start );
782         }
783 
784         byte c = Strings.byteAt( filter, pos.start );
785 
786         switch ( c )
787         {
788             case '&':
789                 // This is a AND node
790                 pos.start++;
791                 node = new AndNode();
792                 node = parseBranchNode( schemaManager, node, filter, pos, relaxed );
793                 break;
794 
795             case '|':
796                 // This is an OR node
797                 pos.start++;
798                 node = new OrNode();
799                 node = parseBranchNode( schemaManager, node, filter, pos, relaxed );
800                 break;
801 
802             case '!':
803                 // This is a NOT node
804                 pos.start++;
805                 node = new NotNode();
806                 node = parseBranchNode( schemaManager, node, filter, pos, relaxed );
807                 break;
808 
809             default:
810                 // This is an item
811                 node = parseItem( schemaManager, filter, pos, c, relaxed );
812                 break;
813 
814         }
815 
816         return node;
817     }
818 
819 
820     /**
821      * Pasre the grammar rule :
822      * filter ::= '(' filterComp ')'
823      */
824     private static ExprNode parseFilterInternal( SchemaManager schemaManager, byte[] filter, Position pos,
825         boolean relaxed ) throws ParseException, LdapException
826     {
827         // Check for the left '('
828         if ( !Strings.isCharASCII( filter, pos.start, '(' ) )
829         {
830             // No more node, get out
831             if ( ( pos.start == 0 ) && ( pos.length != 0 ) )
832             {
833                 throw new ParseException( I18n.err( I18n.ERR_04155 ), 0 );
834             }
835             else
836             {
837                 return UndefinedNode.UNDEFINED_NODE;
838             }
839         }
840 
841         pos.start++;
842 
843         // parse the filter component
844         ExprNode node = parseFilterComp( schemaManager, filter, pos, relaxed );
845 
846         if ( node == UndefinedNode.UNDEFINED_NODE )
847         {
848             return UndefinedNode.UNDEFINED_NODE;
849         }
850 
851         // Check that we have a right ')'
852         if ( !Strings.isCharASCII( filter, pos.start, ')' ) )
853         {
854             throw new ParseException( I18n.err( I18n.ERR_04157 ), pos.start );
855         }
856 
857         pos.start++;
858 
859         return node;
860     }
861 
862 
863     /**
864      * Parses a search filter from it's string representation to an expression node object.
865      * 
866      * @param filter the search filter in it's string representation
867      * @return the expression node object
868      */
869     public static ExprNode parse( String filter ) throws ParseException
870     {
871         return parse( null, Strings.getBytesUtf8( filter ), false );
872     }
873 
874 
875     /**
876      * @see FilterParser#parse(String)
877      */
878     public static ExprNode parse( byte[] filter ) throws ParseException
879     {
880         return parse( null, filter, false );
881     }
882 
883 
884     /**
885      * @see FilterParser#parse(String)
886      */
887     public static ExprNode parse( SchemaManager schemaManager, String filter ) throws ParseException
888     {
889         return parse( schemaManager, Strings.getBytesUtf8( filter ), false );
890     }
891 
892 
893     /**
894      * @see FilterParser#parse(String)
895      */
896     public static ExprNode parse( SchemaManager schemaManager, byte[] filter ) throws ParseException
897     {
898         return parse( schemaManager, filter, false );
899     }
900 
901 
902     private static ExprNode parse( SchemaManager schemaManager, byte[] filter, boolean relaxed )
903         throws ParseException
904     {
905         // The filter must not be null. This is a defensive test
906         if ( Strings.isEmpty( filter ) )
907         {
908             throw new ParseException( I18n.err( I18n.ERR_04158 ), 0 );
909         }
910 
911         Position pos = new Position();
912         pos.start = 0;
913         pos.end = 0;
914         pos.length = filter.length;
915 
916         try
917         {
918             return parseFilterInternal( schemaManager, filter, pos, relaxed );
919         }
920         catch ( LdapException le )
921         {
922             throw new ParseException( le.getMessage(), pos.start );
923         }
924     }
925 
926 
927     /**
928      * @see FilterParser#parse(String)
929      */
930     public static ExprNode parse( SchemaManager schemaManager, String filter, Position pos ) throws ParseException
931     {
932         // The filter must not be null. This is a defensive test
933         if ( Strings.isEmpty( filter ) )
934         {
935             throw new ParseException( I18n.err( I18n.ERR_04158 ), 0 );
936         }
937 
938         pos.start = 0;
939         pos.end = 0;
940         pos.length = filter.length();
941 
942         try
943         {
944             return parseFilterInternal( schemaManager, Strings.getBytesUtf8( filter ), pos, false );
945         }
946         catch ( LdapException le )
947         {
948             throw new ParseException( le.getMessage(), pos.start );
949         }
950     }
951 
952 
953     /**
954      * Parses a search filter from it's string representation to an expression node object.
955      * 
956      * In <code>relaxed</code> mode the filter may violate RFC 4515, e.g. the underscore in attribute names is allowed.
957      * 
958      * @param filter the search filter in it's string representation
959      * @param relaxed <code>true</code> to parse the filter in relaxed mode
960      * @return the expression node object
961      */
962     public static ExprNode parse( String filter, boolean relaxed ) throws ParseException
963     {
964         return parse( null, Strings.getBytesUtf8( filter ), relaxed );
965     }
966 }