File indexing completed on 2025-02-09 07:20:08
0001 <?php 0002 /** 0003 * Zend Framework 0004 * 0005 * LICENSE 0006 * 0007 * This source file is subject to the new BSD license that is bundled 0008 * with this package in the file LICENSE.txt. 0009 * It is also available through the world-wide-web at this URL: 0010 * http://framework.zend.com/license/new-bsd 0011 * If you did not receive a copy of the license and are unable to 0012 * obtain it through the world-wide-web, please send an email 0013 * to license@zend.com so we can send you a copy immediately. 0014 * 0015 * @category Zend 0016 * @package Zend_Ldap 0017 * @subpackage Ldif 0018 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0019 * @license http://framework.zend.com/license/new-bsd New BSD License 0020 * @version $Id$ 0021 */ 0022 0023 /** 0024 * Zend_Ldap_Ldif_Encoder provides methods to encode and decode LDAP data into/from LDIF. 0025 * 0026 * @category Zend 0027 * @package Zend_Ldap 0028 * @subpackage Ldif 0029 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com) 0030 * @license http://framework.zend.com/license/new-bsd New BSD License 0031 */ 0032 class Zend_Ldap_Ldif_Encoder 0033 { 0034 /** 0035 * Additional options used during encoding 0036 * 0037 * @var array 0038 */ 0039 protected $_options = array( 0040 'sort' => true, 0041 'version' => 1, 0042 'wrap' => 78 0043 ); 0044 0045 /** 0046 * @var boolean 0047 */ 0048 protected $_versionWritten = false; 0049 0050 /** 0051 * Constructor. 0052 * 0053 * @param array $options Additional options used during encoding 0054 * @return void 0055 */ 0056 protected function __construct(array $options = array()) 0057 { 0058 $this->_options = array_merge($this->_options, $options); 0059 } 0060 0061 /** 0062 * Decodes the string $string into an array of LDIF items 0063 * 0064 * @param string $string 0065 * @return array 0066 */ 0067 public static function decode($string) 0068 { 0069 $encoder = new self(array()); 0070 return $encoder->_decode($string); 0071 } 0072 0073 /** 0074 * Decodes the string $string into an array of LDIF items 0075 * 0076 * @param string $string 0077 * @return array 0078 */ 0079 protected function _decode($string) 0080 { 0081 $items = array(); 0082 $item = array(); 0083 $last = null; 0084 foreach (explode("\n", $string) as $line) { 0085 $line = rtrim($line, "\x09\x0A\x0D\x00\x0B"); 0086 $matches = array(); 0087 if (substr($line, 0, 1) === ' ' && $last !== null) { 0088 $last[2] .= substr($line, 1); 0089 } else if (substr($line, 0, 1) === '#') { 0090 continue; 0091 } else if (preg_match('/^([a-z0-9;-]+)(:[:<]?\s*)([^:<]*)$/i', $line, $matches)) { 0092 $name = strtolower($matches[1]); 0093 $type = trim($matches[2]); 0094 $value = $matches[3]; 0095 if ($last !== null) { 0096 $this->_pushAttribute($last, $item); 0097 } 0098 if ($name === 'version') { 0099 continue; 0100 } else if (count($item) > 0 && $name === 'dn') { 0101 $items[] = $item; 0102 $item = array(); 0103 $last = null; 0104 } 0105 $last = array($name, $type, $value); 0106 } else if (trim($line) === '') { 0107 continue; 0108 } 0109 } 0110 if ($last !== null) { 0111 $this->_pushAttribute($last, $item); 0112 } 0113 $items[] = $item; 0114 return (count($items)>1) ? $items : $items[0]; 0115 } 0116 0117 /** 0118 * Pushes a decoded attribute to the stack 0119 * 0120 * @param array $attribute 0121 * @param array $entry 0122 */ 0123 protected function _pushAttribute(array $attribute, array &$entry) 0124 { 0125 $name = $attribute[0]; 0126 $type = $attribute[1]; 0127 $value = $attribute[2]; 0128 if ($type === '::') { 0129 $value = base64_decode($value); 0130 } 0131 if ($name === 'dn') { 0132 $entry[$name] = $value; 0133 } else if (isset($entry[$name]) && $value !== '') { 0134 $entry[$name][] = $value; 0135 } else { 0136 $entry[$name] = ($value !== '') ? array($value) : array(); 0137 } 0138 } 0139 0140 /** 0141 * Encode $value into a LDIF representation 0142 * 0143 * @param mixed $value The value to be encoded 0144 * @param array $options Additional options used during encoding 0145 * @return string The encoded value 0146 */ 0147 public static function encode($value, array $options = array()) 0148 { 0149 $encoder = new self($options); 0150 return $encoder->_encode($value); 0151 } 0152 0153 /** 0154 * Recursive driver which determines the type of value to be encoded 0155 * and then dispatches to the appropriate method. 0156 * 0157 * @param mixed $value The value to be encoded 0158 * @return string Encoded value 0159 */ 0160 protected function _encode($value) 0161 { 0162 if (is_scalar($value)) { 0163 return $this->_encodeString($value); 0164 } else if (is_array($value)) { 0165 return $this->_encodeAttributes($value); 0166 } else if ($value instanceof Zend_Ldap_Node) { 0167 return $value->toLdif($this->_options); 0168 } 0169 return null; 0170 } 0171 0172 /** 0173 * Encodes $string according to RFC2849 0174 * 0175 * @link http://www.faqs.org/rfcs/rfc2849.html 0176 * 0177 * @param string $string 0178 * @param boolen $base64 0179 * @return string 0180 */ 0181 protected function _encodeString($string, &$base64 = null) 0182 { 0183 $string = (string)$string; 0184 if (!is_numeric($string) && empty($string)) { 0185 return ''; 0186 } 0187 0188 /* 0189 * SAFE-INIT-CHAR = %x01-09 / %x0B-0C / %x0E-1F / 0190 * %x21-39 / %x3B / %x3D-7F 0191 * ; any value <= 127 except NUL, LF, CR, 0192 * ; SPACE, colon (":", ASCII 58 decimal) 0193 * ; and less-than ("<" , ASCII 60 decimal) 0194 * 0195 */ 0196 $unsafe_init_char = array(0, 10, 13, 32, 58, 60); 0197 /* 0198 * SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-7F 0199 * ; any value <= 127 decimal except NUL, LF, 0200 * ; and CR 0201 */ 0202 $unsafe_char = array(0, 10, 13); 0203 0204 $base64 = false; 0205 for ($i = 0; $i < strlen($string); $i++) { 0206 $char = ord(substr($string, $i, 1)); 0207 if ($char >= 127) { 0208 $base64 = true; 0209 break; 0210 } else if ($i === 0 && in_array($char, $unsafe_init_char)) { 0211 $base64 = true; 0212 break; 0213 } else if (in_array($char, $unsafe_char)) { 0214 $base64 = true; 0215 break; 0216 } 0217 } 0218 // Test for ending space 0219 if (substr($string, -1) == ' ') { 0220 $base64 = true; 0221 } 0222 0223 if ($base64 === true) { 0224 $string = base64_encode($string); 0225 } 0226 0227 return $string; 0228 } 0229 0230 /** 0231 * Encodes an attribute with $name and $value according to RFC2849 0232 * 0233 * @link http://www.faqs.org/rfcs/rfc2849.html 0234 * 0235 * @param string $name 0236 * @param array|string $value 0237 * @return string 0238 */ 0239 protected function _encodeAttribute($name, $value) 0240 { 0241 if (!is_array($value)) { 0242 $value = array($value); 0243 } 0244 0245 $output = ''; 0246 0247 if (count($value) < 1) { 0248 return $name . ': '; 0249 } 0250 0251 foreach ($value as $v) { 0252 $base64 = null; 0253 $v = $this->_encodeString($v, $base64); 0254 $attribute = $name . ':'; 0255 if ($base64 === true) { 0256 $attribute .= ': ' . $v; 0257 } else { 0258 $attribute .= ' ' . $v; 0259 } 0260 if (isset($this->_options['wrap']) && strlen($attribute) > $this->_options['wrap']) { 0261 $attribute = trim(chunk_split($attribute, $this->_options['wrap'], PHP_EOL . ' ')); 0262 } 0263 $output .= $attribute . PHP_EOL; 0264 } 0265 return trim($output, PHP_EOL); 0266 } 0267 0268 /** 0269 * Encodes a collection of attributes according to RFC2849 0270 * 0271 * @link http://www.faqs.org/rfcs/rfc2849.html 0272 * 0273 * @param array $attributes 0274 * @return string 0275 */ 0276 protected function _encodeAttributes(array $attributes) 0277 { 0278 $string = ''; 0279 $attributes = array_change_key_case($attributes, CASE_LOWER); 0280 if (!$this->_versionWritten && array_key_exists('dn', $attributes) && isset($this->_options['version']) 0281 && array_key_exists('objectclass', $attributes)) { 0282 $string .= sprintf('version: %d', $this->_options['version']) . PHP_EOL; 0283 $this->_versionWritten = true; 0284 } 0285 0286 if (isset($this->_options['sort']) && $this->_options['sort'] === true) { 0287 ksort($attributes, SORT_STRING); 0288 if (array_key_exists('objectclass', $attributes)) { 0289 $oc = $attributes['objectclass']; 0290 unset($attributes['objectclass']); 0291 $attributes = array_merge(array('objectclass' => $oc), $attributes); 0292 } 0293 if (array_key_exists('dn', $attributes)) { 0294 $dn = $attributes['dn']; 0295 unset($attributes['dn']); 0296 $attributes = array_merge(array('dn' => $dn), $attributes); 0297 } 0298 } 0299 foreach ($attributes as $key => $value) { 0300 $string .= $this->_encodeAttribute($key, $value) . PHP_EOL; 0301 } 0302 return trim($string, PHP_EOL); 0303 } 0304 }