Skip to content

Commit

Permalink
Improve DefaultRDnNormalizer.
Browse files Browse the repository at this point in the history
Add separate properties for name and value normalization.
Provide common implementations for name and value normalization.
  • Loading branch information
dfish3r committed Sep 27, 2023
1 parent 53f9076 commit cf0311b
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 30 deletions.
76 changes: 76 additions & 0 deletions core/src/main/java/org/ldaptive/LdapUtils.java
Expand Up @@ -261,6 +261,82 @@ public static String percentEncodeControlChars(final String value)
}


/**
* Removes the space character from both the beginning and end of the supplied value.
*
* @param value to trim space character from
*
* @return trimmed value or same value if no trim was performed
*/
public static String trimSpace(final String value)
{
if (value == null || value.isEmpty()) {
return value;
}

int startIndex = 0;
int endIndex = value.length();
while (startIndex < endIndex && value.charAt(startIndex) == ' ') {
startIndex++;
}
while (startIndex < endIndex && value.charAt(endIndex - 1) == ' ') {
endIndex--;
}
if (startIndex == 0 && endIndex == value.length()) {
return value;
}
return value.substring(startIndex, endIndex);
}


/**
* Changes the supplied value by replacing multiple spaces with a single space.
*
* @param value to compress spaces
* @param trim whether to remove any leading or trailing space characters
*
* @return normalized value or value if no compress was performed
*/
public static String compressSpace(final String value, final boolean trim)
{
if (value == null || value.isEmpty()) {
return value;
}

final StringBuilder sb = new StringBuilder();
boolean foundSpace = false;
for (int i = 0; i < value.length(); i++) {
final char ch = value.charAt(i);
if (ch == ' ') {
if (i == value.length() - 1) {
// last char is a space
sb.append(ch);
}
foundSpace = true;
} else {
if (foundSpace) {
sb.append(' ');
}
sb.append(ch);
foundSpace = false;
}
}

if (sb.length() == 0 && foundSpace) {
return trim ? "" : " ";
}
if (trim) {
if (sb.length() > 0 && sb.charAt(0) == ' ') {
sb.deleteCharAt(0);
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ' ') {
sb.deleteCharAt(sb.length() - 1);
}
}
return sb.toString();
}


/**
* This will decode the supplied value as a base64 encoded string to a byte[]. Returns null if the supplied string is
* null.
Expand Down
6 changes: 3 additions & 3 deletions core/src/main/java/org/ldaptive/dn/DefaultDnParser.java
Expand Up @@ -37,7 +37,7 @@ public final class DefaultDnParser implements DnParser
*/
public List<RDn> parse(final String dn)
{
if (dn.trim().isEmpty()) {
if (LdapUtils.trimSpace(dn).isEmpty()) {
return Collections.emptyList();
}

Expand All @@ -46,7 +46,7 @@ public List<RDn> parse(final String dn)
int pos = 0;
while (pos < dn.length()) {
final int[] endAttrNamePos = readToChar(dn, new char[] {'='}, pos);
final String attrName = dn.substring(pos, endAttrNamePos[0]).trim();
final String attrName = LdapUtils.trimSpace(dn.substring(pos, endAttrNamePos[0]));
if (attrName.isEmpty()) {
throw new IllegalArgumentException("Invalid RDN: no attribute name found for " + dn);
} else if (attrName.contains("+") || attrName.contains(",")) {
Expand All @@ -59,7 +59,7 @@ public List<RDn> parse(final String dn)
}

final int[] endAttrValuePos = readToChar(dn, new char[] {'+', ','}, pos);
final String attrValue = dn.substring(pos, endAttrValuePos[0]).trim();
final String attrValue = LdapUtils.trimSpace(dn.substring(pos, endAttrValuePos[0]));
if (attrValue.isEmpty()) {
nameValues.add(new NameValue(attrName, ""));
} else if (attrValue.startsWith("#")) {
Expand Down
154 changes: 127 additions & 27 deletions core/src/main/java/org/ldaptive/dn/DefaultRDnNormalizer.java
Expand Up @@ -4,31 +4,104 @@
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.ldaptive.LdapUtils;

/**
* Normalizes a RDN by performing the following operations:
* <ul>
* <li>lowercase attribute names</li>
* <li>lowercase attribute values</li>
* <li>compress duplicate spaces in attribute values</li>
* <li>escape attribute value characters</li>
* <li>sort multi value RDNs by name</li>
* </ul>
*
* This API provides properties to control attribute name normalization, attribute value normalization and attribute
* value escaping in order to customize the behavior. Note that attribute value normalization occurs before escaping.
*
* @author Middleware Services
*/
public class DefaultRDnNormalizer implements RDnNormalizer
{

/** Value escaper. */
private final AttributeValueEscaper valueEscaper;
/** Function that returns the value unchanged. */
public static final Function<String, String> NOOP = new Function<>() {
@Override
public String apply(final String s)
{
return s;
}

@Override
public String toString()
{
return "NOOP";
}
};

/** Function that lowercases the value. */
public static final Function<String, String> LOWERCASE = new Function<>() {
@Override
public String apply(final String s)
{
return LdapUtils.toLowerCase(s);
}

@Override
public String toString()
{
return "LOWERCASE";
}
};

/** Function that removes duplicate spaces from the value. */
public static final Function<String, String> COMPRESS = new Function<>() {
@Override
public String apply(final String s)
{
return LdapUtils.compressSpace(s, false);
}

@Override
public String toString()
{
return "COMPRESS";
}
};

/** Function that lowercases and removes duplicate spaces from the value. */
public static final Function<String, String> LOWERCASE_COMPRESS = new Function<>() {
@Override
public String apply(final String s)
{
return LdapUtils.toLowerCase(LdapUtils.compressSpace(s, false));
}

@Override
public String toString()
{
return "LOWERCASE_COMPRESS";
}
};

/** Attribute name function. */
private final Function<String, String> attributeNameFunction;

/** Attribute value function. */
private final Function<String, String> attributeValueFunction;

/** Attribute value escaper. */
private final AttributeValueEscaper attributeValueEscaper;


/**
* Creates a new default RDN normalizer.
*/
public DefaultRDnNormalizer()
{
this(new DefaultAttributeValueEscaper());
this(new DefaultAttributeValueEscaper(), LOWERCASE, LOWERCASE_COMPRESS);
}


Expand All @@ -39,54 +112,81 @@ public DefaultRDnNormalizer()
*/
public DefaultRDnNormalizer(final AttributeValueEscaper escaper)
{
valueEscaper = escaper;
this(escaper, LOWERCASE, LOWERCASE_COMPRESS);
}


/**
* Returns the value escaper.
* Creates a new default RDN normalizer.
*
* @return value escaper
* @param escaper to escape attribute values
* @param nameNormalizer to normalize attribute names
* @param valueNormalizer to normalize attribute values
*/
public AttributeValueEscaper getValueEscaper()
public DefaultRDnNormalizer(
final AttributeValueEscaper escaper,
final Function<String, String> nameNormalizer,
final Function<String, String> valueNormalizer)
{
return valueEscaper;
attributeValueEscaper = escaper;
attributeNameFunction = nameNormalizer;
attributeValueFunction = valueNormalizer;
}


@Override
public RDn normalize(final RDn rdn)
/**
* Returns the value escaper.
*
* @return value escaper
*/
public AttributeValueEscaper getValueEscaper()
{
final Set<NameValue> nameValues = rdn.getNameValues().stream()
.map(nv -> new NameValue(normalizeName(nv.getName()), normalizeValue(nv.getStringValue())))
.sorted(Comparator.comparing(NameValue::getName))
.collect(Collectors.toCollection(LinkedHashSet::new));
return new RDn(nameValues);
return attributeValueEscaper;
}


/**
* Lower cases the supplied name.
*
* @param name to normalize
* Returns the attribute name function.
*
* @return normalized name
* @return function for attribute names
*/
private String normalizeName(final String name)
public Function<String, String> getNameFunction()
{
return LdapUtils.toLowerCase(name);
return attributeNameFunction;
}


/**
* Escapes the supplied value.
* Returns the attribute value function.
*
* @param value to normalize
*
* @return normalized value
* @return function for attribute values
*/
private String normalizeValue(final String value)
public Function<String, String> getValueFunction()
{
return attributeValueFunction;
}


@Override
public RDn normalize(final RDn rdn)
{
final Set<NameValue> nameValues = rdn.getNameValues().stream()
.map(
nv -> new NameValue(
attributeNameFunction.apply(nv.getName()),
attributeValueEscaper.escape(attributeValueFunction.apply(nv.getStringValue()))))
.sorted(Comparator.comparing(NameValue::getName))
.collect(Collectors.toCollection(LinkedHashSet::new));
return new RDn(nameValues);
}


@Override
public String toString()
{
return valueEscaper.escape(value);
return getClass().getName() + "@" + hashCode() + "::" +
"attributeNameFunction=" + attributeNameFunction + ", " +
"attributeValueFunction=" + attributeValueFunction + ", " +
"attributeValueEscaper=" + attributeValueEscaper;
}
}

0 comments on commit cf0311b

Please sign in to comment.