org.ldaptive.DnParser Maven / Gradle / Ivy
/*
$Id: DnParser.java 3120 2015-10-01 15:50:02Z daniel_fisher $
Copyright (C) 2003-2014 Virginia Tech.
All rights reserved.
SEE LICENSE FOR MORE INFORMATION
Author: Middleware Services
Email: [email protected]
Version: $Revision: 3120 $
Updated: $Date: 2015-10-01 11:50:02 -0400 (Thu, 01 Oct 2015) $
*/
package org.ldaptive;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.ldaptive.asn1.DERParser;
import org.ldaptive.asn1.OctetStringType;
import org.ldaptive.asn1.ParseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Parses DNs following the rules in RFC 4514. Attempts to be as
* generous as possible in the format of allowed DNs.
*
* @author Middleware Services
* @version $Revision: 3120 $ $Date: 2015-10-01 11:50:02 -0400 (Thu, 01 Oct 2015) $
*/
public final class DnParser
{
/** Logger for this class. */
private static final Logger LOGGER = LoggerFactory.getLogger(DnParser.class);
/** Hexadecimal radix. */
private static final int HEX_RADIX = 16;
/** Default constructor. */
private DnParser() {}
/**
* Returns the RDN values for the attribute type with the supplied name.
*
* @param dn to parse
* @param name of the attribute type to return values for
*
* @return DN attribute values
*/
public static Collection getValues(final String dn, final String name)
{
final Collection values = new ArrayList();
for (LdapAttribute la : convertDnToAttributes(dn)) {
if (la.getName().equalsIgnoreCase(name)) {
values.addAll(la.getStringValues());
}
}
return values;
}
/**
* Returns the RDN value for the attribute type with the supplied name. If the
* component has multiple values, the first one is returned.
*
* @param dn to parse
* @param name of the attribute to return value for
*
* @return DN attribute value
*/
public static String getValue(final String dn, final String name)
{
final Collection values = getValues(dn, name);
if (values.isEmpty()) {
return "";
}
return values.iterator().next();
}
/**
* Returns a string representation of the supplied DN beginning at the
* supplied index. The leftmost RDN component begins at index 0.
*
* @param dn to parse
* @param beginIndex index of first RDN to include in the result in the
* range [0, N-1] where N is the number of elements in
* the DN
*
* @return DN from the supplied index
*
* @throws IndexOutOfBoundsException if beginIndex is less than 0 or greater
* than the number of RDNs
*/
public static String substring(final String dn, final int beginIndex)
{
if (beginIndex < 0) {
throw new IndexOutOfBoundsException("beginIndex cannot be negative");
}
final List attrs = convertDnToAttributes(dn);
if (beginIndex >= attrs.size()) {
throw new IndexOutOfBoundsException(
"beginIndex cannot be larger than the number of RDNs");
}
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < attrs.size(); i++) {
if (i >= beginIndex) {
final LdapAttribute la = attrs.get(i);
sb.append(la.getName()).append("=").append(la.getStringValue()).append(
",");
}
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ',') {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/**
* Returns a string representation of the supplied DN beginning at beginIndex
* (inclusive) and ending at endIndex (exclusive). The leftmost RDN component
* begins at index 0. Where n is the number of RDNs, both beginIndex and
* endIndex are on the range [0, N-1].
*
* @param dn to parse
* @param beginIndex index of first RDN to include in the result in the
* range [0, N-2] where N is the number of elements in
* the DN
* @param endIndex index of last RDN to include in the result in the range
* [1, N-1] where N is the number of elements in the RDN
*
* @return DN from beginIndex (inclusive) to endIndex (exclusive)
*
* @throws IndexOutOfBoundsException if beginIndex is less than 0, if
* beginIndex is greater than endIndex, or endIndex is greater than the
* number of RDNs
*/
public static String substring(
final String dn,
final int beginIndex,
final int endIndex)
{
if (beginIndex < 0) {
throw new IndexOutOfBoundsException("beginIndex cannot be negative");
}
if (beginIndex > endIndex) {
throw new IndexOutOfBoundsException(
"beginIndex cannot be larger than endIndex");
}
final List attrs = convertDnToAttributes(dn);
if (endIndex > attrs.size()) {
throw new IndexOutOfBoundsException(
"endIndex cannot be larger than the number of RDNs");
}
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < attrs.size(); i++) {
if (i >= beginIndex && i < endIndex) {
final LdapAttribute la = attrs.get(i);
sb.append(
la.getName()).append("=").append(la.getStringValue()).append(",");
}
}
if (sb.length() > 0 && sb.charAt(sb.length() - 1) == ',') {
sb.deleteCharAt(sb.length() - 1);
}
return sb.toString();
}
/**
* Parses the supplied DN and converts each RDN into a {@link LdapAttribute}.
*
* @param dn to parse
*
* @return list of ldap attributes for each RDN
*/
public static List convertDnToAttributes(final String dn)
{
LOGGER.debug("parsing DN: {}", dn);
final List attributes = new ArrayList();
if (dn.isEmpty()) {
return attributes;
}
int pos = 0;
while (pos < dn.length()) {
final int endAttrNamePos = readToChar(dn, new char[] {'='}, pos);
final String attrName = dn.substring(pos, endAttrNamePos);
LOGGER.trace("read attribute name: [{}]", attrName);
pos = endAttrNamePos;
// error if char isn't an '='
if (pos >= dn.length() || dn.charAt(pos++) != '=') {
throw new IllegalArgumentException("Invalid DN: " + dn);
}
final int endAttrValuePos = readToChar(dn, new char[] {'+', ','}, pos);
String attrValue = dn.substring(pos, endAttrValuePos);
LOGGER.trace("read attribute value: [{}]", attrValue);
attrValue = attrValue.trim();
// error if attribute value is empty
if (attrValue.isEmpty()) {
throw new IllegalArgumentException("Invalid DN: " + dn);
}
if (attrValue.startsWith("#")) {
final DERParser parser = new DERParser();
final OctetStringHandler handler = new OctetStringHandler();
parser.registerHandler("/OCTSTR", handler);
final String hexData = attrValue.substring(1, attrValue.length());
parser.parse(ByteBuffer.wrap(decodeHexValue(hexData.toCharArray())));
attributes.add(
new LdapAttribute(attrName.trim(), handler.getDecodedValue()));
} else {
attributes.add(
new LdapAttribute(attrName.trim(), decodeStringValue(attrValue)));
}
pos = endAttrValuePos + 1;
}
LOGGER.debug("parsed DN into: {}", attributes);
return attributes;
}
/**
* Decodes the supplied hexadecimal value.
*
* @param value hex to decode
*
* @return decoded bytes
*/
protected static byte[] decodeHexValue(final char[] value)
{
if (value == null || value.length == 0) {
throw new IllegalArgumentException(
"Invalid HEX value: value cannot be null or empty");
}
return LdapUtils.hexDecode(value);
}
/**
* Decodes the supplied string attribute value. Unescapes escaped characters.
* If escaped character is a hex value, it is decoded.
*
* @param value to decode
*
* @return decoded string
*/
protected static String decodeStringValue(final String value)
{
final StringBuilder sb = new StringBuilder();
int pos = 0;
final StringBuilder hexValue = new StringBuilder();
while (pos < value.length()) {
char c = value.charAt(pos);
boolean appendHex = false;
boolean appendValue = false;
switch (c) {
case '\\':
if (pos + 1 < value.length()) {
c = value.charAt(++pos);
// if hexadecimal character add to buffer to decode later
if (Character.digit(c, HEX_RADIX) != -1) {
if (pos + 1 < value.length()) {
hexValue.append(c).append(value.charAt(++pos));
if (pos + 1 == value.length()) {
appendHex = true;
}
} else {
throw new IllegalArgumentException("Invalid HEX value: " + c);
}
} else {
appendHex = hexValue.length() > 0;
appendValue = true;
}
}
break;
default:
appendHex = hexValue.length() > 0;
appendValue = true;
break;
}
if (appendHex) {
sb.append(
LdapUtils.utf8Encode(
decodeHexValue(hexValue.toString().toCharArray())));
hexValue.setLength(0);
}
if (appendValue) {
sb.append(c);
}
pos++;
}
return sb.toString();
}
/**
* Reads the supplied string starting at the supplied position until one of
* the supplied characters is found. Characters escaped with '\' are ignored.
*
* @param s to read
* @param chars to match
* @param pos to start reading at
*
* @return string index that matched a character or the last index in the
* string
*/
private static int readToChar(
final String s,
final char[] chars,
final int pos)
{
int i = pos;
while (i < s.length()) {
boolean match = false;
final char sChar = s.charAt(i);
// ignore escaped characters
if (sChar == '\\' && i + 1 < s.length()) {
i++;
} else {
for (char c : chars) {
if (c == s.charAt(i)) {
match = true;
}
}
if (match) {
break;
}
}
i++;
}
return i;
}
/** Parse handler for decoding octet strings. */
private static class OctetStringHandler implements ParseHandler
{
/** Decoded octet string. */
private String decoded;
/** {@inheritDoc} */
@Override
public void handle(final DERParser parser, final ByteBuffer encoded)
{
decoded = OctetStringType.decode(encoded);
}
/**
* Returns the decoded octet string value.
*
* @return decoded octet string
*/
public String getDecodedValue()
{
return decoded;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy