All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.ldaptive.DnParser Maven / Gradle / Ivy

There is a newer version: 2.3.2
Show newest version
/*
  $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