Encoder.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  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. * @subpackage Ldif
  18. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * Zend_Ldap_Ldif_Encoder provides methods to encode and decode LDAP data into/from LDIF.
  24. *
  25. * @category Zend
  26. * @package Zend_Ldap
  27. * @subpackage Ldif
  28. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  29. * @license http://framework.zend.com/license/new-bsd New BSD License
  30. */
  31. class Zend_Ldap_Ldif_Encoder
  32. {
  33. /**
  34. * Additional options used during encoding
  35. *
  36. * @var array
  37. */
  38. protected $_options = array(
  39. 'sort' => true,
  40. 'version' => 1,
  41. 'wrap' => 78
  42. );
  43. protected $_versionWritten = false;
  44. /**
  45. * Constructor.
  46. *
  47. * @param array $options Additional options used during encoding
  48. * @return void
  49. */
  50. protected function __construct(array $options = array())
  51. {
  52. $this->_options = array_merge($this->_options, $options);
  53. }
  54. /**
  55. * Decodes the string $string into an array of LDIF items
  56. *
  57. * @param string $string
  58. * @return array
  59. */
  60. public static function decode($string)
  61. {
  62. $encoder = new self(array());
  63. return $encoder->_decode($string);
  64. }
  65. /**
  66. * Decodes the string $string into an array of LDIF items
  67. *
  68. * @param string $string
  69. * @return array
  70. */
  71. protected function _decode($string)
  72. {
  73. $items = array();
  74. $item = array();
  75. $last = null;
  76. foreach (explode("\n", $string) as $line) {
  77. $line = rtrim($line, "\x09\x0A\x0D\x00\x0B");
  78. $matches = array();
  79. if (substr($line, 0, 1) === ' ' && $last !== null) {
  80. $last[2] .= substr($line, 1);
  81. } else if (substr($line, 0, 1) === '#') {
  82. continue;
  83. } else if (preg_match('/^([a-z0-9;-]+)(:[:<]?\s*)([^:<]*)$/i', $line, $matches)) {
  84. $name = strtolower($matches[1]);
  85. $type = trim($matches[2]);
  86. $value = $matches[3];
  87. if ($last !== null) {
  88. $this->_pushAttribute($last, $item);
  89. }
  90. if ($name === 'version') {
  91. continue;
  92. } else if (count($item) > 0 && $name === 'dn') {
  93. $items[] = $item;
  94. $item = array();
  95. $last = null;
  96. }
  97. $last = array($name, $type, $value);
  98. } else if (trim($line) === '') {
  99. continue;
  100. }
  101. }
  102. if ($last !== null) {
  103. $this->_pushAttribute($last, $item);
  104. }
  105. $items[] = $item;
  106. return (count($items)>1) ? $items : $items[0];
  107. }
  108. /**
  109. * Pushes a decoded attribute to the stack
  110. *
  111. * @param array $attribute
  112. * @param array $entry
  113. */
  114. protected function _pushAttribute(array $attribute, array &$entry)
  115. {
  116. $name = $attribute[0];
  117. $type = $attribute[1];
  118. $value = $attribute[2];
  119. if ($type === '::') {
  120. $value = base64_decode($value);
  121. }
  122. if ($name === 'dn') {
  123. $entry[$name] = $value;
  124. } else if (isset($entry[$name]) && $value !== '') {
  125. $entry[$name][] = $value;
  126. } else {
  127. $entry[$name] = ($value !== '') ? array($value) : array();
  128. }
  129. }
  130. /**
  131. * Encode $value into a LDIF representation
  132. *
  133. * @param mixed $value The value to be encoded
  134. * @param array $options Additional options used during encoding
  135. * @return string The encoded value
  136. */
  137. public static function encode($value, array $options = array())
  138. {
  139. $encoder = new self($options);
  140. return $encoder->_encode($value);
  141. }
  142. /**
  143. * Recursive driver which determines the type of value to be encoded
  144. * and then dispatches to the appropriate method.
  145. *
  146. * @param $value mixed The value to be encoded
  147. * @return string Encoded value
  148. */
  149. protected function _encode($value)
  150. {
  151. if (is_scalar($value)) {
  152. return $this->_encodeString($value);
  153. } else if (is_array($value)) {
  154. return $this->_encodeAttributes($value);
  155. } else if ($value instanceof Zend_Ldap_Node) {
  156. return $value->toLdif($this->_options);
  157. }
  158. return null;
  159. }
  160. /**
  161. * Encodes $string according to RFC2849
  162. *
  163. * @link http://www.faqs.org/rfcs/rfc2849.html
  164. *
  165. * @param string $string
  166. * @param boolen $base64
  167. * @return string
  168. */
  169. protected function _encodeString($string, &$base64 = null)
  170. {
  171. $string = (string)$string;
  172. if (empty($string)) {
  173. return '';
  174. }
  175. /*
  176. * SAFE-INIT-CHAR = %x01-09 / %x0B-0C / %x0E-1F /
  177. * %x21-39 / %x3B / %x3D-7F
  178. * ; any value <= 127 except NUL, LF, CR,
  179. * ; SPACE, colon (":", ASCII 58 decimal)
  180. * ; and less-than ("<" , ASCII 60 decimal)
  181. *
  182. */
  183. $unsafe_init_char = array(0, 10, 13, 32, 58, 60);
  184. /*
  185. * SAFE-CHAR = %x01-09 / %x0B-0C / %x0E-7F
  186. * ; any value <= 127 decimal except NUL, LF,
  187. * ; and CR
  188. */
  189. $unsafe_char = array(0, 10, 13);
  190. $base64 = false;
  191. for ($i = 0; $i < strlen($string); $i++) {
  192. $char = ord(substr($string, $i, 1));
  193. if ($char >= 127) {
  194. $base64 = true;
  195. break;
  196. } else if ($i === 0 && in_array($char, $unsafe_init_char)) {
  197. $base64 = true;
  198. break;
  199. } else if (in_array($char, $unsafe_char)) {
  200. $base64 = true;
  201. break;
  202. }
  203. }
  204. // Test for ending space
  205. if (substr($string, -1) == ' ') {
  206. $base64 = true;
  207. }
  208. if ($base64 === true) {
  209. $string = base64_encode($string);
  210. }
  211. return $string;
  212. }
  213. /**
  214. * Encodes an attribute with $name and $value according to RFC2849
  215. *
  216. * @link http://www.faqs.org/rfcs/rfc2849.html
  217. *
  218. * @param string $name
  219. * @param array|string $value
  220. * @return string
  221. */
  222. protected function _encodeAttribute($name, $value)
  223. {
  224. if (!is_array($value)) {
  225. $value = array($value);
  226. }
  227. $output = '';
  228. if (count($value) < 1) {
  229. return $name . ': ';
  230. }
  231. foreach ($value as $v) {
  232. $base64 = null;
  233. $v = $this->_encodeString($v, $base64);
  234. $attribute = $name . ':';
  235. if ($base64 === true) {
  236. $attribute .= ': ' . $v;
  237. } else {
  238. $attribute .= ' ' . $v;
  239. }
  240. if (isset($this->_options['wrap']) && strlen($attribute) > $this->_options['wrap']) {
  241. $attribute = trim(chunk_split($attribute, $this->_options['wrap'], PHP_EOL . ' '));
  242. }
  243. $output .= $attribute . PHP_EOL;
  244. }
  245. return trim($output, PHP_EOL);
  246. }
  247. /**
  248. * Encodes a collection of attributes according to RFC2849
  249. *
  250. * @link http://www.faqs.org/rfcs/rfc2849.html
  251. *
  252. * @param array $attributes
  253. * @return string
  254. */
  255. protected function _encodeAttributes(array $attributes)
  256. {
  257. $string = '';
  258. $attributes = array_change_key_case($attributes, CASE_LOWER);
  259. if (!$this->_versionWritten && array_key_exists('dn', $attributes) && isset($this->_options['version'])
  260. && array_key_exists('objectclass', $attributes)) {
  261. $string .= sprintf('version: %d', $this->_options['version']) . PHP_EOL;
  262. $this->_versionWritten = true;
  263. }
  264. if (isset($this->_options['sort']) && $this->_options['sort'] === true) {
  265. ksort($attributes, SORT_STRING);
  266. if (array_key_exists('objectclass', $attributes)) {
  267. $oc = $attributes['objectclass'];
  268. unset($attributes['objectclass']);
  269. $attributes = array_merge(array('objectclass' => $oc), $attributes);
  270. }
  271. if (array_key_exists('dn', $attributes)) {
  272. $dn = $attributes['dn'];
  273. unset($attributes['dn']);
  274. $attributes = array_merge(array('dn' => $dn), $attributes);
  275. }
  276. }
  277. foreach ($attributes as $key => $value) {
  278. $string .= $this->_encodeAttribute($key, $value) . PHP_EOL;
  279. }
  280. return trim($string, PHP_EOL);
  281. }
  282. }