001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    https://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.api.ldap.model.filter;
021
022
023import java.text.ParseException;
024
025import org.apache.directory.api.i18n.I18n;
026import org.apache.directory.api.ldap.model.entry.AttributeUtils;
027import org.apache.directory.api.ldap.model.entry.Value;
028import org.apache.directory.api.ldap.model.exception.LdapException;
029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
030import org.apache.directory.api.ldap.model.schema.AttributeType;
031import org.apache.directory.api.ldap.model.schema.SchemaManager;
032import org.apache.directory.api.util.Chars;
033import org.apache.directory.api.util.Hex;
034import org.apache.directory.api.util.Position;
035import org.apache.directory.api.util.Strings;
036import org.apache.directory.api.util.Unicode;
037
038
039/**
040 * This class parse a Ldap filter. The grammar is given in RFC 4515
041 *
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 */
044public final class FilterParser
045{
046    private FilterParser()
047    {
048    }
049
050    
051    /**
052     * Parses a search filter from it's string representation to an expression node object.
053     * 
054     * @param filter the search filter in it's string representation
055     * @return the expression node object
056     * @throws ParseException If the filter is invalid
057     */
058    public static ExprNode parse( String filter ) throws ParseException
059    {
060        return parse( null, filter, false );
061    }
062
063
064    /**
065     * Parses a search filter from it's string representation to an expression node object.
066     * 
067     * In <code>relaxed</code> mode the filter may violate RFC 4515, e.g. the underscore in attribute names is allowed.
068     * 
069     * @param filter the search filter in it's string representation
070     * @param relaxed <code>true</code> to parse the filter in relaxed mode
071     * @return the expression node object
072     * @throws ParseException If the filter is invalid
073     */
074    public static ExprNode parse( String filter, boolean relaxed ) throws ParseException
075    {
076        return parse( null, filter, relaxed );
077    }
078
079    
080    /**
081     * Parses a search filter from it's string representation to an expression node object,
082     * using the provided SchemaManager 
083     * 
084     * @param schemaManager The SchemaManager to use
085     * @param filter the search filter in it's string representation
086     * @return the expression node object
087     * @throws ParseException If the filter is invalid
088     */
089    public static ExprNode parse( SchemaManager schemaManager, String filter ) throws ParseException
090    {
091        return parse( schemaManager, filter, false );
092    }
093    
094    
095    /**
096     * Skip the white spaces (0x20, 0x09, 0x0a and 0x0d)
097     * 
098     * @param filter The filter being parsed
099     * @param pos The current position in the filter
100     */
101    private static void skipWhiteSpaces( byte[] filter, Position pos )
102    {
103        while ( Strings.isCharASCII( filter, pos.start, ' ' )
104                || Strings.isCharASCII( filter, pos.start, '\t' )
105                || Strings.isCharASCII( filter, pos.start, '\n' ) )
106        {
107            pos.start++;
108        }
109    }
110
111
112    /**
113     * Parses a search filter from it's string representation to an expression node object,
114     * using the provided SchemaManager 
115     * 
116     * @param schemaManager The SchemaManager to use
117     * @param filter the search filter in it's string representation
118     * @param relaxed <code>true</code> to parse the filter in relaxed mode
119     * @return the expression node object
120     * @throws ParseException If the filter is invalid
121     */
122    public static ExprNode parse( SchemaManager schemaManager, String filter, boolean relaxed ) throws ParseException
123    {
124        if ( Strings.isEmpty( filter ) )
125        {
126            throw new ParseException( I18n.err( I18n.ERR_13316_EMPTY_FILTER ), 0 );
127        }
128
129        /** Convert the filter to an array of bytes, as this is what we expect */
130        byte[] filterBytes = Strings.getBytesUtf8( filter );
131        
132        Position pos = new Position();
133        pos.start = 0;
134        pos.end = 0;
135        pos.length = filterBytes.length;
136
137        try
138        {
139            ExprNode node = parseFilterInternal( schemaManager, filterBytes, pos, relaxed );
140            
141            if ( node == UndefinedNode.UNDEFINED_NODE )
142            {
143                return null;
144            }
145            else
146            {
147                return node;
148            }
149        }
150        catch ( LdapException le )
151        {
152            throw new ParseException( le.getMessage(), pos.start );
153        }
154    }
155
156
157    /**
158     * Parse an extensible
159     *
160     *<pre>
161     * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
162     *                  / ( [":dn"] ':' oid ":=" assertionvalue )
163     * matchingrule   = ":" oid
164     * </pre>
165     * 
166     * @param schemaManager The {@link SchemaManager}
167     * @param attribute The filter's attribute
168     * @param filterBytes The filter bytes to parse
169     * @param pos The position in the filter bytes
170     * @param relaxed If the filter is analyzed in relaxed mode or not
171     * @return the created {@link ExprNode}
172     * @throws ParseException If the Node can't be parsed
173     * @throws LdapException If we met an error while parsing a filter element
174     */
175    private static ExprNode parseExtensible( SchemaManager schemaManager, String attribute, byte[] filterBytes,
176        Position pos, boolean relaxed ) throws LdapException, ParseException
177    {
178        ExtensibleNode node;
179
180        if ( schemaManager != null )
181        {
182            AttributeType attributeType = schemaManager.getAttributeType( attribute );
183
184            if ( attributeType != null )
185            {
186                node = new ExtensibleNode( attributeType );
187            }
188            else
189            {
190                return UndefinedNode.UNDEFINED_NODE;
191            }
192        }
193        else
194        {
195            node = new ExtensibleNode( attribute );
196        }
197
198        if ( attribute != null )
199        {
200            // First check if we have a ":dn"
201            if ( Strings.isCharASCII( filterBytes, pos.start, 'd' ) && Strings.isCharASCII( filterBytes, pos.start + 1, 'n' ) )
202            {
203                // Set the dnAttributes flag and move forward in the string
204                node.setDnAttributes( true );
205                pos.start += 2;
206            }
207            else
208            {
209                // Push back the ':'
210                pos.start--;
211            }
212
213            // Do we have a MatchingRule ?
214            if ( Strings.byteAt( filterBytes, pos.start ) == ':' )
215            {
216                pos.start++;
217
218                if ( Strings.byteAt( filterBytes, pos.start ) == '=' )
219                {
220                    pos.start++;
221
222                    // Get the assertionValue
223                    node.setValue( parseAssertionValue( schemaManager, filterBytes, pos ) );
224
225                    return node;
226                }
227                else
228                {
229                    String matchingRuleId = AttributeUtils.parseAttribute( filterBytes, pos, false, relaxed );
230
231                    node.setMatchingRuleId( matchingRuleId );
232
233                    if ( Strings.isCharASCII( filterBytes, pos.start, ':' ) && Strings.isCharASCII( filterBytes, pos.start + 1, '=' ) )
234                    {
235                        pos.start += 2;
236
237                        // Get the assertionValue
238                        node.setValue( parseAssertionValue( schemaManager, filterBytes, pos ) );
239
240                        return node;
241                    }
242                    else
243                    {
244                        throw new ParseException( I18n.err( I18n.ERR_13305_ASSERTION_VALUE_EXPECTED ), pos.start );
245                    }
246                }
247            }
248            else
249            {
250                throw new ParseException( I18n.err( I18n.ERR_13306_MR_OR_ASSERTION_VALUE_EXPECTED ), pos.start );
251            }
252        }
253        else
254        {
255            // No attribute
256            boolean oidRequested = false;
257
258            // First check if we have a ":dn"
259            if ( Strings.isCharASCII( filterBytes, pos.start, ':' )
260                 && Strings.isCharASCII( filterBytes, pos.start + 1, 'd' )
261                 && Strings.isCharASCII( filterBytes, pos.start + 2, 'n' ) )
262            {
263                // Set the dnAttributes flag and move forward in the string
264                node.setDnAttributes( true );
265                pos.start += 3;
266            }
267            else
268            {
269                oidRequested = true;
270            }
271
272            // Do we have a MatchingRule ?
273            if ( Strings.byteAt( filterBytes, pos.start ) == ':' )
274            {
275                pos.start++;
276
277                if ( Strings.byteAt( filterBytes, pos.start ) == '=' )
278                {
279                    if ( oidRequested )
280                    {
281                        throw new ParseException( I18n.err( I18n.ERR_13307_MATCHING_RULE_EXPECTED ), pos.start );
282                    }
283
284                    pos.start++;
285
286                    // Get the assertionValue
287                    node.setValue( parseAssertionValue( schemaManager, null, filterBytes, pos ) );
288
289                    return node;
290                }
291                else
292                {
293                    String matchingRuleId = AttributeUtils.parseAttribute( filterBytes, pos, false, relaxed );
294
295                    node.setMatchingRuleId( matchingRuleId );
296
297                    if ( Strings.isCharASCII( filterBytes, pos.start, ':' ) && Strings.isCharASCII( filterBytes, pos.start + 1, '=' ) )
298                    {
299                        pos.start += 2;
300
301                        // Get the assertionValue
302                        node.setValue( parseAssertionValue( schemaManager, null, filterBytes, pos ) );
303
304                        return node;
305                    }
306                    else
307                    {
308                        throw new ParseException( I18n.err( I18n.ERR_13305_ASSERTION_VALUE_EXPECTED ), pos.start );
309                    }
310                }
311            }
312            else
313            {
314                throw new ParseException( I18n.err( I18n.ERR_13306_MR_OR_ASSERTION_VALUE_EXPECTED ), pos.start );
315            }
316        }
317    }
318
319
320    /**
321     * An assertion value :
322     * 
323     * <pre>
324     * assertionvalue = valueencoding
325     * valueencoding  = 0*(normal / escaped)
326     * normal         = UTF1SUBSET / UTFMB
327     * escaped        = '\' HEX HEX
328     * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
329     * UTF1SUBSET     = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\')
330     * UTFMB          = UTF2 / UTF3 / UTF4
331     * UTF0           = %x80-BF
332     * UTF2           = %xC2-DF UTF0
333     * UTF3           = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0
334     * UTF4           = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0
335     * </pre>
336     *
337     * With the specific constraints (RFC 4515):
338     * 
339     * <pre>
340     *    "The &lt;valueencoding&lt; rule ensures that the entire filter string is a"
341     *    "valid UTF-8 string and provides that the octets that represent the"
342     *    "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII"
343     *    "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a"
344     *    "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits"
345     *    "representing the value of the encoded octet."
346     * </pre>
347     * 
348     * The incoming String is already transformed from UTF-8 to unicode, so we must assume that the
349     * grammar we have to check is the following :
350     * 
351     * <pre>
352     * assertionvalue = valueencoding
353     * valueencoding  = 0*(normal / escaped)
354     * normal         = unicodeSubset
355     * escaped        = '\' HEX HEX
356     * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
357     * unicodeSubset     = %x01-27 / %x2B-5B / %x5D-FFFF
358     * </pre>
359     * 
360     * @param schemaManager The {@link SchemaManager}
361     * @param attribute The associated Attribute
362     * @param filterBytes The filter bytes to parse
363     * @param pos The position in the filter bytes
364     * @return The parsed value
365     * @throws ParseException If the value can't be parsed
366     * @throws LdapInvalidAttributeValueException If the value is invalid
367     */
368    private static Value parseAssertionValue( SchemaManager schemaManager, String attribute, byte[] filterBytes,
369        Position pos ) throws ParseException, LdapInvalidAttributeValueException
370    {
371        byte b = Strings.byteAt( filterBytes, pos.start );
372
373        // Create a buffer big enough to contain the value once converted
374        byte[] value = new byte[filterBytes.length - pos.start];
375        int current = 0;
376
377        do
378        {
379            if ( Unicode.isUnicodeSubset( b ) )
380            {
381                value[current++] = b;
382                pos.start++;
383            }
384            else if ( Strings.isCharASCII( filterBytes, pos.start, '\\' ) )
385            {
386                // Maybe an escaped
387                pos.start++;
388
389                // First hex
390                if ( Chars.isHex( filterBytes, pos.start ) )
391                {
392                    pos.start++;
393                }
394                else
395                {
396                    throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start );
397                }
398
399                // second hex
400                if ( Chars.isHex( filterBytes, pos.start ) )
401                {
402                    value[current++] = Hex.getHexValue( filterBytes[pos.start - 1], filterBytes[pos.start] );
403                    pos.start++;
404                }
405                else
406                {
407                    throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start );
408                }
409            }
410            else
411            {
412                // not a valid char, so let's get out
413                break;
414            }
415
416            b = Strings.byteAt( filterBytes, pos.start );
417        }
418        while ( b != '\0' );
419        
420        if ( current != 0 )
421        {
422            if ( schemaManager != null )
423            {
424                AttributeType attributeType = schemaManager.getAttributeType( attribute );
425
426                if ( attributeType == null )
427                {
428                    byte[] bytes = new byte[current];
429                    System.arraycopy( value, 0, bytes, 0, current );
430                    
431                    return new Value( bytes );
432                }
433
434                if ( attributeType.getSyntax().isHumanReadable() )
435                {
436                    return new Value( attributeType, Strings.utf8ToString( value, current ) );
437                }
438                else
439                {
440                    byte[] bytes = new byte[current];
441                    System.arraycopy( value, 0, bytes, 0, current );
442                    
443                    return new Value( attributeType, bytes );
444                }
445            }
446            else
447            {
448                byte[] bytes = new byte[current];
449                System.arraycopy( value, 0, bytes, 0, current );
450                
451                return new Value( bytes );
452            }
453        }
454        else
455        {
456            if ( schemaManager != null )
457            {
458                AttributeType attributeType = schemaManager.getAttributeType( attribute );
459
460                if ( attributeType.getEquality().getSyntax().isHumanReadable() )
461                {
462                    return new Value( attributeType, ( String ) null );
463                }
464                else
465                {
466                    return new Value( attributeType, ( byte[] ) null );
467                }
468            }
469            else
470            {
471                return new Value( ( byte[] ) null );
472            }
473        }
474    }
475
476
477    /**
478     * An assertion value :
479     * 
480     * <pre>
481     * assertionvalue = valueencoding
482     * valueencoding  = 0*(normal / escaped)
483     * normal         = UTF1SUBSET / UTFMB
484     * escaped        = '\' HEX HEX
485     * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
486     * UTF1SUBSET     = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\')
487     * UTFMB          = UTF2 / UTF3 / UTF4
488     * UTF0           = %x80-BF
489     * UTF2           = %xC2-DF UTF0
490     * UTF3           = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0
491     * UTF4           = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0
492     * </pre>
493     *
494     * With the specific constraints (RFC 4515):
495     * 
496     * <pre>
497     *    "The &lt;valueencoding&gt; rule ensures that the entire filter string is a"
498     *    "valid UTF-8 string and provides that the octets that represent the"
499     *    "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII"
500     *    "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a"
501     *    "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits"
502     *    "representing the value of the encoded octet."
503     * </pre>
504     *
505     * The incoming String is already transformed from UTF-8 to unicode, so we must assume that the
506     * grammar we have to check is the following :
507     *
508     * <pre>
509     * assertionvalue = valueencoding
510     * valueencoding  = 0*(normal / escaped)
511     * normal         = unicodeSubset
512     * escaped        = '\' HEX HEX
513     * HEX            = '0'-'9' / 'A'-'F' / 'a'-'f'
514     * unicodeSubset     = %x01-27 / %x2B-5B / %x5D-FFFF
515     * </pre>
516     * 
517     * @param schemaManager The {@link SchemaManager}
518     * @param filterBytes The filter bytes to parse
519     * @param pos The position in the filter bytes
520     * @return The parsed value
521     * @throws ParseException If the value can't be parsed
522     */
523    private static Value parseAssertionValue( SchemaManager schemaManager, byte[] filterBytes, Position pos )
524        throws ParseException
525    {
526        byte b = Strings.byteAt( filterBytes, pos.start );
527
528        // Create a buffer big enough to contain the value once converted
529        byte[] value = new byte[filterBytes.length - pos.start];
530        int current = 0;
531
532        do
533        {
534            if ( Unicode.isUnicodeSubset( b ) )
535            {
536                value[current++] = b;
537                pos.start++;
538            }
539            else if ( Strings.isCharASCII( filterBytes, pos.start, '\\' ) )
540            {
541                // Maybe an escaped
542                pos.start++;
543
544                // First hex
545                if ( Chars.isHex( filterBytes, pos.start ) )
546                {
547                    pos.start++;
548                }
549                else
550                {
551                    throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start );
552                }
553
554                // second hex
555                if ( Chars.isHex( filterBytes, pos.start ) )
556                {
557                    value[current++] = Hex.getHexValue( filterBytes[pos.start - 1], filterBytes[pos.start] );
558                    pos.start++;
559                }
560                else
561                {
562                    throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start );
563                }
564            }
565            else
566            {
567                // not a valid char, so let's get out
568                break;
569            }
570
571            b = Strings.byteAt( filterBytes, pos.start );
572        }
573        while ( b != '\0' );
574
575        if ( current != 0 )
576        {
577            byte[] result = new byte[current];
578            System.arraycopy( value, 0, result, 0, current );
579
580            return new Value( result );
581        }
582        else
583        {
584            return new Value( ( byte[] ) null );
585        }
586    }
587
588
589    /**
590     * Parse a substring
591     * 
592     * @param schemaManager The {@link SchemaManager}
593     * @param attribute The filter's attribute
594     * @param initial The filter's initial part
595     * @param filterBytes The filter bytes to parse
596     * @param pos The position in the filter bytes
597     * @return the created {@link ExprNode}
598     * @throws ParseException If the Node can't be parsed
599     * @throws LdapException If we met an error while parsing a filter element
600     */
601    private static ExprNode parseSubstring( SchemaManager schemaManager, String attribute, Value initial,
602        byte[] filterBytes, Position pos ) throws ParseException, LdapException
603    {
604        SubstringNode node;
605
606        if ( schemaManager != null )
607        {
608            AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute );
609
610            if ( attributeType != null )
611            {
612                node = new SubstringNode( schemaManager.lookupAttributeTypeRegistry( attribute ) );
613            }
614            else
615            {
616                return null;
617            }
618        }
619        else
620        {
621            node = new SubstringNode( attribute );
622        }
623
624        if ( ( initial != null ) && !initial.isNull() )
625        {
626            // We have a substring starting with a value : val*...
627            // Set the initial value. It must be a String
628            String initialStr = initial.getString();
629            node.setInitial( initialStr );
630        }
631
632        if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) )
633        {
634            // No any or final, we are done
635            return node;
636        }
637
638        //
639        while ( true )
640        {
641            Value assertionValue = parseAssertionValue( schemaManager, attribute, filterBytes, pos );
642
643            // Is there anything else but a ')' after the value ?
644            if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) )
645            {
646                // Nope : as we have had [initial] '*' (any '*' ) *,
647                // this is the final
648                if ( !assertionValue.isNull() )
649                {
650                    String finalStr = assertionValue.getString();
651                    node.setFinal( finalStr );
652                }
653
654                return node;
655            }
656            else if ( Strings.isCharASCII( filterBytes, pos.start, '*' ) )
657            {
658                // We have a '*' : it's an any
659                // If the value is empty, that means we have more than
660                // one consecutive '*' : do nothing in this case.
661                if ( !assertionValue.isNull() )
662                {
663                    String anyStr = assertionValue.getString();
664                    node.addAny( anyStr );
665                }
666
667                pos.start++;
668
669                // Skip any following '*'
670                while ( Strings.isCharASCII( filterBytes, pos.start, '*' ) )
671                {
672                    pos.start++;
673                }
674
675                // that may have been the closing '*'
676                if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) )
677                {
678                    return node;
679                }
680
681            }
682            else
683            {
684                // This is an error
685                throw new ParseException( I18n.err( I18n.ERR_13309_BAD_SUBSTRING ), pos.start );
686            }
687        }
688    }
689
690
691    /**
692     * Here is the grammar to parse :
693     * <pre>
694     * simple    ::= '=' assertionValue
695     * present   ::= '=' '*'
696     * substring ::= '=' [initial] any [final]
697     * initial   ::= assertionValue
698     * any       ::= '*' ( assertionValue '*')*
699     * </pre>
700     * As we can see, there is an ambiguity in the grammar : attr=* can be
701     * seen as a present or as a substring. As stated in the RFC :
702     *
703     * <pre>
704     * "Note that although both the &lt;substring&gt; and &lt;present&gt; productions in"
705     * "the grammar above can produce the "attr=*" construct, this construct"
706     * "is used only to denote a presence filter." (RFC 4515, 3)
707     * </pre>
708     * 
709     * We have also to consider the difference between a substring and the
710     * equality node : this last node does not contain a '*'
711     * 
712     * @param schemaManager The {@link SchemaManager}
713     * @param attribute The filter's attribute
714     * @param filterBytes The filter bytes to parse
715     * @param pos The position in the filter bytes
716     * @return the created {@link ExprNode}
717     * @throws ParseException If the Node can't be parsed
718     * @throws LdapException If we met an error while parsing a filter element
719     */
720    private static ExprNode parsePresenceEqOrSubstring( SchemaManager schemaManager, String attribute, byte[] filterBytes,
721        Position pos ) throws ParseException, LdapException
722    {
723        byte b = Strings.byteAt( filterBytes, pos.start );
724
725        switch ( b )
726        {
727            case '*' :
728                // To be a present node, the next char should be a ')'
729                pos.start++;
730    
731                if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) )
732                {
733                    // This is a present node
734                    if ( schemaManager != null )
735                    {
736                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
737    
738                        if ( attributeType != null )
739                        {
740                            return new PresenceNode( attributeType );
741                        }
742                        else
743                        {
744                            return null;
745                        }
746                    }
747                    else
748                    {
749                        return new PresenceNode( attribute );
750                    }
751                }
752                else
753                {
754                    // Definitively a substring with no initial or an error
755                    return parseSubstring( schemaManager, attribute, null, filterBytes, pos );
756                }
757                
758            case ')' :
759                // An empty equality Node
760                if ( schemaManager != null )
761                {
762                    AttributeType attributeType = schemaManager.getAttributeType( attribute );
763    
764                    if ( attributeType != null )
765                    {
766                        return new EqualityNode( attributeType, new Value( ( byte[] ) null ) );
767                    }
768    
769                    else
770                    {
771                        return null;
772                    }
773                }
774                else
775                {
776                    return new EqualityNode( attribute, ( byte[] ) null );
777                }
778                
779            default :
780                // A substring or an equality node
781                Value value = parseAssertionValue( schemaManager, attribute, filterBytes, pos );
782
783                // Is there anything else but a ')' after the value ?
784                b = Strings.byteAt( filterBytes, pos.start );
785
786                switch ( b )
787                {
788                    case ')' :
789                        // This is an equality node
790                        if ( schemaManager != null )
791                        {
792                            AttributeType attributeType = schemaManager.getAttributeType( attribute );
793        
794                            if ( attributeType != null )
795                            {
796                                return new EqualityNode( attributeType, value );
797                            }
798                            else
799                            {
800                                return null;
801                            }
802                        }
803                        else
804                        {
805                            return new EqualityNode( attribute, value.getBytes() );
806                        }
807                        
808                    case '*' :
809                        pos.start++;
810                        
811                        return parseSubstring( schemaManager, attribute, value, filterBytes, pos );
812                        
813                        
814                    default :
815                        // This is an error
816                        throw new ParseException( I18n.err( I18n.ERR_13309_BAD_SUBSTRING ), pos.start );
817                }
818        }
819    }
820
821
822    /**
823     * Parse the following grammar :
824     * 
825     * <pre>
826     * item           = simple / present / substring / extensible
827     * simple         = attr WSP* filtertype WSP* assertionvalue
828     * filtertype     = '=' / '~=' / '&gt;=' / '&lt;='
829     * present        = attr WSP* '=' '*'
830     * substring      = attr WSP* '=' WSP* [initial] any [final]
831     * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
832     *                  / ( [":dn"] ':' oid ":=" assertionvalue )
833     * matchingrule   = ":" oid
834     * </pre>
835     * An item starts with an attribute or a colon.
836     * 
837     * @param schemaManager The {@link SchemaManager}
838     * @param filterBytes The filter bytes to parse
839     * @param pos The position in the filter bytes
840     * @param b The type of item
841     * @param relaxed If the filter is analyzed in relaxed mode or not
842     * @return the created {@link ExprNode}
843     * @throws ParseException If the Node can't be parsed
844     * @throws LdapException If we met an error while parsing a filter element
845     */
846    @SuppressWarnings({ "rawtypes", })
847    private static ExprNode parseItem( SchemaManager schemaManager, byte[] filterBytes, Position pos, byte b,
848        boolean relaxed ) throws ParseException, LdapException
849    {
850        String attribute;
851
852        if ( b == '\0' )
853        {
854            throw new ParseException( I18n.err( I18n.ERR_13310_BAD_CHAR ), pos.start );
855        }
856
857        if ( b == ':' )
858        {
859            // If we have a colon, then the item is an extensible one
860            return parseExtensible( schemaManager, null, filterBytes, pos, relaxed );
861        }
862        else
863        {
864            // We must have an attribute
865            attribute = AttributeUtils.parseAttribute( filterBytes, pos, true, relaxed );
866
867            // Skip spaces
868            skipWhiteSpaces( filterBytes, pos );
869            
870            // Now, we may have a present, substring, simple or an extensible
871            b = Strings.byteAt( filterBytes, pos.start );
872
873            switch ( b )
874            {
875                case '=':
876                    // It can be a presence, an equal or a substring
877                    pos.start++;
878                    
879                    return parsePresenceEqOrSubstring( schemaManager, attribute, filterBytes, pos );
880
881                case '~':
882                    // Approximate node
883                    pos.start++;
884
885                    // Check that we have a '='
886                    if ( !Strings.isCharASCII( filterBytes, pos.start, '=' ) )
887                    {
888                        throw new ParseException( I18n.err( I18n.ERR_13311_EXPECTING_EQUAL ), pos.start );
889                    }
890
891                    pos.start++;
892                    
893                    // Parse the value and create the node
894                    if ( schemaManager == null )
895                    {
896                        return new ApproximateNode( attribute, parseAssertionValue( schemaManager, attribute, filterBytes,
897                            pos ).getBytes() );
898                    }
899                    else
900                    {
901                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
902
903                        if ( attributeType != null )
904                        {
905                            return new ApproximateNode( attributeType, parseAssertionValue( schemaManager, attribute,
906                                filterBytes, pos ) );
907                        }
908                        else
909                        {
910                            return UndefinedNode.UNDEFINED_NODE;
911                        }
912                    }
913
914                case '>':
915                    // Greater or equal node
916                    pos.start++;
917
918                    // Check that we have a '='
919                    if ( !Strings.isCharASCII( filterBytes, pos.start, '=' ) )
920                    {
921                        throw new ParseException( I18n.err( I18n.ERR_13311_EXPECTING_EQUAL ), pos.start );
922                    }
923
924                    pos.start++;
925                    
926                    // Parse the value and create the node
927                    if ( schemaManager == null )
928                    {
929                        return new GreaterEqNode( attribute,
930                            parseAssertionValue( schemaManager, attribute, filterBytes, pos ).getBytes() );
931                    }
932                    else
933                    {
934                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
935
936                        if ( attributeType != null )
937                        {
938                            return new GreaterEqNode( attributeType, parseAssertionValue( schemaManager, attribute,
939                                filterBytes, pos ) );
940                        }
941                        else
942                        {
943                            return UndefinedNode.UNDEFINED_NODE;
944                        }
945                    }
946
947                case '<':
948                    // Less or equal node
949                    pos.start++;
950
951                    // Check that we have a '='
952                    if ( !Strings.isCharASCII( filterBytes, pos.start, '=' ) )
953                    {
954                        throw new ParseException( I18n.err( I18n.ERR_13311_EXPECTING_EQUAL ), pos.start );
955                    }
956
957                    pos.start++;
958                    
959                    // Parse the value and create the node
960                    if ( schemaManager == null )
961                    {
962                        return new LessEqNode( attribute, 
963                            parseAssertionValue( schemaManager, attribute, filterBytes, pos ).getBytes() );
964                    }
965                    else
966                    {
967                        AttributeType attributeType = schemaManager.getAttributeType( attribute );
968
969                        if ( attributeType != null )
970                        {
971                            return new LessEqNode( attributeType, parseAssertionValue( schemaManager, attribute,
972                                filterBytes, pos ) );
973                        }
974                        else
975                        {
976                            return UndefinedNode.UNDEFINED_NODE;
977                        }
978                    }
979
980                case ':':
981                    // An extensible node
982                    pos.start++;
983                    
984                    return parseExtensible( schemaManager, attribute, filterBytes, pos, relaxed );
985
986                default:
987                    // This is an error
988                    throw new ParseException( I18n.err( I18n.ERR_13312_ITEM_EXPECTED ), pos.start );
989            }
990        }
991    }
992
993
994    /**
995     * Parse AND, OR and NOT nodes :
996     * <pre>
997     * and            = '&amp;' filterlist
998     * or             = '|' filterlist
999     * not            = '!' filter
1000     * filterlist     = 1*filter
1001     * </pre>
1002     * 
1003     * @param schemaManager The {@link SchemaManager}
1004     * @param node The node to feed
1005     * @param filterBytes The filter bytes to parse
1006     * @param pos The position in the filter bytes
1007     * @param relaxed If the filter is analyzed in relaxed mode or not
1008     * @return the created {@link ExprNode}
1009     * @throws ParseException If the Node can't be parsed
1010     * @throws LdapException If we met an error while parsing a filter element
1011     */
1012    private static ExprNode parseBranchNode( SchemaManager schemaManager, ExprNode node, byte[] filterBytes, Position pos,
1013        boolean relaxed ) throws ParseException, LdapException
1014    {
1015        BranchNode branchNode = ( BranchNode ) node;
1016        int nbChildren = 0;
1017
1018        // We must have at least one filter
1019        ExprNode child = parseFilterInternal( schemaManager, filterBytes, pos, relaxed );
1020
1021        if ( child != UndefinedNode.UNDEFINED_NODE )
1022        {
1023            // Add the child to the node children
1024            branchNode.addNode( child );
1025
1026            if ( branchNode instanceof NotNode )
1027            {
1028                return node;
1029            }
1030
1031            nbChildren++;
1032        }
1033        else if ( node instanceof AndNode )
1034        {
1035            return UndefinedNode.UNDEFINED_NODE;
1036        }
1037
1038        // Now, iterate recusively though all the remaining filters, if any
1039        while ( ( child = parseFilterInternal( schemaManager, filterBytes, pos, relaxed ) ) != UndefinedNode.UNDEFINED_NODE )
1040        {
1041            // Add the child to the node children if not null
1042            if ( child != null )
1043            {
1044                branchNode.addNode( child );
1045                nbChildren++;
1046            }
1047            else if ( node instanceof AndNode )
1048            {
1049                return UndefinedNode.UNDEFINED_NODE;
1050            }
1051        }
1052
1053        if ( nbChildren > 0 )
1054        {
1055            return node;
1056        }
1057        else
1058        {
1059            return UndefinedNode.UNDEFINED_NODE;
1060        }
1061    }
1062
1063
1064    /**
1065     * <pre>
1066     * filtercomp     = and / or / not / item
1067     * and            = '&amp;' WSP* filterlist
1068     * or             = '|' WSP* filterlist
1069     * not            = '!' WSP* filter
1070     * item           = simple / present / substring / extensible
1071     * simple         = attr WSP* filtertype WSP* assertionvalue
1072     * present        = attr WSP* EQUALS ASTERISK
1073     * substring      = attr WSP* EQUALS WSP* [initial] any [final]
1074     * extensible     = ( attr [dnattrs]
1075     *                    [matchingrule] COLON EQUALS assertionvalue )
1076     *                    / ( [dnattrs]
1077     *                         matchingrule COLON EQUALS assertionvalue )
1078     * </pre>
1079     * 
1080     * @param schemaManager The {@link SchemaManager}
1081     * @param filterBytes The filter bytes to parse
1082     * @param pos The position in the filter bytes
1083     * @param relaxed If the filter is analyzed in relaxed mode or not
1084     * @return the created {@link ExprNode}
1085     * @throws ParseException If the Node can't be parsed
1086     * @throws LdapException If we met an error while parsing a filter element
1087     */
1088    private static ExprNode parseFilterComp( SchemaManager schemaManager, byte[] filterBytes, Position pos,
1089        boolean relaxed ) throws ParseException, LdapException
1090    {
1091        ExprNode node;
1092
1093        if ( pos.start == pos.length )
1094        {
1095            throw new ParseException( I18n.err( I18n.ERR_13313_EMPTY_FILTERCOMP ), pos.start );
1096        }
1097
1098        byte b = Strings.byteAt( filterBytes, pos.start );
1099
1100        switch ( b )
1101        {
1102            case '&':
1103                // This is a AND node
1104                pos.start++;
1105
1106                // Skip spaces
1107                skipWhiteSpaces( filterBytes, pos );
1108                
1109                node = new AndNode();
1110                node = parseBranchNode( schemaManager, node, filterBytes, pos, relaxed );
1111                break;
1112
1113            case '|':
1114                // This is an OR node
1115                pos.start++;
1116
1117                // Skip spaces
1118                skipWhiteSpaces( filterBytes, pos );
1119                
1120                node = new OrNode();
1121                node = parseBranchNode( schemaManager, node, filterBytes, pos, relaxed );
1122                break;
1123
1124            case '!':
1125                // This is a NOT node
1126                pos.start++;
1127
1128                // Skip spaces
1129                skipWhiteSpaces( filterBytes, pos );
1130                
1131                node = new NotNode();
1132                node = parseBranchNode( schemaManager, node, filterBytes, pos, relaxed );
1133                break;
1134
1135            default:
1136                // This is an item
1137                node = parseItem( schemaManager, filterBytes, pos, b, relaxed );
1138                break;
1139        }
1140
1141        return node;
1142    }
1143
1144
1145    /**
1146     * Parse the grammar rule :
1147     * <pre>
1148     * filter ::= WSP* '(' WSP* filterComp WSP* ')' WSP*
1149     * </pre>
1150     * 
1151     * @param schemaManager The {@link SchemaManager}
1152     * @param filterBytes The filter bytes to parse
1153     * @param pos The position in the filter bytes
1154     * @param relaxed If the filter is analyzed in relaxed mode or not
1155     * @return the created {@link ExprNode}
1156     * @throws ParseException If the Node can't be parsed
1157     * @throws LdapException If we met an error while parsing a filter element
1158     */
1159    private static ExprNode parseFilterInternal( SchemaManager schemaManager, byte[] filterBytes, Position pos,
1160        boolean relaxed ) throws ParseException, LdapException
1161    {
1162        // Skip spaces
1163        skipWhiteSpaces( filterBytes, pos );
1164        
1165        // Check for the left '('
1166        if ( !Strings.isCharASCII( filterBytes, pos.start, '(' ) )
1167        {
1168            // No more node, get out
1169            if ( ( pos.start == 0 ) && ( pos.length != 0 ) )
1170            {
1171                throw new ParseException( I18n.err( I18n.ERR_13314_FILTER_MISSING_OPEN_PAR ), 0 );
1172            }
1173            else
1174            {
1175                return UndefinedNode.UNDEFINED_NODE;
1176            }
1177        }
1178
1179        pos.start++;
1180
1181        // Skip spaces
1182        skipWhiteSpaces( filterBytes, pos );
1183        
1184        // parse the filter component
1185        ExprNode node = parseFilterComp( schemaManager, filterBytes, pos, relaxed );
1186
1187        if ( node == UndefinedNode.UNDEFINED_NODE )
1188        {
1189            return UndefinedNode.UNDEFINED_NODE;
1190        }
1191
1192        // Skip spaces
1193        skipWhiteSpaces( filterBytes, pos );
1194        
1195        // Check that we have a right ')'
1196        if ( !Strings.isCharASCII( filterBytes, pos.start, ')' ) )
1197        {
1198            throw new ParseException( I18n.err( I18n.ERR_13315_FILTER_MISSING_CLOSE_PAR ), pos.start );
1199        }
1200
1201        pos.start++;
1202
1203        // Skip spaces
1204        skipWhiteSpaces( filterBytes, pos );
1205        
1206        return node;
1207    }
1208}