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 *    http://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.BinaryValue;
028import org.apache.directory.api.ldap.model.entry.StringValue;
029import org.apache.directory.api.ldap.model.entry.Value;
030import org.apache.directory.api.ldap.model.exception.LdapException;
031import org.apache.directory.api.ldap.model.schema.AttributeType;
032import org.apache.directory.api.ldap.model.schema.SchemaManager;
033import org.apache.directory.api.util.Chars;
034import org.apache.directory.api.util.Hex;
035import org.apache.directory.api.util.Position;
036import org.apache.directory.api.util.Strings;
037import org.apache.directory.api.util.Unicode;
038
039
040/**
041 * This class parse a Ldap filter. The grammar is given in RFC 4515
042 *
043 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
044 */
045public class FilterParser
046{
047    /**
048     * Creates a filter parser implementation.
049     */
050    public FilterParser()
051    {
052    }
053
054
055    /**
056     * Parse an extensible
057     *
058     * extensible     = ( attr [":dn"] [':' oid] ":=" assertionvalue )
059     *                  / ( [":dn"] ':' oid ":=" assertionvalue )
060     * matchingrule   = ":" oid
061     */
062    private static ExprNode parseExtensible( SchemaManager schemaManager, String attribute, byte[] filter,
063        Position pos, boolean relaxed ) throws LdapException, ParseException
064    {
065        ExtensibleNode node = null;
066
067        if ( schemaManager != null )
068        {
069            AttributeType attributeType = schemaManager.getAttributeType( attribute );
070
071            if ( attributeType != null )
072            {
073                node = new ExtensibleNode( attributeType );
074            }
075            else
076            {
077                return UndefinedNode.UNDEFINED_NODE;
078            }
079        }
080        else
081        {
082            node = new ExtensibleNode( attribute );
083        }
084
085        if ( attribute != null )
086        {
087            // First check if we have a ":dn"
088            if ( Strings.areEquals( filter, pos.start, "dn" ) >= 0 )
089            {
090                // Set the dnAttributes flag and move forward in the string
091                node.setDnAttributes( true );
092                pos.start += 2;
093            }
094            else
095            {
096                // Push back the ':'
097                pos.start--;
098            }
099
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}