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 }