Net_LDAP
[ class tree: Net_LDAP ] [ index: Net_LDAP ] [ all elements ]

Source for file Util.php

Documentation is available at Util.php

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

Documentation generated on Mon, 11 Mar 2019 15:09:47 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.