File indexing completed on 2024-12-22 05:36:48

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  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0018  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0019  * @version    $Id$
0020  */
0021 
0022 /**
0023  * Zend_Ldap_Converter is a collection of useful LDAP related conversion functions.
0024  *
0025  * @category   Zend
0026  * @package    Zend_Ldap
0027  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0028  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0029  */
0030 class Zend_Ldap_Converter
0031 {
0032     const STANDARD         = 0;
0033     const BOOLEAN          = 1;
0034     const GENERALIZED_TIME = 2;
0035 
0036     /**
0037      * Converts all ASCII chars < 32 to "\HEX"
0038      *
0039      * @see Net_LDAP2_Util::asc2hex32() from Benedikt Hallinger <beni@php.net>
0040      * @link http://pear.php.net/package/Net_LDAP2
0041      * @author Benedikt Hallinger <beni@php.net>
0042      *
0043      * @param  string $string String to convert
0044      * @return string
0045      */
0046     public static function ascToHex32($string)
0047     {
0048         for ($i = 0; $i<strlen($string); $i++) {
0049             $char = substr($string, $i, 1);
0050             if (ord($char)<32) {
0051                 $hex = dechex(ord($char));
0052                 if (strlen($hex) == 1) $hex = '0' . $hex;
0053                 $string = str_replace($char, '\\' . $hex, $string);
0054             }
0055         }
0056         return $string;
0057     }
0058 
0059     /**
0060      * Converts all Hex expressions ("\HEX") to their original ASCII characters
0061      *
0062      * @see Net_LDAP2_Util::hex2asc() from Benedikt Hallinger <beni@php.net>,
0063      * heavily based on work from DavidSmith@byu.net
0064      * @link http://pear.php.net/package/Net_LDAP2
0065      * @author Benedikt Hallinger <beni@php.net>, heavily based on work from DavidSmith@byu.net
0066      *
0067      * @param  string $string String to convert
0068      * @return string
0069      */
0070     public static function hex32ToAsc($string)
0071     {
0072         // Using a callback, since PHP 5.5 has deprecated the /e modifier in preg_replace.
0073         $string = preg_replace_callback("/\\\([0-9A-Fa-f]{2})/", array('Zend_Ldap_Converter', '_charHex32ToAsc'), $string);
0074         return $string;
0075     }
0076 
0077     /**
0078      * Convert a single slash-prefixed character from Hex32 to ASCII.
0079      * Used as a callback in @see hex32ToAsc()
0080      * @param array $matches
0081      *
0082      * @return string
0083      */
0084     private static function _charHex32ToAsc(array $matches)
0085     {
0086         return chr(hexdec($matches[0]));
0087     }
0088 
0089     /**
0090      * Convert any value to an LDAP-compatible value.
0091      *
0092      * By setting the <var>$type</var>-parameter the conversion of a certain
0093      * type can be forced
0094      *
0095      * @todo write more tests
0096      *
0097      * @param    mixed     $value     The value to convert
0098      * @param    int       $ytpe      The conversion type to use
0099      * @return    string
0100      * @throws    Zend_Ldap_Converter_Exception
0101      */
0102     public static function toLdap($value, $type = self::STANDARD)
0103     {
0104         try {
0105             switch ($type) {
0106                 case self::BOOLEAN:
0107                     return self::toldapBoolean($value);
0108                     break;
0109                 case self::GENERALIZED_TIME:
0110                     return self::toLdapDatetime($value);
0111                     break;
0112                 default:
0113                     if (is_string($value)) {
0114                         return $value;
0115                     } else if (is_int($value) || is_float($value)) {
0116                         return (string)$value;
0117                     } else if (is_bool($value)) {
0118                         return self::toldapBoolean($value);
0119                     } else if (is_object($value)) {
0120                         if ($value instanceof DateTime) {
0121                             return self::toLdapDatetime($value);
0122                         } else if ($value instanceof Zend_Date) {
0123                             return self::toLdapDatetime($value);
0124                         } else {
0125                             return self::toLdapSerialize($value);
0126                         }
0127                     } else if (is_array($value)) {
0128                         return self::toLdapSerialize($value);
0129                     } else if (is_resource($value) && get_resource_type($value) === 'stream') {
0130                         return stream_get_contents($value);
0131                     } else {
0132                         return null;
0133                     }
0134                     break;
0135             }
0136         } catch (Exception $e) {
0137             throw new Zend_Ldap_Converter_Exception($e->getMessage(), $e->getCode(), $e);
0138         }
0139     }
0140 
0141     /**
0142      * Converts a date-entity to an LDAP-compatible date-string
0143      *
0144      * The date-entity <var>$date</var> can be either a timestamp, a
0145      * DateTime Object, a string that is parseable by strtotime() or a Zend_Date
0146      * Object.
0147      *
0148      * @param    integer|string|DateTimt|Zend_Date        $date    The date-entity
0149      * @param    boolean                                    $asUtc    Whether to return the LDAP-compatible date-string
0150      *                                                          as UTC or as local value
0151      * @return    string
0152      * @throws    InvalidArgumentException
0153      */
0154     public static function toLdapDateTime($date, $asUtc = true)
0155     {
0156         if (!($date instanceof DateTime)) {
0157             if (is_int($date)) {
0158                 $date = new DateTime('@' . $date);
0159                 $date->setTimezone(new DateTimeZone(date_default_timezone_get()));
0160             } else if (is_string($date)) {
0161                 $date = new DateTime($date);
0162             } else if ($date instanceof Zend_Date) {
0163                 $date = new DateTime($date->get(Zend_Date::ISO_8601));
0164             } else {
0165                 throw new InvalidArgumentException('Parameter $date is not of the expected type');
0166             }
0167         }
0168         $timezone = $date->format('O');
0169         if (true === $asUtc) {
0170             $date->setTimezone(new DateTimeZone('UTC'));
0171             $timezone = 'Z';
0172         }
0173         if ( '+0000' === $timezone ) {
0174             $timezone = 'Z';
0175         }
0176         return $date->format('YmdHis') . $timezone;
0177     }
0178 
0179     /**
0180      * Convert a boolean value to an LDAP-compatible string
0181      *
0182      * This converts a boolean value of TRUE, an integer-value of 1 and a
0183      * case-insensitive string 'true' to an LDAP-compatible 'TRUE'. All other
0184      * other values are converted to an LDAP-compatible 'FALSE'.
0185      *
0186      * @param    boolean|integer|string        $value    The boolean value to encode
0187      * @return    string
0188      */
0189     public static function toLdapBoolean($value)
0190     {
0191         $return = 'FALSE';
0192         if (!is_scalar($value)) {
0193             return $return;
0194         }
0195         if (true === $value || 'true' === strtolower($value) || 1 === $value) {
0196             $return = 'TRUE';
0197         }
0198         return $return;
0199     }
0200 
0201     /**
0202      * Serialize any value for storage in LDAP
0203      *
0204      * @param    mixed        $value    The value to serialize
0205      * @return    string
0206      */
0207     public static function toLdapSerialize($value)
0208     {
0209         return serialize($value);
0210     }
0211 
0212     /**
0213      * Convert an LDAP-compatible value to a corresponding PHP-value.
0214      *
0215      * By setting the <var>$type</var>-parameter the conversion of a certain
0216      * type can be forced
0217      * .
0218      * @param    string    $value             The value to convert
0219      * @param    int        $ytpe              The conversion type to use
0220      * @param    boolean    $dateTimeAsUtc    Return DateTime values in UTC timezone
0221      * @return    mixed
0222      * @throws    Zend_Ldap_Converter_Exception
0223      */
0224     public static function fromLdap($value, $type = self::STANDARD, $dateTimeAsUtc = true)
0225     {
0226         switch ($type) {
0227             case self::BOOLEAN:
0228                 return self::fromldapBoolean($value);
0229                 break;
0230             case self::GENERALIZED_TIME:
0231                 return self::fromLdapDateTime($value);
0232                 break;
0233             default:
0234                 if (is_numeric($value)) {
0235                     // prevent numeric values to be treated as date/time
0236                     return $value;
0237                 } else if ('TRUE' === $value || 'FALSE' === $value) {
0238                     return self::fromLdapBoolean($value);
0239                 }
0240                 if (preg_match('/^\d{4}[\d\+\-Z\.]*$/', $value)) {
0241                     return self::fromLdapDateTime($value, $dateTimeAsUtc);
0242                 }
0243                 try {
0244                     return self::fromLdapUnserialize($value);
0245                 } catch (UnexpectedValueException $e) { }
0246                 break;
0247         }
0248         return $value;
0249     }
0250 
0251     /**
0252      * Convert an LDAP-Generalized-Time-entry into a DateTime-Object
0253      *
0254      * CAVEAT: The DateTime-Object returned will alwasy be set to UTC-Timezone.
0255      *
0256      * @param    string        $date    The generalized-Time
0257      * @param    boolean        $asUtc    Return the DateTime with UTC timezone
0258      * @return    DateTime
0259      * @throws    InvalidArgumentException if a non-parseable-format is given
0260      */
0261     public static function fromLdapDateTime($date, $asUtc = true)
0262     {
0263         $datepart = array ();
0264         if (!preg_match('/^(\d{4})/', $date, $datepart) ) {
0265             throw new InvalidArgumentException('Invalid date format found');
0266         }
0267 
0268         if ($datepart[1] < 4) {
0269             throw new InvalidArgumentException('Invalid date format found (too short)');
0270         }
0271 
0272         $time = array (
0273             // The year is mandatory!
0274             'year'   => $datepart[1],
0275             'month'  => 1,
0276             'day'    => 1,
0277             'hour'   => 0,
0278             'minute' => 0,
0279             'second' => 0,
0280             'offdir' => '+',
0281             'offsethours' => 0,
0282             'offsetminutes' => 0
0283         );
0284 
0285         $length = strlen($date);
0286 
0287         // Check for month.
0288         if ($length >= 6) {
0289             $month = substr($date, 4, 2);
0290             if ($month < 1 || $month > 12) {
0291                 throw new InvalidArgumentException('Invalid date format found (invalid month)');
0292             }
0293             $time['month'] = $month;
0294         }
0295 
0296         // Check for day
0297         if ($length >= 8) {
0298             $day = substr($date, 6, 2);
0299             if ($day < 1 || $day > 31) {
0300                 throw new InvalidArgumentException('Invalid date format found (invalid day)');
0301             }
0302             $time['day'] = $day;
0303         }
0304 
0305         // Check for Hour
0306         if ($length >= 10) {
0307             $hour = substr($date, 8, 2);
0308             if ($hour < 0 || $hour > 23) {
0309                 throw new InvalidArgumentException('Invalid date format found (invalid hour)');
0310             }
0311             $time['hour'] = $hour;
0312         }
0313 
0314         // Check for minute
0315         if ($length >= 12) {
0316             $minute = substr($date, 10, 2);
0317             if ($minute < 0 || $minute > 59) {
0318                 throw new InvalidArgumentException('Invalid date format found (invalid minute)');
0319             }
0320             $time['minute'] = $minute;
0321         }
0322 
0323         // Check for seconds
0324         if ($length >= 14) {
0325             $second = substr($date, 12, 2);
0326             if ($second < 0 || $second > 59) {
0327                 throw new InvalidArgumentException('Invalid date format found (invalid second)');
0328             }
0329             $time['second'] = $second;
0330         }
0331 
0332         // Set Offset
0333         $offsetRegEx = '/([Z\-\+])(\d{2}\'?){0,1}(\d{2}\'?){0,1}$/';
0334         $off         = array ();
0335         if (preg_match($offsetRegEx, $date, $off)) {
0336             $offset = $off[1];
0337             if ($offset == '+' || $offset == '-') {
0338                 $time['offdir'] = $offset;
0339                 // we have an offset, so lets calculate it.
0340                 if (isset($off[2])) {
0341                     $offsetHours = substr($off[2], 0, 2);
0342                     if ($offsetHours < 0 || $offsetHours > 12) {
0343                         throw new InvalidArgumentException('Invalid date format found (invalid offset hour)');
0344                     }
0345                     $time['offsethours'] = $offsetHours;
0346                 }
0347                 if (isset($off[3])) {
0348                     $offsetMinutes = substr($off[3], 0, 2);
0349                     if ($offsetMinutes < 0 || $offsetMinutes > 59) {
0350                         throw new InvalidArgumentException('Invalid date format found (invalid offset minute)');
0351                     }
0352                     $time['offsetminutes'] = $offsetMinutes;
0353                 }
0354             }
0355         }
0356 
0357         // Raw-Data is present, so lets create a DateTime-Object from it.
0358         $offset = $time['offdir']
0359                 . str_pad($time['offsethours'],2,'0',STR_PAD_LEFT)
0360                 . str_pad($time['offsetminutes'],2,'0',STR_PAD_LEFT);
0361         $timestring = $time['year'] . '-'
0362                     . str_pad($time['month'], 2, '0', STR_PAD_LEFT) . '-'
0363                     . str_pad($time['day'], 2, '0', STR_PAD_LEFT) . ' '
0364                     . str_pad($time['hour'], 2, '0', STR_PAD_LEFT) . ':'
0365                     . str_pad($time['minute'], 2, '0', STR_PAD_LEFT) . ':'
0366                     . str_pad($time['second'], 2, '0', STR_PAD_LEFT)
0367                     . $time['offdir']
0368                     . str_pad($time['offsethours'], 2, '0', STR_PAD_LEFT)
0369                     . str_pad($time['offsetminutes'], 2, '0', STR_PAD_LEFT);
0370         $date = new DateTime($timestring);
0371         if ($asUtc) {
0372             $date->setTimezone(new DateTimeZone('UTC'));
0373         }
0374         return $date;
0375     }
0376 
0377     /**
0378      * Convert an LDAP-compatible boolean value into a PHP-compatible one
0379      *
0380      * @param    string        $value        The value to convert
0381      * @return    boolean
0382      * @throws    InvalidArgumentException
0383      */
0384     public static function fromLdapBoolean($value)
0385     {
0386         if ( 'TRUE' === $value ) {
0387             return true;
0388         } else if ( 'FALSE' === $value ) {
0389             return false;
0390         } else {
0391             throw new InvalidArgumentException('The given value is not a boolean value');
0392         }
0393     }
0394 
0395     /**
0396      * Unserialize a serialized value to return the corresponding object
0397      *
0398      * @param    string        $value    The value to convert
0399      * @return    mixed
0400      * @throws    UnexpectedValueException
0401      */
0402     public static function fromLdapUnserialize($value)
0403     {
0404         $v = @unserialize($value);
0405         if (false===$v && $value != 'b:0;') {
0406             throw new UnexpectedValueException('The given value could not be unserialized');
0407         }
0408         return $v;
0409     }
0410 }