Updated LDAP2 extlib to latest version.

This commit is contained in:
Mikael Nordfeldth 2014-09-25 08:34:55 +02:00
parent a6545d09b8
commit ee41bc560c
7 changed files with 513 additions and 162 deletions

View File

@ -13,7 +13,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: LDAP2.php 286788 2009-08-04 06:05:49Z beni $
* @version SVN: $Id: LDAP2.php 332308 2013-12-09 09:15:47Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
@ -39,7 +39,7 @@ define('NET_LDAP2_ERROR', 1000);
/**
* Net_LDAP2 Version
*/
define('NET_LDAP2_VERSION', '2.0.7');
define('NET_LDAP2_VERSION', '2.1.0');
/**
* Net_LDAP2 - manipulate LDAP servers the right way!
@ -612,30 +612,47 @@ class Net_LDAP2 extends PEAR
*/
public function startTLS()
{
// Test to see if the server supports TLS first.
// This is done via testing the extensions offered by the server.
// The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
/* Test to see if the server supports TLS first.
This is done via testing the extensions offered by the server.
The OID 1.3.6.1.4.1.1466.20037 tells us, if TLS is supported.
Note, that not all servers allow to feth either the rootDSE or
attributes over an unencrypted channel, so we must ignore errors. */
$rootDSE = $this->rootDse();
if (self::isError($rootDSE)) {
return $this->raiseError("Unable to fetch rootDSE entry ".
"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
}
$supported_extensions = $rootDSE->getValue('supportedExtension');
if (self::isError($supported_extensions)) {
return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
}
if (in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
if (false === @ldap_start_tls($this->_link)) {
return $this->raiseError("TLS not started: " .
@ldap_error($this->_link),
@ldap_errno($this->_link));
}
return true;
/* IGNORE this error, because server may refuse fetching the
RootDSE over an unencrypted connection. */
//return $this->raiseError("Unable to fetch rootDSE entry ".
//"to see if TLS is supoported: ".$rootDSE->getMessage(), $rootDSE->getCode());
} else {
return $this->raiseError("Server reports that it does not support TLS");
/* Fetch suceeded, see, if the server supports TLS. Again, we
ignore errors, because the server may refuse to return
attributes over unencryted connections. */
$supported_extensions = $rootDSE->getValue('supportedExtension');
if (self::isError($supported_extensions)) {
/* IGNORE error, because server may refuse attribute
returning over an unencrypted connection. */
//return $this->raiseError("Unable to fetch rootDSE attribute 'supportedExtension' ".
//"to see if TLS is supoported: ".$supported_extensions->getMessage(), $supported_extensions->getCode());
} else {
// fetch succeedet, lets see if the server supports it.
// if not, then drop an error. If supported, then do nothing,
// because then we try to issue TLS afterwards.
if (!in_array('1.3.6.1.4.1.1466.20037', $supported_extensions)) {
return $this->raiseError("Server reports that it does not support TLS.");
}
}
}
// Try to establish TLS.
if (false === @ldap_start_tls($this->_link)) {
// Starting TLS failed. This may be an error, or because
// the server does not support it but did not enable us to
// detect that above.
return $this->raiseError("TLS could not be started: " .
@ldap_error($this->_link),
@ldap_errno($this->_link));
} else {
return true; // TLS is started now.
}
}
@ -728,7 +745,7 @@ class Net_LDAP2 extends PEAR
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = @ldap_errno($link);
$error_name = $this->errorMessage($error_code);
$error_name = Net_LDAP2::errorMessage($error_code);
if (($error_name === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
@ -802,9 +819,9 @@ class Net_LDAP2 extends PEAR
// We have a failure. What type?
// We may be able to reconnect and try again.
$error_code = @ldap_errno($link);
$error_name = $this->errorMessage($error_code);
$error_name = Net_LDAP2::errorMessage($error_code);
if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
// operation. We should try again, possibly with a
@ -898,9 +915,9 @@ class Net_LDAP2 extends PEAR
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = $msg->getCode();
$error_name = $this->errorMessage($error_code);
$error_name = Net_LDAP2::errorMessage($error_code);
if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
@ -937,9 +954,9 @@ class Net_LDAP2 extends PEAR
// We have a failure. What type? We may be able to reconnect
// and try again.
$error_code = $msg->getCode();
$error_name = $this->errorMessage($error_code);
$error_name = Net_LDAP2::errorMessage($error_code);
if (($this->errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
if ((Net_LDAP2::errorMessage($error_code) === 'LDAP_OPERATIONS_ERROR') &&
($this->_config['auto_reconnect'])) {
// The server has become disconnected before trying the
@ -1078,14 +1095,14 @@ class Net_LDAP2 extends PEAR
return $obj = new Net_LDAP2_Search ($search, $this, $attributes);
} elseif ($err == 87) {
// bad search filter
return $this->raiseError($this->errorMessage($err) . "($filter)", $err);
return $this->raiseError(Net_LDAP2::errorMessage($err) . "($filter)", $err);
} elseif (($err == 1) && ($this->_config['auto_reconnect'])) {
// Errorcode 1 = LDAP_OPERATIONS_ERROR but we can try a reconnect.
$this->_link = false;
$this->performReconnect();
} else {
$msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope";
return $this->raiseError($this->errorMessage($err) . $msg, $err);
return $this->raiseError(Net_LDAP2::errorMessage($err) . $msg, $err);
}
} else {
return $obj = new Net_LDAP2_Search($search, $this, $attributes);
@ -1114,7 +1131,7 @@ class Net_LDAP2 extends PEAR
$msg = @ldap_err2str($err);
} else {
$err = NET_LDAP2_ERROR;
$msg = $this->errorMessage($err);
$msg = Net_LDAP2::errorMessage($err);
}
return $this->raiseError($msg, $err);
}
@ -1146,7 +1163,7 @@ class Net_LDAP2 extends PEAR
$msg = @ldap_err2str($err);
} else {
$err = NET_LDAP2_ERROR;
$msg = $this->errorMessage($err);
$msg = Net_LDAP2::errorMessage($err);
}
return $this->raiseError($msg, $err);
}
@ -1239,30 +1256,21 @@ class Net_LDAP2 extends PEAR
return PEAR::raiseError('Parameter $dn is not a string nor an entry object!');
}
// make dn relative to parent
$base = Net_LDAP2_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false));
if (self::isError($base)) {
return $base;
}
$entry_rdn = array_shift($base);
if (is_array($entry_rdn)) {
// maybe the dn consist of a multivalued RDN, we must build the dn in this case
// because the $entry_rdn is an array!
$filter_dn = Net_LDAP2_Util::canonical_dn($entry_rdn);
}
$base = Net_LDAP2_Util::canonical_dn($base);
// search LDAP for that DN by performing a baselevel search for any
// object. We can only find the DN in question this way, or nothing.
$s_opts = array(
'scope' => 'base',
'sizelimit' => 1,
'attributes' => '1.1' // select no attrs
);
$search = $this->search($dn, '(objectClass=*)', $s_opts);
$result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1);
if (@ldap_count_entries($this->_link, $result)) {
return true;
if (self::isError($search)) {
return $search;
}
if (ldap_errno($this->_link) == 32) {
return false;
}
if (ldap_errno($this->_link) != 0) {
return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link));
}
return false;
// retun wehter the DN exists; that is, we found an entry
return ($search->count() == 0)? false : true;
}
@ -1400,7 +1408,7 @@ class Net_LDAP2 extends PEAR
*
* @return string The errorstring for the error.
*/
public function errorMessage($errorcode)
public static function errorMessage($errorcode)
{
$errorMessages = array(
0x00 => "LDAP_SUCCESS",
@ -1629,7 +1637,7 @@ class Net_LDAP2 extends PEAR
}
/**
* Encodes given attributes to UTF8 if needed by schema
* Encodes given attributes from ISO-8859-1 to UTF-8 if needed by schema
*
* This function takes attributes in an array and then checks against the schema if they need
* UTF8 encoding. If that is so, they will be encoded. An encoded array will be returned and
@ -1650,7 +1658,7 @@ class Net_LDAP2 extends PEAR
}
/**
* Decodes the given attribute values if needed by schema
* Decodes the given attribute values from UTF-8 to ISO-8859-1 if needed by schema
*
* $attributes is expected to be an array with keys describing
* the attribute names and the values as the value of this attribute:
@ -1668,7 +1676,7 @@ class Net_LDAP2 extends PEAR
}
/**
* Encodes or decodes attribute values if needed
* Encodes or decodes UTF-8/ISO-8859-1 attribute values if needed by schema
*
* @param array $attributes Array of attributes
* @param array $function Function to apply to attribute values
@ -1701,7 +1709,10 @@ class Net_LDAP2 extends PEAR
continue;
}
if (false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
// Encoding is needed if this is a DIR_STR. We assume also
// needed encoding in case the schema contains no syntax
// information (he does not need to, see rfc2252, 4.2)
if (!array_key_exists('syntax', $attr) || false !== strpos($attr['syntax'], '1.3.6.1.4.1.1466.115.121.1.15')) {
$encode = true;
} else {
$encode = false;

View File

@ -12,7 +12,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Jan Wagner, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Entry.php 286787 2009-08-04 06:03:12Z beni $
* @version SVN: $Id: Entry.php 332301 2013-12-09 08:17:14Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
@ -20,7 +20,7 @@
* Includes
*/
require_once 'PEAR.php';
require_once 'Util.php';
require_once 'Net/LDAP2/Util.php';
/**
* Object representation of a directory entry
@ -309,7 +309,7 @@ class Net_LDAP2_Entry extends PEAR
public function dn($dn = null)
{
if (false == is_null($dn)) {
if (is_null($this->_dn)) {
if (is_null($this->_dn) ) {
$this->_dn = $dn;
} else {
$this->_newdn = $dn;
@ -419,6 +419,7 @@ class Net_LDAP2_Entry extends PEAR
* The returned hash has the form
* <code>array('attributename' => 'single value',
* 'attributename' => array('value1', value2', value3'))</code>
* Only attributes present at the entry will be returned.
*
* @access public
* @return array Hash of all attributes with their values
@ -437,31 +438,59 @@ class Net_LDAP2_Entry extends PEAR
*
* The first parameter is the name of the attribute
* The second parameter influences the way the value is returned:
* 'single': only the first value is returned as string
* 'all': all values including the value count are returned in an
* array
* 'single': only the first value is returned as string
* 'all': all values are returned in an array
* 'default': in all other cases an attribute value with a single value is
* returned as string, if it has multiple values it is returned
* as an array (without value count)
* as an array
*
* @param string $attr Attribute name
* @param string $option Option
* If the attribute is not set at this entry (no value or not defined in
* schema), "false" is returned when $option is 'single', an empty string if
* 'default', and an empty array when 'all'.
*
* You may use Net_LDAP2_Schema->checkAttribute() to see if the attribute
* is defined for the objectClasses of this entry.
*
* @param string $attr Attribute name
* @param string $option Option
*
* @access public
* @return string|array|PEAR_Error string, array or PEAR_Error
* @return string|array
*/
public function getValue($attr, $option = null)
{
$attr = $this->getAttrName($attr);
if (false == array_key_exists($attr, $this->_attributes)) {
return PEAR::raiseError("Unknown attribute ($attr) requested");
}
// return depending on set $options
if (!array_key_exists($attr, $this->_attributes)) {
// attribute not set
switch ($option) {
case 'single':
$value = false;
break;
case 'all':
$value = array();
break;
default:
$value = '';
}
$value = $this->_attributes[$attr];
if ($option == "single" || (count($value) == 1 && $option != 'all')) {
$value = array_shift($value);
} else {
// attribute present
switch ($option) {
case 'single':
$value = $this->_attributes[$attr][0];
break;
case 'all':
$value = $this->_attributes[$attr];
break;
default:
$value = $this->_attributes[$attr];
if (count($value) == 1) {
$value = array_shift($value);
}
}
}
return $value;
@ -529,6 +558,9 @@ class Net_LDAP2_Entry extends PEAR
if (false == is_array($attr)) {
return PEAR::raiseError("Parameter must be an array");
}
if ($this->isNew()) {
$this->setAttributes($attr);
}
foreach ($attr as $k => $v) {
$k = $this->getAttrName($k);
if (false == is_array($v)) {
@ -547,11 +579,12 @@ class Net_LDAP2_Entry extends PEAR
$this->_attributes[$k] = $v;
}
// save changes for update()
if (empty($this->_changes["add"][$k])) {
if (!isset($this->_changes["add"][$k])) {
$this->_changes["add"][$k] = array();
}
$this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v));
}
$return = true;
return $return;
}
@ -760,6 +793,14 @@ class Net_LDAP2_Entry extends PEAR
$this->_changes['replace'] = array();
$this->_original = $this->_attributes;
// In case the "new" entry was moved after creation, we must
// adjust the internal DNs as the entry was already created
// with the most current DN.
if (false == is_null($this->_newdn)) {
$this->_dn = $this->_newdn;
$this->_newdn = null;
}
$return = true;
return $return;
}
@ -785,7 +826,8 @@ class Net_LDAP2_Entry extends PEAR
$parent = Net_LDAP2_Util::canonical_dn($parent);
// rename/move
if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) {
if (false == @ldap_rename($link, $this->_dn, $child, $parent, false)) {
return PEAR::raiseError("Entry not renamed: " .
@ldap_error($link), @ldap_errno($link));
}
@ -795,51 +837,55 @@ class Net_LDAP2_Entry extends PEAR
}
/*
* Carry out modifications to the entry
* Retrieve a entry that has all attributes we need so that the list of changes to build is created accurately
*/
$fullEntry = $ldap->getEntry( $this->dn() );
if ( Net_LDAP2::isError($fullEntry) ) {
return PEAR::raiseError("Could not retrieve a full set of attributes to reconcile changes with");
}
$modifications = array();
// ADD
foreach ($this->_changes["add"] as $attr => $value) {
// if attribute exists, add new values
if ($this->exists($attr)) {
if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not add new values to attribute $attr: " .
@ldap_error($link), @ldap_errno($link));
}
} else {
// new attribute
if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not add new attribute $attr: " .
@ldap_error($link), @ldap_errno($link));
}
}
// all went well here, I guess
unset($this->_changes["add"][$attr]);
// if attribute exists, we need to combine old and new values
if ($fullEntry->exists($attr)) {
$currentValue = $fullEntry->getValue($attr, "all");
$value = array_merge( $currentValue, $value );
}
$modifications[$attr] = $value;
}
// DELETE
foreach ($this->_changes["delete"] as $attr => $value) {
// In LDAPv3 you need to specify the old values for deleting
if (is_null($value) && $ldap->getLDAPVersion() === 3) {
$value = $this->_original[$attr];
$value = $fullEntry->getValue($attr);
}
if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not delete attribute $attr: " .
@ldap_error($link), @ldap_errno($link));
if (!is_array($value)) {
$value = array($value);
}
unset($this->_changes["delete"][$attr]);
// Find out what is missing from $value and exclude it
$currentValue = isset($modifications[$attr]) ? $modifications[$attr] : $fullEntry->getValue($attr, "all");
$modifications[$attr] = array_values( array_diff( $currentValue, $value ) );
}
// REPLACE
foreach ($this->_changes["replace"] as $attr => $value) {
if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) {
return PEAR::raiseError("Could not replace attribute $attr values: " .
@ldap_error($link), @ldap_errno($link));
}
unset($this->_changes["replace"][$attr]);
$modifications[$attr] = $value;
}
// all went well, so _original (server) becomes _attributes (local copy)
$this->_original = $this->_attributes;
// COMMIT
if (false === @ldap_modify($link, $this->dn(), $modifications)) {
return PEAR::raiseError("Could not modify the entry: " . @ldap_error($link), @ldap_errno($link));
}
// all went well, so _original (server) becomes _attributes (local copy), reset _changes too...
$this->_changes['add'] = array();
$this->_changes['delete'] = array();
$this->_changes['replace'] = array();
$this->_original = $this->_attributes;
$return = true;
return $return;
@ -964,11 +1010,6 @@ class Net_LDAP2_Entry extends PEAR
// fetch attribute values
$attr = $this->getValue($attr_name, 'all');
if (Net_LDAP2::isError($attr)) {
return $attr;
} else {
unset($attr['count']);
}
// perform preg_match() on all values
$match = false;

View File

@ -10,7 +10,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Filter.php 289978 2009-10-27 09:56:41Z beni $
* @version SVN: $Id: Filter.php 332305 2013-12-09 08:51:41Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
@ -18,7 +18,8 @@
* Includes
*/
require_once 'PEAR.php';
require_once 'Util.php';
require_once 'Net/LDAP2/Util.php';
require_once 'Net/LDAP2/Entry.php';
/**
* Object representation of a part of a LDAP filter.
@ -134,6 +135,9 @@ class Net_LDAP2_Filter extends PEAR
* - lessOrEqual: The attributes value is less or equal than $value
* - approx: One of the attributes values is similar to $value
*
* Negation ("not") can be done by prepending the above operators with the
* "not" or "!" keyword, see example below.
*
* If $escape is set to true (default) then $value will be escaped
* properly. If it is set to false then $value will be treaten as raw filter value string.
* You should escape yourself using {@link Net_LDAP2_Util::escape_filter_value()}!
@ -141,10 +145,13 @@ class Net_LDAP2_Filter extends PEAR
* Examples:
* <code>
* // This will find entries that contain an attribute "sn" that ends with "foobar":
* $filter = new Net_LDAP2_Filter('sn', 'ends', 'foobar');
* $filter = Net_LDAP2_Filter::create('sn', 'ends', 'foobar');
*
* // This will find entries that contain an attribute "sn" that has any value set:
* $filter = new Net_LDAP2_Filter('sn', 'any');
* $filter = Net_LDAP2_Filter::create('sn', 'any');
*
* // This will build a negated equals filter:
* $filter = Net_LDAP2_Filter::create('sn', 'not equals', 'foobar');
* </code>
*
* @param string $attr_name Name of the attribute the filter should apply to
@ -161,8 +168,22 @@ class Net_LDAP2_Filter extends PEAR
$array = Net_LDAP2_Util::escape_filter_value(array($value));
$value = $array[0];
}
switch (strtolower($match)) {
$match = strtolower($match);
// detect negation
$neg_matches = array();
$negate_filter = false;
if (preg_match('/^(?:not|!)[\s_-](.+)/', $match, $neg_matches)) {
$negate_filter = true;
$match = $neg_matches[1];
}
// build basic filter
switch ($match) {
case 'equals':
case '=':
case '==':
$leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')';
break;
case 'begins':
@ -175,9 +196,11 @@ class Net_LDAP2_Filter extends PEAR
$leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)';
break;
case 'greater':
case '>':
$leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')';
break;
case 'less':
case '<':
$leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')';
break;
case 'greaterorequal':
@ -199,6 +222,12 @@ class Net_LDAP2_Filter extends PEAR
default:
return PEAR::raiseError('Net_LDAP2_Filter create error: matching rule "' . $match . '" not known!');
}
// negate if requested
if ($negate_filter) {
$leaf_filter = Net_LDAP2_Filter::combine('!', $leaf_filter);
}
return $leaf_filter;
}
@ -207,10 +236,10 @@ class Net_LDAP2_Filter extends PEAR
*
* This static method combines two or more filter objects and returns one single
* filter object that contains all the others.
* Call this method statically: $filter = Net_LDAP2_Filter('or', array($filter1, $filter2))
* Call this method statically: $filter = Net_LDAP2_Filter::combine('or', array($filter1, $filter2))
* If the array contains filter strings instead of filter objects, we will try to parse them.
*
* @param string $log_op The locicall operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
* @param string $log_op The locical operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!"
* @param array|Net_LDAP2_Filter $filters array with Net_LDAP2_Filter objects
*
* @return Net_LDAP2_Filter|Net_LDAP2_Error
@ -241,8 +270,13 @@ class Net_LDAP2_Filter extends PEAR
$filters = array($filter_o);
}
} elseif (is_array($filters)) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
return $err;
if (count($filters) != 1) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is an array!');
return $err;
} elseif (!($filters[0] instanceof Net_LDAP2_Filter)) {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
}
} else {
$err = PEAR::raiseError('Net_LDAP2_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP2_Filter nor a filter string!');
return $err;
@ -294,6 +328,17 @@ class Net_LDAP2_Filter extends PEAR
public static function parse($FILTER)
{
if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) {
// Check for right bracket syntax: count of unescaped opening
// brackets must match count of unescaped closing brackets.
// At this stage we may have:
// 1. one filter component with already removed outer brackets
// 2. one or more subfilter components
$c_openbracks = preg_match_all('/(?<!\\\\)\(/' , $matches[1], $notrelevant);
$c_closebracks = preg_match_all('/(?<!\\\\)\)/' , $matches[1], $notrelevant);
if ($c_openbracks != $c_closebracks) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - opening brackets do not match close brackets!");
}
if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) {
// Subfilter processing: pass subfilters to parse() and combine
// the objects using the logical operator detected
@ -378,7 +423,7 @@ class Net_LDAP2_Filter extends PEAR
if (stristr($matches[1], ')(')) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!");
} else {
$filter_parts = preg_split('/(?<!\\\\)(=|=~|>|<|>=|<=)/', $matches[1], 2, PREG_SPLIT_DELIM_CAPTURE);
$filter_parts = Net_LDAP2_Util::split_attribute_string($matches[1], true, true);
if (count($filter_parts) != 3) {
return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used");
} else {
@ -510,5 +555,121 @@ class Net_LDAP2_Filter extends PEAR
return true; // Leaf!
}
}
/**
* Filter entries using this filter or see if a filter matches
*
* @todo Currently slow and naive implementation with preg_match, could be optimized (esp. begins, ends filters etc)
* @todo Currently only "="-based matches (equals, begins, ends, contains, any) implemented; Implement all the stuff!
* @todo Implement expert code with schema checks in case $entry is connected to a directory
* @param array|Net_LDAP2_Entry The entry (or array with entries) to check
* @param array If given, the array will be appended with entries who matched the filter. Return value is true if any entry matched.
* @return int|Net_LDAP2_Error Returns the number of matched entries or error
*/
function matches(&$entries, &$results=array()) {
$numOfMatches = 0;
if (!is_array($entries)) {
$all_entries = array(&$entries);
} else {
$all_entries = &$entries;
}
foreach ($all_entries as $entry) {
// look at the current entry and see if filter matches
$entry_matched = false;
// if this is not a single component, do calculate all subfilters,
// then assert the partial results with the given combination modifier
if (!$this->isLeaf()) {
// get partial results from subfilters
$partial_results = array();
foreach ($this->_subfilters as $filter) {
$partial_results[] = $filter->matches($entry);
}
// evaluate partial results using this filters combination rule
switch ($this->_match) {
case '!':
// result is the neagtive result of the assertion
$entry_matched = !$partial_results[0];
break;
case '&':
// all partial results have to be boolean-true
$entry_matched = !in_array(false, $partial_results);
break;
case '|':
// at least one partial result has to be true
$entry_matched = in_array(true, $partial_results);
break;
}
} else {
// Leaf filter: assert given entry
// [TODO]: Could be optimized to avoid preg_match especially with "ends", "begins" etc
// Translate the LDAP-match to some preg_match expression and evaluate it
list($attribute, $match, $assertValue) = $this->getComponents();
switch ($match) {
case '=':
$regexp = '/^'.str_replace('*', '.*', $assertValue).'$/i'; // not case sensitive unless specified by schema
$entry_matched = $entry->pregMatch($regexp, $attribute);
break;
// -------------------------------------
// [TODO]: implement <, >, <=, >= and =~
// -------------------------------------
default:
$err = PEAR::raiseError("Net_LDAP2_Filter match error: unsupported match rule '$match'!");
return $err;
}
}
// process filter matching result
if ($entry_matched) {
$numOfMatches++;
$results[] = $entry;
}
}
return $numOfMatches;
}
/**
* Retrieve this leaf-filters attribute, match and value component.
*
* For leaf filters, this returns array(attr, match, value).
* Match is be the logical operator, not the text representation,
* eg "=" instead of "equals". Note that some operators are really
* a combination of operator+value with wildcard, like
* "begins": That will return "=" with the value "value*"!
*
* For non-leaf filters this will drop an error.
*
* @todo $this->_match is not always available and thus not usable here; it would be great if it would set in the factory methods and constructor.
* @return array|Net_LDAP2_Error
*/
function getComponents() {
if ($this->isLeaf()) {
$raw_filter = preg_replace('/^\(|\)$/', '', $this->_filter);
$parts = Net_LDAP2_Util::split_attribute_string($raw_filter, true, true);
if (count($parts) != 3) {
return PEAR::raiseError("Net_LDAP2_Filter getComponents() error: invalid filter syntax - unknown matching rule used");
} else {
return $parts;
}
} else {
return PEAR::raiseError('Net_LDAP2_Filter getComponents() call is invalid for non-leaf filters!');
}
}
}
?>

View File

@ -10,7 +10,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: LDIF.php 286718 2009-08-03 07:30:49Z beni $
* @version SVN: $Id: LDIF.php 324918 2012-04-06 12:31:04Z clockwerx $
* @link http://pear.php.net/package/Net_LDAP2/
*/
@ -340,6 +340,7 @@ class Net_LDAP2_LDIF extends PEAR
+ count($entry_attrs_changes['replace'])
+ count($entry_attrs_changes['delete']);
$is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved());
// write version if not done yet
@ -556,10 +557,10 @@ class Net_LDAP2_LDIF extends PEAR
$attributes = array();
$dn = false;
foreach ($lines as $line) {
if (preg_match('/^(\w+)(:|::|:<)\s(.+)$/', $line, $matches)) {
$attr =& $matches[1];
$delim =& $matches[2];
$data =& $matches[3];
if (preg_match('/^(\w+(;binary)?)(:|::|:<)\s(.+)$/', $line, $matches)) {
$attr =& $matches[1] . $matches[2];
$delim =& $matches[3];
$data =& $matches[4];
if ($delim == ':') {
// normal data
@ -682,20 +683,22 @@ class Net_LDAP2_LDIF extends PEAR
if (preg_match('/^version:\s(.+)$/', $data, $match)) {
// version statement, set version
$this->version($match[1]);
} elseif (preg_match('/^\w+::?\s.+$/', $data)) {
} elseif (preg_match('/^\w+(;binary)?::?\s.+$/', $data)) {
// normal attribute: add line
$commentmode = false;
$this->_lines_next[] = trim($data);
$datalines_read++;
} elseif (preg_match('/^\s(.+)$/', $data, $matches)) {
// wrapped data: unwrap if not in comment mode
// note that the \s above is some more liberal than
// the RFC requests as it also matches tabs etc.
if (!$commentmode) {
if ($datalines_read == 0) {
// first line of entry: wrapped data is illegal
$this->dropError('Net_LDAP2_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line);
} else {
$last = array_pop($this->_lines_next);
$last = $last.trim($matches[1]);
$last = $last.$matches[1];
$this->_lines_next[] = $last;
$datalines_read++;
}

View File

@ -11,7 +11,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Jan Wagner, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Schema.php 286718 2009-08-03 07:30:49Z beni $
* @version SVN: $Id: Schema.php 296515 2010-03-22 14:46:41Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
* @todo see the comment at the end of the file
*/
@ -168,12 +168,16 @@ class Net_LDAP2_Schema extends PEAR
array('attributes' => array_values($schema_o->types),
'scope' => 'base'));
if (Net_LDAP2::isError($result)) {
return $result;
return PEAR::raiseError('Could not fetch Subschema entry: '.$result->getMessage());
}
$entry = $result->shiftEntry();
if (!$entry instanceof Net_LDAP2_Entry) {
return PEAR::raiseError('Could not fetch Subschema entry');
if ($entry instanceof Net_LDAP2_Error) {
return PEAR::raiseError('Could not fetch Subschema entry: '.$entry->getMessage());
} else {
return PEAR::raiseError('Could not fetch Subschema entry (search returned '.$result->count().' entries. Check parameter \'basedn\')');
}
}
$schema_o->parse($entry);
@ -183,7 +187,7 @@ class Net_LDAP2_Schema extends PEAR
/**
* Return a hash of entries for the given type
*
* Returns a hash of entry for th givene type. Types may be:
* Returns a hash of entry for the givene type. Types may be:
* objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules,
* matchingruleuses, nameforms, syntaxes
*
@ -508,9 +512,111 @@ class Net_LDAP2_Schema extends PEAR
return $return;
}
// [TODO] add method that allows us to see to which objectclasses a certain attribute belongs to
// it should return the result structured, e.g. sorted in "may" and "must". Optionally it should
// be able to return it just "flat", e.g. array_merge()d.
// We could use get_all() to achieve this easily, i think
/**
* See if an schema element exists
*
* @param string $type Type of name, see get()
* @param string $name Name or OID
*
* @return boolean
*/
public function exists($type, $name)
{
$entry = $this->get($type, $name);
if ($entry instanceof Net_LDAP2_ERROR) {
return false;
} else {
return true;
}
}
/**
* See if an attribute is defined in the schema
*
* @param string $attribute Name or OID of the attribute
* @return boolean
*/
public function attributeExists($attribute)
{
return $this->exists('attribute', $attribute);
}
/**
* See if an objectClass is defined in the schema
*
* @param string $ocl Name or OID of the objectClass
* @return boolean
*/
public function objectClassExists($ocl)
{
return $this->exists('objectclass', $ocl);
}
/**
* See to which ObjectClasses an attribute is assigned
*
* The objectclasses are sorted into the keys 'may' and 'must'.
*
* @param string $attribute Name or OID of the attribute
*
* @return array|Net_LDAP2_Error Associative array with OCL names or Error
*/
public function getAssignedOCLs($attribute)
{
$may = array();
$must = array();
// Test if the attribute type is defined in the schema,
// if so, retrieve real name for lookups
$attr_entry = $this->get('attribute', $attribute);
if ($attr_entry instanceof Net_LDAP2_ERROR) {
return PEAR::raiseError("Attribute $attribute not defined in schema: ".$attr_entry->getMessage());
} else {
$attribute = $attr_entry['name'];
}
// We need to get all defined OCLs for this.
$ocls = $this->getAll('objectclasses');
foreach ($ocls as $ocl => $ocl_data) {
// Fetch the may and must attrs and see if our searched attr is contained.
// If so, record it in the corresponding array.
$ocl_may_attrs = $this->may($ocl);
$ocl_must_attrs = $this->must($ocl);
if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
array_push($may, $ocl_data['name']);
}
if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
array_push($must, $ocl_data['name']);
}
}
return array('may' => $may, 'must' => $must);
}
/**
* See if an attribute is available in a set of objectClasses
*
* @param string $attribute Attribute name or OID
* @param array $ocls Names of OCLs to check for
*
* @return boolean TRUE, if the attribute is defined for at least one of the OCLs
*/
public function checkAttribute($attribute, $ocls)
{
foreach ($ocls as $ocl) {
$ocl_entry = $this->get('objectclass', $ocl);
$ocl_may_attrs = $this->may($ocl);
$ocl_must_attrs = $this->must($ocl);
if (is_array($ocl_may_attrs) && in_array($attribute, $ocl_may_attrs)) {
return true;
}
if (is_array($ocl_must_attrs) && in_array($attribute, $ocl_must_attrs)) {
return true;
}
}
return false; // no ocl for the ocls found.
}
}
?>
?>

View File

@ -11,7 +11,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Tarjej Huse, Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Search.php 286718 2009-08-03 07:30:49Z beni $
* @version SVN: $Id: Search.php 328961 2013-01-03 09:04:30Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
@ -106,13 +106,22 @@ class Net_LDAP2_Search extends PEAR implements Iterator
/**
* Cache variable for storing entries fetched internally
*
* This currently is only used by {@link pop_entry()}
* This currently is not used by all functions and need consolidation.
*
* @access protected
* @var array
*/
protected $_entry_cache = false;
/**
* Cache variable for count()
*
* @see count()
* @access protected
* @var int
*/
protected $_count_cache = null;
/**
* Constructor
*
@ -143,7 +152,7 @@ class Net_LDAP2_Search extends PEAR implements Iterator
}
/**
* Returns an array of entry objects
* Returns an array of entry objects.
*
* @return array Array of entry objects.
*/
@ -151,15 +160,19 @@ class Net_LDAP2_Search extends PEAR implements Iterator
{
$entries = array();
while ($entry = $this->shiftEntry()) {
$entries[] = $entry;
if (false === $this->_entry_cache) {
// cache is empty: fetch from LDAP
while ($entry = $this->shiftEntry()) {
$entries[] = $entry;
}
$this->_entry_cache = $entries; // store result in cache
}
return $entries;
return $this->_entry_cache;
}
/**
* Get the next entry in the searchresult.
* Get the next entry in the searchresult from LDAP server.
*
* This will return a valid Net_LDAP2_Entry object or false, so
* you can use this method to easily iterate over the entries inside
@ -169,22 +182,20 @@ class Net_LDAP2_Search extends PEAR implements Iterator
*/
public function &shiftEntry()
{
if ($this->count() == 0 ) {
$false = false;
return $false;
}
if (is_null($this->_entry)) {
$this->_entry = @ldap_first_entry($this->_link, $this->_search);
if(!$this->_entry = @ldap_first_entry($this->_link, $this->_search)) {
$false = false;
return $false;
}
$entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
if ($entry instanceof Net_LDAP2_Error) $entry = false;
if ($entry instanceof PEAR_Error) $entry = false;
} else {
if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) {
$false = false;
return $false;
}
$entry = Net_LDAP2_Entry::createConnected($this->_ldap, $this->_entry);
if ($entry instanceof Net_LDAP2_Error) $entry = false;
if ($entry instanceof PEAR_Error) $entry = false;
}
return $entry;
}
@ -461,7 +472,13 @@ class Net_LDAP2_Search extends PEAR implements Iterator
if (!$this->_search) {
return 0;
}
return @ldap_count_entries($this->_link, $this->_search);
// ldap_count_entries is slow (see pear bug #18752) with large results,
// so we cache the result internally.
if ($this->_count_cache === null) {
$this->_count_cache = @ldap_count_entries($this->_link, $this->_search);
}
return $this->_count_cache;
}
/**

View File

@ -10,7 +10,7 @@
* @author Benedikt Hallinger <beni@php.net>
* @copyright 2009 Benedikt Hallinger
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
* @version SVN: $Id: Util.php 286718 2009-08-03 07:30:49Z beni $
* @version SVN: $Id: Util.php 332278 2013-12-05 11:01:15Z beni $
* @link http://pear.php.net/package/Net_LDAP2/
*/
@ -525,17 +525,29 @@ class Net_LDAP2_Util extends PEAR
}
/**
* Splits a attribute=value syntax into an array
* Splits an attribute=value syntax into an array
*
* The split will occur at the first unescaped '=' character.
* If escaped delimeters are used, they are returned escaped as well.
* The split will occur at the first unescaped delimeter character.
* In case an invalid delimeter is given, no split will be performed and an
* one element array gets returned.
* Optional also filter-assertion delimeters can be considered (>, <, >=, <=, ~=).
*
* @param string $attr Attribute and Value Syntax
* @param string $attr Attribute and Value Syntax ("foo=bar")
* @param boolean $extended If set to true, also filter-assertion delimeter will be matched
* @param boolean $withDelim If set to true, the return array contains the delimeter at index 1, putting the value to index 2
*
* @return array Indexed array: 0=attribute name, 1=attribute value
* @return array Indexed array: 0=attribute name, 1=attribute value OR ($withDelim=true): 0=attr, 1=delimeter, 2=value
*/
public static function split_attribute_string($attr)
public static function split_attribute_string($attr, $extended=false, $withDelim=false)
{
return preg_split('/(?<!\\\\)=/', $attr, 2);
if ($withDelim) $withDelim = PREG_SPLIT_DELIM_CAPTURE;
if (!$extended) {
return preg_split('/(?<!\\\\)(=)/', $attr, 2, $withDelim);
} else {
return preg_split('/(?<!\\\\)(>=|<=|>|<|~=|=)/', $attr, 2, $withDelim);
}
}
/**