| 1 |
lars |
1 |
<?php
|
|
|
2 |
/* vim: set expandtab tabstop=4 shiftwidth=4: */
|
|
|
3 |
/**
|
|
|
4 |
* File containing the Net_LDAP2_Util interface class.
|
|
|
5 |
*
|
|
|
6 |
* PHP version 5
|
|
|
7 |
*
|
|
|
8 |
* @category Net
|
|
|
9 |
* @package Net_LDAP2
|
|
|
10 |
* @author Benedikt Hallinger <beni@php.net>
|
|
|
11 |
* @copyright 2009 Benedikt Hallinger
|
|
|
12 |
* @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPLv3
|
|
|
13 |
* @version SVN: $Id: Util.php 286718 2009-08-03 07:30:49Z beni $
|
|
|
14 |
* @link http://pear.php.net/package/Net_LDAP2/
|
|
|
15 |
*/
|
|
|
16 |
|
|
|
17 |
/**
|
|
|
18 |
* Includes
|
|
|
19 |
*/
|
|
|
20 |
require_once 'PEAR.php';
|
|
|
21 |
|
|
|
22 |
/**
|
|
|
23 |
* Utility Class for Net_LDAP2
|
|
|
24 |
*
|
|
|
25 |
* This class servers some functionality to the other classes of Net_LDAP2 but most of
|
|
|
26 |
* the methods can be used separately as well.
|
|
|
27 |
*
|
|
|
28 |
* @category Net
|
|
|
29 |
* @package Net_LDAP2
|
|
|
30 |
* @author Benedikt Hallinger <beni@php.net>
|
|
|
31 |
* @license http://www.gnu.org/copyleft/lesser.html LGPL
|
|
|
32 |
* @link http://pear.php.net/package/Net_LDAP22/
|
|
|
33 |
*/
|
|
|
34 |
class Net_LDAP2_Util extends PEAR
|
|
|
35 |
{
|
|
|
36 |
/**
|
|
|
37 |
* Constructor
|
|
|
38 |
*
|
|
|
39 |
* @access public
|
|
|
40 |
*/
|
|
|
41 |
public function __construct()
|
|
|
42 |
{
|
|
|
43 |
// We do nothing here, since all methods can be called statically.
|
|
|
44 |
// In Net_LDAP <= 0.7, we needed a instance of Util, because
|
|
|
45 |
// it was possible to do utf8 encoding and decoding, but this
|
|
|
46 |
// has been moved to the LDAP class. The constructor remains only
|
|
|
47 |
// here to document the downward compatibility of creating an instance.
|
|
|
48 |
}
|
|
|
49 |
|
|
|
50 |
/**
|
|
|
51 |
* Explodes the given DN into its elements
|
|
|
52 |
*
|
|
|
53 |
* {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence
|
|
|
54 |
* of Relative Distinguished Names (RDNs), which themselves
|
|
|
55 |
* are sets of Attributes. For each RDN a array is constructed where the RDN part is stored.
|
|
|
56 |
*
|
|
|
57 |
* For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to:
|
|
|
58 |
* <kbd>array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' )</kbd>
|
|
|
59 |
*
|
|
|
60 |
* [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of
|
|
|
61 |
* the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded
|
|
|
62 |
* and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to
|
|
|
63 |
* the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to:
|
|
|
64 |
* [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ];
|
|
|
65 |
* See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER.
|
|
|
66 |
*
|
|
|
67 |
* It also performs the following operations on the given DN:
|
|
|
68 |
* - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair
|
|
|
69 |
* and strings beginning with "#".
|
|
|
70 |
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
|
|
|
71 |
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
|
|
|
72 |
*
|
|
|
73 |
* OPTIONS is a list of name/value pairs, valid options are:
|
|
|
74 |
* casefold Controls case folding of attribute types names.
|
|
|
75 |
* Attribute values are not affected by this option.
|
|
|
76 |
* The default is to uppercase. Valid values are:
|
|
|
77 |
* lower Lowercase attribute types names.
|
|
|
78 |
* upper Uppercase attribute type names. This is the default.
|
|
|
79 |
* none Do not change attribute type names.
|
|
|
80 |
* reverse If TRUE, the RDN sequence is reversed.
|
|
|
81 |
* onlyvalues If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo')
|
|
|
82 |
*
|
|
|
83 |
|
|
|
84 |
* @param string $dn The DN that should be exploded
|
|
|
85 |
* @param array $options Options to use
|
|
|
86 |
*
|
|
|
87 |
* @static
|
|
|
88 |
* @return array Parts of the exploded DN
|
|
|
89 |
* @todo implement BER
|
|
|
90 |
*/
|
|
|
91 |
public static function ldap_explode_dn($dn, $options = array('casefold' => 'upper'))
|
|
|
92 |
{
|
|
|
93 |
if (!isset($options['onlyvalues'])) $options['onlyvalues'] = false;
|
|
|
94 |
if (!isset($options['reverse'])) $options['reverse'] = false;
|
|
|
95 |
if (!isset($options['casefold'])) $options['casefold'] = 'upper';
|
|
|
96 |
|
|
|
97 |
// Escaping of DN and stripping of "OID."
|
|
|
98 |
$dn = self::canonical_dn($dn, array('casefold' => $options['casefold']));
|
|
|
99 |
|
|
|
100 |
// splitting the DN
|
|
|
101 |
$dn_array = preg_split('/(?<=[^\\\\]),/', $dn);
|
|
|
102 |
|
|
|
103 |
// clear wrong splitting (possibly we have split too much)
|
|
|
104 |
// /!\ Not clear, if this is neccessary here
|
|
|
105 |
//$dn_array = self::correct_dn_splitting($dn_array, ',');
|
|
|
106 |
|
|
|
107 |
// construct subarrays for multivalued RDNs and unescape DN value
|
|
|
108 |
// also convert to output format and apply casefolding
|
|
|
109 |
foreach ($dn_array as $key => $value) {
|
|
|
110 |
$value_u = self::unescape_dn_value($value);
|
|
|
111 |
$rdns = self::split_rdn_multival($value_u[0]);
|
|
|
112 |
if (count($rdns) > 1) {
|
|
|
113 |
// MV RDN!
|
|
|
114 |
foreach ($rdns as $subrdn_k => $subrdn_v) {
|
|
|
115 |
// Casefolding
|
|
|
116 |
if ($options['casefold'] == 'upper') $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $subrdn_v);
|
|
|
117 |
if ($options['casefold'] == 'lower') $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $subrdn_v);
|
|
|
118 |
|
|
|
119 |
if ($options['onlyvalues']) {
|
|
|
120 |
preg_match('/(.+?)(?<!\\\\)=(.+)/', $subrdn_v, $matches);
|
|
|
121 |
$rdn_ocl = $matches[1];
|
|
|
122 |
$rdn_val = $matches[2];
|
|
|
123 |
$unescaped = self::unescape_dn_value($rdn_val);
|
|
|
124 |
$rdns[$subrdn_k] = $unescaped[0];
|
|
|
125 |
} else {
|
|
|
126 |
$unescaped = self::unescape_dn_value($subrdn_v);
|
|
|
127 |
$rdns[$subrdn_k] = $unescaped[0];
|
|
|
128 |
}
|
|
|
129 |
}
|
|
|
130 |
|
|
|
131 |
$dn_array[$key] = $rdns;
|
|
|
132 |
} else {
|
|
|
133 |
// normal RDN
|
|
|
134 |
|
|
|
135 |
// Casefolding
|
|
|
136 |
if ($options['casefold'] == 'upper') $value = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $value);
|
|
|
137 |
if ($options['casefold'] == 'lower') $value = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $value);
|
|
|
138 |
|
|
|
139 |
if ($options['onlyvalues']) {
|
|
|
140 |
preg_match('/(.+?)(?<!\\\\)=(.+)/', $value, $matches);
|
|
|
141 |
$dn_ocl = $matches[1];
|
|
|
142 |
$dn_val = $matches[2];
|
|
|
143 |
$unescaped = self::unescape_dn_value($dn_val);
|
|
|
144 |
$dn_array[$key] = $unescaped[0];
|
|
|
145 |
} else {
|
|
|
146 |
$unescaped = self::unescape_dn_value($value);
|
|
|
147 |
$dn_array[$key] = $unescaped[0];
|
|
|
148 |
}
|
|
|
149 |
}
|
|
|
150 |
}
|
|
|
151 |
|
|
|
152 |
if ($options['reverse']) {
|
|
|
153 |
return array_reverse($dn_array);
|
|
|
154 |
} else {
|
|
|
155 |
return $dn_array;
|
|
|
156 |
}
|
|
|
157 |
}
|
|
|
158 |
|
|
|
159 |
/**
|
|
|
160 |
* Escapes a DN value according to RFC 2253
|
|
|
161 |
*
|
|
|
162 |
* Escapes the given VALUES according to RFC 2253 so that they can be safely used in LDAP DNs.
|
|
|
163 |
* The characters ",", "+", """, "\", "<", ">", ";", "#", "=" with a special meaning in RFC 2252
|
|
|
164 |
* are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair.
|
|
|
165 |
* Finally all leading and trailing spaces are converted to sequences of \20.
|
|
|
166 |
*
|
|
|
167 |
* @param array $values An array containing the DN values that should be escaped
|
|
|
168 |
*
|
|
|
169 |
* @static
|
|
|
170 |
* @return array The array $values, but escaped
|
|
|
171 |
*/
|
|
|
172 |
public static function escape_dn_value($values = array())
|
|
|
173 |
{
|
|
|
174 |
// Parameter validation
|
|
|
175 |
if (!is_array($values)) {
|
|
|
176 |
$values = array($values);
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
foreach ($values as $key => $val) {
|
|
|
180 |
// Escaping of filter meta characters
|
|
|
181 |
$val = str_replace('\\', '\\\\', $val);
|
|
|
182 |
$val = str_replace(',', '\,', $val);
|
|
|
183 |
$val = str_replace('+', '\+', $val);
|
|
|
184 |
$val = str_replace('"', '\"', $val);
|
|
|
185 |
$val = str_replace('<', '\<', $val);
|
|
|
186 |
$val = str_replace('>', '\>', $val);
|
|
|
187 |
$val = str_replace(';', '\;', $val);
|
|
|
188 |
$val = str_replace('#', '\#', $val);
|
|
|
189 |
$val = str_replace('=', '\=', $val);
|
|
|
190 |
|
|
|
191 |
// ASCII < 32 escaping
|
|
|
192 |
$val = self::asc2hex32($val);
|
|
|
193 |
|
|
|
194 |
// Convert all leading and trailing spaces to sequences of \20.
|
|
|
195 |
if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) {
|
|
|
196 |
$val = $matches[2];
|
|
|
197 |
for ($i = 0; $i < strlen($matches[1]); $i++) {
|
|
|
198 |
$val = '\20'.$val;
|
|
|
199 |
}
|
|
|
200 |
for ($i = 0; $i < strlen($matches[3]); $i++) {
|
|
|
201 |
$val = $val.'\20';
|
|
|
202 |
}
|
|
|
203 |
}
|
|
|
204 |
|
|
|
205 |
if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
|
|
|
206 |
|
|
|
207 |
$values[$key] = $val;
|
|
|
208 |
}
|
|
|
209 |
|
|
|
210 |
return $values;
|
|
|
211 |
}
|
|
|
212 |
|
|
|
213 |
/**
|
|
|
214 |
* Undoes the conversion done by escape_dn_value().
|
|
|
215 |
*
|
|
|
216 |
* Any escape sequence starting with a baskslash - hexpair or special character -
|
|
|
217 |
* will be transformed back to the corresponding character.
|
|
|
218 |
*
|
|
|
219 |
* @param array $values Array of DN Values
|
|
|
220 |
*
|
|
|
221 |
* @return array Same as $values, but unescaped
|
|
|
222 |
* @static
|
|
|
223 |
*/
|
|
|
224 |
public static function unescape_dn_value($values = array())
|
|
|
225 |
{
|
|
|
226 |
// Parameter validation
|
|
|
227 |
if (!is_array($values)) {
|
|
|
228 |
$values = array($values);
|
|
|
229 |
}
|
|
|
230 |
|
|
|
231 |
foreach ($values as $key => $val) {
|
|
|
232 |
// strip slashes from special chars
|
|
|
233 |
$val = str_replace('\\\\', '\\', $val);
|
|
|
234 |
$val = str_replace('\,', ',', $val);
|
|
|
235 |
$val = str_replace('\+', '+', $val);
|
|
|
236 |
$val = str_replace('\"', '"', $val);
|
|
|
237 |
$val = str_replace('\<', '<', $val);
|
|
|
238 |
$val = str_replace('\>', '>', $val);
|
|
|
239 |
$val = str_replace('\;', ';', $val);
|
|
|
240 |
$val = str_replace('\#', '#', $val);
|
|
|
241 |
$val = str_replace('\=', '=', $val);
|
|
|
242 |
|
|
|
243 |
// Translate hex code into ascii
|
|
|
244 |
$values[$key] = self::hex2asc($val);
|
|
|
245 |
}
|
|
|
246 |
|
|
|
247 |
return $values;
|
|
|
248 |
}
|
|
|
249 |
|
|
|
250 |
/**
|
|
|
251 |
* Returns the given DN in a canonical form
|
|
|
252 |
*
|
|
|
253 |
* Returns false if DN is not a valid Distinguished Name.
|
|
|
254 |
* DN can either be a string or an array
|
|
|
255 |
* as returned by ldap_explode_dn, which is useful when constructing a DN.
|
|
|
256 |
* The DN array may have be indexed (each array value is a OCL=VALUE pair)
|
|
|
257 |
* or associative (array key is OCL and value is VALUE).
|
|
|
258 |
*
|
|
|
259 |
* It performs the following operations on the given DN:
|
|
|
260 |
* - Removes the leading 'OID.' characters if the type is an OID instead of a name.
|
|
|
261 |
* - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair.
|
|
|
262 |
* - Converts all leading and trailing spaces in values to be \20.
|
|
|
263 |
* - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order.
|
|
|
264 |
*
|
|
|
265 |
* OPTIONS is a list of name/value pairs, valid options are:
|
|
|
266 |
* casefold Controls case folding of attribute type names.
|
|
|
267 |
* Attribute values are not affected by this option. The default is to uppercase.
|
|
|
268 |
* Valid values are:
|
|
|
269 |
* lower Lowercase attribute type names.
|
|
|
270 |
* upper Uppercase attribute type names. This is the default.
|
|
|
271 |
* none Do not change attribute type names.
|
|
|
272 |
* [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}.
|
|
|
273 |
* reverse If TRUE, the RDN sequence is reversed.
|
|
|
274 |
* separator Separator to use between RDNs. Defaults to comma (',').
|
|
|
275 |
*
|
|
|
276 |
* Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test,
|
|
|
277 |
* because an empty string evaluates to false. Use the "===" operator instead.
|
|
|
278 |
*
|
|
|
279 |
* @param array|string $dn The DN
|
|
|
280 |
* @param array $options Options to use
|
|
|
281 |
*
|
|
|
282 |
* @static
|
|
|
283 |
* @return false|string The canonical DN or FALSE
|
|
|
284 |
* @todo implement option mbcescape
|
|
|
285 |
*/
|
|
|
286 |
public static function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ','))
|
|
|
287 |
{
|
|
|
288 |
if ($dn === '') return $dn; // empty DN is valid!
|
|
|
289 |
|
|
|
290 |
// options check
|
|
|
291 |
if (!isset($options['reverse'])) {
|
|
|
292 |
$options['reverse'] = false;
|
|
|
293 |
} else {
|
|
|
294 |
$options['reverse'] = true;
|
|
|
295 |
}
|
|
|
296 |
if (!isset($options['casefold'])) $options['casefold'] = 'upper';
|
|
|
297 |
if (!isset($options['separator'])) $options['separator'] = ',';
|
|
|
298 |
|
|
|
299 |
|
|
|
300 |
if (!is_array($dn)) {
|
|
|
301 |
// It is not clear to me if the perl implementation splits by the user defined
|
|
|
302 |
// separator or if it just uses this separator to construct the new DN
|
|
|
303 |
$dn = preg_split('/(?<=[^\\\\])'.$options['separator'].'/', $dn);
|
|
|
304 |
|
|
|
305 |
// clear wrong splitting (possibly we have split too much)
|
|
|
306 |
$dn = self::correct_dn_splitting($dn, $options['separator']);
|
|
|
307 |
} else {
|
|
|
308 |
// Is array, check, if the array is indexed or associative
|
|
|
309 |
$assoc = false;
|
|
|
310 |
foreach ($dn as $dn_key => $dn_part) {
|
|
|
311 |
if (!is_int($dn_key)) {
|
|
|
312 |
$assoc = true;
|
|
|
313 |
}
|
|
|
314 |
}
|
|
|
315 |
// convert to indexed, if associative array detected
|
|
|
316 |
if ($assoc) {
|
|
|
317 |
$newdn = array();
|
|
|
318 |
foreach ($dn as $dn_key => $dn_part) {
|
|
|
319 |
if (is_array($dn_part)) {
|
|
|
320 |
ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative
|
|
|
321 |
$newdn[] = $dn_part; // copy array as-is, so we can resolve it later
|
|
|
322 |
} else {
|
|
|
323 |
$newdn[] = $dn_key.'='.$dn_part;
|
|
|
324 |
}
|
|
|
325 |
}
|
|
|
326 |
$dn =& $newdn;
|
|
|
327 |
}
|
|
|
328 |
}
|
|
|
329 |
|
|
|
330 |
// Escaping and casefolding
|
|
|
331 |
foreach ($dn as $pos => $dnval) {
|
|
|
332 |
if (is_array($dnval)) {
|
|
|
333 |
// subarray detected, this means very surely, that we had
|
|
|
334 |
// a multivalued dn part, which must be resolved
|
|
|
335 |
$dnval_new = '';
|
|
|
336 |
foreach ($dnval as $subkey => $subval) {
|
|
|
337 |
// build RDN part
|
|
|
338 |
if (!is_int($subkey)) {
|
|
|
339 |
$subval = $subkey.'='.$subval;
|
|
|
340 |
}
|
|
|
341 |
$subval_processed = self::canonical_dn($subval);
|
|
|
342 |
if (false === $subval_processed) return false;
|
|
|
343 |
$dnval_new .= $subval_processed.'+';
|
|
|
344 |
}
|
|
|
345 |
$dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus
|
|
|
346 |
} else {
|
|
|
347 |
// try to split multivalued RDNS into array
|
|
|
348 |
$rdns = self::split_rdn_multival($dnval);
|
|
|
349 |
if (count($rdns) > 1) {
|
|
|
350 |
// Multivalued RDN was detected!
|
|
|
351 |
// The RDN value is expected to be correctly split by split_rdn_multival().
|
|
|
352 |
// It's time to sort the RDN and build the DN!
|
|
|
353 |
$rdn_string = '';
|
|
|
354 |
sort($rdns, SORT_STRING); // Sort RDN keys alphabetically
|
|
|
355 |
foreach ($rdns as $rdn) {
|
|
|
356 |
$subval_processed = self::canonical_dn($rdn);
|
|
|
357 |
if (false === $subval_processed) return false;
|
|
|
358 |
$rdn_string .= $subval_processed.'+';
|
|
|
359 |
}
|
|
|
360 |
|
|
|
361 |
$dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus
|
|
|
362 |
|
|
|
363 |
} else {
|
|
|
364 |
// no multivalued RDN!
|
|
|
365 |
// split at first unescaped "="
|
|
|
366 |
$dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2);
|
|
|
367 |
$ocl = ltrim($dn_comp[0]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma)
|
|
|
368 |
$val = $dn_comp[1];
|
|
|
369 |
|
|
|
370 |
// strip 'OID.', otherwise apply casefolding and escaping
|
|
|
371 |
if (substr(strtolower($ocl), 0, 4) == 'oid.') {
|
|
|
372 |
$ocl = substr($ocl, 4);
|
|
|
373 |
} else {
|
|
|
374 |
if ($options['casefold'] == 'upper') $ocl = strtoupper($ocl);
|
|
|
375 |
if ($options['casefold'] == 'lower') $ocl = strtolower($ocl);
|
|
|
376 |
$ocl = self::escape_dn_value(array($ocl));
|
|
|
377 |
$ocl = $ocl[0];
|
|
|
378 |
}
|
|
|
379 |
|
|
|
380 |
// escaping of dn-value
|
|
|
381 |
$val = self::escape_dn_value(array($val));
|
|
|
382 |
$val = str_replace('/', '\/', $val[0]);
|
|
|
383 |
|
|
|
384 |
$dn[$pos] = $ocl.'='.$val;
|
|
|
385 |
}
|
|
|
386 |
}
|
|
|
387 |
}
|
|
|
388 |
|
|
|
389 |
if ($options['reverse']) $dn = array_reverse($dn);
|
|
|
390 |
return implode($options['separator'], $dn);
|
|
|
391 |
}
|
|
|
392 |
|
|
|
393 |
/**
|
|
|
394 |
* Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters.
|
|
|
395 |
*
|
|
|
396 |
* Any control characters with an ACII code < 32 as well as the characters with special meaning in
|
|
|
397 |
* LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a
|
|
|
398 |
* backslash followed by two hex digits representing the hexadecimal value of the character.
|
|
|
399 |
*
|
|
|
400 |
* @param array $values Array of values to escape
|
|
|
401 |
*
|
|
|
402 |
* @static
|
|
|
403 |
* @return array Array $values, but escaped
|
|
|
404 |
*/
|
|
|
405 |
public static function escape_filter_value($values = array())
|
|
|
406 |
{
|
|
|
407 |
// Parameter validation
|
|
|
408 |
if (!is_array($values)) {
|
|
|
409 |
$values = array($values);
|
|
|
410 |
}
|
|
|
411 |
|
|
|
412 |
foreach ($values as $key => $val) {
|
|
|
413 |
// Escaping of filter meta characters
|
|
|
414 |
$val = str_replace('\\', '\5c', $val);
|
|
|
415 |
$val = str_replace('*', '\2a', $val);
|
|
|
416 |
$val = str_replace('(', '\28', $val);
|
|
|
417 |
$val = str_replace(')', '\29', $val);
|
|
|
418 |
|
|
|
419 |
// ASCII < 32 escaping
|
|
|
420 |
$val = self::asc2hex32($val);
|
|
|
421 |
|
|
|
422 |
if (null === $val) $val = '\0'; // apply escaped "null" if string is empty
|
|
|
423 |
|
|
|
424 |
$values[$key] = $val;
|
|
|
425 |
}
|
|
|
426 |
|
|
|
427 |
return $values;
|
|
|
428 |
}
|
|
|
429 |
|
|
|
430 |
/**
|
|
|
431 |
* Undoes the conversion done by {@link escape_filter_value()}.
|
|
|
432 |
*
|
|
|
433 |
* Converts any sequences of a backslash followed by two hex digits into the corresponding character.
|
|
|
434 |
*
|
|
|
435 |
* @param array $values Array of values to escape
|
|
|
436 |
*
|
|
|
437 |
* @static
|
|
|
438 |
* @return array Array $values, but unescaped
|
|
|
439 |
*/
|
|
|
440 |
public static function unescape_filter_value($values = array())
|
|
|
441 |
{
|
|
|
442 |
// Parameter validation
|
|
|
443 |
if (!is_array($values)) {
|
|
|
444 |
$values = array($values);
|
|
|
445 |
}
|
|
|
446 |
|
|
|
447 |
foreach ($values as $key => $value) {
|
|
|
448 |
// Translate hex code into ascii
|
|
|
449 |
$values[$key] = self::hex2asc($value);
|
|
|
450 |
}
|
|
|
451 |
|
|
|
452 |
return $values;
|
|
|
453 |
}
|
|
|
454 |
|
|
|
455 |
/**
|
|
|
456 |
* Converts all ASCII chars < 32 to "\HEX"
|
|
|
457 |
*
|
|
|
458 |
* @param string $string String to convert
|
|
|
459 |
*
|
|
|
460 |
* @static
|
|
|
461 |
* @return string
|
|
|
462 |
*/
|
|
|
463 |
public static function asc2hex32($string)
|
|
|
464 |
{
|
|
|
465 |
for ($i = 0; $i < strlen($string); $i++) {
|
|
|
466 |
$char = substr($string, $i, 1);
|
|
|
467 |
if (ord($char) < 32) {
|
|
|
468 |
$hex = dechex(ord($char));
|
|
|
469 |
if (strlen($hex) == 1) $hex = '0'.$hex;
|
|
|
470 |
$string = str_replace($char, '\\'.$hex, $string);
|
|
|
471 |
}
|
|
|
472 |
}
|
|
|
473 |
return $string;
|
|
|
474 |
}
|
|
|
475 |
|
|
|
476 |
/**
|
|
|
477 |
* Converts all Hex expressions ("\HEX") to their original ASCII characters
|
|
|
478 |
*
|
|
|
479 |
* @param string $string String to convert
|
|
|
480 |
*
|
|
|
481 |
* @static
|
|
|
482 |
* @author beni@php.net, heavily based on work from DavidSmith@byu.net
|
|
|
483 |
* @return string
|
|
|
484 |
*/
|
|
|
485 |
public static function hex2asc($string)
|
|
|
486 |
{
|
|
|
487 |
$string = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $string);
|
|
|
488 |
return $string;
|
|
|
489 |
}
|
|
|
490 |
|
|
|
491 |
/**
|
|
|
492 |
* Split an multivalued RDN value into an Array
|
|
|
493 |
*
|
|
|
494 |
* A RDN can contain multiple values, spearated by a plus sign.
|
|
|
495 |
* This function returns each separate ocl=value pair of the RDN part.
|
|
|
496 |
*
|
|
|
497 |
* If no multivalued RDN is detected, an array containing only
|
|
|
498 |
* the original rdn part is returned.
|
|
|
499 |
*
|
|
|
500 |
* For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to:
|
|
|
501 |
* <kbd>array([0] => 'OU=Sales', [1] => 'CN=J. Smith')</kbd>
|
|
|
502 |
*
|
|
|
503 |
* The method trys to be smart if it encounters unescaped "+" characters, but may fail,
|
|
|
504 |
* so ensure escaped "+"es in attr names and attr values.
|
|
|
505 |
*
|
|
|
506 |
* [BUG] If you have a multivalued RDN with unescaped plus characters
|
|
|
507 |
* and there is a unescaped plus sign at the end of an value followed by an
|
|
|
508 |
* attribute name containing an unescaped plus, then you will get wrong splitting:
|
|
|
509 |
* $rdn = 'OU=Sales+C+N=J. Smith';
|
|
|
510 |
* returns:
|
|
|
511 |
* array('OU=Sales+C', 'N=J. Smith');
|
|
|
512 |
* The "C+" is treaten as value of the first pair instead as attr name of the second pair.
|
|
|
513 |
* To prevent this, escape correctly.
|
|
|
514 |
*
|
|
|
515 |
* @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar)
|
|
|
516 |
*
|
|
|
517 |
* @static
|
|
|
518 |
* @return array Array with the components of the multivalued RDN or Error
|
|
|
519 |
*/
|
|
|
520 |
public static function split_rdn_multival($rdn)
|
|
|
521 |
{
|
|
|
522 |
$rdns = preg_split('/(?<!\\\\)\+/', $rdn);
|
|
|
523 |
$rdns = self::correct_dn_splitting($rdns, '+');
|
|
|
524 |
return array_values($rdns);
|
|
|
525 |
}
|
|
|
526 |
|
|
|
527 |
/**
|
|
|
528 |
* Splits a attribute=value syntax into an array
|
|
|
529 |
*
|
|
|
530 |
* The split will occur at the first unescaped '=' character.
|
|
|
531 |
*
|
|
|
532 |
* @param string $attr Attribute and Value Syntax
|
|
|
533 |
*
|
|
|
534 |
* @return array Indexed array: 0=attribute name, 1=attribute value
|
|
|
535 |
*/
|
|
|
536 |
public static function split_attribute_string($attr)
|
|
|
537 |
{
|
|
|
538 |
return preg_split('/(?<!\\\\)=/', $attr, 2);
|
|
|
539 |
}
|
|
|
540 |
|
|
|
541 |
/**
|
|
|
542 |
* Corrects splitting of dn parts
|
|
|
543 |
*
|
|
|
544 |
* @param array $dn Raw DN array
|
|
|
545 |
* @param array $separator Separator that was used when splitting
|
|
|
546 |
*
|
|
|
547 |
* @return array Corrected array
|
|
|
548 |
* @access protected
|
|
|
549 |
*/
|
|
|
550 |
protected static function correct_dn_splitting($dn = array(), $separator = ',')
|
|
|
551 |
{
|
|
|
552 |
foreach ($dn as $key => $dn_value) {
|
|
|
553 |
$dn_value = $dn[$key]; // refresh value (foreach caches!)
|
|
|
554 |
// if the dn_value is not in attr=value format, then we had an
|
|
|
555 |
// unescaped separator character inside the attr name or the value.
|
|
|
556 |
// We assume, that it was the attribute value.
|
|
|
557 |
// [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class
|
|
|
558 |
// must remain independent from the other classes or connections.
|
|
|
559 |
if (!preg_match('/.+(?<!\\\\)=.+/', $dn_value)) {
|
|
|
560 |
unset($dn[$key]);
|
|
|
561 |
if (array_key_exists($key-1, $dn)) {
|
|
|
562 |
$dn[$key-1] = $dn[$key-1].$separator.$dn_value; // append to previous attr value
|
|
|
563 |
} else {
|
|
|
564 |
$dn[$key+1] = $dn_value.$separator.$dn[$key+1]; // first element: prepend to next attr name
|
|
|
565 |
}
|
|
|
566 |
}
|
|
|
567 |
}
|
|
|
568 |
return array_values($dn);
|
|
|
569 |
}
|
|
|
570 |
}
|
|
|
571 |
|
|
|
572 |
?>
|