Source for file Util.php
Documentation is available at Util.php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
* Utility Class for Net_LDAP
* This class servers some functionality to the other classes of Net_LDAP but most of
* the methods can be used separately as well.
* @author Benedikt Hallinger <beni@php.net>
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* @version CVS: $Id: Util.php,v 1.28 2007/10/24 06:29:32 beni Exp $
* @link http://pear.php.net/package/Net_LDAP/
* Private empty Constructur
// We do nothing here, since all methods can be called statically.
// In Net_LDAP <= 0.7, we needed a instance of Util, because
// it was possible to do utf8 encoding and decoding, but this
// has been moved to the LDAP class. The constructor remains only
// here to document the downward compatibility of creating a instance.
* Explodes the given DN into its elements
* {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence
* of Relative Distinguished Names (RDNs), which themselves
* are sets of Attributes. For each RDN a array is constructed where the RDN part is stored.
* For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
* <kbd>array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' )</kbd>
* [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of
* the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded
* and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to
* the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
* [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ];
* See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER.
* It also performs the following operations on the given DN:
* - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair
* and strings beginning with "#".
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
* OPTIONS is a list of name/value pairs, valid options are:
* casefold Controls case folding of attribute types names.
* Attribute values are not affected by this option.
* The default is to uppercase. Valid values are:
* lower Lowercase attribute types names.
* upper Uppercase attribute type names. This is the default.
* none Do not change attribute type names.
* reverse If TRUE, the RDN sequence is reversed.
* onlyvalues If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo')
* @param string $dn The DN that should be exploded
* @param array $options Options to use
* @return array Parts of the exploded DN
if (!isset ($options['onlyvalues'])) $options['onlyvalues'] = false;
if (!isset ($options['reverse'])) $options['reverse'] = false;
if (!isset ($options['casefold'])) $options['casefold'] = 'upper';
// Escaping of DN and stripping of "OID."
// construct subarrays for multivalued RDNs and unescape DN value
// also convert to output format and apply casefolding
foreach ($dn_array as $key => $value) {
foreach ($rdns as $subrdn_k => $subrdn_v) {
if ($options['casefold'] == 'upper') $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $subrdn_v);
if ($options['casefold'] == 'lower') $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $subrdn_v);
if ($options['onlyvalues']) {
preg_match('/(.+?)(?<!\\\\)=(.+)/', $subrdn_v, $matches);
$rdns[$subrdn_k] = $unescaped[0 ];
$rdns[$subrdn_k] = $unescaped[0 ];
if ($options['casefold'] == 'upper') $value = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $value);
if ($options['casefold'] == 'lower') $value = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $value);
if ($options['onlyvalues']) {
preg_match('/(.+?)(?<!\\\\)=(.+)/', $value, $matches);
$dn_array[$key] = $unescaped[0 ];
$dn_array[$key] = $unescaped[0 ];
if ($options['reverse']) {
* Escapes a DN value according to RFC 2253
* Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
* The characters ",", "+", """, "\", "<", ">", ";", "#", "=" with a special meaning in RFC 2252
* are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
* Finally all leading and trailing spaces are converted to sequences of \20.
* @param array $values An array containing the DN values that should be escaped
* @return array The array $values, but escaped
$values = array ($values);
foreach ($values as $key => $val) {
// Escaping of filter meta characters
// Convert all leading and trailing spaces to sequences of \20.
if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
for ($i = 0; $i < strlen($matches[1 ]); $i++ ) {
for ($i = 0; $i < strlen($matches[3 ]); $i++ ) {
if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
* Undoes the conversion done by escape_dn_value().
* Any escape sequence starting with a baskslash - hexpair or special character -
* will be transformed back to the corresponding character.
* @param array $values Array of DN Values
* @return array Same as $values, but unescaped
$values = array ($values);
foreach ($values as $key => $val) {
// strip slashes from special chars
// Translate hex code into ascii
* Returns the given DN in a canonical form
* Returns false if DN is not a valid Distinguished Name.
* DN can either be a string or an array
* as returned by ldap_explode_dn, which is useful when constructing a DN.
* The DN array may have be indexed (each array value is a OCL=VALUE pair)
* or associative (array key is OCL and value is VALUE).
* It performs the following operations on the given DN:
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
* - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair.
* - Converts all leading and trailing spaces in values to be \20.
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
* OPTIONS is a list of name/value pairs, valid options are:
* casefold Controls case folding of attribute type names.
* Attribute values are not affected by this option. The default is to uppercase.
* lower Lowercase attribute type names.
* upper Uppercase attribute type names. This is the default.
* none Do not change attribute type names.
* [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}.
* reverse If TRUE, the RDN sequence is reversed.
* separator Separator to use between RDNs. Defaults to comma (',').
* Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test,
* because an empty string evaluates to false. Use the "===" operator instead.
* @param array|string$dn The DN
* @param array $options Options to use
* @return false|stringThe canonical DN or FALSE
* @todo implement option mbcescape
function canonical_dn($dn, $options = array ('casefold' => 'upper', 'separator' => ','))
if ($dn === '') return $dn; // empty DN is valid!
if (!isset ($options['reverse'])) {
$options['reverse'] = false;
$options['reverse'] = true;
if (!isset ($options['casefold'])) $options['casefold'] = 'upper';
if (!isset ($options['separator'])) $options['separator'] = ',';
// It is not clear to me if the perl implementation splits by the user defined
// separator or if it just uses this separator to construct the new DN
$dn = preg_split('/(?<=[^\\\\])'. $options['separator']. '/', $dn);
// clear wrong splitting (possibly we have split too much)
$dn = Net_LDAP_Util::_correct_dn_splitting ($dn, $options['separator']);
// Is array, check, if the array is indexed or associative
foreach ($dn as $dn_key => $dn_part) {
// convert to indexed, if associative array detected
foreach ($dn as $dn_key => $dn_part) {
ksort($dn_part, SORT_STRING ); // we assume here, that the rdn parts are also associative
$newdn[] = $dn_part; // copy array as-is, so we can resolve it later
$newdn[] = $dn_key. '='. $dn_part;
// Escaping and casefolding
foreach ($dn as $pos => $dnval) {
// subarray detected, this means very surely, that we had
// a multivalued dn part, which must be resolved
foreach ($dnval as $subkey => $subval) {
$subval = $subkey. '='. $subval;
if (false === $subval_processed) return false;
$dnval_new .= $subval_processed. '+';
$dn[$pos] = substr($dnval_new, 0 , -1 ); // store RDN part, strip last plus
// try to split multivalued RDNS into array
// Multivalued RDN was detected!
// The RDN value is expected to be correctly split by split_rdn_multival().
// It's time to sort the RDN and build the DN!
sort($rdns, SORT_STRING ); // Sort RDN keys alphabetically
foreach ($rdns as $rdn) {
if (false === $subval_processed) return false;
$rdn_string .= $subval_processed. '+';
$dn[$pos] = substr($rdn_string, 0 , -1 ); // store RDN part, strip last plus
// split at first unescaped "="
$dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0 ], 2 );
$ocl = ltrim($dn_comp[0 ]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma)
// strip 'OID.', otherwise apply casefolding and escaping
if ($options['casefold'] == 'upper') $ocl = strtoupper($ocl);
if ($options['casefold'] == 'lower') $ocl = strtolower($ocl);
$dn[$pos] = $ocl. '='. $val;
return implode($options['separator'], $dn);
* Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
* Any control characters with an ACII code < 32 as well as the characters with special meaning in
* LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
* backslash followed by two hex digits representing the hexadecimal value of the character.
* @param array $values Array of values to escape
* @return array Array $values, but escaped
$values = array ($values);
foreach ($values as $key => $val) {
// Escaping of filter meta characters
if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
* Undoes the conversion done by {@link escape_filter_value()}.
* Converts any sequences of a backslash followed by two hex digits into the corresponding character.
* @param array $values Array of values to escape
* @return array Array $values, but unescaped
$values = array ($values);
foreach ($values as $key => $value) {
// Translate hex code into ascii
* Converts all ASCII chars < 32 to "\HEX"
* @param string $string String to convert
for ($i = 0; $i < strlen($string); $i++ ) {
$char = substr($string, $i, 1 );
if (strlen($hex) == 1 ) $hex = '0'. $hex;
* Converts all Hex expressions ("\HEX") to their original ASCII characters
* @param string $string String to convert
* @author beni@php.net, heavily based on work from DavidSmith@byu.net
$string = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $string);
* Split an multivalued RDN value into an Array
* A RDN can contain multiple values, spearated by a plus sign.
* This function returns each separate ocl=value pair of the RDN part.
* If no multivalued RDN is detected, a array containing only
* the original rdn part is returned.
* For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to:
* <kbd>array([0] => 'OU=Sales', [1] => 'CN=J. Smith')</kbd>
* The method trys to be smart if it encounters unescaped "+" characters, but may fail,
* so ensure escaped "+"es in attr names and attr values.
* [BUG] If you use string mode and have a multivalued RDN with unescaped plus characters
* and there is a unescaped plus sign at the end of an value followed by an
* attribute name containing an unescaped plus, then you will get wrong splitting:
* $rdn = 'OU=Sales+C+N=J. Smith';
* array('OU=Sales+C', 'N=J. Smith');
* The "C+" is treaten as value of the first pair instead as attr name of the second pair.
* To prevent this, escape correctly.
* @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar)
* @return array Array with the components of the multivalued RDN or Error
* Splits a attribute=value syntax into an array
* The split will occur at the first unescaped '=' character.
* @param string $attr Attribute and Value Syntax
* @return array Indexed array: 0=attribute name, 1=attribute value
* Corrects splitting of dn parts
* @param array $dn Raw DN array
* @param array $separator Separator that was used when splitting
* @return array Corrected array
function _correct_dn_splitting ($dn = array (), $separator = ',')
foreach ($dn as $key => $dn_value) {
$dn_value = $dn[$key]; // refresh value (foreach caches!)
// if the dn_value is not in attr=value format, then we had an
// unescaped separator character inside the attr name or the value.
// We assume, that it was the attribute value.
// [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class
// must remain independent from the other classes or connections.
$dn[$key-1 ] = $dn[$key-1 ]. $separator. $dn_value; // append to previous attr value
$dn[$key+1 ] = $dn_value. $separator. $dn[$key+1 ]; // first element: prepend to next attr name
Documentation generated on Mon, 11 Mar 2019 15:22:34 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.
|