web/enmi/Zend/Ldap/Dn.php
changeset 19 1c2f13fd785c
parent 0 4eba9c11703f
equal deleted inserted replaced
18:bd595ad770fc 19:1c2f13fd785c
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Ldap
       
    17  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    18  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    19  * @version    $Id: Dn.php 22662 2010-07-24 17:37:36Z mabe $
       
    20  */
       
    21 
       
    22 /**
       
    23  * Zend_Ldap_Dn provides an API for DN manipulation
       
    24  *
       
    25  * @category   Zend
       
    26  * @package    Zend_Ldap
       
    27  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    28  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    29  */
       
    30 class Zend_Ldap_Dn implements ArrayAccess
       
    31 {
       
    32     const ATTR_CASEFOLD_NONE  = 'none';
       
    33     const ATTR_CASEFOLD_UPPER = 'upper';
       
    34     const ATTR_CASEFOLD_LOWER = 'lower';
       
    35 
       
    36     /**
       
    37      * The default case fold to use
       
    38      *
       
    39      * @var string
       
    40      */
       
    41     protected static $_defaultCaseFold = self::ATTR_CASEFOLD_NONE;
       
    42 
       
    43     /**
       
    44      * The case fold used for this instance
       
    45      *
       
    46      * @var string
       
    47      */
       
    48     protected $_caseFold;
       
    49 
       
    50     /**
       
    51      * The DN data
       
    52      *
       
    53      * @var array
       
    54      */
       
    55     protected $_dn;
       
    56 
       
    57     /**
       
    58      * Creates a DN from an array or a string
       
    59      *
       
    60      * @param  string|array $dn
       
    61      * @param  string|null  $caseFold
       
    62      * @return Zend_Ldap_Dn
       
    63      * @throws Zend_Ldap_Exception
       
    64      */
       
    65     public static function factory($dn, $caseFold = null)
       
    66     {
       
    67         if (is_array($dn)) {
       
    68             return self::fromArray($dn, $caseFold);
       
    69         } else if (is_string($dn)) {
       
    70             return self::fromString($dn, $caseFold);
       
    71         } else {
       
    72             /**
       
    73              * Zend_Ldap_Exception
       
    74              */
       
    75             require_once 'Zend/Ldap/Exception.php';
       
    76             throw new Zend_Ldap_Exception(null, 'Invalid argument type for $dn');
       
    77         }
       
    78     }
       
    79 
       
    80     /**
       
    81      * Creates a DN from a string
       
    82      *
       
    83      * @param  string      $dn
       
    84      * @param  string|null $caseFold
       
    85      * @return Zend_Ldap_Dn
       
    86      * @throws Zend_Ldap_Exception
       
    87      */
       
    88     public static function fromString($dn, $caseFold = null)
       
    89     {
       
    90         $dn = trim($dn);
       
    91         if (empty($dn)) {
       
    92             $dnArray = array();
       
    93         } else {
       
    94             $dnArray = self::explodeDn((string)$dn);
       
    95         }
       
    96         return new self($dnArray, $caseFold);
       
    97     }
       
    98 
       
    99     /**
       
   100      * Creates a DN from an array
       
   101      *
       
   102      * @param  array       $dn
       
   103      * @param  string|null $caseFold
       
   104      * @return Zend_Ldap_Dn
       
   105      * @throws Zend_Ldap_Exception
       
   106      */
       
   107     public static function fromArray(array $dn, $caseFold = null)
       
   108     {
       
   109          return new self($dn, $caseFold);
       
   110     }
       
   111 
       
   112     /**
       
   113      * Constructor
       
   114      *
       
   115      * @param array       $dn
       
   116      * @param string|null $caseFold
       
   117      */
       
   118     protected function __construct(array $dn, $caseFold)
       
   119     {
       
   120         $this->_dn = $dn;
       
   121         $this->setCaseFold($caseFold);
       
   122     }
       
   123 
       
   124     /**
       
   125      * Gets the RDN of the current DN
       
   126      *
       
   127      * @param  string $caseFold
       
   128      * @return array
       
   129      * @throws Zend_Ldap_Exception if DN has no RDN (empty array)
       
   130      */
       
   131     public function getRdn($caseFold = null)
       
   132     {
       
   133         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
       
   134         return self::_caseFoldRdn($this->get(0, 1, $caseFold), null);
       
   135     }
       
   136 
       
   137     /**
       
   138      * Gets the RDN of the current DN as a string
       
   139      *
       
   140      * @param  string $caseFold
       
   141      * @return string
       
   142      * @throws Zend_Ldap_Exception if DN has no RDN (empty array)
       
   143      */
       
   144     public function getRdnString($caseFold = null)
       
   145     {
       
   146         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
       
   147         return self::implodeRdn($this->getRdn(), $caseFold);
       
   148     }
       
   149 
       
   150     /**
       
   151      * Get the parent DN $levelUp levels up the tree
       
   152      *
       
   153      * @param  int $levelUp
       
   154      * @return Zend_Ldap_Dn
       
   155      */
       
   156     public function getParentDn($levelUp = 1)
       
   157     {
       
   158         $levelUp = (int)$levelUp;
       
   159         if ($levelUp < 1 || $levelUp >= count($this->_dn)) {
       
   160             /**
       
   161              * Zend_Ldap_Exception
       
   162              */
       
   163             require_once 'Zend/Ldap/Exception.php';
       
   164             throw new Zend_Ldap_Exception(null, 'Cannot retrieve parent DN with given $levelUp');
       
   165         }
       
   166         $newDn = array_slice($this->_dn, $levelUp);
       
   167         return new self($newDn, $this->_caseFold);
       
   168     }
       
   169 
       
   170     /**
       
   171      * Get a DN part
       
   172      *
       
   173      * @param  int    $index
       
   174      * @param  int    $length
       
   175      * @param  string $caseFold
       
   176      * @return array
       
   177      * @throws Zend_Ldap_Exception if index is illegal
       
   178      */
       
   179     public function get($index, $length = 1, $caseFold = null)
       
   180     {
       
   181         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
       
   182         $this->_assertIndex($index);
       
   183         $length = (int)$length;
       
   184         if ($length <= 0) {
       
   185             $length = 1;
       
   186         }
       
   187         if ($length === 1) {
       
   188             return self::_caseFoldRdn($this->_dn[$index], $caseFold);
       
   189         }
       
   190         else {
       
   191             return self::_caseFoldDn(array_slice($this->_dn, $index, $length, false), $caseFold);
       
   192         }
       
   193     }
       
   194 
       
   195     /**
       
   196      * Set a DN part
       
   197      *
       
   198      * @param  int   $index
       
   199      * @param  array $value
       
   200      * @return Zend_Ldap_Dn Provides a fluent interface
       
   201      * @throws Zend_Ldap_Exception if index is illegal
       
   202      */
       
   203     public function set($index, array $value)
       
   204     {
       
   205         $this->_assertIndex($index);
       
   206         self::_assertRdn($value);
       
   207         $this->_dn[$index] = $value;
       
   208         return $this;
       
   209     }
       
   210 
       
   211     /**
       
   212      * Remove a DN part
       
   213      *
       
   214      * @param  int $index
       
   215      * @param  int $length
       
   216      * @return Zend_Ldap_Dn Provides a fluent interface
       
   217      * @throws Zend_Ldap_Exception if index is illegal
       
   218      */
       
   219     public function remove($index, $length = 1)
       
   220     {
       
   221         $this->_assertIndex($index);
       
   222         $length = (int)$length;
       
   223         if ($length <= 0) {
       
   224             $length = 1;
       
   225         }
       
   226         array_splice($this->_dn, $index, $length, null);
       
   227         return $this;
       
   228     }
       
   229 
       
   230     /**
       
   231      * Append a DN part
       
   232      *
       
   233      * @param  array $value
       
   234      * @return Zend_Ldap_Dn Provides a fluent interface
       
   235      */
       
   236     public function append(array $value)
       
   237     {
       
   238         self::_assertRdn($value);
       
   239         $this->_dn[] = $value;
       
   240         return $this;
       
   241     }
       
   242 
       
   243     /**
       
   244      * Prepend a DN part
       
   245      *
       
   246      * @param  array $value
       
   247      * @return Zend_Ldap_Dn Provides a fluent interface
       
   248      */
       
   249     public function prepend(array $value)
       
   250     {
       
   251         self::_assertRdn($value);
       
   252         array_unshift($this->_dn, $value);
       
   253         return $this;
       
   254     }
       
   255 
       
   256     /**
       
   257      * Insert a DN part
       
   258      *
       
   259      * @param  int   $index
       
   260      * @param  array $value
       
   261      * @return Zend_Ldap_Dn Provides a fluent interface
       
   262      * @throws Zend_Ldap_Exception if index is illegal
       
   263      */
       
   264     public function insert($index, array $value)
       
   265     {
       
   266         $this->_assertIndex($index);
       
   267         self::_assertRdn($value);
       
   268         $first = array_slice($this->_dn, 0, $index + 1);
       
   269         $second = array_slice($this->_dn, $index + 1);
       
   270         $this->_dn = array_merge($first, array($value), $second);
       
   271         return $this;
       
   272     }
       
   273 
       
   274     /**
       
   275      * Assert index is correct and usable
       
   276      *
       
   277      * @param  mixed $index
       
   278      * @return boolean
       
   279      * @throws Zend_Ldap_Exception
       
   280      */
       
   281     protected function _assertIndex($index)
       
   282     {
       
   283         if (!is_int($index)) {
       
   284             /**
       
   285              * Zend_Ldap_Exception
       
   286              */
       
   287             require_once 'Zend/Ldap/Exception.php';
       
   288             throw new Zend_Ldap_Exception(null, 'Parameter $index must be an integer');
       
   289         }
       
   290         if ($index < 0 || $index >= count($this->_dn)) {
       
   291             /**
       
   292              * Zend_Ldap_Exception
       
   293              */
       
   294             require_once 'Zend/Ldap/Exception.php';
       
   295             throw new Zend_Ldap_Exception(null, 'Parameter $index out of bounds');
       
   296         }
       
   297         return true;
       
   298     }
       
   299 
       
   300     /**
       
   301      * Assert if value is in a correct RDN format
       
   302      *
       
   303      * @param  array $value
       
   304      * @return boolean
       
   305      * @throws Zend_Ldap_Exception
       
   306      */
       
   307     protected static function _assertRdn(array $value)
       
   308     {
       
   309         if (count($value)<1) {
       
   310             /**
       
   311              * Zend_Ldap_Exception
       
   312              */
       
   313             require_once 'Zend/Ldap/Exception.php';
       
   314             throw new Zend_Ldap_Exception(null, 'RDN Array is malformed: it must have at least one item');
       
   315         }
       
   316 
       
   317         foreach (array_keys($value) as $key) {
       
   318             if (!is_string($key)) {
       
   319                 /**
       
   320                  * Zend_Ldap_Exception
       
   321                  */
       
   322                 require_once 'Zend/Ldap/Exception.php';
       
   323                 throw new Zend_Ldap_Exception(null, 'RDN Array is malformed: it must use string keys');
       
   324             }
       
   325         }
       
   326     }
       
   327 
       
   328     /**
       
   329      * Sets the case fold
       
   330      *
       
   331      * @param string|null $caseFold
       
   332      */
       
   333     public function setCaseFold($caseFold)
       
   334     {
       
   335         $this->_caseFold = self::_sanitizeCaseFold($caseFold, self::$_defaultCaseFold);
       
   336     }
       
   337 
       
   338     /**
       
   339      * Return DN as a string
       
   340      *
       
   341      * @param  string $caseFold
       
   342      * @return string
       
   343      * @throws Zend_Ldap_Exception
       
   344      */
       
   345     public function toString($caseFold = null)
       
   346     {
       
   347         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
       
   348         return self::implodeDn($this->_dn, $caseFold);
       
   349     }
       
   350 
       
   351     /**
       
   352      * Return DN as an array
       
   353      *
       
   354      * @param  string $caseFold
       
   355      * @return array
       
   356      */
       
   357     public function toArray($caseFold = null)
       
   358     {
       
   359         $caseFold = self::_sanitizeCaseFold($caseFold, $this->_caseFold);
       
   360 
       
   361         if ($caseFold === self::ATTR_CASEFOLD_NONE) {
       
   362             return $this->_dn;
       
   363         } else {
       
   364             return self::_caseFoldDn($this->_dn, $caseFold);
       
   365         }
       
   366     }
       
   367 
       
   368     /**
       
   369      * Do a case folding on a RDN
       
   370      *
       
   371      * @param  array  $part
       
   372      * @param  string $caseFold
       
   373      * @return array
       
   374      */
       
   375     protected static function _caseFoldRdn(array $part, $caseFold)
       
   376     {
       
   377         switch ($caseFold) {
       
   378             case self::ATTR_CASEFOLD_UPPER:
       
   379                 return array_change_key_case($part, CASE_UPPER);
       
   380             case self::ATTR_CASEFOLD_LOWER:
       
   381                 return array_change_key_case($part, CASE_LOWER);
       
   382             case self::ATTR_CASEFOLD_NONE:
       
   383             default:
       
   384                 return $part;
       
   385         }
       
   386     }
       
   387 
       
   388     /**
       
   389      * Do a case folding on a DN ort part of it
       
   390      *
       
   391      * @param  array  $dn
       
   392      * @param  string $caseFold
       
   393      * @return array
       
   394      */
       
   395     protected static function _caseFoldDn(array $dn, $caseFold)
       
   396     {
       
   397         $return = array();
       
   398         foreach ($dn as $part) {
       
   399             $return[] = self::_caseFoldRdn($part, $caseFold);
       
   400         }
       
   401         return $return;
       
   402     }
       
   403 
       
   404     /**
       
   405      * Cast to string representation {@see toString()}
       
   406      *
       
   407      * @return string
       
   408      */
       
   409     public function __toString()
       
   410     {
       
   411         return $this->toString();
       
   412     }
       
   413 
       
   414     /**
       
   415      * Required by the ArrayAccess implementation
       
   416      *
       
   417      * @param  int $offset
       
   418      * @return boolean
       
   419      */
       
   420     public function offsetExists($offset)
       
   421     {
       
   422         $offset = (int)$offset;
       
   423         if ($offset < 0 || $offset >= count($this->_dn)) {
       
   424             return false;
       
   425         } else {
       
   426             return true;
       
   427         }
       
   428     }
       
   429 
       
   430     /**
       
   431      * Proxy to {@see get()}
       
   432      * Required by the ArrayAccess implementation
       
   433      *
       
   434      * @param  int $offset
       
   435      * @return array
       
   436      */
       
   437      public function offsetGet($offset)
       
   438      {
       
   439          return $this->get($offset, 1, null);
       
   440      }
       
   441 
       
   442      /**
       
   443       * Proxy to {@see set()}
       
   444       * Required by the ArrayAccess implementation
       
   445       *
       
   446       * @param int   $offset
       
   447       * @param array $value
       
   448       */
       
   449      public function offsetSet($offset, $value)
       
   450      {
       
   451          $this->set($offset, $value);
       
   452      }
       
   453 
       
   454      /**
       
   455       * Proxy to {@see remove()}
       
   456       * Required by the ArrayAccess implementation
       
   457       *
       
   458       * @param int $offset
       
   459       */
       
   460      public function offsetUnset($offset)
       
   461      {
       
   462          $this->remove($offset, 1);
       
   463      }
       
   464 
       
   465     /**
       
   466      * Sets the default case fold
       
   467      *
       
   468      * @param string $caseFold
       
   469      */
       
   470     public static function setDefaultCaseFold($caseFold)
       
   471     {
       
   472         self::$_defaultCaseFold = self::_sanitizeCaseFold($caseFold, self::ATTR_CASEFOLD_NONE);
       
   473     }
       
   474 
       
   475     /**
       
   476      * Sanitizes the case fold
       
   477      *
       
   478      * @param  string $caseFold
       
   479      * @return string
       
   480      */
       
   481     protected static function _sanitizeCaseFold($caseFold, $default)
       
   482     {
       
   483         switch ($caseFold) {
       
   484             case self::ATTR_CASEFOLD_NONE:
       
   485             case self::ATTR_CASEFOLD_UPPER:
       
   486             case self::ATTR_CASEFOLD_LOWER:
       
   487                 return $caseFold;
       
   488                 break;
       
   489             default:
       
   490                 return $default;
       
   491                 break;
       
   492         }
       
   493     }
       
   494 
       
   495     /**
       
   496      * Escapes a DN value according to RFC 2253
       
   497      *
       
   498      * Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
       
   499      * The characters ",", "+", """, "\", "<", ">", ";", "#", " = " with a special meaning in RFC 2252
       
   500      * are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
       
   501      * Finally all leading and trailing spaces are converted to sequences of \20.
       
   502      * @see Net_LDAP2_Util::escape_dn_value() from Benedikt Hallinger <beni@php.net>
       
   503      * @link http://pear.php.net/package/Net_LDAP2
       
   504      * @author Benedikt Hallinger <beni@php.net>
       
   505      *
       
   506      * @param  string|array $values An array containing the DN values that should be escaped
       
   507      * @return array The array $values, but escaped
       
   508      */
       
   509     public static function escapeValue($values = array())
       
   510     {
       
   511         /**
       
   512          * @see Zend_Ldap_Converter
       
   513          */
       
   514         require_once 'Zend/Ldap/Converter.php';
       
   515 
       
   516         if (!is_array($values)) $values = array($values);
       
   517         foreach ($values as $key => $val) {
       
   518             // Escaping of filter meta characters
       
   519             $val = str_replace(array('\\', ',', '+', '"', '<', '>', ';', '#', '=', ),
       
   520                 array('\\\\', '\,', '\+', '\"', '\<', '\>', '\;', '\#', '\='), $val);
       
   521             $val = Zend_Ldap_Converter::ascToHex32($val);
       
   522 
       
   523             // Convert all leading and trailing spaces to sequences of \20.
       
   524             if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
       
   525                 $val = $matches[2];
       
   526                 for ($i = 0; $i<strlen($matches[1]); $i++) {
       
   527                     $val = '\20' . $val;
       
   528                 }
       
   529                 for ($i = 0; $i<strlen($matches[3]); $i++) {
       
   530                     $val = $val . '\20';
       
   531                 }
       
   532             }
       
   533             if (null === $val) $val = '\0';  // apply escaped "null" if string is empty
       
   534             $values[$key] = $val;
       
   535         }
       
   536         return (count($values) == 1) ? $values[0] : $values;
       
   537     }
       
   538 
       
   539     /**
       
   540      * Undoes the conversion done by {@link escapeValue()}.
       
   541      *
       
   542      * Any escape sequence starting with a baskslash - hexpair or special character -
       
   543      * will be transformed back to the corresponding character.
       
   544      * @see Net_LDAP2_Util::escape_dn_value() from Benedikt Hallinger <beni@php.net>
       
   545      * @link http://pear.php.net/package/Net_LDAP2
       
   546      * @author Benedikt Hallinger <beni@php.net>
       
   547      *
       
   548      * @param  string|array $values Array of DN Values
       
   549      * @return array Same as $values, but unescaped
       
   550      */
       
   551     public static function unescapeValue($values = array())
       
   552     {
       
   553         /**
       
   554          * @see Zend_Ldap_Converter
       
   555          */
       
   556         require_once 'Zend/Ldap/Converter.php';
       
   557 
       
   558         if (!is_array($values)) $values = array($values);
       
   559         foreach ($values as $key => $val) {
       
   560             // strip slashes from special chars
       
   561             $val = str_replace(array('\\\\', '\,', '\+', '\"', '\<', '\>', '\;', '\#', '\='),
       
   562                 array('\\', ',', '+', '"', '<', '>', ';', '#', '=', ), $val);
       
   563             $values[$key] = Zend_Ldap_Converter::hex32ToAsc($val);
       
   564         }
       
   565         return (count($values) == 1) ? $values[0] : $values;
       
   566     }
       
   567 
       
   568     /**
       
   569      * Creates an array containing all parts of the given DN.
       
   570      *
       
   571      * Array will be of type
       
   572      * array(
       
   573      *      array("cn" => "name1", "uid" => "user"),
       
   574      *      array("cn" => "name2"),
       
   575      *      array("dc" => "example"),
       
   576      *      array("dc" => "org")
       
   577      * )
       
   578      * for a DN of cn=name1+uid=user,cn=name2,dc=example,dc=org.
       
   579      *
       
   580      * @param  string $dn
       
   581      * @param  array  $keys     An optional array to receive DN keys (e.g. CN, OU, DC, ...)
       
   582      * @param  array  $vals     An optional array to receive DN values
       
   583      * @param  string $caseFold
       
   584      * @return array
       
   585      * @throws Zend_Ldap_Exception
       
   586      */
       
   587     public static function explodeDn($dn, array &$keys = null, array &$vals = null,
       
   588         $caseFold = self::ATTR_CASEFOLD_NONE)
       
   589     {
       
   590         $k = array();
       
   591         $v = array();
       
   592         if (!self::checkDn($dn, $k, $v, $caseFold)) {
       
   593             /**
       
   594              * Zend_Ldap_Exception
       
   595              */
       
   596             require_once 'Zend/Ldap/Exception.php';
       
   597             throw new Zend_Ldap_Exception(null, 'DN is malformed');
       
   598         }
       
   599         $ret = array();
       
   600         for ($i = 0; $i < count($k); $i++) {
       
   601             if (is_array($k[$i]) && is_array($v[$i]) && (count($k[$i]) === count($v[$i]))) {
       
   602                 $multi = array();
       
   603                 for ($j = 0; $j < count($k[$i]); $j++) {
       
   604                     $key=$k[$i][$j];
       
   605                     $val=$v[$i][$j];
       
   606                     $multi[$key] = $val;
       
   607                 }
       
   608                 $ret[] = $multi;
       
   609             } else if (is_string($k[$i]) && is_string($v[$i])) {
       
   610                 $ret[] = array($k[$i] => $v[$i]);
       
   611             }
       
   612         }
       
   613         if ($keys !== null) $keys = $k;
       
   614         if ($vals !== null) $vals = $v;
       
   615         return $ret;
       
   616     }
       
   617 
       
   618     /**
       
   619      * @param  string $dn       The DN to parse
       
   620      * @param  array  $keys     An optional array to receive DN keys (e.g. CN, OU, DC, ...)
       
   621      * @param  array  $vals     An optional array to receive DN values
       
   622      * @param  string $caseFold
       
   623      * @return boolean True if the DN was successfully parsed or false if the string is not a valid DN.
       
   624      */
       
   625     public static function checkDn($dn, array &$keys = null, array &$vals = null,
       
   626         $caseFold = self::ATTR_CASEFOLD_NONE)
       
   627     {
       
   628         /* This is a classic state machine parser. Each iteration of the
       
   629          * loop processes one character. State 1 collects the key. When equals ( = )
       
   630          * is encountered the state changes to 2 where the value is collected
       
   631          * until a comma (,) or semicolon (;) is encountered after which we switch back
       
   632          * to state 1. If a backslash (\) is encountered, state 3 is used to collect the
       
   633          * following character without engaging the logic of other states.
       
   634          */
       
   635         $key = null;
       
   636         $value = null;
       
   637         $slen = strlen($dn);
       
   638         $state = 1;
       
   639         $ko = $vo = 0;
       
   640         $multi = false;
       
   641         $ka = array();
       
   642         $va = array();
       
   643         for ($di = 0; $di <= $slen; $di++) {
       
   644             $ch = ($di == $slen) ? 0 : $dn[$di];
       
   645             switch ($state) {
       
   646                 case 1: // collect key
       
   647                     if ($ch === '=') {
       
   648                         $key = trim(substr($dn, $ko, $di - $ko));
       
   649                         if ($caseFold == self::ATTR_CASEFOLD_LOWER) $key = strtolower($key);
       
   650                         else if ($caseFold == self::ATTR_CASEFOLD_UPPER) $key = strtoupper($key);
       
   651                         if (is_array($multi)) {
       
   652                             $keyId = strtolower($key);
       
   653                             if (in_array($keyId, $multi)) {
       
   654                                 return false;
       
   655                             }
       
   656                             $ka[count($ka)-1][] = $key;
       
   657                             $multi[] = $keyId;
       
   658                         } else {
       
   659                             $ka[] = $key;
       
   660                         }
       
   661                         $state = 2;
       
   662                         $vo = $di + 1;
       
   663                     } else if ($ch === ',' || $ch === ';' || $ch === '+') {
       
   664                         return false;
       
   665                     }
       
   666                     break;
       
   667                 case 2: // collect value
       
   668                     if ($ch === '\\') {
       
   669                         $state = 3;
       
   670                     } else if ($ch === ',' || $ch === ';' || $ch === 0 || $ch === '+') {
       
   671                         $value = self::unescapeValue(trim(substr($dn, $vo, $di - $vo)));
       
   672                         if (is_array($multi)) {
       
   673                             $va[count($va)-1][] = $value;
       
   674                         } else {
       
   675                             $va[] = $value;
       
   676                         }
       
   677                         $state = 1;
       
   678                         $ko = $di + 1;
       
   679                         if ($ch === '+' && $multi === false) {
       
   680                             $lastKey = array_pop($ka);
       
   681                             $lastVal = array_pop($va);
       
   682                             $ka[] = array($lastKey);
       
   683                             $va[] = array($lastVal);
       
   684                             $multi = array(strtolower($lastKey));
       
   685                         } else if ($ch === ','|| $ch === ';' || $ch === 0) {
       
   686                             $multi = false;
       
   687                         }
       
   688                     } else if ($ch === '=') {
       
   689                         return false;
       
   690                     }
       
   691                     break;
       
   692                 case 3: // escaped
       
   693                     $state = 2;
       
   694                     break;
       
   695             }
       
   696         }
       
   697 
       
   698         if ($keys !== null) {
       
   699             $keys = $ka;
       
   700         }
       
   701         if ($vals !== null) {
       
   702             $vals = $va;
       
   703         }
       
   704 
       
   705         return ($state === 1 && $ko > 0);
       
   706     }
       
   707 
       
   708     /**
       
   709      * Returns a DN part in the form $attribute = $value
       
   710      *
       
   711      * This method supports the creation of multi-valued RDNs
       
   712      * $part must contain an even number of elemets.
       
   713      *
       
   714      * @param  array  $attribute
       
   715      * @param  string $caseFold
       
   716      * @return string
       
   717      * @throws Zend_Ldap_Exception
       
   718      */
       
   719     public static function implodeRdn(array $part, $caseFold = null)
       
   720     {
       
   721         self::_assertRdn($part);
       
   722         $part = self::_caseFoldRdn($part, $caseFold);
       
   723         $rdnParts = array();
       
   724         foreach ($part as $key => $value) {
       
   725             $value = self::escapeValue($value);
       
   726             $keyId = strtolower($key);
       
   727             $rdnParts[$keyId] =  implode('=', array($key, $value));
       
   728         }
       
   729         ksort($rdnParts, SORT_STRING);
       
   730         return implode('+', $rdnParts);
       
   731     }
       
   732 
       
   733     /**
       
   734      * Implodes an array in the form delivered by {@link explodeDn()}
       
   735      * to a DN string.
       
   736      *
       
   737      * $dnArray must be of type
       
   738      * array(
       
   739      *      array("cn" => "name1", "uid" => "user"),
       
   740      *      array("cn" => "name2"),
       
   741      *      array("dc" => "example"),
       
   742      *      array("dc" => "org")
       
   743      * )
       
   744      *
       
   745      * @param  array  $dnArray
       
   746      * @param  string $caseFold
       
   747      * @param  string $separator
       
   748      * @return string
       
   749      * @throws Zend_Ldap_Exception
       
   750      */
       
   751     public static function implodeDn(array $dnArray, $caseFold = null, $separator = ',')
       
   752     {
       
   753         $parts = array();
       
   754         foreach ($dnArray as $p) {
       
   755             $parts[] = self::implodeRdn($p, $caseFold);
       
   756         }
       
   757         return implode($separator, $parts);
       
   758     }
       
   759 
       
   760     /**
       
   761      * Checks if given $childDn is beneath $parentDn subtree.
       
   762      *
       
   763      * @param  string|Zend_Ldap_Dn $childDn
       
   764      * @param  string|Zend_Ldap_Dn $parentDn
       
   765      * @return boolean
       
   766      */
       
   767     public static function isChildOf($childDn, $parentDn)
       
   768     {
       
   769         try {
       
   770             $keys = array();
       
   771             $vals = array();
       
   772             if ($childDn instanceof Zend_Ldap_Dn) {
       
   773                 $cdn = $childDn->toArray(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
       
   774             } else {
       
   775                 $cdn = self::explodeDn($childDn, $keys, $vals, Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
       
   776             }
       
   777             if ($parentDn instanceof Zend_Ldap_Dn) {
       
   778                 $pdn = $parentDn->toArray(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
       
   779             } else {
       
   780                 $pdn = self::explodeDn($parentDn, $keys, $vals, Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
       
   781             }
       
   782         }
       
   783         catch (Zend_Ldap_Exception $e) {
       
   784             return false;
       
   785         }
       
   786 
       
   787         $startIndex = count($cdn)-count($pdn);
       
   788         if ($startIndex<0) return false;
       
   789         for ($i = 0; $i<count($pdn); $i++) {
       
   790             if ($cdn[$i+$startIndex] != $pdn[$i]) return false;
       
   791         }
       
   792         return true;
       
   793     }
       
   794 }