/*
 * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.jndi.ldap;

import javax.naming.NamingException;
import javax.naming.directory.InvalidSearchFilterException;

import java.io.IOException;

/**
 * LDAP (RFC-1960) and LDAPv3 (RFC-2254) search filters.
 *
 * @author Xuelei Fan
 * @author Vincent Ryan
 * @author Jagane Sundar
 * @author Rosanna Lee
 */

final class Filter {

    /**
     * First convert filter string into byte[].
     * For LDAP v3, the conversion uses Unicode -> UTF8
     * For LDAP v2, the conversion uses Unicode -> ISO 8859 (Latin-1)
     *
     * Then parse the byte[] as a filter, converting \hh to
     * a single byte, and encoding the resulting filter
     * into the supplied BER buffer
     */
    static void encodeFilterString(BerEncoder ber, String filterStr,
        boolean isLdapv3) throws IOException, NamingException {

        if ((filterStr == null) || (filterStr.equals(""))) {
            throw new InvalidSearchFilterException("Empty filter");
        }
        byte[] filter;
        int filterLen;
        if (isLdapv3) {
            filter = filterStr.getBytes("UTF8");
        } else {
            filter = filterStr.getBytes("8859_1");
        }
        filterLen = filter.length;
        if (dbg) {
            dbgIndent = 0;
            System.err.println("String filter: " + filterStr);
            System.err.println("size: " + filterLen);
            dprint("original: ", filter, 0, filterLen);
        }

        encodeFilter(ber, filter, 0, filterLen);
    }

    private static void encodeFilter(BerEncoder ber, byte[] filter,
        int filterStart, int filterEnd) throws IOException, NamingException {

        if (dbg) {
            dprint("encFilter: ",  filter, filterStart, filterEnd);
            dbgIndent++;
        }

        if ((filterEnd - filterStart) <= 0) {
            throw new InvalidSearchFilterException("Empty filter");
        }

        int nextOffset;
        int parens, balance;
        boolean escape;

        parens = 0;

        int filtOffset[] = new int[1];

        for (filtOffset[0] = filterStart; filtOffset[0] < filterEnd;) {
            switch (filter[filtOffset[0]]) {
            case '(':
                filtOffset[0]++;
                parens++;
                switch (filter[filtOffset[0]]) {
                case '&':
                    encodeComplexFilter(ber, filter,
                        LDAP_FILTER_AND, filtOffset, filterEnd);
                    // filtOffset[0] has pointed to char after right paren
                    parens--;
                    break;

                case '|':
                    encodeComplexFilter(ber, filter,
                        LDAP_FILTER_OR, filtOffset, filterEnd);
                    // filtOffset[0] has pointed to char after right paren
                    parens--;
                    break;

                case '!':
                    encodeComplexFilter(ber, filter,
                        LDAP_FILTER_NOT, filtOffset, filterEnd);
                    // filtOffset[0] has pointed to char after right paren
                    parens--;
                    break;

                default:
                    balance = 1;
                    escape = false;
                    nextOffset = filtOffset[0];
                    while (nextOffset < filterEnd && balance > 0) {
                        if (!escape) {
                            if (filter[nextOffset] == '(')
                                balance++;
                            else if (filter[nextOffset] == ')')
                                balance--;
                        }
                        if (filter[nextOffset] == '\\' && !escape)
                            escape = true;
                        else
                            escape = false;
                        if (balance > 0)
                            nextOffset++;
                    }
                    if (balance != 0)
                        throw new InvalidSearchFilterException(
                                  "Unbalanced parenthesis");

                    encodeSimpleFilter(ber, filter, filtOffset[0], nextOffset);

                    // points to the char after right paren.
                    filtOffset[0] = nextOffset + 1;

                    parens--;
                    break;

                }
                break;

            case ')':
                //
                // End of sequence
                //
                ber.endSeq();
                filtOffset[0]++;
                parens--;
                break;

            case ' ':
                filtOffset[0]++;
                break;

            default:    // assume simple type=value filter
                encodeSimpleFilter(ber, filter, filtOffset[0], filterEnd);
                filtOffset[0] = filterEnd; // force break from outer
                break;
            }

            if (parens < 0) {
                throw new InvalidSearchFilterException(
                                                "Unbalanced parenthesis");
            }
        }

        if (parens != 0) {
            throw new InvalidSearchFilterException("Unbalanced parenthesis");
        }

        if (dbg) {
            dbgIndent--;
        }

    }

    /**
     * convert character 'c' that represents a hexadecimal digit to an integer.
     * if 'c' is not a hexadecimal digit [0-9A-Fa-f], -1 is returned.
     * otherwise the converted value is returned.
     */
    private static int hexchar2int( byte c ) {
        if ( c >= '0' && c <= '9' ) {
            return( c - '0' );
        }
        if ( c >= 'A' && c <= 'F' ) {
            return( c - 'A' + 10 );
        }
        if ( c >= 'a' && c <= 'f' ) {
            return( c - 'a' + 10 );
        }
        return( -1 );
    }

    // called by the LdapClient.compare method
    static byte[] unescapeFilterValue(byte[] orig, int start, int end)
        throws NamingException {
        boolean escape = false, escStart = false;
        int ival;
        byte ch;

        if (dbg) {
            dprint("unescape: " , orig, start, end);
        }

        int len = end - start;
        byte tbuf[] = new byte[len];
        int j = 0;
        for (int i = start; i < end; i++) {
            ch = orig[i];
            if (escape) {
                // Try LDAP V3 escape (\xx)
                if ((ival = hexchar2int(ch)) < 0) {

                    /**
                     * If there is no hex char following a '\' when
                     * parsing a LDAP v3 filter (illegal by v3 way)
                     * we fallback to the way we unescape in v2.
                     */
                    if (escStart) {
                        // V2: \* \( \)
                        escape = false;
                        tbuf[j++] = ch;
                    } else {
                        // escaping already started but we can't find 2nd hex
                        throw new InvalidSearchFilterException("invalid escape sequence: " + orig);
                    }
                } else {
                    if (escStart) {
                        tbuf[j] = (byte)(ival<<4);
                        escStart = false;
                    } else {
                        tbuf[j++] |= (byte)ival;
                        escape = false;
                    }
                }
            } else if (ch != '\\') {
                tbuf[j++] = ch;
                escape = false;
            } else {
                escStart = escape = true;
            }
        }
        byte[] answer = new byte[j];
        System.arraycopy(tbuf, 0, answer, 0, j);
        if (dbg) {
            Ber.dumpBER(System.err, "", answer, 0, j);
        }
        return answer;
    }

    private static int indexOf(byte[] str, char ch, int start, int end) {
        for (int i = start; i < end; i++) {
            if (str[i] == ch)
                return i;
        }
        return -1;
    }

    private static int indexOf(byte[] str, String target, int start, int end) {
        int where = indexOf(str, target.charAt(0), start, end);
        if (where >= 0) {
            for (int i = 1; i < target.length(); i++) {
                if (str[where+i] != target.charAt(i)) {
                    return -1;
                }
            }
        }
        return where;
    }

    private static int findUnescaped(byte[] str, char ch, int start, int end) {
        while (start < end) {
            int where = indexOf(str, ch, start, end);

            /*
             * Count the immediate preceding '\' to find out if
             * this is an escaped '*'. This is a made-up way for
             * parsing an escaped '*' in v2. This is how the other leading
             * SDK vendors interpret v2.
             * For v3 we fallback to the way we parse "\*" in v2.
             * It's not legal in v3 to use "\*" to escape '*'; the right
             * way is to use "\2a" instead.
             */
            int backSlashPos;
            int backSlashCnt = 0;
            for (backSlashPos = where - 1;
                    ((backSlashPos >= start) && (str[backSlashPos] == '\\'));
                    backSlashPos--, backSlashCnt++);

            // if at start of string, or not there at all, or if not escaped
            if (where == start || where == -1 || ((backSlashCnt % 2) == 0))
                return where;

            // start search after escaped star
            start = where + 1;
        }
        return -1;
    }


    private static void encodeSimpleFilter(BerEncoder ber, byte[] filter,
        int filtStart, int filtEnd) throws IOException, NamingException {

        if (dbg) {
            dprint("encSimpleFilter: ", filter, filtStart, filtEnd);
            dbgIndent++;
        }

        String type, value;
        int valueStart, valueEnd, typeStart, typeEnd;

        int eq;
        if ((eq = indexOf(filter, '=', filtStart, filtEnd)) == -1) {
            throw new InvalidSearchFilterException("Missing 'equals'");
        }


        valueStart = eq + 1;        // value starts after equal sign
        valueEnd = filtEnd;
        typeStart = filtStart;      // beginning of string

        int ftype;

        switch (filter[eq - 1]) {
        case '<':
            ftype = LDAP_FILTER_LE;
            typeEnd = eq - 1;
            break;
        case '>':
            ftype = LDAP_FILTER_GE;
            typeEnd = eq - 1;
            break;
        case '~':
            ftype = LDAP_FILTER_APPROX;
            typeEnd = eq - 1;
            break;
        case ':':
            ftype = LDAP_FILTER_EXT;
            typeEnd = eq - 1;
            break;
        default:
            typeEnd = eq;
            //initializing ftype to make the compiler happy
            ftype = 0x00;
            break;
        }

        if (dbg) {
            System.err.println("type: " + typeStart + ", " + typeEnd);
            System.err.println("value: " + valueStart + ", " + valueEnd);
        }

        // check validity of type
        //
        // RFC4512 defines the type as the following ABNF:
        //     attr = attributedescription
        //     attributedescription = attributetype options
        //     attributetype = oid
        //     oid = descr / numericoid
        //     descr = keystring
        //     keystring = leadkeychar *keychar
        //     leadkeychar = ALPHA
        //     keychar = ALPHA / DIGIT / HYPHEN
        //     numericoid = number 1*( DOT number )
        //     number  = DIGIT / ( LDIGIT 1*DIGIT )
        //     options = *( SEMI option )
        //     option = 1*keychar
        //
        // And RFC4515 defines the extensible type as the following ABNF:
        //     attr [dnattrs] [matchingrule] / [dnattrs] matchingrule
        int optionsStart = -1;
        int extensibleStart = -1;
        if ((filter[typeStart] >= '0' && filter[typeStart] <= '9') ||
            (filter[typeStart] >= 'A' && filter[typeStart] <= 'Z') ||
            (filter[typeStart] >= 'a' && filter[typeStart] <= 'z')) {

            boolean isNumericOid =
                filter[typeStart] >= '0' && filter[typeStart] <= '9';
            for (int i = typeStart + 1; i < typeEnd; i++) {
                // ';' is an indicator of attribute options
                if (filter[i] == ';') {
                    if (isNumericOid && filter[i - 1] == '.') {
                        throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                    }

                    // attribute options
                    optionsStart = i;
                    break;
                }

                // ':' is an indicator of extensible rules
                if (filter[i] == ':' && ftype == LDAP_FILTER_EXT) {
                    if (isNumericOid && filter[i - 1] == '.') {
                        throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                    }

                    // extensible matching
                    extensibleStart = i;
                    break;
                }

                if (isNumericOid) {
                    // numeric object identifier
                    if ((filter[i] == '.' && filter[i - 1] == '.') ||
                        (filter[i] != '.' &&
                            !(filter[i] >= '0' && filter[i] <= '9'))) {
                        throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                    }
                } else {
                    // descriptor
                    // The underscore ("_") character is not allowed by
                    // the LDAP specification. We allow it here to
                    // tolerate the incorrect use in practice.
                    if (filter[i] != '-' && filter[i] != '_' &&
                        !(filter[i] >= '0' && filter[i] <= '9') &&
                        !(filter[i] >= 'A' && filter[i] <= 'Z') &&
                        !(filter[i] >= 'a' && filter[i] <= 'z')) {
                        throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                    }
                }
            }
        } else if (ftype == LDAP_FILTER_EXT && filter[typeStart] == ':') {
            // extensible matching
            extensibleStart = typeStart;
        } else {
            throw new InvalidSearchFilterException(
                                    "invalid attribute description");
        }

        // check attribute options
        if (optionsStart > 0) {
            for (int i = optionsStart + 1; i < typeEnd; i++) {
                if (filter[i] == ';') {
                    if (filter[i - 1] == ';') {
                        throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                    }
                    continue;
                }

                // ':' is an indicator of extensible rules
                if (filter[i] == ':' && ftype == LDAP_FILTER_EXT) {
                    if (filter[i - 1] == ';') {
                        throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                    }

                    // extensible matching
                    extensibleStart = i;
                    break;
                }

                // The underscore ("_") character is not allowed by
                // the LDAP specification. We allow it here to
                // tolerate the incorrect use in practice.
                if (filter[i] != '-' && filter[i] != '_' &&
                        !(filter[i] >= '0' && filter[i] <= '9') &&
                        !(filter[i] >= 'A' && filter[i] <= 'Z') &&
                        !(filter[i] >= 'a' && filter[i] <= 'z')) {
                    throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                }
            }
        }

        // check extensible matching
        if (extensibleStart > 0) {
            boolean isMatchingRule = false;
            for (int i = extensibleStart + 1; i < typeEnd; i++) {
                if (filter[i] == ':') {
                    throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                } else if ((filter[i] >= '0' && filter[i] <= '9') ||
                           (filter[i] >= 'A' && filter[i] <= 'Z') ||
                           (filter[i] >= 'a' && filter[i] <= 'z')) {
                    boolean isNumericOid = filter[i] >= '0' && filter[i] <= '9';
                    i++;
                    for (int j = i; j < typeEnd; j++, i++) {
                        // allows no more than two extensible rules
                        if (filter[j] == ':') {
                            if (isMatchingRule) {
                                throw new InvalidSearchFilterException(
                                            "invalid attribute description");
                            }
                            if (isNumericOid && filter[j - 1] == '.') {
                                throw new InvalidSearchFilterException(
                                            "invalid attribute description");
                            }

                            isMatchingRule = true;
                            break;
                        }

                        if (isNumericOid) {
                            // numeric object identifier
                            if ((filter[j] == '.' && filter[j - 1] == '.') ||
                                (filter[j] != '.' &&
                                    !(filter[j] >= '0' && filter[j] <= '9'))) {
                                throw new InvalidSearchFilterException(
                                            "invalid attribute description");
                            }
                        } else {
                            // descriptor
                            // The underscore ("_") character is not allowed by
                            // the LDAP specification. We allow it here to
                            // tolerate the incorrect use in practice.
                            if (filter[j] != '-' && filter[j] != '_' &&
                                !(filter[j] >= '0' && filter[j] <= '9') &&
                                !(filter[j] >= 'A' && filter[j] <= 'Z') &&
                                !(filter[j] >= 'a' && filter[j] <= 'z')) {
                                throw new InvalidSearchFilterException(
                                            "invalid attribute description");
                            }
                        }
                    }
                } else {
                    throw new InvalidSearchFilterException(
                                    "invalid attribute description");
                }
            }
        }

        // ensure the latest byte is not isolated
        if (filter[typeEnd - 1] == '.' || filter[typeEnd - 1] == ';' ||
                                          filter[typeEnd - 1] == ':') {
            throw new InvalidSearchFilterException(
                "invalid attribute description");
        }

        if (typeEnd == eq) { // filter type is of "equal"
            if (findUnescaped(filter, '*', valueStart, valueEnd) == -1) {
                ftype = LDAP_FILTER_EQUALITY;
            } else if (filter[valueStart] == '*' &&
                            valueStart == (valueEnd - 1)) {
                ftype = LDAP_FILTER_PRESENT;
            } else {
                encodeSubstringFilter(ber, filter,
                    typeStart, typeEnd, valueStart, valueEnd);
                return;
            }
        }

        if (ftype == LDAP_FILTER_PRESENT) {
            ber.encodeOctetString(filter, ftype, typeStart, typeEnd-typeStart);
        } else if (ftype == LDAP_FILTER_EXT) {
            encodeExtensibleMatch(ber, filter,
                typeStart, typeEnd, valueStart, valueEnd);
        } else {
            ber.beginSeq(ftype);
                ber.encodeOctetString(filter, Ber.ASN_OCTET_STR,
                    typeStart, typeEnd - typeStart);
                ber.encodeOctetString(
                    unescapeFilterValue(filter, valueStart, valueEnd),
                    Ber.ASN_OCTET_STR);
            ber.endSeq();
        }

        if (dbg) {
            dbgIndent--;
        }
    }

    private static void encodeSubstringFilter(BerEncoder ber, byte[] filter,
        int typeStart, int typeEnd, int valueStart, int valueEnd)
        throws IOException, NamingException {

        if (dbg) {
            dprint("encSubstringFilter: type ", filter, typeStart, typeEnd);
            dprint(", val : ", filter, valueStart, valueEnd);
            dbgIndent++;
        }

        ber.beginSeq(LDAP_FILTER_SUBSTRINGS);
            ber.encodeOctetString(filter, Ber.ASN_OCTET_STR,
                    typeStart, typeEnd-typeStart);
            ber.beginSeq(LdapClient.LBER_SEQUENCE);
                int index;
                int previndex = valueStart;
                while ((index = findUnescaped(filter, '*', previndex, valueEnd)) != -1) {
                    if (previndex == valueStart) {
                      if (previndex < index) {
                          if (dbg)
                              System.err.println(
                                  "initial: " + previndex + "," + index);
                        ber.encodeOctetString(
                            unescapeFilterValue(filter, previndex, index),
                            LDAP_SUBSTRING_INITIAL);
                      }
                    } else {
                      if (previndex < index) {
                          if (dbg)
                              System.err.println("any: " + previndex + "," + index);
                        ber.encodeOctetString(
                            unescapeFilterValue(filter, previndex, index),
                            LDAP_SUBSTRING_ANY);
                      }
                    }
                    previndex = index + 1;
                }
                if (previndex < valueEnd) {
                    if (dbg)
                        System.err.println("final: " + previndex + "," + valueEnd);
                  ber.encodeOctetString(
                      unescapeFilterValue(filter, previndex, valueEnd),
                      LDAP_SUBSTRING_FINAL);
                }
            ber.endSeq();
        ber.endSeq();

        if (dbg) {
            dbgIndent--;
        }
    }

    // The complex filter types look like:
    //     "&(type=val)(type=val)"
    //     "|(type=val)(type=val)"
    //     "!(type=val)"
    //
    // The filtOffset[0] pointing to the '&', '|', or '!'.
    //
    private static void encodeComplexFilter(BerEncoder ber, byte[] filter,
        int filterType, int filtOffset[], int filtEnd)
        throws IOException, NamingException {

        if (dbg) {
            dprint("encComplexFilter: ", filter, filtOffset[0], filtEnd);
            dprint(", type: " + Integer.toString(filterType, 16));
            dbgIndent++;
        }

        filtOffset[0]++;

        ber.beginSeq(filterType);

            int[] parens = findRightParen(filter, filtOffset, filtEnd);
            encodeFilterList(ber, filter, filterType, parens[0], parens[1]);

        ber.endSeq();

        if (dbg) {
            dbgIndent--;
        }

    }

    //
    // filter at filtOffset[0] - 1 points to a (. Find ) that matches it
    // and return substring between the parens. Adjust filtOffset[0] to
    // point to char after right paren
    //
    private static int[] findRightParen(byte[] filter, int filtOffset[], int end)
    throws IOException, NamingException {

        int balance = 1;
        boolean escape = false;
        int nextOffset = filtOffset[0];

        while (nextOffset < end && balance > 0) {
            if (!escape) {
                if (filter[nextOffset] == '(')
                    balance++;
                else if (filter[nextOffset] == ')')
                    balance--;
            }
            if (filter[nextOffset] == '\\' && !escape)
                escape = true;
            else
                escape = false;
            if (balance > 0)
                nextOffset++;
        }
        if (balance != 0) {
            throw new InvalidSearchFilterException("Unbalanced parenthesis");
        }

        // String tmp = filter.substring(filtOffset[0], nextOffset);

        int[] tmp = new int[] {filtOffset[0], nextOffset};

        filtOffset[0] = nextOffset + 1;

        return tmp;

    }

    //
    // Encode filter list of type "(filter1)(filter2)..."
    //
    private static void encodeFilterList(BerEncoder ber, byte[] filter,
        int filterType, int start, int end) throws IOException, NamingException {

        if (dbg) {
            dprint("encFilterList: ", filter, start, end);
            dbgIndent++;
        }

        int filtOffset[] = new int[1];
        int listNumber = 0;
        for (filtOffset[0] = start; filtOffset[0] < end; filtOffset[0]++) {
            if (Character.isSpaceChar((char)filter[filtOffset[0]]))
                continue;

            if ((filterType == LDAP_FILTER_NOT) && (listNumber > 0)) {
                throw new InvalidSearchFilterException(
                    "Filter (!) cannot be followed by more than one filters");
            }

            if (filter[filtOffset[0]] == '(') {
                continue;
            }

            int[] parens = findRightParen(filter, filtOffset, end);

            // add enclosing parens
            int len = parens[1]-parens[0];
            byte[] newfilter = new byte[len+2];
            System.arraycopy(filter, parens[0], newfilter, 1, len);
            newfilter[0] = (byte)'(';
            newfilter[len+1] = (byte)')';
            encodeFilter(ber, newfilter, 0, newfilter.length);

            listNumber++;
        }

        if (dbg) {
            dbgIndent--;
        }

    }

    //
    // Encode extensible match
    //
    private static void encodeExtensibleMatch(BerEncoder ber, byte[] filter,
        int matchStart, int matchEnd, int valueStart, int valueEnd)
        throws IOException, NamingException {

        boolean matchDN = false;
        int colon;
        int colon2;
        int i;

        ber.beginSeq(LDAP_FILTER_EXT);

            // test for colon separator
            if ((colon = indexOf(filter, ':', matchStart, matchEnd)) >= 0) {

                // test for match DN
                if ((i = indexOf(filter, ":dn", colon, matchEnd)) >= 0) {
                    matchDN = true;
                }

                // test for matching rule
                if (((colon2 = indexOf(filter, ':', colon + 1, matchEnd)) >= 0)
                    || (i == -1)) {

                    if (i == colon) {
                        ber.encodeOctetString(filter, LDAP_FILTER_EXT_RULE,
                            colon2 + 1, matchEnd - (colon2 + 1));

                    } else if ((i == colon2) && (i >= 0)) {
                        ber.encodeOctetString(filter, LDAP_FILTER_EXT_RULE,
                            colon + 1, colon2 - (colon + 1));

                    } else {
                        ber.encodeOctetString(filter, LDAP_FILTER_EXT_RULE,
                            colon + 1, matchEnd - (colon + 1));
                    }
                }

                // test for attribute type
                if (colon > matchStart) {
                    ber.encodeOctetString(filter,
                        LDAP_FILTER_EXT_TYPE, matchStart, colon - matchStart);
                }
            } else {
                ber.encodeOctetString(filter, LDAP_FILTER_EXT_TYPE, matchStart,
                    matchEnd - matchStart);
            }

            ber.encodeOctetString(
                unescapeFilterValue(filter, valueStart, valueEnd),
                LDAP_FILTER_EXT_VAL);

            /*
             * This element is defined in RFC-2251 with an ASN.1 DEFAULT tag.
             * However, for Active Directory interoperability it is transmitted
             * even when FALSE.
             */
            ber.encodeBoolean(matchDN, LDAP_FILTER_EXT_DN);

        ber.endSeq();
    }

    ////////////////////////////////////////////////////////////////////////////
    //
    // some debug print code that does indenting. Useful for debugging
    // the filter generation code
    //
    ////////////////////////////////////////////////////////////////////////////

    private static final boolean dbg = false;
    private static int dbgIndent = 0;

    private static void dprint(String msg) {
        dprint(msg, new byte[0], 0, 0);
    }

    private static void dprint(String msg, byte[] str) {
        dprint(msg, str, 0, str.length);
    }

    private static void dprint(String msg, byte[] str, int start, int end) {
        String dstr = "  ";
        int i = dbgIndent;
        while (i-- > 0) {
            dstr += "  ";
        }
        dstr += msg;

        System.err.print(dstr);
        for (int j = start; j < end; j++) {
            System.err.print((char)str[j]);
        }
        System.err.println();
    }

    /////////////// Constants used for encoding filter //////////////

    static final int LDAP_FILTER_AND = 0xa0;
    static final int LDAP_FILTER_OR = 0xa1;
    static final int LDAP_FILTER_NOT = 0xa2;
    static final int LDAP_FILTER_EQUALITY = 0xa3;
    static final int LDAP_FILTER_SUBSTRINGS = 0xa4;
    static final int LDAP_FILTER_GE = 0xa5;
    static final int LDAP_FILTER_LE = 0xa6;
    static final int LDAP_FILTER_PRESENT = 0x87;
    static final int LDAP_FILTER_APPROX = 0xa8;
    static final int LDAP_FILTER_EXT = 0xa9;            // LDAPv3

    static final int LDAP_FILTER_EXT_RULE = 0x81;       // LDAPv3
    static final int LDAP_FILTER_EXT_TYPE = 0x82;       // LDAPv3
    static final int LDAP_FILTER_EXT_VAL = 0x83;        // LDAPv3
    static final int LDAP_FILTER_EXT_DN = 0x84;         // LDAPv3

    static final int LDAP_SUBSTRING_INITIAL = 0x80;
    static final int LDAP_SUBSTRING_ANY = 0x81;
    static final int LDAP_SUBSTRING_FINAL = 0x82;
}
