]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Updated LDAP2 extlib to latest version.
authorMikael Nordfeldth <mmn@hethane.se>
Thu, 25 Sep 2014 06:34:55 +0000 (08:34 +0200)
committerMikael Nordfeldth <mmn@hethane.se>
Thu, 25 Sep 2014 06:34:55 +0000 (08:34 +0200)
plugins/LdapCommon/extlib/Net/LDAP2.php
plugins/LdapCommon/extlib/Net/LDAP2/Entry.php
plugins/LdapCommon/extlib/Net/LDAP2/Filter.php
plugins/LdapCommon/extlib/Net/LDAP2/LDIF.php
plugins/LdapCommon/extlib/Net/LDAP2/Schema.php
plugins/LdapCommon/extlib/Net/LDAP2/Search.php
plugins/LdapCommon/extlib/Net/LDAP2/Util.php

index 26f5e75600dcc1010ebf3e4153a2e6456ac0bb2f..1ad1cf334526899c7a0ac9c02f4076dfd03576ef 100644 (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());
+            /* 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 {
+            /* 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.");
+                 }
+            }
         }
 
-        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;
+        // 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 $this->raiseError("Server reports that it does not support TLS");
+            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 (ldap_errno($this->_link) == 32) {
-            return false;
+        if (self::isError($search)) {
+            return $search;
         }
-        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;
index 66de966780ac73006aa4afce84db115a2d44e55e..cdbd70136a30cdabdad248aeb9336cf85c7ecd91 100644 (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");
-        }
-
-        $value = $this->_attributes[$attr];
+        // 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 = '';
+            }
 
-        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;
index 0723edab2b3fe91c40f87bf5a633bd34203a75f4..557fc892e35c9eb296277a08d573d34ccf5f8c0e 100644 (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!');
+        }
+    }
+
+
 }
 ?>
index 34f3e75dd54c6358f4e6221d654304a79d41beaf..384b8e2b797fab542d8a54b4e2abe68839ab297a 100644 (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++;
                                 }
index b590eabc51c2de70432f3e1713ecfe6af19279bd..7eb15662eb442a710588056b4e6f349426b9cfb0 100644 (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.
+    }
 }
-?>
+?>
\ No newline at end of file
index de4fde122c2877d4ccf21cac01e70ae9eb7df55c..d18554f5784114cecafb9fce6a040a652a85f3df 100644 (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;
     }
 
     /**
index 48b03f9f992e9cafeff1dc7dcdb95b9f04820f9e..9693de2bacfa5a95f7de46fa3f0fbb27aca919cf 100644 (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);
+        }
     }
 
     /**