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

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