DN.java revision f6f67fe1cb1f1f08c392de2e4565ec1a89dd182a
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2006-2009 Sun Microsystems, Inc.
* Portions Copyright 2012-2015 ForgeRock AS
*/
/**
* This class defines a data structure for storing and interacting
* with the distinguished names associated with entries in the
* Directory Server.
*/
mayInstantiate=true,
mayExtend=false,
mayInvoke=true)
{
/**
* A singleton instance of the null DN (a DN with no components).
*/
/** RDN separator for normalized byte string of a DN. */
public static final byte NORMALIZED_RDN_SEPARATOR = 0x00;
/** AVA separator for normalized byte string of a DN. */
public static final byte NORMALIZED_AVA_SEPARATOR = 0x01;
/** Escape byte for normalized byte string of a DN. */
public static final byte NORMALIZED_ESC_BYTE = 0x02;
/**
* The serial version identifier required to satisfy the compiler
* because this class implements the
* <CODE>java.io.Serializable</CODE> interface. This value was
* generated using the <CODE>serialver</CODE> command-line utility
* included with the Java SDK.
*/
private static final long serialVersionUID = 1184263456768819888L;
/** The number of RDN components that comprise this DN. */
private final int numComponents;
/**
* The set of RDN components that comprise this DN, arranged with the suffix
* as the last element.
*/
private final RDN[] rdnComponents;
/** The string representation of this DN. */
/**
* The normalized byte string representation of this DN, which is not
* a valid DN and is not reversible to a valid DN.
*/
private ByteString normalizedDN;
/**
* Creates a new DN with no RDN components (i.e., a null DN or root
* DSE).
*/
public DN()
{
this(new RDN[0]);
}
/**
* Creates a new DN with the provided set of RDNs, arranged with the
* suffix as the last element.
*
* @param rdnComponents The set of RDN components that make up
* this DN.
*/
{
if (rdnComponents == null)
{
}
else
{
this.rdnComponents = rdnComponents;
}
normalizedDN = null;
}
/**
* Creates a new DN with the provided set of RDNs, arranged with the
* suffix as the last element.
*
* @param rdnComponents The set of RDN components that make up
* this DN.
*/
{
{
}
else
{
}
normalizedDN = null;
}
/**
* Creates a new DN with the given RDN below the specified parent.
*
* @param rdn The RDN to use for the new DN. It must not be
* {@code null}.
* @param parentDN The DN of the entry below which the new DN
* should exist. It must not be {@code null}.
*/
{
{
}
else
{
}
normalizedDN = null;
}
/**
* Retrieves a singleton instance of the null DN.
*
* @return A singleton instance of the null DN.
*/
{
return NULL_DN;
}
/**
* Indicates whether this represents a null DN. This could target
* the root DSE for the Directory Server, or the authorization DN
* for an anonymous or unauthenticated client.
*
* @return <CODE>true</CODE> if this does represent a null DN, or
* <CODE>false</CODE> if it does not.
*/
public boolean isRootDN()
{
return numComponents == 0;
}
/**
* Retrieves the number of RDN components for this DN.
*
* @return The number of RDN components for this DN.
*/
public int size()
{
return numComponents;
}
/**
* Retrieves the outermost RDN component for this DN (i.e., the one
* that is furthest from the suffix).
*
* @return The outermost RDN component for this DN, or
* <CODE>null</CODE> if there are no RDN components in the
* DN.
*/
{
if (numComponents == 0)
{
return null;
}
else
{
return rdnComponents[0];
}
}
/**
* Returns a copy of this DN whose parent DN, {@code fromDN}, has been renamed
* to the new parent DN, {@code toDN}. If this DN is not subordinate or equal
* to {@code fromDN} then this DN is returned (i.e. the DN is not renamed).
*
* @param fromDN
* The old parent DN.
* @param toDN
* The new parent DN.
* @return The renamed DN, or this DN if no renaming was performed.
*/
{
if (!isDescendantOf(fromDN))
{
return this;
}
{
return toDN;
}
else
{
}
}
/**
* Retrieves the RDN component at the specified position in the set
* of components for this DN.
*
* @param pos The position of the RDN component to retrieve.
*
* @return The RDN component at the specified position in the set
* of components for this DN.
*/
{
return rdnComponents[pos];
}
/**
* Retrieves the DN of the entry that is the immediate parent for
* this entry. Note that this method does not take the server's
* naming context configuration into account when making the
* determination.
*
* @return The DN of the entry that is the immediate parent for
* this entry, or <CODE>null</CODE> if the entry with this
* DN does not have a parent.
*/
{
if (numComponents <= 1)
{
return null;
}
numComponents-1);
return new DN(parentComponents);
}
/**
* Retrieves the DN of the entry that is the immediate parent for
* this entry. This method does take the server's naming context
* configuration into account, so if the current DN is a naming
* context for the server, then it will not be considered to have a
* parent.
*
* @return The DN of the entry that is the immediate parent for
* this entry, or <CODE>null</CODE> if the entry with this
* DN does not have a parent (either because there is only
* a single RDN component or because this DN is a suffix
* defined in the server).
*/
public DN getParentDNInSuffix()
{
{
return null;
}
return new DN(parentComponents);
}
/**
* Creates a new DN that is a child of this DN, using the specified
* RDN.
*
* @param rdn The RDN for the child of this DN.
*
* @return A new DN that is a child of this DN, using the specified
* RDN.
*/
{
return new DN(newComponents);
}
/**
* Creates a new DN that is a descendant of this DN, using the
* specified RDN components.
*
* @param rdnComponents The RDN components for the descendant of
* this DN.
*
* @return A new DN that is a descendant of this DN, using the
* specified RDN components.
*/
{
RDN[] newComponents =
return new DN(newComponents);
}
/**
* Creates a new DN that is a descendant of this DN, using the
* specified DN as a relative base DN. That is, the resulting DN
* will first have the components of the provided DN followed by the
* components of this DN.
*
* @param relativeBaseDN The relative base DN to concatenate onto
* this DN.
*
* @return A new DN that is a descendant of this DN, using the
* specified DN as a relative base DN.
*/
{
RDN[] newComponents =
return new DN(newComponents);
}
/**
* Indicates whether this DN is a descendant of the provided DN
* (i.e., that the RDN components of the provided DN are the
* same as the last RDN components for this DN). Note that if
* this DN equals the provided DN it is still considered to be
* a descendant of the provided DN by this method as both then
* reside within the same subtree.
*
* @param dn The DN for which to make the determination.
*
* @return <CODE>true</CODE> if this DN is a descendant of the
* provided DN, or <CODE>false</CODE> if not.
*/
{
if (offset < 0)
{
return false;
}
{
{
return false;
}
}
return true;
}
/**
* Indicates whether this DN is an ancestor of the provided DN
* (i.e., that the RDN components of this DN are the same as the
* last RDN components for the provided DN).
*
* @param dn The DN for which to make the determination.
*
* @return <CODE>true</CODE> if this DN is an ancestor of the
* provided DN, or <CODE>false</CODE> if not.
*/
{
if (offset < 0)
{
return false;
}
for (int i=0; i < numComponents; i++)
{
{
return false;
}
}
return true;
}
/**
* Indicates whether this entry falls within the range of the
* provided search base DN and scope.
*
* @param baseDN The base DN for which to make the determination.
* @param scope The search scope for which to make the
* determination.
*
* @return <CODE>true</CODE> if this entry is within the given
* base and scope, or <CODE>false</CODE> if it is not.
*/
{
{
case BASE_OBJECT:
// The base DN must equal this DN.
case SINGLE_LEVEL:
// The parent DN must equal the base DN.
case WHOLE_SUBTREE:
// This DN must be a descendant of the provided base DN.
return isDescendantOf(baseDN);
case SUBORDINATES:
// This DN must be a descendant of the provided base DN, but
// not equal to it.
default:
// This is a scope that we don't recognize.
return false;
}
}
/**
* Decodes the provided ASN.1 octet string as a DN.
*
* @param dnString The ASN.1 octet string to decode as a DN.
*
* @return The decoded DN.
*
* @throws DirectoryException If a problem occurs while trying to
* decode the provided ASN.1 octet
* string as a DN.
*/
throws DirectoryException
{
// A null or empty DN is acceptable.
{
return NULL_DN;
}
if (length == 0)
{
return NULL_DN;
}
// See if we are dealing with any non-ASCII characters, or any
// escaped characters. If so, then the easiest and safest
// approach is to convert the DN to a string and decode it that
// way.
byte b;
for (int i = 0; i < length; i++)
{
if ((b & 0x7F) != b || b == '\\')
{
}
}
// Iterate through the DN string. The first thing to do is to get
// rid of any leading spaces.
b = ' ';
{}
if(b == ' ')
{
// This means that the DN was completely comprised of spaces
// and therefore should be considered the same as a null or
// empty DN.
return NULL_DN;
}
// We know that it's not an empty DN, so we can do the real
// processing. Create a loop and iterate through all the RDN
// components.
boolean allowExceptions =
while (true)
{
// Make sure that we're not at the end of the DN string because
// that would be invalid.
{
}
// Skip over any spaces between the attribute name and its value.
b = ' ';
{}
if(b == ' ')
{
// This means that we hit the end of the value before
// finding a '='. This is illegal because there is no
// attribute-value separator.
}
// The next character must be an equal sign. If it is not,
// then that's an error.
if (b != '=')
{
}
// Skip over any spaces after the equal sign.
b = ' ';
{}
// If we are at the end of the DN string, then that must mean
// that the attribute value was empty. This will probably never
// happen in a real-world environment, but technically isn't
// illegal. If it does happen, then go ahead and create the RDN
// component and return the DN.
if (b == ' ')
{
return new DN(rdnComponents);
}
// Parse the value for this RDN component.
// Create the new RDN with the provided information.
// Skip over any spaces that might be after the attribute value.
b = ' ';
{}
// Most likely, we will be at either the end of the RDN
// component or the end of the DN. If so, then handle that
// appropriately.
if (b == ' ')
{
// We're at the end of the DN string and should have a valid
// DN so return it.
return new DN(rdnComponents);
}
else if (b == ',' || b == ';')
{
// We're at the end of the RDN component, so add it to the
// component.
continue;
}
else if (b != '+')
{
// This should not happen. At any rate, it's an illegal
// character, so throw an exception.
}
// If we have gotten here, then this must be a multi-valued RDN.
// add them to the RDN that we've already created.
while (true)
{
// Skip over the plus sign and any spaces that may follow it
// before the next attribute name.
b = ' ';
{}
// Parse the attribute name from the DN string.
// Make sure that we're not at the end of the DN string
// because that would be invalid.
if (b == ' ')
{
}
// Skip over any spaces between the attribute name and its value.
b = ' ';
{}
if(b == ' ')
{
// This means that we hit the end of the value before
// finding a '='. This is illegal because there is no
// attribute-value separator.
}
// The next character must be an equal sign. If it is not,
// then that's an error.
if (b != '=')
{
}
// Skip over any spaces after the equal sign.
b = ' ';
{}
// If we are at the end of the DN string, then that must mean
// that the attribute value was empty. This will probably
// never happen in a real-world environment, but technically
// isn't illegal. If it does happen, then go ahead and create
// the RDN component and return the DN.
if (b == ' ')
{
return new DN(rdnComponents);
}
// Parse the value for this RDN component.
// Skip over any spaces that might be after the attribute value.
// Skip over any spaces that might be after the attribute value.
b = ' ';
{}
// Most likely, we will be at either the end of the RDN
// component or the end of the DN. If so, then handle that
// appropriately.
if (b == ' ')
{
// We're at the end of the DN string and should have a valid
// DN so return it.
return new DN(rdnComponents);
}
else if (b == ',' || b == ';')
{
// We're at the end of the RDN component, so add it to the
// next component.
break;
}
else if (b != '+')
{
// This should not happen. At any rate, it's an illegal
// character, so throw an exception.
}
}
}
}
{
}
{
}
{
}
/**
* Decodes the provided string as a DN.
*
* @param dnString The string to decode as a DN.
*
* @return The decoded DN.
*
* @throws DirectoryException If a problem occurs while trying to
* decode the provided string as a DN.
*/
throws DirectoryException
{
// A null or empty DN is acceptable.
{
return NULL_DN;
}
if (length == 0)
{
return NULL_DN;
}
// Iterate through the DN string. The first thing to do is to get
// rid of any leading spaces.
int pos = 0;
while (c == ' ')
{
pos++;
{
// This means that the DN was completely comprised of spaces
// and therefore should be considered the same as a null or
// empty DN.
return NULL_DN;
}
else
{
}
}
// We know that it's not an empty DN, so we can do the real
// processing. Create a loop and iterate through all the RDN
// components.
boolean allowExceptions =
while (true)
{
// Make sure that we're not at the end of the DN string because
// that would be invalid.
{
}
// Skip over any spaces between the attribute name and its value.
while (c == ' ')
{
pos++;
{
// This means that we hit the end of the value before
// finding a '='. This is illegal because there is no
// attribute-value separator.
}
}
// The next character must be an equal sign. If it is not, then
// that's an error.
if (c == '=')
{
pos++;
}
else
{
}
// Skip over any spaces after the equal sign.
{
pos++;
}
// If we are at the end of the DN string, then that must mean
// that the attribute value was empty. This will probably never
// happen in a real-world environment, but technically isn't
// illegal. If it does happen, then go ahead and create the
// RDN component and return the DN.
{
return new DN(rdnComponents);
}
// Parse the value for this RDN component.
// Create the new RDN with the provided information.
// Skip over any spaces that might be after the attribute value.
{
pos++;
}
// Most likely, we will be at either the end of the RDN
// component or the end of the DN. If so, then handle that
// appropriately.
{
// We're at the end of the DN string and should have a valid
// DN so return it.
return new DN(rdnComponents);
}
else if (c == ',' || c == ';')
{
// We're at the end of the RDN component, so add it to the
// component.
pos++;
continue;
}
else if (c != '+')
{
// This should not happen. At any rate, it's an illegal
// character, so throw an exception.
message);
}
// If we have gotten here, then this must be a multi-valued RDN.
// add them to the RDN that we've already created.
while (true)
{
// Skip over the plus sign and any spaces that may follow it
// before the next attribute name.
pos++;
{
pos++;
}
// Parse the attribute name from the DN string.
attributeName = new StringBuilder();
// Make sure that we're not at the end of the DN string
// because that would be invalid.
{
}
// Skip over any spaces between the attribute name and its value.
while (c == ' ')
{
pos++;
{
// This means that we hit the end of the value before
// finding a '='. This is illegal because there is no
// attribute-value separator.
}
}
// The next character must be an equal sign. If it is not,
// then that's an error.
if (c == '=')
{
pos++;
}
else
{
}
// Skip over any spaces after the equal sign.
{
pos++;
}
// If we are at the end of the DN string, then that must mean
// that the attribute value was empty. This will probably
// never happen in a real-world environment, but technically
// isn't illegal. If it does happen, then go ahead and create
// the RDN component and return the DN.
{
return new DN(rdnComponents);
}
// Parse the value for this RDN component.
parsedValue.clear();
// Create the new RDN with the provided information.
// Skip over any spaces that might be after the attribute value.
{
pos++;
}
// Most likely, we will be at either the end of the RDN
// component or the end of the DN. If so, then handle that
// appropriately.
{
// We're at the end of the DN string and should have a valid
// DN so return it.
return new DN(rdnComponents);
}
else if (c == ',' || c == ';')
{
// We're at the end of the RDN component, so add it to the
// next component.
pos++;
break;
}
else if (c != '+')
{
// This should not happen. At any rate, it's an illegal
// character, so throw an exception.
message);
}
}
}
}
{
}
{
}
/**
* Parses an attribute name from the provided DN string starting at
* the specified location.
*
* @param dnBytes The byte array containing the DN to
* parse.
* @param allowExceptions Indicates whether to allow certain
* exceptions to the strict requirements
* for attribute names.
*
* @return The parsed attribute name.
*
* @throws DirectoryException If it was not possible to parse a
* valid attribute name from the
* provided DN string.
*/
boolean allowExceptions)
throws DirectoryException
{
// Skip over any leading spaces.
{}
{
// This means that the remainder of the DN was completely
// comprised of spaces. If we have gotten here, then we
// know that there is at least one RDN component, and
// therefore the last non-space character of the DN must
// have been a comma. This is not acceptable.
}
// Next, we should find the attribute name for this RDN component.
// It may either be a name (with only letters, digits, and dashes
// and starting with a letter) or an OID (with only digits and
// periods, optionally prefixed with "oid."), and there is also a
// special case in which we will allow underscores. Because of
// the complexity involved, read the entire name first with
// minimal validation and then do more thorough validation later.
boolean checkForOID = false;
boolean endOfName = false;
{
// To make the switch more efficient, we'll include all ASCII
// characters in the range of allowed values and then reject the
// ones that aren't allowed.
switch (b)
{
case ' ':
// This should denote the end of the attribute name.
endOfName = true;
break;
case '!':
case '"':
case '#':
case '$':
case '%':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
// None of these are allowed in an attribute name or any
// character immediately following it.
case '-':
// This will be allowed as long as it isn't the first
// character in the attribute name.
{
}
break;
case '.':
// The period could be allowed if the attribute name is
// actually expressed as an OID. We'll accept it for now,
// but make sure to check it later.
checkForOID = true;
break;
case '/':
// This is not allowed in an attribute name or any character
// immediately following it.
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// Digits are always allowed if they are not the first
// character. However, they may be allowed if they are the
// first character if the valid is an OID or if the
// attribute name exceptions option is enabled. Therefore,
// we'll accept it now and check it later.
break;
case ':':
case ';': // NOTE: attribute options are not allowed in a DN.
case '<':
// None of these are allowed in an attribute name or any
// character immediately following it.
case '=':
// This should denote the end of the attribute name.
endOfName = true;
break;
case '>':
case '?':
case '@':
// None of these are allowed in an attribute name or any
// character immediately following it.
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
// These will always be allowed.
break;
case '[':
case '\\':
case ']':
case '^':
// None of these are allowed in an attribute name or any
// character immediately following it.
case '_':
// This will never be allowed as the first character. It
// may be allowed for subsequent characters if the attribute
// name exceptions option is enabled.
{
}
else if (!allowExceptions)
{
}
break;
case '`':
// This is not allowed in an attribute name or any character
// immediately following it.
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
// These will always be allowed.
break;
default:
// This is not allowed in an attribute name or any character
// immediately following it.
}
if (endOfName)
{
break;
}
}
// We should now have the full attribute name. However, we may
// still need to perform some validation, particularly if the name
// contains a period or starts with a digit. It must also have at
// least one character.
{
}
else if (checkForOID)
{
boolean validOID = true;
int namePos = 0;
{
if (nameLength <= 4)
{
validOID = false;
}
else
{
{
nameLength -= 4;
}
else
{
validOID = false;
}
}
}
{
{
{
namePos++;
}
{
validOID = false;
}
}
else if (ch == '.')
{
{
validOID = false;
}
}
else
{
validOID = false;
}
}
{
validOID = false;
}
if (!validOID)
{
}
}
{
}
return nameBytes;
}
{
}
/**
* Parses an attribute name from the provided DN string starting at
* the specified location.
*
* @param dnString The DN string to be parsed.
* @param pos The position at which to start parsing
* the attribute name.
* @param attributeName The buffer to which to append the parsed
* attribute name.
* @param allowExceptions Indicates whether to allow certain
* exceptions to the strict requirements
* for attribute names.
*
* @return The position of the first character that is not part of
* the attribute name.
*
* @throws DirectoryException If it was not possible to parse a
* valid attribute name from the
* provided DN string.
*/
boolean allowExceptions)
throws DirectoryException
{
// Skip over any leading spaces.
{
{
pos++;
{
// This means that the remainder of the DN was completely
// comprised of spaces. If we have gotten here, then we
// know that there is at least one RDN component, and
// therefore the last non-space character of the DN must
// have been a comma. This is not acceptable.
message);
}
}
}
// Next, we should find the attribute name for this RDN component.
// It may either be a name (with only letters, digits, and dashes
// and starting with a letter) or an OID (with only digits and
// periods, optionally prefixed with "oid."), and there is also a
// special case in which we will allow underscores. Because of
// the complexity involved, read the entire name first with
// minimal validation and then do more thorough validation later.
boolean checkForOID = false;
boolean endOfName = false;
{
// To make the switch more efficient, we'll include all ASCII
// characters in the range of allowed values and then reject the
// ones that aren't allowed.
switch (c)
{
case ' ':
// This should denote the end of the attribute name.
endOfName = true;
break;
case '!':
case '"':
case '#':
case '$':
case '%':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
// None of these are allowed in an attribute name or any
// character immediately following it.
message);
case '-':
// This will be allowed as long as it isn't the first
// character in the attribute name.
{
attributeName.append(c);
}
else
{
message);
}
break;
case '.':
// The period could be allowed if the attribute name is
// actually expressed as an OID. We'll accept it for now,
// but make sure to check it later.
attributeName.append(c);
checkForOID = true;
break;
case '/':
// This is not allowed in an attribute name or any character
// immediately following it.
message);
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// Digits are always allowed if they are not the first
// character. However, they may be allowed if they are the
// first character if the valid is an OID or if the
// attribute name exceptions option is enabled. Therefore,
// we'll accept it now and check it later.
attributeName.append(c);
break;
case ':':
case ';': // NOTE: attribute options are not allowed in a DN.
case '<':
// None of these are allowed in an attribute name or any
// character immediately following it.
message);
case '=':
// This should denote the end of the attribute name.
endOfName = true;
break;
case '>':
case '?':
case '@':
// None of these are allowed in an attribute name or any
// character immediately following it.
message);
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
case 'G':
case 'H':
case 'I':
case 'J':
case 'K':
case 'L':
case 'M':
case 'N':
case 'O':
case 'P':
case 'Q':
case 'R':
case 'S':
case 'T':
case 'U':
case 'V':
case 'W':
case 'X':
case 'Y':
case 'Z':
// These will always be allowed.
attributeName.append(c);
break;
case '[':
case '\\':
case ']':
case '^':
// None of these are allowed in an attribute name or any
// character immediately following it.
message);
case '_':
// This will never be allowed as the first character. It
// may be allowed for subsequent characters if the attribute
// name exceptions option is enabled.
{
message =
message);
}
else if (allowExceptions)
{
attributeName.append(c);
}
else
{
message);
}
break;
case '`':
// This is not allowed in an attribute name or any character
// immediately following it.
message);
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 'r':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
case 'x':
case 'y':
case 'z':
// These will always be allowed.
attributeName.append(c);
break;
default:
// This is not allowed in an attribute name or any character
// immediately following it.
message);
}
if (endOfName)
{
break;
}
pos++;
}
// We should now have the full attribute name. However, we may
// still need to perform some validation, particularly if the
// name contains a period or starts with a digit. It must also
// have at least one character.
{
message);
}
else if (checkForOID)
{
boolean validOID = true;
int namePos = 0;
{
if (nameLength <= 4)
{
validOID = false;
}
else
{
{
nameLength -= 4;
}
else
{
validOID = false;
}
}
}
{
{
{
namePos++;
}
{
validOID = false;
}
}
else if (ch == '.')
{
{
validOID = false;
}
}
else
{
validOID = false;
}
}
{
validOID = false;
}
if (! validOID)
{
}
}
{
}
return pos;
}
/**
* Parses the attribute value from the provided DN string starting
* at the specified location. When the value has been parsed, it
* will be assigned to the provided ASN.1 octet string.
*
* @param dnBytes The byte array containing the DN to be
* parsed.
*
* @return The parsed attribute value.
*
* @throws DirectoryException If it was not possible to parse a
* valid attribute value from the
* provided DN string.
*/
throws DirectoryException
{
// All leading spaces have already been stripped so we can start
// reading the value. However, it may be empty so check for that.
{
return ByteString.empty();
}
// Look at the first character. If it is an octothorpe (#), then
// that means that the value should be a hex string.
if (b == '#')
{
// The first two characters must be hex characters.
{
}
for (int i=0; i < 2; i++)
{
if (isHexDigit(b))
{
}
else
{
}
}
// The rest of the value must be a multiple of two hex
// characters. The end of the value may be designated by the
// end of the DN, a comma or semicolon, a plus sign, or a space.
{
if (isHexDigit(b))
{
{
if (isHexDigit(b))
{
}
else
{
}
}
else
{
}
}
else if (b == ' ' || b == ',' || b == ';' || b == '+')
{
// This denotes the end of the value.
break;
}
else
{
}
}
// At this point, we should have a valid hex string. Convert it
// to a byte array and set that as the value of the provided
// octet string.
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
}
// If the first character is a quotation mark, then the value
// should continue until the corresponding closing quotation mark.
else if (b == '"')
{
// Keep reading until we find a closing quotation mark.
while (true)
{
{
// We hit the end of the DN before the closing quote.
// That's an error.
}
{
// This is the end of the value.
break;
}
}
return bs;
}
else if(b == '+' || b == ',')
{
//We don't allow an empty attribute value. So do not allow the
// first character to be a '+' or ',' since it is not escaped
// by the user.
}
// Otherwise, use general parsing to find the end of the value.
else
{
// the end of the DN.
while (true)
{
{
// This is the end of the DN and therefore the end of the value.
break;
}
if (b == ',' || b == ';' || b == '+')
{
break;
}
if(b != ' ')
{
}
}
// Convert the byte buffer to an array.
}
}
/**
* Parses the attribute value from the provided DN string starting
* at the specified location. When the value has been parsed, it
* will be assigned to the provided ASN.1 octet string.
*
* @param dnString The DN string to be parsed.
* @param pos The position of the first character in
* the attribute value to parse.
* @param attributeValue The ASN.1 octet string whose value should
* be set to the parsed attribute value when
* this method completes successfully.
*
* @return The position of the first character that is not part of
* the attribute value.
*
* @throws DirectoryException If it was not possible to parse a
* valid attribute value from the
* provided DN string.
*/
throws DirectoryException
{
// All leading spaces have already been stripped so we can start
// reading the value. However, it may be empty so check for that.
{
return pos;
}
// Look at the first character. If it is an octothorpe (#), then
// that means that the value should be a hex string.
if (c == '#')
{
// The first two characters must be hex characters.
{
message);
}
for (int i=0; i < 2; i++)
{
if (isHexDigit(c))
{
}
else
{
message);
}
}
// The rest of the value must be a multiple of two hex
// characters. The end of the value may be designated by the
// end of the DN, a comma or semicolon, or a space.
{
if (isHexDigit(c))
{
{
if (isHexDigit(c))
{
}
else
{
throw new DirectoryException(
}
}
else
{
message);
}
}
else if (c == ' ' || c == ',' || c == ';')
{
// This denotes the end of the value.
pos--;
break;
}
else
{
message);
}
}
// At this point, we should have a valid hex string. Convert it
// to a byte array and set that as the value of the provided
// octet string.
try
{
return pos;
}
catch (Exception e)
{
logger.traceException(e);
}
}
// If the first character is a quotation mark, then the value
// should continue until the corresponding closing quotation mark.
else if (c == '"')
{
// Keep reading until we find an unescaped closing quotation
// mark.
boolean escaped = false;
while (true)
{
{
// We hit the end of the DN before the closing quote.
// That's an error.
message);
}
if (escaped)
{
// The previous character was an escape, so we'll take this
// one no matter what.
valueString.append(c);
escaped = false;
}
else if (c == '\\')
{
// The next character is escaped. Set a flag to denote
// this, but don't include the backslash.
escaped = true;
}
else if (c == '"')
{
// This is the end of the value.
break;
}
else
{
// This is just a regular character that should be in the value.
valueString.append(c);
}
}
return pos;
}
else if(c == '+' || c == ',')
{
//We don't allow an empty attribute value. So do not allow the
// first character to be a '+' or ',' since it is not escaped
// by the user.
message);
}
// Otherwise, use general parsing to find the end of the value.
else
{
boolean escaped;
if (c == '\\')
{
escaped = true;
}
else
{
escaped = false;
valueString.append(c);
}
// Keep reading until we find an unescaped comma or plus sign or
// the end of the DN.
while (true)
{
{
// This is the end of the DN and therefore the end of the
// value. If there are any hex characters, then we need to
// deal with them accordingly.
break;
}
if (escaped)
{
// The previous character was an escape, so we'll take this
// one. However, this could be a hex digit, and if that's
// the case then the escape would actually be in front of
// two hex digits that should be treated as a special
// character.
if (isHexDigit(c))
{
// It is a hexadecimal digit, so the next digit must be
// one too. However, this could be just one in a series
// of escaped hex pairs that is used in a string
// containing one or more multi-byte UTF-8 characters so
// we can't just treat this byte in isolation. Collect
// all the bytes together and make sure to take care of
// these hex bytes before appending anything else to the value.
{
throw new DirectoryException(
}
else
{
if (isHexDigit(c2))
{
}
else
{
throw new DirectoryException(
}
}
}
else
{
valueString.append(c);
}
escaped = false;
}
else if (c == '\\')
{
escaped = true;
}
else if (c == ',' || c == ';')
{
pos--;
break;
}
else if (c == '+')
{
pos--;
break;
}
else
{
valueString.append(c);
}
}
// Strip off any unescaped spaces that may be at the end of the value.
{
while (lastPos > 0)
{
{
lastPos--;
}
else
{
break;
}
}
}
return pos;
}
}
/**
* Decodes a hexadecimal string from the provided
* <CODE>hexChars</CODE> buffer, converts it to a byte array, and
* then converts that to a UTF-8 string. The resulting UTF-8 string
* will be appended to the provided <CODE>valueString</CODE> buffer,
* and the <CODE>hexChars</CODE> buffer will be cleared.
*
* @param dnString The DN string that is being decoded.
* @param valueString The buffer containing the value to which the
* decoded string should be appended.
* @param hexChars The buffer containing the hexadecimal
* characters to decode to a UTF-8 string.
*
* @throws DirectoryException If any problem occurs during the
* decoding process.
*/
throws DirectoryException
{
{
return;
}
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
}
/**
* Indicates whether the provided object is equal to this DN. In
* order for the object to be considered equal, it must be a DN with
* the same number of RDN components and each corresponding RDN
* component must be equal.
*
* @param o The object for which to make the determination.
*
* @return <CODE>true</CODE> if the provided object is a DN that is
* equal to this DN, or <CODE>false</CODE> if it is not.
*/
{
if (this == o)
{
return true;
}
if (o instanceof DN)
{
}
return false;
}
/**
* Returns the hash code for this DN.
*
* @return The hash code for this DN.
*/
public int hashCode()
{
return toNormalizedByteString().hashCode();
}
/**
* Retrieves a string representation of this DN.
*
* @return A string representation of this DN.
*/
{
{
if (numComponents == 0)
{
dnString = "";
}
else
{
for (int i=1; i < numComponents; i++)
{
}
}
}
return dnString;
}
/**
* Appends a string representation of this DN to the provided
* buffer.
*
* @param buffer The buffer to which the information should be
* appended.
*/
{
}
/**
* Retrieves a normalized string representation of this DN.
* <p>
*
* This representation is safe to use in an URL or in a file name.
* However, it is not a valid DN and can't be reverted to a valid DN.
*
* @return The normalized string representation of this DN.
*/
public String toNormalizedUrlSafeString()
{
{
return "";
}
{
}
}
/**
* Retrieves a normalized byte string representation of this DN.
* <p>
* This representation is suitable for equality and comparisons, and for providing a
* natural hierarchical ordering.
* However, it is not a valid DN and can't be reverted to a valid DN.
*
* You should consider using a {@code CompactDn} as an alternative.
*
* @return The normalized string representation of this DN.
*/
public ByteString toNormalizedByteString()
{
if (normalizedDN == null)
{
if (numComponents == 0)
{
}
else
{
{
}
}
}
return normalizedDN;
}
/**
* Compares this DN with the provided DN based on a natural order, as defined by
* the toNormalizedByteString() method.
*
* @param other
* The DN against which to compare this DN.
* @return A negative integer if this DN should come before the provided DN, a
* positive integer if this DN should come after the provided DN, or
* zero if there is no difference with regard to ordering.
*/
{
}
}