PatternDN.java revision 040cba63ba4af5bed76846f0edb63c853b009da9
/*
* 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 2008 Sun Microsystems, Inc.
* Portions Copyright 2014 ForgeRock AS
*/
/**
* This class is used to encapsulate DN pattern matching using wildcards.
* The following wildcard uses are supported.
*
* Value substring: Any number of wildcards may appear in RDN attribute
* values where they match zero or more characters, just like substring filters:
* uid=b*jensen*
*
* Whole-Type: A single wildcard may also be used to match any RDN attribute
* type, and the wildcard in this case may be omitted as a shorthand:
* *=bjensen
* bjensen
*
* Whole-RDN. A single wildcard may be used to match exactly one RDN component
* (which may be single or multi-valued):
* *,dc=example,dc=com
*
* Multiple-Whole-RDN: A double wildcard may be used to match one or more
* RDN components:
* uid=bjensen,**,dc=example,dc=com
*
*/
public class PatternDN
{
/**
* If the pattern did not include any Multiple-Whole-RDN wildcards, then
* this is the sequence of RDN patterns in the DN pattern. Otherwise it
* is null.
*/
/**
* If the pattern included any Multiple-Whole-RDN wildcards, then these
* are the RDN pattern sequences that appear between those wildcards.
*/
/**
* When there is no initial sequence, this is used to distinguish between
* the case where we have a suffix pattern (zero or more RDN components
* allowed before matching elements) and the case where it is not a
* suffix pattern but the pattern started with a Multiple-Whole-RDN wildcard
* (one or more RDN components allowed before matching elements).
*/
boolean isSuffix = false;
/**
* Create a DN pattern that does not include any Multiple-Whole-RDN wildcards.
* @param equality The sequence of RDN patterns making up the DN pattern.
*/
{
}
/**
* Create a DN pattern that includes Multiple-Whole-RDN wildcards.
* @param subInitial The sequence of RDN patterns appearing at the
* start of the DN, or null if there are none.
* @param subAnyElements The list of sequences of RDN patterns appearing
* in order anywhere in the DN.
* @param subFinal The sequence of RDN patterns appearing at the
* end of the DN, or null if there are none.
*/
{
this.subInitial = subInitial;
this.subAnyElements = subAnyElements;
}
/**
* Determine whether a given DN matches this pattern.
* @param dn The DN to be matched.
* @return true if the DN matches the pattern.
*/
{
{
// There are no Multiple-Whole-RDN wildcards in the pattern.
{
return false;
}
{
{
return false;
}
}
return true;
}
else
{
// There are Multiple-Whole-RDN wildcards in the pattern.
int pos = 0;
if (subInitial != null)
{
if (initialLength > valueLength)
{
return false;
}
{
{
return false;
}
}
pos++;
}
else
{
if (!isSuffix)
{
pos++;
}
}
{
{
boolean match = false;
{
{
boolean subMatch = true;
for (int i=1; i < anyLength; i++)
{
{
subMatch = false;
break;
}
}
if (subMatch)
{
break;
}
}
}
if (match)
{
}
else
{
return false;
}
}
}
{
{
return false;
}
{
{
return false;
}
}
}
return pos <= valueLength;
}
}
/**
* Create a new DN pattern matcher to match a suffix.
* @param pattern The suffix pattern string.
* @throws org.opends.server.types.DirectoryException If the pattern string
* is not valid.
* @return A new DN pattern matcher.
*/
throws DirectoryException
{
// Parse the user supplied pattern.
// Adjust the pattern so that it matches any DN ending with the pattern.
{
// The pattern contained no Multiple-Whole-RDN wildcards,
// so we just convert the whole thing into a final fragment.
}
{
// The pattern had an initial fragment so we need to convert that into
// the head of the list of any elements.
}
return patternDN;
}
/**
* Create a new DN pattern matcher from a pattern string.
* @param dnString The DN pattern string.
* @throws org.opends.server.types.DirectoryException If the pattern string
* is not valid.
* @return A new DN pattern matcher.
*/
throws DirectoryException
{
// A null or empty DN is acceptable.
{
}
if (length == 0)
{
}
// 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.
}
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.
while (true)
{
int attributePos = pos;
// Make sure that we're not at the end of the DN string because
// that would be invalid.
{
{
break;
}
{
break;
}
else
{
name = "*";
c = '=';
}
}
else
{
// Skip over any spaces between the attribute name and its
// value.
while (c == ' ')
{
pos++;
{
{
break rdnLoop;
}
{
break rdnLoop;
}
else
{
name = "*";
c = '=';
}
}
else
{
}
}
}
if (c == '=')
{
pos++;
}
else if ((c == ',' || c == ';'))
{
{
pos++;
continue;
}
{
pos++;
continue;
}
else
{
pos = attributePos;
name = "*";
}
}
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.
{
break;
}
// 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.
break;
}
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.
message);
}
else
{
}
}
// The next character must be an equal sign. If it is not,
// then that's an error.
if (c == '=')
{
pos++;
}
else
{
message);
}
// 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.
{
break;
}
// 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.
break;
}
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);
}
}
}
if (doubleWildPos.isEmpty())
{
}
else
{
int i = 0;
if (to != 0)
{
// Initial piece.
}
int from;
{
}
if (i < doubleWildPos.size())
{
if (from != numComponents)
{
// Final piece.
}
}
}
}
/**
* Parses an attribute name pattern from the provided DN pattern string
* starting at the specified location.
*
* @param dnString The DN pattern string to be parsed.
* @param pos The position at which to start parsing
* the attribute name pattern.
* @param attributeName The buffer to which to append the parsed
* attribute name pattern.
*
* @return The position of the first character that is not part of
* the attribute name pattern.
*
* @throws DirectoryException If it was not possible to parse a
* valid attribute name pattern from the
* provided DN pattern string.
*/
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.
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 ')':
// None of these are allowed in an attribute name or any
// character immediately following it.
message);
case '*':
// Wildcard character.
attributeName.append(c);
break;
case '+':
// None of these are allowed in an attribute name or any
// character immediately following it.
message =
message);
case ',':
// This should denote the end of the attribute name.
endOfName = true;
break;
case '-':
// This will be allowed as long as it isn't the first
// character in the attribute name.
{
attributeName.append(c);
}
else
{
message =
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 =
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 ':':
// Not allowed in an attribute name or any
// character immediately following it.
message =
message);
case ';': // NOTE: attribute options are not allowed in a DN.
// This should denote the end of the attribute name.
endOfName = true;
break;
case '<':
// None of these are allowed in an attribute name or any
// character immediately following it.
message =
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 =
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 =
message);
case '_':
attributeName.append(c);
break;
case '`':
// This is not allowed in an attribute name or any character
// immediately following it.
message =
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 =
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++;
}
if ((namePos < nameLength) &&
{
validOID = false;
}
}
else if (ch == '.')
{
if ((namePos == 1) ||
{
validOID = false;
}
}
else
{
validOID = false;
}
}
{
validOID = false;
}
if (! validOID)
{
}
}
return pos;
}
/**
* Parses the attribute value pattern from the provided DN pattern
* string starting at the specified location. The value is split up
* according to the wildcard locations, and the fragments are inserted
* into the provided list.
*
* @param dnString The DN pattern string to be parsed.
* @param pos The position of the first character in
* the attribute value pattern to parse.
* @param attributeValues The list whose elements should be set to
* the parsed attribute value fragments 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 pattern 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;
}
// Otherwise, use general parsing to find the end of the value.
else
{
boolean escaped;
if (c == '\\')
{
escaped = true;
}
else if (c == '*')
{
escaped = false;
}
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 if (c == '*')
{
{
message);
}
valueString = new StringBuilder();
hexChars = new StringBuilder();
}
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
{
try
{
}
catch (Exception e)
{
logger.traceException(e);
}
}
}