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