Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2007-2023 Ping Identity Corporation
* All Rights Reserved.
*/
/*
* Copyright 2007-2023 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright (C) 2007-2023 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see .
*/
package com.unboundid.ldap.sdk;
import java.io.Serializable;
import java.util.ArrayList;
import com.unboundid.util.ByteStringBuffer;
import com.unboundid.util.Debug;
import com.unboundid.util.NotMutable;
import com.unboundid.util.NotNull;
import com.unboundid.util.Nullable;
import com.unboundid.util.StaticUtils;
import com.unboundid.util.ThreadSafety;
import com.unboundid.util.ThreadSafetyLevel;
import com.unboundid.util.Validator;
import static com.unboundid.ldap.sdk.LDAPMessages.*;
/**
* This class provides a data structure for interacting with LDAP URLs. It may
* be used to encode and decode URLs, as well as access the various elements
* that they contain. Note that this implementation currently does not support
* the use of extensions in an LDAP URL.
*
* The components that may be included in an LDAP URL include:
*
*
Scheme -- This specifies the protocol to use when communicating with
* the server. The official LDAP URL specification only allows a scheme
* of "{@code ldap}", but this implementation also supports the use of the
* "{@code ldaps}" scheme to indicate that clients should attempt to
* perform SSL-based communication with the target server (LDAPS) rather
* than unencrypted LDAP. It will also accept "{@code ldapi}", which is
* LDAP over UNIX domain sockets, although the LDAP SDK does not directly
* support that mechanism of communication.
*
Host -- This specifies the address of the directory server to which the
* URL refers. If no host is provided, then it is expected that the
* client has some prior knowledge of the host (it often implies the same
* server from which the URL was retrieved).
*
Port -- This specifies the port of the directory server to which the
* URL refers. If no host or port is provided, then it is assumed that
* the client has some prior knowledge of the instance to use (it often
* implies the same instance from which the URL was retrieved). If a host
* is provided without a port, then it should be assumed that the standard
* LDAP port of 389 should be used (or the standard LDAPS port of 636 if
* the scheme is "{@code ldaps}", or a value of 0 if the scheme is
* "{@code ldapi}").
*
Base DN -- This specifies the base DN for the URL. If no base DN is
* provided, then a default of the null DN should be assumed.
*
Requested attributes -- This specifies the set of requested attributes
* for the URL. If no attributes are specified, then the behavior should
* be the same as if no attributes had been provided for a search request
* (i.e., all user attributes should be included).
*
* In the string representation of an LDAP URL, the names of the requested
* attributes (if more than one is provided) should be separated by
* commas.
*
Scope -- This specifies the scope for the URL. It should be one of the
* standard scope values as defined in the {@link SearchRequest}
* class. If no scope is provided, then it should be assumed that a
* scope of {@link SearchScope#BASE} should be used.
*
* In the string representation, the names of the scope values that are
* allowed include:
*
*
base -- Equivalent to {@link SearchScope#BASE}.
*
one -- Equivalent to {@link SearchScope#ONE}.
*
sub -- Equivalent to {@link SearchScope#SUB}.
*
subordinates -- Equivalent to
* {@link SearchScope#SUBORDINATE_SUBTREE}.
*
*
Filter -- This specifies the filter for the URL. If no filter is
* provided, then a default of "{@code (objectClass=*)}" should be
* assumed.
*
* An LDAP URL encapsulates many of the properties of a search request, and in
* fact the {@link LDAPURL#toSearchRequest} method may be used to create a
* {@link SearchRequest} object from an LDAP URL.
*
* See RFC 4516 for a complete
* description of the LDAP URL syntax. Some examples of LDAP URLs include:
*
*
{@code ldap://} -- This is the smallest possible LDAP URL that can be
* represented. The default values will be used for all components other
* than the scheme.
*
{@code
* ldap://server.example.com:1234/dc=example,dc=com?cn,sn?sub?(uid=john)}
* -- This is an example of a URL containing all of the elements. The
* scheme is "{@code ldap}", the host is "{@code server.example.com}",
* the port is "{@code 1234}", the base DN is "{@code dc=example,dc=com}",
* the requested attributes are "{@code cn}" and "{@code sn}", the scope
* is "{@code sub}" (which indicates a subtree scope equivalent to
* {@link SearchScope#SUB}), and a filter of
* "{@code (uid=john)}".
*
*/
@NotMutable()
@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
public final class LDAPURL
implements Serializable
{
/**
* The default filter that will be used if none is provided.
*/
@NotNull private static final Filter DEFAULT_FILTER =
Filter.createPresenceFilter("objectClass");
/**
* The default port number that will be used for LDAP URLs if none is
* provided.
*/
public static final int DEFAULT_LDAP_PORT = 389;
/**
* The default port number that will be used for LDAPS URLs if none is
* provided.
*/
public static final int DEFAULT_LDAPS_PORT = 636;
/**
* The default port number that will be used for LDAPI URLs if none is
* provided.
*/
public static final int DEFAULT_LDAPI_PORT = 0;
/**
* The default scope that will be used if none is provided.
*/
@NotNull private static final SearchScope DEFAULT_SCOPE = SearchScope.BASE;
/**
* The default base DN that will be used if none is provided.
*/
@NotNull private static final DN DEFAULT_BASE_DN = DN.NULL_DN;
/**
* The default set of attributes that will be used if none is provided.
*/
@NotNull private static final String[] DEFAULT_ATTRIBUTES =
StaticUtils.NO_STRINGS;
/**
* The serial version UID for this serializable class.
*/
private static final long serialVersionUID = 3420786933570240493L;
// Indicates whether the attribute list was provided in the URL.
private final boolean attributesProvided;
// Indicates whether the base DN was provided in the URL.
private final boolean baseDNProvided;
// Indicates whether the filter was provided in the URL.
private final boolean filterProvided;
// Indicates whether the port was provided in the URL.
private final boolean portProvided;
// Indicates whether the scope was provided in the URL.
private final boolean scopeProvided;
// The base DN used by this URL.
@NotNull private final DN baseDN;
// The filter used by this URL.
@NotNull private final Filter filter;
// The port used by this URL.
private final int port;
// The search scope used by this URL.
@NotNull private final SearchScope scope;
// The host used by this URL.
@Nullable private final String host;
// The normalized representation of this LDAP URL.
@Nullable private volatile String normalizedURLString;
// The scheme used by this LDAP URL. The standard only accepts "ldap", but
// we will also accept "ldaps" and "ldapi".
@NotNull private final String scheme;
// The string representation of this LDAP URL.
@NotNull private final String urlString;
// The set of attributes included in this URL.
@NotNull private final String[] attributes;
/**
* Creates a new LDAP URL from the provided string representation.
*
* @param urlString The string representation for this LDAP URL. It must
* not be {@code null}.
*
* @throws LDAPException If the provided URL string cannot be parsed as an
* LDAP URL.
*/
public LDAPURL(@NotNull final String urlString)
throws LDAPException
{
Validator.ensureNotNull(urlString);
this.urlString = urlString;
// Find the location of the first colon. It should mark the end of the
// scheme.
final int colonPos = urlString.indexOf("://");
if (colonPos < 0)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_NO_COLON_SLASHES.get());
}
scheme = StaticUtils.toLowerCase(urlString.substring(0, colonPos));
final int defaultPort;
if (scheme.equals("ldap"))
{
defaultPort = DEFAULT_LDAP_PORT;
}
else if (scheme.equals("ldaps"))
{
defaultPort = DEFAULT_LDAPS_PORT;
}
else if (scheme.equals("ldapi"))
{
defaultPort = DEFAULT_LDAPI_PORT;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_SCHEME.get(scheme));
}
// Look for the first slash after the "://". It will designate the end of
// the hostport section.
final int slashPos = urlString.indexOf('/', colonPos+3);
if (slashPos < 0)
{
// This is fine. It just means that the URL won't have a base DN,
// attribute list, scope, or filter, and that the rest of the value is
// the hostport element.
baseDN = DEFAULT_BASE_DN;
baseDNProvided = false;
attributes = DEFAULT_ATTRIBUTES;
attributesProvided = false;
scope = DEFAULT_SCOPE;
scopeProvided = false;
filter = DEFAULT_FILTER;
filterProvided = false;
final String hostPort = urlString.substring(colonPos+3);
final StringBuilder hostBuffer = new StringBuilder(hostPort.length());
final int portValue = decodeHostPort(hostPort, hostBuffer);
if (portValue < 0)
{
port = defaultPort;
portProvided = false;
}
else
{
port = portValue;
portProvided = true;
}
if (hostBuffer.length() == 0)
{
host = null;
}
else
{
host = hostBuffer.toString();
}
return;
}
final String hostPort = urlString.substring(colonPos+3, slashPos);
final StringBuilder hostBuffer = new StringBuilder(hostPort.length());
final int portValue = decodeHostPort(hostPort, hostBuffer);
if (portValue < 0)
{
port = defaultPort;
portProvided = false;
}
else
{
port = portValue;
portProvided = true;
}
if (hostBuffer.length() == 0)
{
host = null;
}
else
{
host = hostBuffer.toString();
}
// Look for the first question mark after the slash. It will designate the
// end of the base DN.
final int questionMarkPos = urlString.indexOf('?', slashPos+1);
if (questionMarkPos < 0)
{
// This is fine. It just means that the URL won't have an attribute list,
// scope, or filter, and that the rest of the value is the base DN.
attributes = DEFAULT_ATTRIBUTES;
attributesProvided = false;
scope = DEFAULT_SCOPE;
scopeProvided = false;
filter = DEFAULT_FILTER;
filterProvided = false;
baseDN = new DN(percentDecode(urlString.substring(slashPos+1)));
baseDNProvided = (! baseDN.isNullDN());
return;
}
baseDN = new DN(percentDecode(urlString.substring(slashPos+1,
questionMarkPos)));
baseDNProvided = (! baseDN.isNullDN());
// Look for the next question mark. It will designate the end of the
// attribute list.
final int questionMark2Pos = urlString.indexOf('?', questionMarkPos+1);
if (questionMark2Pos < 0)
{
// This is fine. It just means that the URL won't have a scope or filter,
// and that the rest of the value is the attribute list.
scope = DEFAULT_SCOPE;
scopeProvided = false;
filter = DEFAULT_FILTER;
filterProvided = false;
attributes = decodeAttributes(urlString.substring(questionMarkPos+1));
attributesProvided = (attributes.length > 0);
return;
}
attributes = decodeAttributes(urlString.substring(questionMarkPos+1,
questionMark2Pos));
attributesProvided = (attributes.length > 0);
// Look for the next question mark. It will designate the end of the scope.
final int questionMark3Pos = urlString.indexOf('?', questionMark2Pos+1);
if (questionMark3Pos < 0)
{
// This is fine. It just means that the URL won't have a filter, and that
// the rest of the value is the scope.
filter = DEFAULT_FILTER;
filterProvided = false;
final String scopeStr =
StaticUtils.toLowerCase(urlString.substring(questionMark2Pos+1));
if (scopeStr.isEmpty())
{
scope = SearchScope.BASE;
scopeProvided = false;
}
else if (scopeStr.equals("base"))
{
scope = SearchScope.BASE;
scopeProvided = true;
}
else if (scopeStr.equals("one"))
{
scope = SearchScope.ONE;
scopeProvided = true;
}
else if (scopeStr.equals("sub"))
{
scope = SearchScope.SUB;
scopeProvided = true;
}
else if (scopeStr.equals("subord") || scopeStr.equals("subordinates"))
{
scope = SearchScope.SUBORDINATE_SUBTREE;
scopeProvided = true;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_SCOPE.get(scopeStr));
}
return;
}
final String scopeStr = StaticUtils.toLowerCase(
urlString.substring(questionMark2Pos+1, questionMark3Pos));
if (scopeStr.isEmpty())
{
scope = SearchScope.BASE;
scopeProvided = false;
}
else if (scopeStr.equals("base"))
{
scope = SearchScope.BASE;
scopeProvided = true;
}
else if (scopeStr.equals("one"))
{
scope = SearchScope.ONE;
scopeProvided = true;
}
else if (scopeStr.equals("sub"))
{
scope = SearchScope.SUB;
scopeProvided = true;
}
else if (scopeStr.equals("subord") || scopeStr.equals("subordinates"))
{
scope = SearchScope.SUBORDINATE_SUBTREE;
scopeProvided = true;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_SCOPE.get(scopeStr));
}
// The remainder of the value must be the filter.
final String filterStr =
percentDecode(urlString.substring(questionMark3Pos+1));
if (filterStr.isEmpty())
{
filter = DEFAULT_FILTER;
filterProvided = false;
}
else
{
filter = Filter.create(filterStr);
filterProvided = true;
}
}
/**
* Creates a new LDAP URL with the provided information.
*
* @param scheme The scheme for this LDAP URL. It must not be
* {@code null} and must be either "ldap", "ldaps", or
* "ldapi".
* @param host The host for this LDAP URL. It may be {@code null} if
* no host is to be included.
* @param port The port for this LDAP URL. It may be {@code null} if
* no port is to be included. If it is provided, it must
* be between 1 and 65535, inclusive.
* @param baseDN The base DN for this LDAP URL. It may be {@code null}
* if no base DN is to be included.
* @param attributes The set of requested attributes for this LDAP URL. It
* may be {@code null} or empty if no attribute list is to
* be included.
* @param scope The scope for this LDAP URL. It may be {@code null} if
* no scope is to be included. Otherwise, it must be a
* value between zero and three, inclusive.
* @param filter The filter for this LDAP URL. It may be {@code null}
* if no filter is to be included.
*
* @throws LDAPException If there is a problem with any of the provided
* arguments.
*/
public LDAPURL(@NotNull final String scheme, @Nullable final String host,
@Nullable final Integer port, @Nullable final DN baseDN,
@Nullable final String[] attributes,
@Nullable final SearchScope scope,
@Nullable final Filter filter)
throws LDAPException
{
Validator.ensureNotNull(scheme);
final StringBuilder buffer = new StringBuilder();
this.scheme = StaticUtils.toLowerCase(scheme);
final int defaultPort;
if (scheme.equals("ldap"))
{
defaultPort = DEFAULT_LDAP_PORT;
}
else if (scheme.equals("ldaps"))
{
defaultPort = DEFAULT_LDAPS_PORT;
}
else if (scheme.equals("ldapi"))
{
defaultPort = DEFAULT_LDAPI_PORT;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_SCHEME.get(scheme));
}
buffer.append(scheme);
buffer.append("://");
if ((host == null) || host.isEmpty())
{
this.host = null;
}
else
{
this.host = host;
buffer.append(host);
}
if (port == null)
{
this.port = defaultPort;
portProvided = false;
}
else
{
this.port = port;
portProvided = true;
buffer.append(':');
buffer.append(port);
if ((port < 1) || (port > 65_535))
{
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_LDAPURL_INVALID_PORT.get(port));
}
}
buffer.append('/');
if (baseDN == null)
{
this.baseDN = DEFAULT_BASE_DN;
baseDNProvided = false;
}
else
{
this.baseDN = baseDN;
baseDNProvided = true;
percentEncode(baseDN.toString(), buffer);
}
final boolean continueAppending;
if (((attributes == null) || (attributes.length == 0)) && (scope == null) &&
(filter == null))
{
continueAppending = false;
}
else
{
continueAppending = true;
}
if (continueAppending)
{
buffer.append('?');
}
if ((attributes == null) || (attributes.length == 0))
{
this.attributes = DEFAULT_ATTRIBUTES;
attributesProvided = false;
}
else
{
this.attributes = attributes;
attributesProvided = true;
for (int i=0; i < attributes.length; i++)
{
if (i > 0)
{
buffer.append(',');
}
buffer.append(attributes[i]);
}
}
if (continueAppending)
{
buffer.append('?');
}
if (scope == null)
{
this.scope = DEFAULT_SCOPE;
scopeProvided = false;
}
else
{
switch (scope.intValue())
{
case 0:
this.scope = scope;
scopeProvided = true;
buffer.append("base");
break;
case 1:
this.scope = scope;
scopeProvided = true;
buffer.append("one");
break;
case 2:
this.scope = scope;
scopeProvided = true;
buffer.append("sub");
break;
case 3:
this.scope = scope;
scopeProvided = true;
buffer.append("subordinates");
break;
default:
throw new LDAPException(ResultCode.PARAM_ERROR,
ERR_LDAPURL_INVALID_SCOPE_VALUE.get(scope));
}
}
if (continueAppending)
{
buffer.append('?');
}
if (filter == null)
{
this.filter = DEFAULT_FILTER;
filterProvided = false;
}
else
{
this.filter = filter;
filterProvided = true;
percentEncode(filter.toString(), buffer);
}
urlString = buffer.toString();
}
/**
* Decodes the provided string as a host and optional port number.
*
* @param hostPort The string to be decoded.
* @param hostBuffer The buffer to which the decoded host address will be
* appended.
*
* @return The port number decoded from the provided string, or -1 if there
* was no port number.
*
* @throws LDAPException If the provided string cannot be decoded as a
* hostport element.
*/
private static int decodeHostPort(@NotNull final String hostPort,
@NotNull final StringBuilder hostBuffer)
throws LDAPException
{
final int length = hostPort.length();
if (length == 0)
{
// It's an empty string, so we'll just use the defaults.
return -1;
}
if (hostPort.charAt(0) == '[')
{
// It starts with a square bracket, which means that the address is an
// IPv6 literal address. Find the closing bracket, and the address
// will be inside them.
final int closingBracketPos = hostPort.indexOf(']');
if (closingBracketPos < 0)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_IPV6_HOST_MISSING_BRACKET.get());
}
hostBuffer.append(hostPort.substring(1, closingBracketPos).trim());
if (hostBuffer.length() == 0)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_IPV6_HOST_EMPTY.get());
}
// The closing bracket must either be the end of the hostport element
// (in which case we'll use the default port), or it must be followed by
// a colon and an integer (which will be the port).
if (closingBracketPos == (length - 1))
{
return -1;
}
else
{
if (hostPort.charAt(closingBracketPos+1) != ':')
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_IPV6_HOST_UNEXPECTED_CHAR.get(
hostPort.charAt(closingBracketPos+1)));
}
else
{
try
{
final int decodedPort =
Integer.parseInt(hostPort.substring(closingBracketPos+2));
if ((decodedPort >= 1) && (decodedPort <= 65_535))
{
return decodedPort;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_PORT.get(
decodedPort));
}
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_PORT_NOT_INT.get(hostPort),
nfe);
}
}
}
}
// If we've gotten here, then the address is either a resolvable name or an
// IPv4 address. If there is a colon in the string, then it will separate
// the address from the port. Otherwise, the remaining value will be the
// address and we'll use the default port.
final int colonPos = hostPort.indexOf(':');
if (colonPos < 0)
{
hostBuffer.append(hostPort);
return -1;
}
else
{
try
{
final int decodedPort =
Integer.parseInt(hostPort.substring(colonPos+1));
if ((decodedPort >= 1) && (decodedPort <= 65_535))
{
hostBuffer.append(hostPort.substring(0, colonPos));
return decodedPort;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_PORT.get(decodedPort));
}
}
catch (final NumberFormatException nfe)
{
Debug.debugException(nfe);
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_PORT_NOT_INT.get(hostPort), nfe);
}
}
}
/**
* Decodes the contents of the provided string as an attribute list.
*
* @param s The string to decode as an attribute list.
*
* @return The array of decoded attribute names.
*
* @throws LDAPException If an error occurred while attempting to decode the
* attribute list.
*/
@NotNull()
private static String[] decodeAttributes(@NotNull final String s)
throws LDAPException
{
final int length = s.length();
if (length == 0)
{
return DEFAULT_ATTRIBUTES;
}
final ArrayList attrList = new ArrayList<>(10);
int startPos = 0;
while (startPos < length)
{
final int commaPos = s.indexOf(',', startPos);
if (commaPos < 0)
{
// There are no more commas, so there can only be one attribute left.
final String attrName = s.substring(startPos).trim();
if (attrName.isEmpty())
{
// This is only acceptable if the attribute list is empty (there was
// probably a space in the attribute list string, which is technically
// not allowed, but we'll accept it). If the attribute list is not
// empty, then there were two consecutive commas, which is not
// allowed.
if (attrList.isEmpty())
{
return DEFAULT_ATTRIBUTES;
}
else
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_ATTRLIST_ENDS_WITH_COMMA.get());
}
}
else
{
attrList.add(attrName);
break;
}
}
else
{
final String attrName = s.substring(startPos, commaPos).trim();
if (attrName.isEmpty())
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_ATTRLIST_EMPTY_ATTRIBUTE.get());
}
else
{
attrList.add(attrName);
startPos = commaPos+1;
if (startPos >= length)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_ATTRLIST_ENDS_WITH_COMMA.get());
}
}
}
}
final String[] attributes = new String[attrList.size()];
attrList.toArray(attributes);
return attributes;
}
/**
* Decodes any percent-encoded values that may be contained in the provided
* string.
*
* @param s The string to be decoded.
*
* @return The percent-decoded form of the provided string.
*
* @throws LDAPException If a problem occurs while attempting to decode the
* provided string.
*/
@NotNull()
public static String percentDecode(@NotNull final String s)
throws LDAPException
{
// First, see if there are any percent characters at all in the provided
// string. If not, then just return the string as-is.
int firstPercentPos = -1;
final int length = s.length();
for (int i=0; i < length; i++)
{
if (s.charAt(i) == '%')
{
firstPercentPos = i;
break;
}
}
if (firstPercentPos < 0)
{
return s;
}
int pos = firstPercentPos;
final ByteStringBuffer buffer = new ByteStringBuffer(2 * length);
buffer.append(s.substring(0, firstPercentPos));
while (pos < length)
{
final char c = s.charAt(pos++);
if (c == '%')
{
if (pos >= length)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_HEX_STRING_TOO_SHORT.get(s));
}
final byte b;
switch (s.charAt(pos++))
{
case '0':
b = 0x00;
break;
case '1':
b = 0x10;
break;
case '2':
b = 0x20;
break;
case '3':
b = 0x30;
break;
case '4':
b = 0x40;
break;
case '5':
b = 0x50;
break;
case '6':
b = 0x60;
break;
case '7':
b = 0x70;
break;
case '8':
b = (byte) 0x80;
break;
case '9':
b = (byte) 0x90;
break;
case 'a':
case 'A':
b = (byte) 0xA0;
break;
case 'b':
case 'B':
b = (byte) 0xB0;
break;
case 'c':
case 'C':
b = (byte) 0xC0;
break;
case 'd':
case 'D':
b = (byte) 0xD0;
break;
case 'e':
case 'E':
b = (byte) 0xE0;
break;
case 'f':
case 'F':
b = (byte) 0xF0;
break;
default:
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_HEX_CHAR.get(
s.charAt(pos-1)));
}
if (pos >= length)
{
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_HEX_STRING_TOO_SHORT.get(s));
}
switch (s.charAt(pos++))
{
case '0':
buffer.append(b);
break;
case '1':
buffer.append((byte) (b | 0x01));
break;
case '2':
buffer.append((byte) (b | 0x02));
break;
case '3':
buffer.append((byte) (b | 0x03));
break;
case '4':
buffer.append((byte) (b | 0x04));
break;
case '5':
buffer.append((byte) (b | 0x05));
break;
case '6':
buffer.append((byte) (b | 0x06));
break;
case '7':
buffer.append((byte) (b | 0x07));
break;
case '8':
buffer.append((byte) (b | 0x08));
break;
case '9':
buffer.append((byte) (b | 0x09));
break;
case 'a':
case 'A':
buffer.append((byte) (b | 0x0A));
break;
case 'b':
case 'B':
buffer.append((byte) (b | 0x0B));
break;
case 'c':
case 'C':
buffer.append((byte) (b | 0x0C));
break;
case 'd':
case 'D':
buffer.append((byte) (b | 0x0D));
break;
case 'e':
case 'E':
buffer.append((byte) (b | 0x0E));
break;
case 'f':
case 'F':
buffer.append((byte) (b | 0x0F));
break;
default:
throw new LDAPException(ResultCode.DECODING_ERROR,
ERR_LDAPURL_INVALID_HEX_CHAR.get(
s.charAt(pos-1)));
}
}
else
{
buffer.append(c);
}
}
return buffer.toString();
}
/**
* Appends an encoded version of the provided string to the given buffer. Any
* special characters contained in the string will be replaced with byte
* representations consisting of one percent sign and two hexadecimal digits
* for each byte in the special character.
*
* @param s The string to be encoded.
* @param buffer The buffer to which the encoded string will be written.
*/
private static void percentEncode(@NotNull final String s,
@NotNull final StringBuilder buffer)
{
final int length = s.length();
for (int i=0; i < length; i++)
{
final char c = s.charAt(i);
switch (c)
{
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':
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':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
case '-':
case '.':
case '_':
case '~':
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
buffer.append(c);
break;
default:
final byte[] charBytes =
StaticUtils.getBytes(new String(new char[] { c }));
for (final byte b : charBytes)
{
buffer.append('%');
StaticUtils.toHex(b, buffer);
}
break;
}
}
}
/**
* Retrieves the scheme for this LDAP URL. It will either be "ldap", "ldaps",
* or "ldapi".
*
* @return The scheme for this LDAP URL.
*/
@NotNull()
public String getScheme()
{
return scheme;
}
/**
* Retrieves the host for this LDAP URL.
*
* @return The host for this LDAP URL, or {@code null} if the URL does not
* include a host and the client is supposed to have some external
* knowledge of what the host should be.
*/
@Nullable()
public String getHost()
{
return host;
}
/**
* Indicates whether the URL explicitly included a host address.
*
* @return {@code true} if the URL explicitly included a host address, or
* {@code false} if it did not.
*/
public boolean hostProvided()
{
return (host != null);
}
/**
* Retrieves the port for this LDAP URL.
*
* @return The port for this LDAP URL.
*/
public int getPort()
{
return port;
}
/**
* Indicates whether the URL explicitly included a port number.
*
* @return {@code true} if the URL explicitly included a port number, or
* {@code false} if it did not and the default should be used.
*/
public boolean portProvided()
{
return portProvided;
}
/**
* Retrieves the base DN for this LDAP URL.
*
* @return The base DN for this LDAP URL.
*/
@NotNull()
public DN getBaseDN()
{
return baseDN;
}
/**
* Indicates whether the URL explicitly included a base DN.
*
* @return {@code true} if the URL explicitly included a base DN, or
* {@code false} if it did not and the default should be used.
*/
public boolean baseDNProvided()
{
return baseDNProvided;
}
/**
* Retrieves the attribute list for this LDAP URL.
*
* @return The attribute list for this LDAP URL.
*/
@NotNull()
public String[] getAttributes()
{
return attributes;
}
/**
* Indicates whether the URL explicitly included an attribute list.
*
* @return {@code true} if the URL explicitly included an attribute list, or
* {@code false} if it did not and the default should be used.
*/
public boolean attributesProvided()
{
return attributesProvided;
}
/**
* Retrieves the scope for this LDAP URL.
*
* @return The scope for this LDAP URL.
*/
@NotNull()
public SearchScope getScope()
{
return scope;
}
/**
* Indicates whether the URL explicitly included a search scope.
*
* @return {@code true} if the URL explicitly included a search scope, or
* {@code false} if it did not and the default should be used.
*/
public boolean scopeProvided()
{
return scopeProvided;
}
/**
* Retrieves the filter for this LDAP URL.
*
* @return The filter for this LDAP URL.
*/
@NotNull()
public Filter getFilter()
{
return filter;
}
/**
* Indicates whether the URL explicitly included a search filter.
*
* @return {@code true} if the URL explicitly included a search filter, or
* {@code false} if it did not and the default should be used.
*/
public boolean filterProvided()
{
return filterProvided;
}
/**
* Creates a search request containing the base DN, scope, filter, and
* requested attributes from this LDAP URL.
*
* @return The search request created from the base DN, scope, filter, and
* requested attributes from this LDAP URL.
*/
@NotNull()
public SearchRequest toSearchRequest()
{
return new SearchRequest(baseDN.toString(), scope, filter, attributes);
}
/**
* Retrieves a hash code for this LDAP URL.
*
* @return A hash code for this LDAP URL.
*/
@Override()
public int hashCode()
{
return toNormalizedString().hashCode();
}
/**
* Indicates whether the provided object is equal to this LDAP URL. In order
* to be considered equal, the provided object must be an LDAP URL with the
* same normalized string representation.
*
* @param o The object for which to make the determination.
*
* @return {@code true} if the provided object is equal to this LDAP URL, or
* {@code false} if not.
*/
@Override()
public boolean equals(@Nullable final Object o)
{
if (o == null)
{
return false;
}
if (o == this)
{
return true;
}
if (! (o instanceof LDAPURL))
{
return false;
}
final LDAPURL url = (LDAPURL) o;
return toNormalizedString().equals(url.toNormalizedString());
}
/**
* Retrieves a string representation of this LDAP URL.
*
* @return A string representation of this LDAP URL.
*/
@Override()
@NotNull()
public String toString()
{
return urlString;
}
/**
* Retrieves a normalized string representation of this LDAP URL.
*
* @return A normalized string representation of this LDAP URL.
*/
@NotNull()
public String toNormalizedString()
{
if (normalizedURLString == null)
{
final StringBuilder buffer = new StringBuilder();
toNormalizedString(buffer);
normalizedURLString = buffer.toString();
}
return normalizedURLString;
}
/**
* Appends a normalized string representation of this LDAP URL to the provided
* buffer.
*
* @param buffer The buffer to which to append the normalized string
* representation of this LDAP URL.
*/
public void toNormalizedString(@NotNull final StringBuilder buffer)
{
buffer.append(scheme);
buffer.append("://");
if (host != null)
{
if (host.indexOf(':') >= 0)
{
buffer.append('[');
buffer.append(StaticUtils.toLowerCase(host));
buffer.append(']');
}
else
{
buffer.append(StaticUtils.toLowerCase(host));
}
}
if (! scheme.equals("ldapi"))
{
buffer.append(':');
buffer.append(port);
}
buffer.append('/');
percentEncode(baseDN.toNormalizedString(), buffer);
buffer.append('?');
for (int i=0; i < attributes.length; i++)
{
if (i > 0)
{
buffer.append(',');
}
buffer.append(StaticUtils.toLowerCase(attributes[i]));
}
buffer.append('?');
switch (scope.intValue())
{
case 0: // BASE
buffer.append("base");
break;
case 1: // ONE
buffer.append("one");
break;
case 2: // SUB
buffer.append("sub");
break;
case 3: // SUBORDINATE_SUBTREE
buffer.append("subordinates");
break;
}
buffer.append('?');
percentEncode(filter.toNormalizedString(), buffer);
}
}