package com.hwlcn.ldap.ldap.matchingrules; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import com.hwlcn.ldap.asn1.ASN1OctetString; import com.hwlcn.ldap.ldap.sdk.LDAPException; import com.hwlcn.ldap.ldap.sdk.ResultCode; import com.hwlcn.core.annotation.ThreadSafety; import com.hwlcn.ldap.util.ThreadSafetyLevel; import static com.hwlcn.ldap.ldap.matchingrules.MatchingRuleMessages.*; import static com.hwlcn.ldap.util.Debug.*; import static com.hwlcn.ldap.util.StaticUtils.*; @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) public final class CaseIgnoreListMatchingRule extends MatchingRule { private static final CaseIgnoreListMatchingRule INSTANCE = new CaseIgnoreListMatchingRule(); public static final String EQUALITY_RULE_NAME = "caseIgnoreListMatch"; static final String LOWER_EQUALITY_RULE_NAME = toLowerCase(EQUALITY_RULE_NAME); public static final String EQUALITY_RULE_OID = "2.5.13.11"; public static final String SUBSTRING_RULE_NAME = "caseIgnoreListSubstringsMatch"; static final String LOWER_SUBSTRING_RULE_NAME = toLowerCase(SUBSTRING_RULE_NAME); public static final String SUBSTRING_RULE_OID = "2.5.13.12"; private static final long serialVersionUID = 7795143670808983466L; public CaseIgnoreListMatchingRule() { } public static CaseIgnoreListMatchingRule getInstance() { return INSTANCE; } @Override() public String getEqualityMatchingRuleName() { return EQUALITY_RULE_NAME; } @Override() public String getEqualityMatchingRuleOID() { return EQUALITY_RULE_OID; } @Override() public String getOrderingMatchingRuleName() { return null; } @Override() public String getOrderingMatchingRuleOID() { return null; } @Override() public String getSubstringMatchingRuleName() { return SUBSTRING_RULE_NAME; } @Override() public String getSubstringMatchingRuleOID() { return SUBSTRING_RULE_OID; } @Override() public boolean valuesMatch(final ASN1OctetString value1, final ASN1OctetString value2) throws LDAPException { return normalize(value1).equals(normalize(value2)); } @Override() public boolean matchesSubstring(final ASN1OctetString value, final ASN1OctetString subInitial, final ASN1OctetString[] subAny, final ASN1OctetString subFinal) throws LDAPException { String normStr = normalize(value).stringValue(); if (subInitial != null) { final String normSubInitial = normalizeSubstring(subInitial, SUBSTRING_TYPE_SUBINITIAL).stringValue(); if (normSubInitial.indexOf('$') >= 0) { throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_SUBSTRING_COMPONENT_CONTAINS_DOLLAR.get( normSubInitial)); } if (! normStr.startsWith(normSubInitial)) { return false; } normStr = normStr.substring(normSubInitial.length()); } if (subFinal != null) { final String normSubFinal = normalizeSubstring(subFinal, SUBSTRING_TYPE_SUBFINAL).stringValue(); if (normSubFinal.indexOf('$') >= 0) { throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_SUBSTRING_COMPONENT_CONTAINS_DOLLAR.get( normSubFinal)); } if (! normStr.endsWith(normSubFinal)) { return false; } normStr = normStr.substring(0, normStr.length() - normSubFinal.length()); } if (subAny != null) { for (final ASN1OctetString s : subAny) { final String normSubAny = normalizeSubstring(s, SUBSTRING_TYPE_SUBANY).stringValue(); if (normSubAny.indexOf('$') >= 0) { throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_SUBSTRING_COMPONENT_CONTAINS_DOLLAR.get( normSubAny)); } final int pos = normStr.indexOf(normSubAny); if (pos < 0) { return false; } normStr = normStr.substring(pos + normSubAny.length()); } } return true; } @Override() public int compareValues(final ASN1OctetString value1, final ASN1OctetString value2) throws LDAPException { throw new LDAPException(ResultCode.INAPPROPRIATE_MATCHING, ERR_CASE_IGNORE_LIST_ORDERING_MATCHING_NOT_SUPPORTED.get()); } @Override() public ASN1OctetString normalize(final ASN1OctetString value) throws LDAPException { final List<String> items = getLowercaseItems(value); final Iterator<String> iterator = items.iterator(); final StringBuilder buffer = new StringBuilder(); while (iterator.hasNext()) { normalizeItem(buffer, iterator.next()); if (iterator.hasNext()) { buffer.append('$'); } } return new ASN1OctetString(buffer.toString()); } @Override() public ASN1OctetString normalizeSubstring(final ASN1OctetString value, final byte substringType) throws LDAPException { return CaseIgnoreStringMatchingRule.getInstance().normalizeSubstring(value, substringType); } public static List<String> getItems(final ASN1OctetString value) throws LDAPException { return getItems(value.stringValue()); } public static List<String> getItems(final String value) throws LDAPException { final ArrayList<String> items = new ArrayList<String>(10); final int length = value.length(); final StringBuilder buffer = new StringBuilder(); for (int i=0; i < length; i++) { final char c = value.charAt(i); if (c == '\\') { try { buffer.append(decodeHexChar(value, i+1)); i += 2; } catch (Exception e) { debugException(e); throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_MALFORMED_HEX_CHAR.get(value), e); } } else if (c == '$') { final String s = buffer.toString().trim(); if (s.length() == 0) { throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_EMPTY_ITEM.get(value)); } items.add(s); buffer.delete(0, buffer.length()); } else { buffer.append(c); } } final String s = buffer.toString().trim(); if (s.length() == 0) { if (items.isEmpty()) { throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_EMPTY_LIST.get(value)); } else { throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_EMPTY_ITEM.get(value)); } } items.add(s); return Collections.unmodifiableList(items); } public static List<String> getLowercaseItems(final ASN1OctetString value) throws LDAPException { return getLowercaseItems(value.stringValue()); } public static List<String> getLowercaseItems(final String value) throws LDAPException { return getItems(toLowerCase(value)); } static void normalizeItem(final StringBuilder buffer, final String item) { final int length = item.length(); boolean lastWasSpace = false; for (int i=0; i < length; i++) { final char c = item.charAt(i); if (c == '\\') { buffer.append("\\5c"); lastWasSpace = false; } else if (c == '$') { buffer.append("\\24"); lastWasSpace = false; } else if (c == ' ') { if (! lastWasSpace) { buffer.append(' '); lastWasSpace = true; } } else { buffer.append(c); lastWasSpace = false; } } } static char decodeHexChar(final String s, final int p) throws LDAPException { char c = 0; for (int i=0, j=p; (i < 2); i++,j++) { c <<= 4; switch (s.charAt(j)) { case '0': break; case '1': c |= 0x01; break; case '2': c |= 0x02; break; case '3': c |= 0x03; break; case '4': c |= 0x04; break; case '5': c |= 0x05; break; case '6': c |= 0x06; break; case '7': c |= 0x07; break; case '8': c |= 0x08; break; case '9': c |= 0x09; break; case 'a': case 'A': c |= 0x0A; break; case 'b': case 'B': c |= 0x0B; break; case 'c': case 'C': c |= 0x0C; break; case 'd': case 'D': c |= 0x0D; break; case 'e': case 'E': c |= 0x0E; break; case 'f': case 'F': c |= 0x0F; break; default: throw new LDAPException(ResultCode.INVALID_ATTRIBUTE_SYNTAX, ERR_CASE_IGNORE_LIST_NOT_HEX_DIGIT.get(s.charAt(j))); } } return c; } }