[4456] | 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 | } |
---|