package com.hwlcn.ldap.ldap.sdk; import java.io.Serializable; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import com.hwlcn.ldap.asn1.ASN1Boolean; import com.hwlcn.ldap.asn1.ASN1Buffer; import com.hwlcn.ldap.asn1.ASN1BufferSequence; import com.hwlcn.ldap.asn1.ASN1BufferSet; import com.hwlcn.ldap.asn1.ASN1Element; import com.hwlcn.ldap.asn1.ASN1Exception; import com.hwlcn.ldap.asn1.ASN1OctetString; import com.hwlcn.ldap.asn1.ASN1Sequence; import com.hwlcn.ldap.asn1.ASN1Set; import com.hwlcn.ldap.asn1.ASN1StreamReader; import com.hwlcn.ldap.asn1.ASN1StreamReaderSequence; import com.hwlcn.ldap.asn1.ASN1StreamReaderSet; import com.hwlcn.ldap.ldap.matchingrules.CaseIgnoreStringMatchingRule; import com.hwlcn.ldap.ldap.matchingrules.MatchingRule; import com.hwlcn.ldap.ldap.sdk.schema.Schema; import com.hwlcn.core.annotation.NotMutable; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.util.ThreadSafetyLevel; import static com.hwlcn.ldap.ldap.sdk.LDAPMessages.*; import static com.hwlcn.ldap.util.Debug.*; import static com.hwlcn.ldap.util.StaticUtils.*; import static com.hwlcn.ldap.util.Validator.*; /** * This class provides a data structure that represents an LDAP search filter. * It provides methods for creating various types of filters, as well as parsing * a filter from a string. See * <A HREF="http://www.ietf.org/rfc/rfc4515.txt">RFC 4515</A> for more * information about representing search filters as strings. * <BR><BR> * The following filter types are defined: * <UL> * <LI><B>AND</B> -- This is used to indicate that a filter should match an * entry only if all of the embedded filter components match that entry. * An AND filter with zero embedded filter components is considered an * LDAP TRUE filter as defined in * <A HREF="http://www.ietf.org/rfc/rfc4526.txt">RFC 4526</A> and will * match any entry. AND filters contain only a set of embedded filter * components, and each of those embedded components can itself be any * type of filter, including an AND, OR, or NOT filter with additional * embedded components.</LI> * <LI><B>OR</B> -- This is used to indicate that a filter should match an * entry only if at least one of the embedded filter components matches * that entry. An OR filter with zero embedded filter components is * considered an LDAP FALSE filter as defined in * <A HREF="http://www.ietf.org/rfc/rfc4526.txt">RFC 4526</A> and will * never match any entry. OR filters contain only a set of embedded * filter components, and each of those embedded components can itself be * any type of filter, including an AND, OR, or NOT filter with additional * embedded components.</LI> * <LI><B>NOT</B> -- This is used to indicate that a filter should match an * entry only if the embedded NOT component does not match the entry. A * NOT filter contains only a single embedded NOT filter component, but * that embedded component can itself be any type of filter, including an * AND, OR, or NOT filter with additional embedded components.</LI> * <LI><B>EQUALITY</B> -- This is used to indicate that a filter should match * an entry only if the entry contains a value for the specified attribute * that is equal to the provided assertion value. An equality filter * contains only an attribute name and an assertion value.</LI> * <LI><B>SUBSTRING</B> -- This is used to indicate that a filter should match * an entry only if the entry contains at least one value for the * specified attribute that matches the provided substring assertion. The * substring assertion must contain at least one element of the following * types: * <UL> * <LI>subInitial -- This indicates that the specified string must * appear at the beginning of the attribute value. There can be at * most one subInitial element in a substring assertion.</LI> * <LI>subAny -- This indicates that the specified string may appear * anywhere in the attribute value. There can be any number of * substring subAny elements in a substring assertion. If there are * multiple subAny elements, then they must match in the order that * they are provided.</LI> * <LI>subFinal -- This indicates that the specified string must appear * at the end of the attribute value. There can be at most one * subFinal element in a substring assertion.</LI> * </UL> * A substring filter contains only an attribute name and subInitial, * subAny, and subFinal elements.</LI> * <LI><B>GREATER-OR-EQUAL</B> -- This is used to indicate that a filter * should match an entry only if that entry contains at least one value * for the specified attribute that is greater than or equal to the * provided assertion value. A greater-or-equal filter contains only an * attribute name and an assertion value.</LI> * <LI><B>LESS-OR-EQUAL</B> -- This is used to indicate that a filter should * match an entry only if that entry contains at least one value for the * specified attribute that is less than or equal to the provided * assertion value. A less-or-equal filter contains only an attribute * name and an assertion value.</LI> * <LI><B>PRESENCE</B> -- This is used to indicate that a filter should match * an entry only if the entry contains at least one value for the * specified attribute. A presence filter contains only an attribute * name.</LI> * <LI><B>APPROXIMATE-MATCH</B> -- This is used to indicate that a filter * should match an entry only if the entry contains at least one value for * the specified attribute that is approximately equal to the provided * assertion value. The definition of "approximately equal to" may vary * from one server to another, and from one attribute to another, but it * is often implemented as a "sounds like" match using a variant of the * metaphone or double-metaphone algorithm. An approximate-match filter * contains only an attribute name and an assertion value.</LI> * <LI><B>EXTENSIBLE-MATCH</B> -- This is used to perform advanced types of * matching against entries, according to the following criteria: * <UL> * <LI>If an attribute name is provided, then the assertion value must * match one of the values for that attribute (potentially including * values contained in the entry's DN). If a matching rule ID is * also provided, then the associated matching rule will be used to * determine whether there is a match; otherwise the default * equality matching rule for that attribute will be used.</LI> * <LI>If no attribute name is provided, then a matching rule ID must be * given, and the corresponding matching rule will be used to * determine whether any attribute in the target entry (potentially * including attributes contained in the entry's DN) has at least * one value that matches the provided assertion value.</LI> * <LI>If the dnAttributes flag is set, then attributes contained in the * entry's DN will also be evaluated to determine if they match the * filter criteria. If it is not set, then attributes contained in * the entry's DN (other than those contained in its RDN which are * also present as separate attributes in the entry) will not be * examined.</LI> * </UL> * An extensible match filter contains only an attribute name, matching * rule ID, dnAttributes flag, and an assertion value.</LI> * </UL> * <BR><BR> * There are two primary ways to create a search filter. The first is to create * a filter from its string representation with the * {@link com.hwlcn.ldap.ldap.sdk.Filter#create(String)} method, using the syntax described in RFC 4515. * For example: * <PRE> * Filter f1 = Filter.create("(objectClass=*)"); * Filter f2 = Filter.create("(uid=john.doe)"); * Filter f3 = Filter.create("(|(givenName=John)(givenName=Johnathan))"); * </PRE> * <BR><BR> * Creating a filter from its string representation is a common approach and * seems to be relatively straightforward, but it does have some hidden dangers. * This primarily comes from the potential for special characters in the filter * string which need to be properly escaped. If this isn't done, then the * search may fail or behave unexpectedly, or worse it could lead to a * vulnerability in the application in which a malicious user could trick the * application into retrieving more information than it should have. To avoid * these problems, it may be better to construct filters from their individual * components rather than their string representations, like: * <PRE> * Filter f1 = Filter.createPresenceFilter("objectClass"); * Filter f2 = Filter.createEqualityFilter("uid", "john.doe"); * Filter f3 = Filter.createORFilter( * Filter.createEqualityFilter("givenName", "John"), * Filter.createEqualityFilter("givenName", "Johnathan")); * </PRE> * In general, it is recommended to avoid creating filters from their string * representations if any of that string representation may include * user-provided data or special characters including non-ASCII characters, * parentheses, asterisks, or backslashes. */ @NotMutable() @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class Filter implements Serializable { public static final byte FILTER_TYPE_AND = (byte) 0xA0; public static final byte FILTER_TYPE_OR = (byte) 0xA1; public static final byte FILTER_TYPE_NOT = (byte) 0xA2; public static final byte FILTER_TYPE_EQUALITY = (byte) 0xA3; public static final byte FILTER_TYPE_SUBSTRING = (byte) 0xA4; public static final byte FILTER_TYPE_GREATER_OR_EQUAL = (byte) 0xA5; public static final byte FILTER_TYPE_LESS_OR_EQUAL = (byte) 0xA6; public static final byte FILTER_TYPE_PRESENCE = (byte) 0x87; public static final byte FILTER_TYPE_APPROXIMATE_MATCH = (byte) 0xA8; public static final byte FILTER_TYPE_EXTENSIBLE_MATCH = (byte) 0xA9; private static final byte SUBSTRING_TYPE_SUBINITIAL = (byte) 0x80; private static final byte SUBSTRING_TYPE_SUBANY = (byte) 0x81; private static final byte SUBSTRING_TYPE_SUBFINAL = (byte) 0x82; private static final byte EXTENSIBLE_TYPE_MATCHING_RULE_ID = (byte) 0x81; private static final byte EXTENSIBLE_TYPE_ATTRIBUTE_NAME = (byte) 0x82; private static final byte EXTENSIBLE_TYPE_MATCH_VALUE = (byte) 0x83; private static final byte EXTENSIBLE_TYPE_DN_ATTRIBUTES = (byte) 0x84; private static final Filter[] NO_FILTERS = new Filter[0]; private static final ASN1OctetString[] NO_SUB_ANY = new ASN1OctetString[0]; private static final long serialVersionUID = -2734184402804691970L; private final ASN1OctetString assertionValue; private final ASN1OctetString subFinal; private final ASN1OctetString subInitial; private final ASN1OctetString[] subAny; private final boolean dnAttributes; private final Filter notComp; private final Filter[] filterComps; private final byte filterType; private final String attrName; private volatile String filterString; private final String matchingRuleID; private volatile String normalizedString; private Filter(final String filterString, final byte filterType, final Filter[] filterComps, final Filter notComp, final String attrName, final ASN1OctetString assertionValue, final ASN1OctetString subInitial, final ASN1OctetString[] subAny, final ASN1OctetString subFinal, final String matchingRuleID, final boolean dnAttributes) { this.filterString = filterString; this.filterType = filterType; this.filterComps = filterComps; this.notComp = notComp; this.attrName = attrName; this.assertionValue = assertionValue; this.subInitial = subInitial; this.subAny = subAny; this.subFinal = subFinal; this.matchingRuleID = matchingRuleID; this.dnAttributes = dnAttributes; } public static Filter createANDFilter(final Filter... andComponents) { ensureNotNull(andComponents); return new Filter(null, FILTER_TYPE_AND, andComponents, null, null, null, null, NO_SUB_ANY, null, null, false); } public static Filter createANDFilter(final List<Filter> andComponents) { ensureNotNull(andComponents); return new Filter(null, FILTER_TYPE_AND, andComponents.toArray(new Filter[andComponents.size()]), null, null, null, null, NO_SUB_ANY, null, null, false); } public static Filter createORFilter(final Filter... orComponents) { ensureNotNull(orComponents); return new Filter(null, FILTER_TYPE_OR, orComponents, null, null, null, null, NO_SUB_ANY, null, null, false); } public static Filter createORFilter(final List<Filter> orComponents) { ensureNotNull(orComponents); return new Filter(null, FILTER_TYPE_OR, orComponents.toArray(new Filter[orComponents.size()]), null, null, null, null, NO_SUB_ANY, null, null, false); } public static Filter createNOTFilter(final Filter notComponent) { ensureNotNull(notComponent); return new Filter(null, FILTER_TYPE_NOT, NO_FILTERS, notComponent, null, null, null, NO_SUB_ANY, null, null, false); } public static Filter createEqualityFilter(final String attributeName, final String assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } public static Filter createEqualityFilter(final String attributeName, final byte[] assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } static Filter createEqualityFilter(final String attributeName, final ASN1OctetString assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_EQUALITY, NO_FILTERS, null, attributeName, assertionValue, null, NO_SUB_ANY, null, null, false); } public static Filter createSubstringFilter(final String attributeName, final String subInitial, final String[] subAny, final String subFinal) { ensureNotNull(attributeName); ensureTrue((subInitial != null) || ((subAny != null) && (subAny.length > 0)) || (subFinal != null)); final ASN1OctetString subInitialOS; if (subInitial == null) { subInitialOS = null; } else { subInitialOS = new ASN1OctetString(subInitial); } final ASN1OctetString[] subAnyArray; if (subAny == null) { subAnyArray = NO_SUB_ANY; } else { subAnyArray = new ASN1OctetString[subAny.length]; for (int i=0; i < subAny.length; i++) { subAnyArray[i] = new ASN1OctetString(subAny[i]); } } final ASN1OctetString subFinalOS; if (subFinal == null) { subFinalOS = null; } else { subFinalOS = new ASN1OctetString(subFinal); } return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, attributeName, null, subInitialOS, subAnyArray, subFinalOS, null, false); } public static Filter createSubstringFilter(final String attributeName, final byte[] subInitial, final byte[][] subAny, final byte[] subFinal) { ensureNotNull(attributeName); ensureTrue((subInitial != null) || ((subAny != null) && (subAny.length > 0)) || (subFinal != null)); final ASN1OctetString subInitialOS; if (subInitial == null) { subInitialOS = null; } else { subInitialOS = new ASN1OctetString(subInitial); } final ASN1OctetString[] subAnyArray; if (subAny == null) { subAnyArray = NO_SUB_ANY; } else { subAnyArray = new ASN1OctetString[subAny.length]; for (int i=0; i < subAny.length; i++) { subAnyArray[i] = new ASN1OctetString(subAny[i]); } } final ASN1OctetString subFinalOS; if (subFinal == null) { subFinalOS = null; } else { subFinalOS = new ASN1OctetString(subFinal); } return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, attributeName, null, subInitialOS, subAnyArray, subFinalOS, null, false); } static Filter createSubstringFilter(final String attributeName, final ASN1OctetString subInitial, final ASN1OctetString[] subAny, final ASN1OctetString subFinal) { ensureNotNull(attributeName); ensureTrue((subInitial != null) || ((subAny != null) && (subAny.length > 0)) || (subFinal != null)); if (subAny == null) { return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, attributeName, null, subInitial, NO_SUB_ANY, subFinal, null, false); } else { return new Filter(null, FILTER_TYPE_SUBSTRING, NO_FILTERS, null, attributeName, null, subInitial, subAny, subFinal, null, false); } } public static Filter createGreaterOrEqualFilter(final String attributeName, final String assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } public static Filter createGreaterOrEqualFilter(final String attributeName, final byte[] assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } static Filter createGreaterOrEqualFilter(final String attributeName, final ASN1OctetString assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_GREATER_OR_EQUAL, NO_FILTERS, null, attributeName, assertionValue, null, NO_SUB_ANY, null, null, false); } public static Filter createLessOrEqualFilter(final String attributeName, final String assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } public static Filter createLessOrEqualFilter(final String attributeName, final byte[] assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } static Filter createLessOrEqualFilter(final String attributeName, final ASN1OctetString assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_LESS_OR_EQUAL, NO_FILTERS, null, attributeName, assertionValue, null, NO_SUB_ANY, null, null, false); } public static Filter createPresenceFilter(final String attributeName) { ensureNotNull(attributeName); return new Filter(null, FILTER_TYPE_PRESENCE, NO_FILTERS, null, attributeName, null, null, NO_SUB_ANY, null, null, false); } public static Filter createApproximateMatchFilter(final String attributeName, final String assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } public static Filter createApproximateMatchFilter(final String attributeName, final byte[] assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, null, false); } static Filter createApproximateMatchFilter(final String attributeName, final ASN1OctetString assertionValue) { ensureNotNull(attributeName, assertionValue); return new Filter(null, FILTER_TYPE_APPROXIMATE_MATCH, NO_FILTERS, null, attributeName, assertionValue, null, NO_SUB_ANY, null, null, false); } public static Filter createExtensibleMatchFilter(final String attributeName, final String matchingRuleID, final boolean dnAttributes, final String assertionValue) { ensureNotNull(assertionValue); ensureFalse((attributeName == null) && (matchingRuleID == null)); return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, matchingRuleID, dnAttributes); } public static Filter createExtensibleMatchFilter(final String attributeName, final String matchingRuleID, final boolean dnAttributes, final byte[] assertionValue) { ensureNotNull(assertionValue); ensureFalse((attributeName == null) && (matchingRuleID == null)); return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, attributeName, new ASN1OctetString(assertionValue), null, NO_SUB_ANY, null, matchingRuleID, dnAttributes); } static Filter createExtensibleMatchFilter(final String attributeName, final String matchingRuleID, final boolean dnAttributes, final ASN1OctetString assertionValue) { ensureNotNull(assertionValue); ensureFalse((attributeName == null) && (matchingRuleID == null)); return new Filter(null, FILTER_TYPE_EXTENSIBLE_MATCH, NO_FILTERS, null, attributeName, assertionValue, null, NO_SUB_ANY, null, matchingRuleID, dnAttributes); } public static Filter create(final String filterString) throws LDAPException { ensureNotNull(filterString); return create(filterString, 0, (filterString.length() - 1), 0); } private static Filter create(final String filterString, final int startPos, final int endPos, final int depth) throws LDAPException { if (depth > 50) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_TOO_DEEP.get()); } final byte filterType; final Filter[] filterComps; final Filter notComp; final String attrName; final ASN1OctetString assertionValue; final ASN1OctetString subInitial; final ASN1OctetString[] subAny; final ASN1OctetString subFinal; final String matchingRuleID; final boolean dnAttributes; if (startPos >= endPos) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_TOO_SHORT.get()); } int l = startPos; int r = endPos; if (filterString.charAt(l) == '(') { if (filterString.charAt(r) == ')') { l++; r--; } else { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_OPEN_WITHOUT_CLOSE.get(l, r)); } } else { if (l != 0) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_MISSING_PARENTHESES.get( filterString.substring(l, r+1))); } } switch (filterString.charAt(l)) { case '&': filterType = FILTER_TYPE_AND; filterComps = parseFilterComps(filterString, l+1, r, depth+1); notComp = null; attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case '|': filterType = FILTER_TYPE_OR; filterComps = parseFilterComps(filterString, l+1, r, depth+1); notComp = null; attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case '!': filterType = FILTER_TYPE_NOT; filterComps = NO_FILTERS; notComp = create(filterString, l+1, r, depth+1); attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case '(': throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(l)); case ':': filterType = FILTER_TYPE_EXTENSIBLE_MATCH; filterComps = NO_FILTERS; notComp = null; attrName = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; final int dnMRIDStart = ++l; while ((l <= r) && (filterString.charAt(l) != ':')) { l++; } if (l > r) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_NO_COLON_AFTER_MRID.get( startPos)); } else if (l == dnMRIDStart) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EMPTY_MRID.get(startPos)); } final String s = filterString.substring(dnMRIDStart, l++); if (s.equalsIgnoreCase("dn")) { dnAttributes = true; final int mrIDStart = l; while ((l < r) && (filterString.charAt(l) != ':')) { l++; } if (l >= r) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_NO_COLON_AFTER_MRID.get( startPos)); } matchingRuleID = filterString.substring(mrIDStart, l); if (matchingRuleID.length() == 0) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EMPTY_MRID.get(startPos)); } if ((++l > r) || (filterString.charAt(l) != '=')) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CHAR_AFTER_MRID.get( filterString.charAt(l), startPos)); } } else { matchingRuleID = s; dnAttributes = false; if ((l > r) || (filterString.charAt(l) != '=')) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_NO_EQUAL_AFTER_MRID.get( startPos)); } } l++; final StringBuilder valueBuffer = new StringBuilder(r - l + 1); while (l <= r) { final char c = filterString.charAt(l); if (c == '\\') { l = readEscapedHexString(filterString, ++l, r, valueBuffer); } else if (c == '(') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_OPEN_PAREN.get(l)); } else if (c == ')') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get(l)); } else { valueBuffer.append(c); l++; } } assertionValue = new ASN1OctetString(valueBuffer.toString()); break; default: filterComps = NO_FILTERS; notComp = null; final int attrStartPos = l; int attrEndPos = -1; byte tempFilterType = 0x00; boolean filterTypeKnown = false; attrNameLoop: while (l <= r) { final char c = filterString.charAt(l++); switch (c) { case ':': tempFilterType = FILTER_TYPE_EXTENSIBLE_MATCH; filterTypeKnown = true; attrEndPos = l - 1; break attrNameLoop; case '>': tempFilterType = FILTER_TYPE_GREATER_OR_EQUAL; filterTypeKnown = true; attrEndPos = l - 1; if (l <= r) { if (filterString.charAt(l++) != '=') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CHAR_AFTER_GT.get( startPos, filterString.charAt(l-1))); } } else { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_END_AFTER_GT.get(startPos)); } break attrNameLoop; case '<': tempFilterType = FILTER_TYPE_LESS_OR_EQUAL; filterTypeKnown = true; attrEndPos = l - 1; if (l <= r) { if (filterString.charAt(l++) != '=') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CHAR_AFTER_LT.get( startPos, filterString.charAt(l-1))); } } else { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_END_AFTER_LT.get(startPos)); } break attrNameLoop; case '~': tempFilterType = FILTER_TYPE_APPROXIMATE_MATCH; filterTypeKnown = true; attrEndPos = l - 1; if (l <= r) { if (filterString.charAt(l++) != '=') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CHAR_AFTER_TILDE.get( startPos, filterString.charAt(l-1))); } } else { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_END_AFTER_TILDE.get( startPos)); } break attrNameLoop; case '=': attrEndPos = l - 1; break attrNameLoop; } } if (attrEndPos <= attrStartPos) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EMPTY_ATTR_NAME.get(startPos)); } attrName = filterString.substring(attrStartPos, attrEndPos); if (filterTypeKnown && (tempFilterType == FILTER_TYPE_EXTENSIBLE_MATCH)) { if (l > r) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_NO_EQUALS.get(startPos)); } final char c = filterString.charAt(l++); if (c == '=') { matchingRuleID = null; dnAttributes = false; } else { boolean equalFound = false; final int substrStartPos = l - 1; while (l <= r) { if (filterString.charAt(l++) == '=') { equalFound = true; break; } } if (! equalFound) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_NO_EQUALS.get(startPos)); } final String substr = filterString.substring(substrStartPos, l-1); final String lowerSubstr = toLowerCase(substr); if (! substr.endsWith(":")) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_CANNOT_PARSE_MRID.get( startPos)); } if (lowerSubstr.equals("dn:")) { matchingRuleID = null; dnAttributes = true; } else if (lowerSubstr.startsWith("dn:")) { matchingRuleID = substr.substring(3, substr.length() - 1); if (matchingRuleID.length() == 0) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EMPTY_MRID.get(startPos)); } dnAttributes = true; } else { matchingRuleID = substr.substring(0, substr.length() - 1); dnAttributes = false; if (matchingRuleID.length() == 0) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EMPTY_MRID.get(startPos)); } } } } else { matchingRuleID = null; dnAttributes = false; } if (l > r) { assertionValue = new ASN1OctetString(); if (! filterTypeKnown) { tempFilterType = FILTER_TYPE_EQUALITY; } subInitial = null; subAny = NO_SUB_ANY; subFinal = null; } else if (l == r) { if (filterTypeKnown) { switch (filterString.charAt(l)) { case '*': case '(': case ')': case '\\': throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get( filterString.charAt(l), startPos)); } assertionValue = new ASN1OctetString(filterString.substring(l, l+1)); } else { final char c = filterString.charAt(l); switch (c) { case '*': tempFilterType = FILTER_TYPE_PRESENCE; assertionValue = null; break; case '\\': case '(': case ')': throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CHAR_IN_AV.get( filterString.charAt(l), startPos)); default: tempFilterType = FILTER_TYPE_EQUALITY; assertionValue = new ASN1OctetString(filterString.substring(l, l+1)); break; } } subInitial = null; subAny = NO_SUB_ANY; subFinal = null; } else { if (! filterTypeKnown) { tempFilterType = FILTER_TYPE_EQUALITY; } final int valueStartPos = l; ASN1OctetString tempSubInitial = null; ASN1OctetString tempSubFinal = null; final ArrayList<ASN1OctetString> subAnyList = new ArrayList<ASN1OctetString>(1); StringBuilder buffer = new StringBuilder(r - l + 1); while (l <= r) { final char c = filterString.charAt(l++); switch (c) { case '*': if (filterTypeKnown) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_ASTERISK.get( startPos)); } else { if ((l-1) == valueStartPos) { } else { if (tempFilterType == FILTER_TYPE_SUBSTRING) { if (buffer.length() == 0) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_DOUBLE_ASTERISK.get( startPos)); } else { subAnyList.add(new ASN1OctetString(buffer.toString())); buffer = new StringBuilder(r - l + 1); } } else { tempSubInitial = new ASN1OctetString(buffer.toString()); buffer = new StringBuilder(r - l + 1); } } tempFilterType = FILTER_TYPE_SUBSTRING; } break; case '\\': l = readEscapedHexString(filterString, l, r, buffer); break; case '(': throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_OPEN_PAREN.get( l)); case ')': throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_UNEXPECTED_CLOSE_PAREN.get( l)); default: buffer.append(c); break; } } if ((tempFilterType == FILTER_TYPE_SUBSTRING) && (buffer.length() > 0)) { tempSubFinal = new ASN1OctetString(buffer.toString()); } subInitial = tempSubInitial; subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]); subFinal = tempSubFinal; if (tempFilterType == FILTER_TYPE_SUBSTRING) { assertionValue = null; } else { assertionValue = new ASN1OctetString(buffer.toString()); } } filterType = tempFilterType; break; } if (startPos == 0) { return new Filter(filterString, filterType, filterComps, notComp, attrName, assertionValue, subInitial, subAny, subFinal, matchingRuleID, dnAttributes); } else { return new Filter(filterString.substring(startPos, endPos+1), filterType, filterComps, notComp, attrName, assertionValue, subInitial, subAny, subFinal, matchingRuleID, dnAttributes); } } private static Filter[] parseFilterComps(final String filterString, final int startPos, final int endPos, final int depth) throws LDAPException { if (startPos > endPos) { return NO_FILTERS; } if (filterString.charAt(startPos) != '(') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EXPECTED_OPEN_PAREN.get(startPos)); } if (filterString.charAt(endPos) != ')') { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_EXPECTED_CLOSE_PAREN.get(startPos)); } final ArrayList<Filter> filterList = new ArrayList<Filter>(5); int filterStartPos = startPos; int pos = startPos; int numOpen = 0; while (pos <= endPos) { final char c = filterString.charAt(pos++); if (c == '(') { numOpen++; } else if (c == ')') { numOpen--; if (numOpen == 0) { filterList.add(create(filterString, filterStartPos, pos-1, depth)); filterStartPos = pos; } } } if (numOpen != 0) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_MISMATCHED_PARENS.get(startPos, endPos)); } return filterList.toArray(new Filter[filterList.size()]); } private static int readEscapedHexString(final String filterString, final int startPos, final int endPos, final StringBuilder buffer) throws LDAPException { int pos = startPos; final ByteBuffer byteBuffer = ByteBuffer.allocate(endPos - startPos); while (pos <= endPos) { byte b; switch (filterString.charAt(pos++)) { case '0': b = 0x00; break; case '1': b = 0x10; break; case '2': b = 0x20; break; case '3': b = 0x30; break; case '4': b = 0x40; break; case '5': b = 0x50; break; case '6': b = 0x60; break; case '7': b = 0x70; break; case '8': b = (byte) 0x80; break; case '9': b = (byte) 0x90; break; case 'a': case 'A': b = (byte) 0xA0; break; case 'b': case 'B': b = (byte) 0xB0; break; case 'c': case 'C': b = (byte) 0xC0; break; case 'd': case 'D': b = (byte) 0xD0; break; case 'e': case 'E': b = (byte) 0xE0; break; case 'f': case 'F': b = (byte) 0xF0; break; default: throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_INVALID_HEX_CHAR.get( filterString.charAt(pos-1), (pos-1))); } if (pos > endPos) { throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_INVALID_ESCAPED_END_CHAR.get( filterString.charAt(pos-1))); } switch (filterString.charAt(pos++)) { case '0': break; case '1': b |= 0x01; break; case '2': b |= 0x02; break; case '3': b |= 0x03; break; case '4': b |= 0x04; break; case '5': b |= 0x05; break; case '6': b |= 0x06; break; case '7': b |= 0x07; break; case '8': b |= 0x08; break; case '9': b |= 0x09; break; case 'a': case 'A': b |= 0x0A; break; case 'b': case 'B': b |= 0x0B; break; case 'c': case 'C': b |= 0x0C; break; case 'd': case 'D': b |= 0x0D; break; case 'e': case 'E': b |= 0x0E; break; case 'f': case 'F': b |= 0x0F; break; default: throw new LDAPException(ResultCode.FILTER_ERROR, ERR_FILTER_INVALID_HEX_CHAR.get( filterString.charAt(pos-1), (pos-1))); } byteBuffer.put(b); if ((pos <= endPos) && (filterString.charAt(pos) == '\\')) { pos++; continue; } else { break; } } byteBuffer.flip(); final byte[] byteArray = new byte[byteBuffer.limit()]; byteBuffer.get(byteArray); buffer.append(toUTF8String(byteArray)); return pos; } public void writeTo(final ASN1Buffer buffer) { switch (filterType) { case FILTER_TYPE_AND: case FILTER_TYPE_OR: final ASN1BufferSet compSet = buffer.beginSet(filterType); for (final Filter f : filterComps) { f.writeTo(buffer); } compSet.end(); break; case FILTER_TYPE_NOT: buffer.addElement( new ASN1Element(filterType, notComp.encode().encode())); break; case FILTER_TYPE_EQUALITY: case FILTER_TYPE_GREATER_OR_EQUAL: case FILTER_TYPE_LESS_OR_EQUAL: case FILTER_TYPE_APPROXIMATE_MATCH: final ASN1BufferSequence avaSequence = buffer.beginSequence(filterType); buffer.addOctetString(attrName); buffer.addElement(assertionValue); avaSequence.end(); break; case FILTER_TYPE_SUBSTRING: final ASN1BufferSequence subFilterSequence = buffer.beginSequence(filterType); buffer.addOctetString(attrName); final ASN1BufferSequence valueSequence = buffer.beginSequence(); if (subInitial != null) { buffer.addOctetString(SUBSTRING_TYPE_SUBINITIAL, subInitial.getValue()); } for (final ASN1OctetString s : subAny) { buffer.addOctetString(SUBSTRING_TYPE_SUBANY, s.getValue()); } if (subFinal != null) { buffer.addOctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue()); } valueSequence.end(); subFilterSequence.end(); break; case FILTER_TYPE_PRESENCE: buffer.addOctetString(filterType, attrName); break; case FILTER_TYPE_EXTENSIBLE_MATCH: final ASN1BufferSequence mrSequence = buffer.beginSequence(filterType); if (matchingRuleID != null) { buffer.addOctetString(EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID); } if (attrName != null) { buffer.addOctetString(EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName); } buffer.addOctetString(EXTENSIBLE_TYPE_MATCH_VALUE, assertionValue.getValue()); if (dnAttributes) { buffer.addBoolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true); } mrSequence.end(); break; } } public ASN1Element encode() { switch (filterType) { case FILTER_TYPE_AND: case FILTER_TYPE_OR: final ASN1Element[] filterElements = new ASN1Element[filterComps.length]; for (int i=0; i < filterComps.length; i++) { filterElements[i] = filterComps[i].encode(); } return new ASN1Set(filterType, filterElements); case FILTER_TYPE_NOT: return new ASN1Element(filterType, notComp.encode().encode()); case FILTER_TYPE_EQUALITY: case FILTER_TYPE_GREATER_OR_EQUAL: case FILTER_TYPE_LESS_OR_EQUAL: case FILTER_TYPE_APPROXIMATE_MATCH: final ASN1OctetString[] attrValueAssertionElements = { new ASN1OctetString(attrName), assertionValue }; return new ASN1Sequence(filterType, attrValueAssertionElements); case FILTER_TYPE_SUBSTRING: final ArrayList<ASN1OctetString> subList = new ArrayList<ASN1OctetString>(2 + subAny.length); if (subInitial != null) { subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBINITIAL, subInitial.getValue())); } for (final ASN1Element subAnyElement : subAny) { subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBANY, subAnyElement.getValue())); } if (subFinal != null) { subList.add(new ASN1OctetString(SUBSTRING_TYPE_SUBFINAL, subFinal.getValue())); } final ASN1Element[] subFilterElements = { new ASN1OctetString(attrName), new ASN1Sequence(subList) }; return new ASN1Sequence(filterType, subFilterElements); case FILTER_TYPE_PRESENCE: return new ASN1OctetString(filterType, attrName); case FILTER_TYPE_EXTENSIBLE_MATCH: final ArrayList<ASN1Element> emElementList = new ArrayList<ASN1Element>(4); if (matchingRuleID != null) { emElementList.add(new ASN1OctetString( EXTENSIBLE_TYPE_MATCHING_RULE_ID, matchingRuleID)); } if (attrName != null) { emElementList.add(new ASN1OctetString( EXTENSIBLE_TYPE_ATTRIBUTE_NAME, attrName)); } emElementList.add(new ASN1OctetString(EXTENSIBLE_TYPE_MATCH_VALUE, assertionValue.getValue())); if (dnAttributes) { emElementList.add(new ASN1Boolean(EXTENSIBLE_TYPE_DN_ATTRIBUTES, true)); } return new ASN1Sequence(filterType, emElementList); default: throw new AssertionError(ERR_FILTER_INVALID_TYPE.get( toHex(filterType))); } } public static Filter readFrom(final ASN1StreamReader reader) throws LDAPException { try { final Filter[] filterComps; final Filter notComp; final String attrName; final ASN1OctetString assertionValue; final ASN1OctetString subInitial; final ASN1OctetString[] subAny; final ASN1OctetString subFinal; final String matchingRuleID; final boolean dnAttributes; final byte filterType = (byte) reader.peek(); switch (filterType) { case FILTER_TYPE_AND: case FILTER_TYPE_OR: final ArrayList<Filter> comps = new ArrayList<Filter>(5); final ASN1StreamReaderSet elementSet = reader.beginSet(); while (elementSet.hasMoreElements()) { comps.add(readFrom(reader)); } filterComps = new Filter[comps.size()]; comps.toArray(filterComps); notComp = null; attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case FILTER_TYPE_NOT: final ASN1Element notFilterElement; try { final ASN1Element e = reader.readElement(); notFilterElement = ASN1Element.decode(e.getValue()); } catch (final ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(getExceptionMessage(ae)), ae); } notComp = decode(notFilterElement); filterComps = NO_FILTERS; attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case FILTER_TYPE_EQUALITY: case FILTER_TYPE_GREATER_OR_EQUAL: case FILTER_TYPE_LESS_OR_EQUAL: case FILTER_TYPE_APPROXIMATE_MATCH: reader.beginSequence(); attrName = reader.readString(); assertionValue = new ASN1OctetString(reader.readBytes()); filterComps = NO_FILTERS; notComp = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case FILTER_TYPE_SUBSTRING: reader.beginSequence(); attrName = reader.readString(); ASN1OctetString tempSubInitial = null; ASN1OctetString tempSubFinal = null; final ArrayList<ASN1OctetString> subAnyList = new ArrayList<ASN1OctetString>(1); final ASN1StreamReaderSequence subSequence = reader.beginSequence(); while (subSequence.hasMoreElements()) { final byte type = (byte) reader.peek(); final ASN1OctetString s = new ASN1OctetString(type, reader.readBytes()); switch (type) { case SUBSTRING_TYPE_SUBINITIAL: tempSubInitial = s; break; case SUBSTRING_TYPE_SUBANY: subAnyList.add(s); break; case SUBSTRING_TYPE_SUBFINAL: tempSubFinal = s; break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_INVALID_SUBSTR_TYPE.get(toHex(type))); } } subInitial = tempSubInitial; subFinal = tempSubFinal; subAny = new ASN1OctetString[subAnyList.size()]; subAnyList.toArray(subAny); filterComps = NO_FILTERS; notComp = null; assertionValue = null; matchingRuleID = null; dnAttributes = false; break; case FILTER_TYPE_PRESENCE: attrName = reader.readString(); filterComps = NO_FILTERS; notComp = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; break; case FILTER_TYPE_EXTENSIBLE_MATCH: String tempAttrName = null; ASN1OctetString tempAssertionValue = null; String tempMatchingRuleID = null; boolean tempDNAttributes = false; final ASN1StreamReaderSequence emSequence = reader.beginSequence(); while (emSequence.hasMoreElements()) { final byte type = (byte) reader.peek(); switch (type) { case EXTENSIBLE_TYPE_ATTRIBUTE_NAME: tempAttrName = reader.readString(); break; case EXTENSIBLE_TYPE_MATCHING_RULE_ID: tempMatchingRuleID = reader.readString(); break; case EXTENSIBLE_TYPE_MATCH_VALUE: tempAssertionValue = new ASN1OctetString(type, reader.readBytes()); break; case EXTENSIBLE_TYPE_DN_ATTRIBUTES: tempDNAttributes = reader.readBoolean(); break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_INVALID_TYPE.get(toHex(type))); } } if ((tempAttrName == null) && (tempMatchingRuleID == null)) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get()); } if (tempAssertionValue == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_NO_VALUE.get()); } attrName = tempAttrName; assertionValue = tempAssertionValue; matchingRuleID = tempMatchingRuleID; dnAttributes = tempDNAttributes; filterComps = NO_FILTERS; notComp = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_ELEMENT_INVALID_TYPE.get(toHex(filterType))); } return new Filter(null, filterType, filterComps, notComp, attrName, assertionValue, subInitial, subAny, subFinal, matchingRuleID, dnAttributes); } catch (LDAPException le) { debugException(le); throw le; } catch (Exception e) { debugException(e); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE.get(getExceptionMessage(e)), e); } } public static Filter decode(final ASN1Element filterElement) throws LDAPException { final byte filterType = filterElement.getType(); final Filter[] filterComps; final Filter notComp; final String attrName; final ASN1OctetString assertionValue; final ASN1OctetString subInitial; final ASN1OctetString[] subAny; final ASN1OctetString subFinal; final String matchingRuleID; final boolean dnAttributes; switch (filterType) { case FILTER_TYPE_AND: case FILTER_TYPE_OR: notComp = null; attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; final ASN1Set compSet; try { compSet = ASN1Set.decodeAsSet(filterElement); } catch (final ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_COMPS.get(getExceptionMessage(ae)), ae); } final ASN1Element[] compElements = compSet.elements(); filterComps = new Filter[compElements.length]; for (int i=0; i < compElements.length; i++) { filterComps[i] = decode(compElements[i]); } break; case FILTER_TYPE_NOT: filterComps = NO_FILTERS; attrName = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; final ASN1Element notFilterElement; try { notFilterElement = ASN1Element.decode(filterElement.getValue()); } catch (final ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_NOT_COMP.get(getExceptionMessage(ae)), ae); } notComp = decode(notFilterElement); break; case FILTER_TYPE_EQUALITY: case FILTER_TYPE_GREATER_OR_EQUAL: case FILTER_TYPE_LESS_OR_EQUAL: case FILTER_TYPE_APPROXIMATE_MATCH: filterComps = NO_FILTERS; notComp = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; final ASN1Sequence avaSequence; try { avaSequence = ASN1Sequence.decodeAsSequence(filterElement); } catch (final ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_AVA.get(getExceptionMessage(ae)), ae); } final ASN1Element[] avaElements = avaSequence.elements(); if (avaElements.length != 2) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_INVALID_AVA_ELEMENT_COUNT.get( avaElements.length)); } attrName = ASN1OctetString.decodeAsOctetString(avaElements[0]).stringValue(); assertionValue = ASN1OctetString.decodeAsOctetString(avaElements[1]); break; case FILTER_TYPE_SUBSTRING: filterComps = NO_FILTERS; notComp = null; assertionValue = null; matchingRuleID = null; dnAttributes = false; final ASN1Sequence subFilterSequence; try { subFilterSequence = ASN1Sequence.decodeAsSequence(filterElement); } catch (final ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(getExceptionMessage(ae)), ae); } final ASN1Element[] subFilterElements = subFilterSequence.elements(); if (subFilterElements.length != 2) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_INVALID_SUBSTR_ASSERTION_COUNT.get( subFilterElements.length)); } attrName = ASN1OctetString.decodeAsOctetString( subFilterElements[0]).stringValue(); final ASN1Sequence subSequence; try { subSequence = ASN1Sequence.decodeAsSequence(subFilterElements[1]); } catch (ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_SUBSTRING.get(getExceptionMessage(ae)), ae); } ASN1OctetString tempSubInitial = null; ASN1OctetString tempSubFinal = null; final ArrayList<ASN1OctetString> subAnyList = new ArrayList<ASN1OctetString>(1); final ASN1Element[] subElements = subSequence.elements(); for (final ASN1Element subElement : subElements) { switch (subElement.getType()) { case SUBSTRING_TYPE_SUBINITIAL: if (tempSubInitial == null) { tempSubInitial = ASN1OctetString.decodeAsOctetString(subElement); } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_MULTIPLE_SUBINITIAL.get()); } break; case SUBSTRING_TYPE_SUBANY: subAnyList.add(ASN1OctetString.decodeAsOctetString(subElement)); break; case SUBSTRING_TYPE_SUBFINAL: if (tempSubFinal == null) { tempSubFinal = ASN1OctetString.decodeAsOctetString(subElement); } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_MULTIPLE_SUBFINAL.get()); } break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_INVALID_SUBSTR_TYPE.get( toHex(subElement.getType()))); } } subInitial = tempSubInitial; subAny = subAnyList.toArray(new ASN1OctetString[subAnyList.size()]); subFinal = tempSubFinal; break; case FILTER_TYPE_PRESENCE: filterComps = NO_FILTERS; notComp = null; assertionValue = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; matchingRuleID = null; dnAttributes = false; attrName = ASN1OctetString.decodeAsOctetString(filterElement).stringValue(); break; case FILTER_TYPE_EXTENSIBLE_MATCH: filterComps = NO_FILTERS; notComp = null; subInitial = null; subAny = NO_SUB_ANY; subFinal = null; final ASN1Sequence emSequence; try { emSequence = ASN1Sequence.decodeAsSequence(filterElement); } catch (ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_CANNOT_DECODE_EXTMATCH.get(getExceptionMessage(ae)), ae); } String tempAttrName = null; ASN1OctetString tempAssertionValue = null; String tempMatchingRuleID = null; boolean tempDNAttributes = false; for (final ASN1Element e : emSequence.elements()) { switch (e.getType()) { case EXTENSIBLE_TYPE_ATTRIBUTE_NAME: if (tempAttrName == null) { tempAttrName = ASN1OctetString.decodeAsOctetString(e).stringValue(); } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_MULTIPLE_ATTRS.get()); } break; case EXTENSIBLE_TYPE_MATCHING_RULE_ID: if (tempMatchingRuleID == null) { tempMatchingRuleID = ASN1OctetString.decodeAsOctetString(e).stringValue(); } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_MULTIPLE_MRIDS.get()); } break; case EXTENSIBLE_TYPE_MATCH_VALUE: if (tempAssertionValue == null) { tempAssertionValue = ASN1OctetString.decodeAsOctetString(e); } else { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_MULTIPLE_VALUES.get()); } break; case EXTENSIBLE_TYPE_DN_ATTRIBUTES: try { if (tempDNAttributes) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_MULTIPLE_DNATTRS.get()); } else { tempDNAttributes = ASN1Boolean.decodeAsBoolean(e).booleanValue(); } } catch (ASN1Exception ae) { debugException(ae); throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_DNATTRS_NOT_BOOLEAN.get( getExceptionMessage(ae)), ae); } break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_INVALID_TYPE.get( toHex(e.getType()))); } } if ((tempAttrName == null) && (tempMatchingRuleID == null)) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_NO_ATTR_OR_MRID.get()); } if (tempAssertionValue == null) { throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_EXTMATCH_NO_VALUE.get()); } attrName = tempAttrName; assertionValue = tempAssertionValue; matchingRuleID = tempMatchingRuleID; dnAttributes = tempDNAttributes; break; default: throw new LDAPException(ResultCode.DECODING_ERROR, ERR_FILTER_ELEMENT_INVALID_TYPE.get( toHex(filterElement.getType()))); } return new Filter(null, filterType, filterComps, notComp, attrName, assertionValue, subInitial, subAny, subFinal, matchingRuleID, dnAttributes); } public byte getFilterType() { return filterType; } public Filter[] getComponents() { return filterComps; } public Filter getNOTComponent() { return notComp; } public String getAttributeName() { return attrName; } public String getAssertionValue() { if (assertionValue == null) { return null; } else { return assertionValue.stringValue(); } } public byte[] getAssertionValueBytes() { if (assertionValue == null) { return null; } else { return assertionValue.getValue(); } } public ASN1OctetString getRawAssertionValue() { return assertionValue; } public String getSubInitialString() { if (subInitial == null) { return null; } else { return subInitial.stringValue(); } } public byte[] getSubInitialBytes() { if (subInitial == null) { return null; } else { return subInitial.getValue(); } } public ASN1OctetString getRawSubInitialValue() { return subInitial; } public String[] getSubAnyStrings() { final String[] subAnyStrings = new String[subAny.length]; for (int i=0; i < subAny.length; i++) { subAnyStrings[i] = subAny[i].stringValue(); } return subAnyStrings; } public byte[][] getSubAnyBytes() { final byte[][] subAnyBytes = new byte[subAny.length][]; for (int i=0; i < subAny.length; i++) { subAnyBytes[i] = subAny[i].getValue(); } return subAnyBytes; } public ASN1OctetString[] getRawSubAnyValues() { return subAny; } public String getSubFinalString() { if (subFinal == null) { return null; } else { return subFinal.stringValue(); } } public byte[] getSubFinalBytes() { if (subFinal == null) { return null; } else { return subFinal.getValue(); } } public ASN1OctetString getRawSubFinalValue() { return subFinal; } public String getMatchingRuleID() { return matchingRuleID; } public boolean getDNAttributes() { return dnAttributes; } public boolean matchesEntry(final Entry entry) throws LDAPException { return matchesEntry(entry, entry.getSchema()); } public boolean matchesEntry(final Entry entry, final Schema schema) throws LDAPException { ensureNotNull(entry); switch (filterType) { case FILTER_TYPE_AND: for (final Filter f : filterComps) { if (! f.matchesEntry(entry, schema)) { return false; } } return true; case FILTER_TYPE_OR: for (final Filter f : filterComps) { if (f.matchesEntry(entry, schema)) { return true; } } return false; case FILTER_TYPE_NOT: return (! notComp.matchesEntry(entry, schema)); case FILTER_TYPE_EQUALITY: Attribute a = entry.getAttribute(attrName, schema); if (a == null) { return false; } MatchingRule matchingRule = MatchingRule.selectEqualityMatchingRule(attrName, schema); for (final ASN1OctetString v : a.getRawValues()) { if (matchingRule.valuesMatch(v, assertionValue)) { return true; } } return false; case FILTER_TYPE_SUBSTRING: a = entry.getAttribute(attrName, schema); if (a == null) { return false; } matchingRule = MatchingRule.selectSubstringMatchingRule(attrName, schema); for (final ASN1OctetString v : a.getRawValues()) { if (matchingRule.matchesSubstring(v, subInitial, subAny, subFinal)) { return true; } } return false; case FILTER_TYPE_GREATER_OR_EQUAL: a = entry.getAttribute(attrName, schema); if (a == null) { return false; } matchingRule = MatchingRule.selectOrderingMatchingRule(attrName, schema); for (final ASN1OctetString v : a.getRawValues()) { if (matchingRule.compareValues(v, assertionValue) >= 0) { return true; } } return false; case FILTER_TYPE_LESS_OR_EQUAL: a = entry.getAttribute(attrName, schema); if (a == null) { return false; } matchingRule = MatchingRule.selectOrderingMatchingRule(attrName, schema); for (final ASN1OctetString v : a.getRawValues()) { if (matchingRule.compareValues(v, assertionValue) <= 0) { return true; } } return false; case FILTER_TYPE_PRESENCE: return (entry.hasAttribute(attrName)); case FILTER_TYPE_APPROXIMATE_MATCH: throw new LDAPException(ResultCode.NOT_SUPPORTED, ERR_FILTER_APPROXIMATE_MATCHING_NOT_SUPPORTED.get()); case FILTER_TYPE_EXTENSIBLE_MATCH: throw new LDAPException(ResultCode.NOT_SUPPORTED, ERR_FILTER_EXTENSIBLE_MATCHING_NOT_SUPPORTED.get()); default: throw new LDAPException(ResultCode.PARAM_ERROR, ERR_FILTER_INVALID_TYPE.get()); } } @Override() public int hashCode() { final CaseIgnoreStringMatchingRule matchingRule = CaseIgnoreStringMatchingRule.getInstance(); int hashCode = filterType; switch (filterType) { case FILTER_TYPE_AND: case FILTER_TYPE_OR: for (final Filter f : filterComps) { hashCode += f.hashCode(); } break; case FILTER_TYPE_NOT: hashCode += notComp.hashCode(); break; case FILTER_TYPE_EQUALITY: case FILTER_TYPE_GREATER_OR_EQUAL: case FILTER_TYPE_LESS_OR_EQUAL: case FILTER_TYPE_APPROXIMATE_MATCH: hashCode += toLowerCase(attrName).hashCode(); hashCode += matchingRule.normalize(assertionValue).hashCode(); break; case FILTER_TYPE_SUBSTRING: hashCode += toLowerCase(attrName).hashCode(); if (subInitial != null) { hashCode += matchingRule.normalizeSubstring(subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL).hashCode(); } for (final ASN1OctetString s : subAny) { hashCode += matchingRule.normalizeSubstring(s, MatchingRule.SUBSTRING_TYPE_SUBANY).hashCode(); } if (subFinal != null) { hashCode += matchingRule.normalizeSubstring(subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL).hashCode(); } break; case FILTER_TYPE_PRESENCE: hashCode += toLowerCase(attrName).hashCode(); break; case FILTER_TYPE_EXTENSIBLE_MATCH: if (attrName != null) { hashCode += toLowerCase(attrName).hashCode(); } if (matchingRuleID != null) { hashCode += toLowerCase(matchingRuleID).hashCode(); } if (dnAttributes) { hashCode++; } hashCode += matchingRule.normalize(assertionValue).hashCode(); break; } return hashCode; } @Override() public boolean equals(final Object o) { if (o == null) { return false; } if (o == this) { return true; } if (! (o instanceof Filter)) { return false; } final Filter f = (Filter) o; if (filterType != f.filterType) { return false; } final CaseIgnoreStringMatchingRule matchingRule = CaseIgnoreStringMatchingRule.getInstance(); switch (filterType) { case FILTER_TYPE_AND: case FILTER_TYPE_OR: if (filterComps.length != f.filterComps.length) { return false; } final HashSet<Filter> compSet = new HashSet<Filter>(); compSet.addAll(Arrays.asList(filterComps)); for (final Filter filterComp : f.filterComps) { if (! compSet.remove(filterComp)) { return false; } } return true; case FILTER_TYPE_NOT: return notComp.equals(f.notComp); case FILTER_TYPE_EQUALITY: case FILTER_TYPE_GREATER_OR_EQUAL: case FILTER_TYPE_LESS_OR_EQUAL: case FILTER_TYPE_APPROXIMATE_MATCH: return (attrName.equalsIgnoreCase(f.attrName) && matchingRule.valuesMatch(assertionValue, f.assertionValue)); case FILTER_TYPE_SUBSTRING: if (! attrName.equalsIgnoreCase(f.attrName)) { return false; } if (subAny.length != f.subAny.length) { return false; } if (subInitial == null) { if (f.subInitial != null) { return false; } } else { if (f.subInitial == null) { return false; } final ASN1OctetString si1 = matchingRule.normalizeSubstring( subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL); final ASN1OctetString si2 = matchingRule.normalizeSubstring( f.subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL); if (! si1.equals(si2)) { return false; } } for (int i=0; i < subAny.length; i++) { final ASN1OctetString sa1 = matchingRule.normalizeSubstring(subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY); final ASN1OctetString sa2 = matchingRule.normalizeSubstring( f.subAny[i], MatchingRule.SUBSTRING_TYPE_SUBANY); if (! sa1.equals(sa2)) { return false; } } if (subFinal == null) { if (f.subFinal != null) { return false; } } else { if (f.subFinal == null) { return false; } final ASN1OctetString sf1 = matchingRule.normalizeSubstring(subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL); final ASN1OctetString sf2 = matchingRule.normalizeSubstring( f.subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL); if (! sf1.equals(sf2)) { return false; } } return true; case FILTER_TYPE_PRESENCE: return (attrName.equalsIgnoreCase(f.attrName)); case FILTER_TYPE_EXTENSIBLE_MATCH: if (attrName == null) { if (f.attrName != null) { return false; } } else { if (f.attrName == null) { return false; } else { if (! attrName.equalsIgnoreCase(f.attrName)) { return false; } } } if (matchingRuleID == null) { if (f.matchingRuleID != null) { return false; } } else { if (f.matchingRuleID == null) { return false; } else { if (! matchingRuleID.equalsIgnoreCase(f.matchingRuleID)) { return false; } } } if (dnAttributes != f.dnAttributes) { return false; } return matchingRule.valuesMatch(assertionValue, f.assertionValue); default: return false; } } @Override() public String toString() { if (filterString == null) { final StringBuilder buffer = new StringBuilder(); toString(buffer); filterString = buffer.toString(); } return filterString; } public void toString(final StringBuilder buffer) { switch (filterType) { case FILTER_TYPE_AND: buffer.append("(&"); for (final Filter f : filterComps) { f.toString(buffer); } buffer.append(')'); break; case FILTER_TYPE_OR: buffer.append("(|"); for (final Filter f : filterComps) { f.toString(buffer); } buffer.append(')'); break; case FILTER_TYPE_NOT: buffer.append("(!"); notComp.toString(buffer); buffer.append(')'); break; case FILTER_TYPE_EQUALITY: buffer.append('('); buffer.append(attrName); buffer.append('='); encodeValue(assertionValue, buffer); buffer.append(')'); break; case FILTER_TYPE_SUBSTRING: buffer.append('('); buffer.append(attrName); buffer.append('='); if (subInitial != null) { encodeValue(subInitial, buffer); } buffer.append('*'); for (final ASN1OctetString s : subAny) { encodeValue(s, buffer); buffer.append('*'); } if (subFinal != null) { encodeValue(subFinal, buffer); } buffer.append(')'); break; case FILTER_TYPE_GREATER_OR_EQUAL: buffer.append('('); buffer.append(attrName); buffer.append(">="); encodeValue(assertionValue, buffer); buffer.append(')'); break; case FILTER_TYPE_LESS_OR_EQUAL: buffer.append('('); buffer.append(attrName); buffer.append("<="); encodeValue(assertionValue, buffer); buffer.append(')'); break; case FILTER_TYPE_PRESENCE: buffer.append('('); buffer.append(attrName); buffer.append("=*)"); break; case FILTER_TYPE_APPROXIMATE_MATCH: buffer.append('('); buffer.append(attrName); buffer.append("~="); encodeValue(assertionValue, buffer); buffer.append(')'); break; case FILTER_TYPE_EXTENSIBLE_MATCH: buffer.append('('); if (attrName != null) { buffer.append(attrName); } if (dnAttributes) { buffer.append(":dn"); } if (matchingRuleID != null) { buffer.append(':'); buffer.append(matchingRuleID); } buffer.append(":="); encodeValue(assertionValue, buffer); buffer.append(')'); break; } } public String toNormalizedString() { if (normalizedString == null) { final StringBuilder buffer = new StringBuilder(); toNormalizedString(buffer); normalizedString = buffer.toString(); } return normalizedString; } public void toNormalizedString(final StringBuilder buffer) { final CaseIgnoreStringMatchingRule mr = CaseIgnoreStringMatchingRule.getInstance(); switch (filterType) { case FILTER_TYPE_AND: buffer.append("(&"); for (final Filter f : filterComps) { f.toNormalizedString(buffer); } buffer.append(')'); break; case FILTER_TYPE_OR: buffer.append("(|"); for (final Filter f : filterComps) { f.toNormalizedString(buffer); } buffer.append(')'); break; case FILTER_TYPE_NOT: buffer.append("(!"); notComp.toNormalizedString(buffer); buffer.append(')'); break; case FILTER_TYPE_EQUALITY: buffer.append('('); buffer.append(toLowerCase(attrName)); buffer.append('='); encodeValue(mr.normalize(assertionValue), buffer); buffer.append(')'); break; case FILTER_TYPE_SUBSTRING: buffer.append('('); buffer.append(toLowerCase(attrName)); buffer.append('='); if (subInitial != null) { encodeValue(mr.normalizeSubstring(subInitial, MatchingRule.SUBSTRING_TYPE_SUBINITIAL), buffer); } buffer.append('*'); for (final ASN1OctetString s : subAny) { encodeValue(mr.normalizeSubstring(s, MatchingRule.SUBSTRING_TYPE_SUBANY), buffer); buffer.append('*'); } if (subFinal != null) { encodeValue(mr.normalizeSubstring(subFinal, MatchingRule.SUBSTRING_TYPE_SUBFINAL), buffer); } buffer.append(')'); break; case FILTER_TYPE_GREATER_OR_EQUAL: buffer.append('('); buffer.append(toLowerCase(attrName)); buffer.append(">="); encodeValue(mr.normalize(assertionValue), buffer); buffer.append(')'); break; case FILTER_TYPE_LESS_OR_EQUAL: buffer.append('('); buffer.append(toLowerCase(attrName)); buffer.append("<="); encodeValue(mr.normalize(assertionValue), buffer); buffer.append(')'); break; case FILTER_TYPE_PRESENCE: buffer.append('('); buffer.append(toLowerCase(attrName)); buffer.append("=*)"); break; case FILTER_TYPE_APPROXIMATE_MATCH: buffer.append('('); buffer.append(toLowerCase(attrName)); buffer.append("~="); encodeValue(mr.normalize(assertionValue), buffer); buffer.append(')'); break; case FILTER_TYPE_EXTENSIBLE_MATCH: buffer.append('('); if (attrName != null) { buffer.append(toLowerCase(attrName)); } if (dnAttributes) { buffer.append(":dn"); } if (matchingRuleID != null) { buffer.append(':'); buffer.append(toLowerCase(matchingRuleID)); } buffer.append(":="); encodeValue(mr.normalize(assertionValue), buffer); buffer.append(')'); break; } } public static String encodeValue(final String value) { ensureNotNull(value); final StringBuilder buffer = new StringBuilder(); encodeValue(new ASN1OctetString(value), buffer); return buffer.toString(); } public static String encodeValue(final byte[]value) { ensureNotNull(value); final StringBuilder buffer = new StringBuilder(); encodeValue(new ASN1OctetString(value), buffer); return buffer.toString(); } private static void encodeValue(final ASN1OctetString value, final StringBuilder buffer) { final String valueString = value.stringValue(); final int length = valueString.length(); for (int i=0; i < length; i++) { final char c = valueString.charAt(i); switch (c) { case '\u0000': case '(': case ')': case '*': case '\\': hexEncode(c, buffer); break; default: if (c <= 0x7F) { buffer.append(c); } else { hexEncode(c, buffer); } break; } } } }