/* * Sun Public License * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is available at http://www.sun.com/ * * The Original Code is the SLAMD Distributed Load Generation Engine. * The Initial Developer of the Original Code is Neil A. Wilson. * Portions created by Neil A. Wilson are Copyright (C) 2004-2010. * Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): Neil A. Wilson */ package com.slamd.tools.ldapdecoder.protocol; import java.util.ArrayList; import com.slamd.asn1.ASN1Boolean; import com.slamd.asn1.ASN1Element; import com.slamd.asn1.ASN1OctetString; import com.slamd.asn1.ASN1Sequence; import com.slamd.asn1.ASN1Set; /** * This class defines an LDAP search filter, which specifies criteria to use for * finding entries in a directory server. * * * @author Neil A. Wilson */ public class SearchFilter { /** * The ASN.1 type for AND filters. */ public static final byte AND_FILTER_TYPE = (byte) 0xA0; /** * The ASN.1 type for OR filters. */ public static final byte OR_FILTER_TYPE = (byte) 0xA1; /** * The ASN.1 type for NOT filters. */ public static final byte NOT_FILTER_TYPE = (byte) 0xA2; /** * The ASN.1 type for equality filters. */ public static final byte EQUALITY_FILTER_TYPE = (byte) 0xA3; /** * The ASN.1 type for substring filters. */ public static final byte SUBSTRING_FILTER_TYPE = (byte) 0xA4; /** * The ASN.1 type for greater or equal filters. */ public static final byte GREATER_OR_EQUAL_FILTER_TYPE = (byte) 0xA5; /** * The ASN.1 type for less or equal filters. */ public static final byte LESS_OR_EQUAL_FILTER_TYPE = (byte) 0xA6; /** * The ASN.1 type for presence filters. */ public static final byte PRESENCE_FILTER_TYPE = (byte) 0x87; /** * The ASN.1 type for approximate filters. */ public static final byte APPROXIMATE_FILTER_TYPE = (byte) 0xA8; /** * The ASN.1 type for extensible matching filters. */ public static final byte EXTENSIBLE_MATCH_FILTER_TYPE = (byte) 0xA9; /** * The ASN.1 type for subInitial value in a substring filter. */ public static final byte SUBINITIAL_TYPE = (byte) 0x80; /** * The ASN.1 type for subAny values in a substring filter. */ public static final byte SUBANY_TYPE = (byte) 0x81; /** * The ASN.1 type for subFinal value in a substring filter. */ public static final byte SUBFINAL_TYPE = (byte) 0x82; /** * The ASN.1 type for the matching rule ID in an extensible match filter. */ public static final byte EXTENSIBLE_MATCH_RULE_ID_TYPE = (byte) 0x81; /** * The ASN.1 type for the attribute type ID in an extensible match filter. */ public static final byte EXTENSIBLE_MATCH_ATTRIBUTE_TYPE = (byte) 0x82; /** * The ASN.1 type for the match value ID in an extensible match filter. */ public static final byte EXTENSIBLE_MATCH_VALUE_TYPE = (byte) 0x83; /** * The ASN.1 type for the indicator of whether to include DN attributes for an * extensible match filter. */ public static final byte EXTENSIBLE_MATCH_INCLUDE_DN_TYPE = (byte) 0x84; // Indicates whether to include DN attributes in the extensible matching. private boolean includeDNAttributes; // The filter type for this search filter. private byte filterType; // The set of subfilters for this search filter. private SearchFilter[] subFilters; // The attribute type for this search filter. private String attributeType; // The assertion value for this search filter. private String assertionValue; // The OID of the matching rule for the extensible matching. private String matchingRuleID; // The subFinal portion of the substring filter. private String subFinalValue; // The subInitial portion of the substring filter. private String subInitialValue; // The subAny portion of the substring filter. private String[] subAnyValues; /** * Creates a new search filter with the provided information. * * @param filterType The filter type for this filter. * @param subFilters The set of subfilters for this filter. * @param attributeType The attribute type for this filter. * @param assertionValue The assertion value for this filter. * @param subInitialValue The subInitial value for this substring * filter. * @param subAnyValues The subAny values for this substring filter. * @param subFinalValue The subFinal value for this substring filter. * @param matchingRuleID The matching rule ID for this extensible * matching filter. * @param includeDNAttributes Indicates whether to include DN attributes in * this extensible matching filter. */ private SearchFilter(byte filterType, SearchFilter[] subFilters, String attributeType, String assertionValue, String subInitialValue, String[] subAnyValues, String subFinalValue, String matchingRuleID, boolean includeDNAttributes) { this.filterType = filterType; this.subFilters = subFilters; this.attributeType = attributeType; this.assertionValue = assertionValue; this.subInitialValue = subInitialValue; this.subAnyValues = subAnyValues; this.subFinalValue = subFinalValue; this.matchingRuleID = matchingRuleID; this.includeDNAttributes = includeDNAttributes; } /** * Creates a new AND search filter containing the provided subfilters. * * @param subFilters The subfilters to contain in the AND filter. * * @return An AND search filter containing the provided subfilters. */ public static SearchFilter createANDFilter(SearchFilter[] subFilters) { return new SearchFilter(AND_FILTER_TYPE, subFilters, null, null, null, null, null, null, false); } /** * Creates a new OR search filter containing the provided subfilters. * * @param subFilters The subfilters to contain in the OR filter. * * @return An OR search filter containing the provided subfilters. */ public static SearchFilter createORFilter(SearchFilter[] subFilters) { return new SearchFilter(OR_FILTER_TYPE, subFilters, null, null, null, null, null, null, false); } /** * Creates a new NOT search filter containing the provided subfilter. * * @param subFilter The subfilter to contain in the NOT filter. * * @return A NOT search filter containing the provided subfilter. */ public static SearchFilter createNOTFilter(SearchFilter subFilter) { return new SearchFilter(NOT_FILTER_TYPE, new SearchFilter[] { subFilter }, null, null, null, null, null, null, false); } /** * Creates a new equality search filter containing the specified attribute * type and assertion value. * * @param attributeType The attribute type for the equality search filter. * @param assertionValue The assertion value for the equality search filter. * * @return The requested equality search filter. */ public static SearchFilter createEqualityFilter(String attributeType, String assertionValue) { return new SearchFilter(EQUALITY_FILTER_TYPE, null, attributeType, assertionValue, null, null, null, null, false); } /** * Creates a new substring search filter with the provided information. * * @param attributeType The attribute type for the substring filter. * @param subInitialValue The subInitial value for the substring filter. * @param subAnyValues The subAny values for the substring filter. * @param subFinalValue The subFinal value for the substring filter. * * @return The requested substring search filter. */ public static SearchFilter createSubstringFilter(String attributeType, String subInitialValue, String[] subAnyValues, String subFinalValue) { return new SearchFilter(SUBSTRING_FILTER_TYPE, null, attributeType, null, subInitialValue, subAnyValues, subFinalValue, null, false); } /** * Creates a new greater or equal search filter containing the specified * attribute type and assertion value. * * @param attributeType The attribute type for the greater or equal search * filter. * @param assertionValue The assertion value for the greater or equal search * filter. * * @return The requested greater or equal search filter. */ public static SearchFilter createGreaterOrEqualFilter(String attributeType, String assertionValue) { return new SearchFilter(GREATER_OR_EQUAL_FILTER_TYPE, null, attributeType, assertionValue, null, null, null, null, false); } /** * Creates a new less or equal search filter containing the specified * attribute type and assertion value. * * @param attributeType The attribute type for the less or equal search * filter. * @param assertionValue The assertion value for the less or equal search * filter. * * @return The requested less or equal search filter. */ public static SearchFilter createLessOrEqualFilter(String attributeType, String assertionValue) { return new SearchFilter(LESS_OR_EQUAL_FILTER_TYPE, null, attributeType, assertionValue, null, null, null, null, false); } /** * Creates a new presence search filter with the specified attribute type. * * @param attributeType The attribute type for the presence search filter. * * @return The requested presence search filter. */ public static SearchFilter createPresenceFilter(String attributeType) { return new SearchFilter(PRESENCE_FILTER_TYPE, null, attributeType, null, null, null, null, null, false); } /** * Creates a new approximate search filter containing the specified attribute * type and assertion value. * * @param attributeType The attribute type for the approximate search * filter. * @param assertionValue The assertion value for the approximate search * filter. * * @return The requested approximate search filter. */ public static SearchFilter createApproximateFilter(String attributeType, String assertionValue) { return new SearchFilter(APPROXIMATE_FILTER_TYPE, null, attributeType, assertionValue, null, null, null, null, false); } /** * Creates a new extensible matching filter with the provided information. * * @param matchingRuleID The OID of the matching rule to use for the * extensible matching filter. * @param attributeType The attribute type to use for the extensible * matching filter. * @param assertionValue The assertion value to use for the extensible * matching filter. * @param includeDNAttributes Includes whether to consider DN attributes * when performing the matching. * * @return The requested extensible matching search filter. */ public static SearchFilter createExtensibleFilter(String matchingRuleID, String attributeType, String assertionValue, boolean includeDNAttributes) { return new SearchFilter(EXTENSIBLE_MATCH_FILTER_TYPE, null, attributeType, assertionValue, null, null, null, matchingRuleID, includeDNAttributes); } /** * Retrieves the filter type for this search filter. * * @return The filter type for this search filter. */ public byte getFilterType() { return filterType; } /** * Retrieves the set of subfilters for this search filter. * * @return The set of subfilters for this search filter. */ public SearchFilter[] getSubFilters() { return subFilters; } /** * Retrieves the attribute type for this search filter. * * @return The attribute type for this search filter. */ public String getAttributeType() { return attributeType; } /** * Retrieves the assertion value for this search filter. * * @return The assertion value for this search filter. */ public String getAssertionValue() { return assertionValue; } /** * Retrieves the subInitial value for this search filter. * * @return The subInitial value for this search filter. */ public String getSubInitialValue() { return subInitialValue; } /** * Retrieves the set of subAny values for this search filter. * * @return The subAny values for this search filter. */ public String[] getSubAnyValues() { return subAnyValues; } /** * Retrieves the subFinal value for this search filter. * * @return The subFinal value for this search filter. */ public String getSubFinalValue() { return subFinalValue; } /** * Retrieves the matching rule ID for this search filter. * * @return The matching rule ID for this search filter. */ public String getMatchingRuleID() { return matchingRuleID; } /** * Indicates whether to include DN attributes when performing the extensible * matching. * * @return <CODE>true</CODE> if DN attributes should be considered when * performing the extensible matching, or <CODE>false</CODE> if not. */ public boolean includeDNAttributes() { return includeDNAttributes; } /** * Encodes this search filter to an ASN.1 element. * * @return The ASN.1 element containing the encoded search filter. */ public ASN1Element encode() { switch (filterType) { case AND_FILTER_TYPE: case OR_FILTER_TYPE: ASN1Element[] elements = new ASN1Element[subFilters.length]; for (int i=0; i < subFilters.length; i++) { elements[i] = subFilters[i].encode(); } return new ASN1Set(filterType, elements); case NOT_FILTER_TYPE: ASN1Element subElement = subFilters[0].encode(); return new ASN1OctetString(filterType, subElement.encode()); case EQUALITY_FILTER_TYPE: case GREATER_OR_EQUAL_FILTER_TYPE: case LESS_OR_EQUAL_FILTER_TYPE: case APPROXIMATE_FILTER_TYPE: elements = new ASN1Element[] { new ASN1OctetString(attributeType), new ASN1OctetString(assertionValue) }; return new ASN1Sequence(filterType, elements); case SUBSTRING_FILTER_TYPE: ArrayList<ASN1Element> valueElementList = new ArrayList<ASN1Element>(); if ((subInitialValue != null) && (subInitialValue.length() > 0)) { valueElementList.add(new ASN1OctetString(SUBINITIAL_TYPE, subInitialValue)); } for (int i=0; ((subAnyValues != null) && (i < subAnyValues.length)); i++) { valueElementList.add(new ASN1OctetString(SUBANY_TYPE, subAnyValues[i])); } if ((subFinalValue != null) && (subFinalValue.length() > 0)) { valueElementList.add(new ASN1OctetString(SUBFINAL_TYPE, subFinalValue)); } ASN1Element[] valueElements = new ASN1Element[valueElementList.size()]; valueElementList.toArray(valueElements); elements = new ASN1Element[] { new ASN1OctetString(attributeType), new ASN1Sequence(valueElements) }; return new ASN1Sequence(filterType, elements); case PRESENCE_FILTER_TYPE: return new ASN1OctetString(filterType, attributeType); case EXTENSIBLE_MATCH_FILTER_TYPE: ArrayList<ASN1Element> elementList = new ArrayList<ASN1Element>(4); if ((matchingRuleID != null) && (matchingRuleID.length() > 0)) { elementList.add(new ASN1OctetString(EXTENSIBLE_MATCH_RULE_ID_TYPE, matchingRuleID)); } if ((attributeType != null) && (attributeType.length() > 0)) { elementList.add(new ASN1OctetString(EXTENSIBLE_MATCH_ATTRIBUTE_TYPE, attributeType)); } elementList.add(new ASN1OctetString(EXTENSIBLE_MATCH_VALUE_TYPE, assertionValue)); if (includeDNAttributes) { elementList.add(new ASN1Boolean(EXTENSIBLE_MATCH_INCLUDE_DN_TYPE, includeDNAttributes)); } elements = new ASN1Element[elementList.size()]; elementList.toArray(elements); return new ASN1Sequence(filterType, elements); default: return null; } } /** * Decodes the provided ASN.1 element as an LDAP search filter. * * @param element The ASN.1 element to be decoded as a search filter. * * @return The decoded search filter. * * @throws ProtocolException If a problem occurs while decoding the search * filter. */ public static SearchFilter decode(ASN1Element element) throws ProtocolException { switch (element.getType()) { case AND_FILTER_TYPE: ASN1Element[] elements; try { elements = element.decodeAsSet().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode element as a set of " + "search filters", e); } SearchFilter[] subFilters = new SearchFilter[elements.length]; for (int i=0; i < elements.length; i++) { try { subFilters[i] = decode(elements[i]); } catch (Exception e) { throw new ProtocolException("Unable to decode AND subfilter", e); } } return createANDFilter(subFilters); case OR_FILTER_TYPE: try { elements = element.decodeAsSet().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode element as a set of " + "search filters", e); } subFilters = new SearchFilter[elements.length]; for (int i=0; i < elements.length; i++) { try { subFilters[i] = decode(elements[i]); } catch (Exception e) { throw new ProtocolException("Unable to decode OR subfilter", e); } } return createORFilter(subFilters); case NOT_FILTER_TYPE: try { ASN1Element filterElement = ASN1Element.decode(element.getValue()); return createNOTFilter(decode(filterElement)); } catch (Exception e) { throw new ProtocolException("Unable to decode NOT filter element", e); } case EQUALITY_FILTER_TYPE: try { elements = element.decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode equality filter " + "sequence", e); } if (elements.length != 2) { throw new ProtocolException("There must be exactly two elements in " + "an equality filter sequence"); } String attributeType; try { attributeType = elements[0].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode equality filter " + "attribute type", e); } String assertionValue; try { assertionValue = elements[1].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode equality filter " + "assertion value", e); } return createEqualityFilter(attributeType, assertionValue); case SUBSTRING_FILTER_TYPE: try { elements = element.decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode substring filter " + "sequence", e); } if (elements.length != 2) { throw new ProtocolException("There must be exactly two elements in " + "a substring filter sequence"); } try { attributeType = elements[0].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode attribute type for " + "substring search filter", e); } ASN1Element[] subValueElements; try { subValueElements = elements[1].decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode substring value " + "elements", e); } ArrayList<String> subAnyList = new ArrayList<String>(); String subInitialValue = null; String subFinalValue = null; String[] subAnyValues = null; for (int i=0; i < subValueElements.length; i++) { switch (subValueElements[i].getType()) { case SUBINITIAL_TYPE: try { subInitialValue = subValueElements[i].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode subInitial value", e); } break; case SUBANY_TYPE: try { String subAnyValue = subValueElements[i].decodeAsOctetString().getStringValue(); subAnyList.add(subAnyValue); } catch (Exception e) { throw new ProtocolException("Unable to decode subAny value", e); } break; case SUBFINAL_TYPE: try { subFinalValue = subValueElements[i].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode subFinal value", e); } break; default: throw new ProtocolException("Unrecognized substring filter " + "type " + subValueElements[i].getType()); } } if (! subAnyList.isEmpty()) { subAnyValues = new String[subAnyList.size()]; subAnyList.toArray(subAnyValues); } return createSubstringFilter(attributeType, subInitialValue, subAnyValues, subFinalValue); case GREATER_OR_EQUAL_FILTER_TYPE: try { elements = element.decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode greater or equal " + "filter sequence", e); } if (elements.length != 2) { throw new ProtocolException("There must be exactly two elements in " + "a greater or equal filter sequence"); } try { attributeType = elements[0].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode greater or equal " + "filter attribute type", e); } try { assertionValue = elements[1].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode greater or equal " + "filter assertion value", e); } return createGreaterOrEqualFilter(attributeType, assertionValue); case LESS_OR_EQUAL_FILTER_TYPE: try { elements = element.decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode less or equal " + "filter sequence", e); } if (elements.length != 2) { throw new ProtocolException("There must be exactly two elements in " + "a less or equal filter sequence"); } try { attributeType = elements[0].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode less or equal " + "filter attribute type", e); } try { assertionValue = elements[1].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode less or equal " + "filter assertion value", e); } return createLessOrEqualFilter(attributeType, assertionValue); case PRESENCE_FILTER_TYPE: try { attributeType = element.decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode presence filter " + "attribute type", e); } return createPresenceFilter(attributeType); case APPROXIMATE_FILTER_TYPE: try { elements = element.decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode approximate " + "filter sequence", e); } if (elements.length != 2) { throw new ProtocolException("There must be exactly two elements in " + "an approximate filter sequence"); } try { attributeType = elements[0].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode approximate " + "filter attribute type", e); } try { assertionValue = elements[1].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode approximate " + "filter assertion value", e); } return createApproximateFilter(attributeType, assertionValue); case EXTENSIBLE_MATCH_FILTER_TYPE: try { elements = element.decodeAsSequence().getElements(); } catch (Exception e) { throw new ProtocolException("Unable to decode extensible matching " + "filter sequence", e); } if ((elements.length < 1) || (elements.length > 4)) { throw new ProtocolException("There must be between 1 and 4 " + "elements in an extensible matching " + "filter sequence"); } attributeType = null; assertionValue = null; String matchingRuleID = null; boolean includeDNAttributes = false; for (int i=0; i < elements.length; i++) { switch (elements[i].getType()) { case EXTENSIBLE_MATCH_RULE_ID_TYPE: try { matchingRuleID = elements[i].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode matching rule " + "ID for extensible matching filter", e); } break; case EXTENSIBLE_MATCH_ATTRIBUTE_TYPE: try { attributeType = elements[i].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode attribute type " + "for extensible matching filter", e); } break; case EXTENSIBLE_MATCH_VALUE_TYPE: try { assertionValue = elements[i].decodeAsOctetString().getStringValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode assertion value" + "for extensible matching filter", e); } break; case EXTENSIBLE_MATCH_INCLUDE_DN_TYPE: try { includeDNAttributes = elements[i].decodeAsBoolean().getBooleanValue(); } catch (Exception e) { throw new ProtocolException("Unable to decode dnAttributes " + "for extensible matching filter", e); } break; default: throw new ProtocolException("Unrecognized extensible matching " + "filter element type " + elements[i].getType()); } } return createExtensibleFilter(matchingRuleID, attributeType, assertionValue, includeDNAttributes); default: throw new ProtocolException("Invalid search filter element type " + element.getType()); } } /** * Retrieves a string representation of this search filter in the format * specified by RFC 2254. * * @return A string representation of this search filter in the format * specified by RFC 2254. */ public String toString() { StringBuilder buffer = new StringBuilder(); toStringBuilder(buffer); return buffer.toString(); } /** * Writes an RFC 2254 representation of this search filter to the provided * string buffer. * * @param buffer The string buffer to which the filter is to be written. */ public void toStringBuilder(StringBuilder buffer) { switch (filterType) { case AND_FILTER_TYPE: buffer.append("(&"); for (int i=0; i < subFilters.length; i++) { subFilters[i].toStringBuilder(buffer); } buffer.append(')'); break; case OR_FILTER_TYPE: buffer.append("(|"); for (int i=0; i < subFilters.length; i++) { subFilters[i].toStringBuilder(buffer); } buffer.append(')'); break; case NOT_FILTER_TYPE: buffer.append("(!"); subFilters[0].toStringBuilder(buffer); buffer.append(')'); break; case EQUALITY_FILTER_TYPE: buffer.append('('); buffer.append(attributeType); buffer.append('='); buffer.append(assertionValue); buffer.append(')'); break; case SUBSTRING_FILTER_TYPE: buffer.append('('); buffer.append(attributeType); buffer.append('='); if ((subInitialValue != null) && (subInitialValue.length() > 0)) { buffer.append(subInitialValue); } for (int i=0; ((subAnyValues != null) && (i < subAnyValues.length)); i++) { buffer.append('*'); buffer.append(subAnyValues[i]); } buffer.append('*'); if ((subFinalValue != null) && (subFinalValue.length() > 0)) { buffer.append(subFinalValue); } buffer.append(')'); break; case GREATER_OR_EQUAL_FILTER_TYPE: buffer.append('('); buffer.append(attributeType); buffer.append(">="); buffer.append(assertionValue); buffer.append(')'); break; case LESS_OR_EQUAL_FILTER_TYPE: buffer.append('('); buffer.append(attributeType); buffer.append("<="); buffer.append(assertionValue); buffer.append(')'); break; case PRESENCE_FILTER_TYPE: buffer.append('('); buffer.append(attributeType); buffer.append("=*)"); break; case APPROXIMATE_FILTER_TYPE: buffer.append('('); buffer.append(attributeType); buffer.append("~="); buffer.append(assertionValue); buffer.append(')'); break; case EXTENSIBLE_MATCH_FILTER_TYPE: buffer.append('('); if ((attributeType != null) && (attributeType.length() > 0)) { buffer.append(attributeType); } if (includeDNAttributes) { buffer.append(":dn"); } if ((matchingRuleID != null) && (matchingRuleID.length() > 0)) { buffer.append(':'); buffer.append(matchingRuleID); } buffer.append(":="); buffer.append(assertionValue); buffer.append(')'); } } }