Support Joomla!

Joomla! 1.5 Documentation

Packages

Package: Unknown

Developer Network License

The Joomla! Developer Network content is © copyright 2006 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution- NonCommercial- ShareAlike 2.5
Source code for file /simplepie/idn/idna_convert.class.php

Documentation is available at idna_convert.class.php

  1. <?php
  2. /* ------------------------------------------------------------------------- */
  3. /* idna_convert.class.php - Encode / Decode Internationalized Domain Names   */
  4. /* (c) 2004-2005 phlyLabs, Berlin (http://phlylabs.de)                       */
  5. /* All rights reserved                                                       */
  6. /* v0.4.2                                                                    */
  7. /* ------------------------------------------------------------------------- */
  8.  
  9. // {{{ license
  10.  
  11. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker: */
  12. //
  13. // +----------------------------------------------------------------------+
  14. // | This library is free software; you can redistribute it and/or modify |
  15. // | it under the terms of the GNU Lesser General Public License as       |
  16. // | published by the Free Software Foundation; either version 2.1 of the |
  17. // | License, or (at your option) any later version.                      |
  18. // |                                                                      |
  19. // | This library is distributed in the hope that it will be useful, but  |
  20. // | WITHOUT ANY WARRANTY; without even the implied warranty of           |
  21. // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
  22. // | Lesser General Public License for more details.                      |
  23. // |                                                                      |
  24. // | You should have received a copy of the GNU Lesser General Public     |
  25. // | License along with this library; if not, write to the Free Software  |
  26. // | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 |
  27. // | USA.                                                                 |
  28. // +----------------------------------------------------------------------+
  29. //
  30.  
  31. // }}}
  32.  
  33. /**
  34.  * Encode/decode Internationalized Domain Names.
  35.  *
  36.  * The class allows to convert internationalized domain names
  37.  * (see RFC 3490 for details) as they can be used with various registries worldwide
  38.  * to be translated between their original (localized) form and their encoded form
  39.  * as it will be used in the DNS (Domain Name System).
  40.  *
  41.  * The class provides two public methods, encode() and decode(), which do exactly
  42.  * what you would expect them to do. You are allowed to use complete domain names,
  43.  * simple strings and complete email addresses as well. That means, that you might
  44.  * use any of the following notations:
  45.  *
  46.  * - www.nörgler.com
  47.  * - xn--nrgler-wxa
  48.  * - xn--brse-5qa.xn--knrz-1ra.info
  49.  *
  50.  * Unicode input might be given as either UTF-8 string, UCS-4 string or UCS-4
  51.  * array. Unicode output is available in the same formats.
  52.  * You can select your preferred format via {@link set_paramter()}.
  53.  *
  54.  * ACE input and output is always expected to be ASCII.
  55.  *
  56.  * @author  Matthias Sommerfeld <[email protected]>
  57.  * @version 0.4.2
  58.  *
  59.  */
  60.  
  61. {
  62.     // {{{ npdata
  63.         /**
  64.      * Holds all relevant mapping tables, loaded from a seperate file on construct
  65.      * See RFC3454 for details
  66.      *
  67.      * @var array 
  68.      * @access private
  69.      */
  70.     var $_np_ = array();
  71.     // }}}
  72.  
  73.  
  74.     // Internal settings, do not mess with them
  75.         var $_punycode_prefix = 'xn--';
  76.     var $_invalid_ucs =     0x80000000;
  77.     var $_max_ucs =         0x10FFFF;
  78.     var $_base =            36;
  79.     var $_tmin =            1;
  80.     var $_tmax =            26;
  81.     var $_skew =            38;
  82.     var $_damp =            700;
  83.     var $_initial_bias =    72;
  84.     var $_initial_n =       0x80;
  85.     var $_sbase =           0xAC00;
  86.     var $_lbase =           0x1100;
  87.     var $_vbase =           0x1161;
  88.     var $_tbase =           0x11a7;
  89.     var $_lcount =          19;
  90.     var $_vcount =          21;
  91.     var $_tcount =          28;
  92.     var $_ncount =          588;   // _vcount * _tcount
  93.         var $_scount =          11172; // _lcount * _tcount * _vcount
  94.         var $_error =           false;
  95.  
  96.     // See set_parameter() for details of how to change the following settings
  97.     // from within your script / application
  98.         var $_api_encoding   =  'utf8'; // Default input charset is UTF-8
  99.         var $_allow_overlong =  false;  // Overlong UTF-8 encodings are forbidden
  100.         var $_strict_mode    =  false;  // Behave strict or not
  101.  
  102.     // The constructor
  103.         function idna_convert($options = false)
  104.     {
  105.         $this->slast = $this->_sbase + $this->_lcount * $this->_vcount * $this->_tcount;
  106.         if (function_exists('file_get_contents')) {
  107.             $this->_np_ = unserialize(file_get_contents(dirname(__FILE__).'/npdata.ser'));
  108.         } else {
  109.             $this->_np_ = unserialize(join('', file(dirname(__FILE__).'/npdata.ser')));
  110.         }
  111.         // If parameters are given, pass these to the respective method
  112.         if (is_array($options)) {
  113.             return $this->set_parameter($options);
  114.         }
  115.         return true;
  116.     }
  117.  
  118.     /**
  119.     * Sets a new option value. Available options and values:
  120.     * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8,
  121.     *         'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8]
  122.     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
  123.     *             to allow this, set this parameter to true, else to false;
  124.     *             default is false.]
  125.     * [strict - true: strict mode, good for registration purposes - Causes errors
  126.     *           on failures; false: loose mode, ideal for "wildlife" applications
  127.     *           by silently ignoring errors and returning the original input instead
  128.     *
  129.     * @param    mixed     Parameter to set (string: single parameter; array of Parameter => Value pairs)
  130.     * @param    string    Value to use (if parameter 1 is a string)
  131.     * @return   boolean   true on success, false otherwise
  132.     * @access   public
  133.     */
  134.     function set_parameter($option, $value = false)
  135.     {
  136.         if (!is_array($option)) {
  137.             $option = array($option => $value);
  138.         }
  139.         foreach ($option as $k => $v) {
  140.             switch ($k) {
  141.             case 'encoding':
  142.                 switch ($v) {
  143.                 case 'utf8':
  144.                 case 'ucs4_string':
  145.                 case 'ucs4_array':
  146.                     $this->_api_encoding = $v;
  147.                     break;
  148.                 default:
  149.                     $this->_error('Set Parameter: Unknown parameter '.$v.' for option '.$k);
  150.                     return false;
  151.                 }
  152.                 break;
  153.             case 'overlong':
  154.                 $this->_allow_overlong = ($v) ? true : false;
  155.                 break;
  156.             case 'strict':
  157.                 $this->_strict_mode = ($v) ? true : false;
  158.                 break;
  159.             default:
  160.                 $this->_error('Set Parameter: Unknown option '.$k);
  161.                 return false;
  162.             }
  163.         }
  164.         return true;
  165.     }
  166.  
  167.     /**
  168.     * Decode a given ACE domain name
  169.     * @param    string   Domain name (ACE string)
  170.     *  [@param    string   Desired output encoding, see {@link set_parameter}]
  171.     * @return   string   Decoded Domain name (UTF-8 or UCS-4)
  172.     * @access   public
  173.     */
  174.     function decode($input, $one_time_encoding = false)
  175.     {
  176.         // Optionally set
  177.         if ($one_time_encoding) {
  178.             switch ($one_time_encoding) {
  179.             case 'utf8':
  180.             case 'ucs4_string':
  181.             case 'ucs4_array':
  182.                 break;
  183.             default:
  184.                 $this->_error('Unknown encoding '.$one_time_encoding);
  185.                 return false;
  186.             }
  187.         }
  188.         // Make sure to drop any newline characters around
  189.         $input = trim($input);
  190.  
  191.         // Negotiate input and try to determine, wether it is a plain string,
  192.         // an email address or something like a complete URL
  193.         if (strpos($input, '@')) { // Maybe it is an email address
  194.             // No no in strict mode
  195.             if ($this->_strict_mode) {
  196.                 $this->_error('Only simple domain name parts can be handled in strict mode');
  197.                 return false;
  198.             }
  199.             list($email_pref, $input) = explode('@', $input, 2);
  200.             $arr = explode('.', $input);
  201.             foreach ($arr as $k => $v) {
  202.                 $conv = $this->_decode($v);
  203.                 if ($conv) $arr[$k] = $conv;
  204.             }
  205.             $return = $email_pref . '@' . join('.', $arr);
  206.         } elseif (preg_match('![:\./]!', $input)) { // Or a complete domain name (with or without paths / parameters)
  207.             // No no in strict mode
  208.             if ($this->_strict_mode) {
  209.                 $this->_error('Only simple domain name parts can be handled in strict mode');
  210.                 return false;
  211.             }
  212.             $parsed = parse_url($input);
  213.             if (isset($parsed['host'])) {
  214.                 $arr = explode('.', $parsed['host']);
  215.                 foreach ($arr as $k => $v) {
  216.                     $conv = $this->_decode($v);
  217.                     if ($conv) $arr[$k] = $conv;
  218.                 }
  219.                 $parsed['host'] = join('.', $arr);
  220.                 $return =
  221.                         (empty($parsed['scheme']) ? '' : $parsed['scheme'].(strtolower($parsed['scheme']) == 'mailto' ? ':' : '://'))
  222.                         .(empty($parsed['user']) ? '' : $parsed['user'].(empty($parsed['pass']) ? '' : ':'.$parsed['pass']).'@')
  223.                         .$parsed['host']
  224.                         .(empty($parsed['port']) ? '' : ':'.$parsed['port'])
  225.                         .$parsed['path']
  226.                         .(empty($parsed['query']) ? '' : '?'.$parsed['query'])
  227.                         .(empty($parsed['fragment']) ? '' : '#'.$parsed['fragment']);
  228.             } else { // parse_url seems to have failed, try without it
  229.                 $arr = explode('.', $input);
  230.                 foreach ($arr as $k => $v) {
  231.                     $conv = $this->_decode($v);
  232.                     if ($conv) $arr[$k] = $conv;
  233.                 }
  234.                 $return = join('.', $arr);
  235.             }
  236.         } else { // Otherwise we consider it being a pure domain name string
  237.             $return = $this->_decode($input);
  238.         }
  239.         // The output is UTF-8 by default, other output formats need conversion here
  240.         // If one time encoding is given, use this, else the objects property
  241.         switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
  242.         case 'utf8':
  243.             return $return;
  244.             break;
  245.         case 'ucs4_string':
  246.            return $this->_ucs4_to_ucs4_string($this->_utf8_to_ucs4($return));
  247.            break;
  248.         case 'ucs4_array':
  249.             return $this->_utf8_to_ucs4($return);
  250.             break;
  251.         default:
  252.             $this->_error('Unsupported output format');
  253.             return false;
  254.         }
  255.     }
  256.  
  257.     /**
  258.     * Encode a given UTF-8 domain name
  259.     * @param    string   Domain name (UTF-8 or UCS-4)
  260.     *  [@param    string   Desired input encoding, see {@link set_parameter}]
  261.     * @return   string   Encoded Domain name (ACE string)
  262.     * @access   public
  263.     */
  264.     function encode($decoded, $one_time_encoding = false)
  265.     {
  266.         // Forcing conversion of input to UCS4 array
  267.         // If one time encoding is given, use this, else the objects property
  268.         switch (($one_time_encoding) ? $one_time_encoding : $this->_api_encoding) {
  269.         case 'utf8':
  270.             $decoded = $this->_utf8_to_ucs4($decoded);
  271.             break;
  272.         case 'ucs4_string':
  273.            $decoded = $this->_ucs4_string_to_ucs4($decoded);
  274.         case 'ucs4_array':
  275.            break;
  276.         default:
  277.             // $this->_error('Unsupported input format: '.$this->_api_encoding);
  278.             $this->_error('Unsupported input format');
  279.             return false;
  280.         }
  281.  
  282.         // No input, no output, what else did you expect?
  283.         if (empty($decoded)) return '';
  284.  
  285.         // Anchors for iteration
  286.         $last_begin = 0;
  287.         // Output string
  288.         $output = '';
  289.         foreach ($decoded as $k => $v) {
  290.             // Make sure to use just the plain dot
  291.             switch($v) {
  292.             case 0x3002:
  293.             case 0xFF0E:
  294.             case 0xFF61:
  295.                 $decoded[$k] = 0x2E;
  296.                 // It's right, no break here
  297.                 // The codepoints above have to be converted to dots anyway
  298.  
  299.             // Stumbling across an anchoring character
  300.             case 0x2E:
  301.             case 0x2F:
  302.             case 0x3A:
  303.             case 0x3F:
  304.             case 0x40:
  305.                 // Neither email addresses nor URLs allowed in strict mode
  306.                 if ($this->_strict_mode) {
  307.                    $this->_error('Neither email addresses nor URLs are allowed in strict mode.');
  308.                    return false;
  309.                 } else {
  310.                     // Skip first char
  311.                     if ($k) {
  312.                         $encoded = '';
  313.                         $encoded = $this->_encode(array_slice($decoded, $last_begin, (($k)-$last_begin)));
  314.                         if ($encoded) {
  315.                             $output .= $encoded;
  316.                         } else {
  317.                             $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($k)-$last_begin)));
  318.                         }
  319.                         $output .= chr($decoded[$k]);
  320.                     }
  321.                     $last_begin = $k + 1;
  322.                 }
  323.             }
  324.         }
  325.         // Catch the rest of the string
  326.         if ($last_begin) {
  327.             $inp_len = sizeof($decoded);
  328.             $encoded = '';
  329.             $encoded = $this->_encode(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
  330.             if ($encoded) {
  331.                 $output .= $encoded;
  332.             } else {
  333.                 $output .= $this->_ucs4_to_utf8(array_slice($decoded, $last_begin, (($inp_len)-$last_begin)));
  334.             }
  335.             return $output;
  336.         } else {
  337.             if ($output = $this->_encode($decoded)) {
  338.                 return $output;
  339.             } else {
  340.                 return $this->_ucs4_to_utf8($decoded);
  341.             }
  342.         }
  343.     }
  344.  
  345.     /**
  346.     * Use this method to get the last error ocurred
  347.     * @param    void 
  348.     * @return   string   The last error, that occured
  349.     * @access   public
  350.     */
  351.     function get_last_error()
  352.     {
  353.         return $this->_error;
  354.     }
  355.  
  356.     /**
  357.     * The actual decoding algorithm
  358.     * @access   private
  359.     */
  360.     function _decode($encoded)
  361.     {
  362.         // We do need to find the Punycode prefix
  363.         if (!preg_match('!^'.preg_quote($this->_punycode_prefix, '!').'!', $encoded)) {
  364.             $this->_error('This is not a punycode string');
  365.             return false;
  366.         }
  367.         $encode_test = preg_replace('!^'.preg_quote($this->_punycode_prefix, '!').'!', '', $encoded);
  368.         // If nothing left after removing the prefix, it is hopeless
  369.         if (!$encode_test) {
  370.             $this->_error('The given encoded string was empty');
  371.             return false;
  372.         }
  373.         // Find last occurence of the delimiter
  374.         $delim_pos = strrpos($encoded, '-');
  375.         if ($delim_pos > strlen($this->_punycode_prefix)) {
  376.             for ($k = strlen($this->_punycode_prefix); $k < $delim_pos; ++$k) {
  377.                 $decoded[] = ord($encoded{$k});
  378.             }
  379.         } else {
  380.             $decoded = array();
  381.         }
  382.         $deco_len = count($decoded);
  383.         $enco_len = strlen($encoded);
  384.  
  385.         // Wandering through the strings; init
  386.         $is_first = true;
  387.         $bias     = $this->_initial_bias;
  388.         $idx      = 0;
  389.         $char     = $this->_initial_n;
  390.  
  391.         for ($enco_idx = ($delim_pos) ? ($delim_pos + 1) : 0; $enco_idx < $enco_len; ++$deco_len) {
  392.             for ($old_idx = $idx, $w = 1, $k = $this->_base; 1 ; $k += $this->_base) {
  393.                 $digit = $this->_decode_digit($encoded{$enco_idx++});
  394.                 $idx += $digit * $w;
  395.                 $t = ($k <= $bias) ? $this->_tmin :
  396.                         (($k >= $bias + $this->_tmax) ? $this->_tmax : ($k - $bias));
  397.                 if ($digit < $t) break;
  398.                 $w = (int) ($w * ($this->_base - $t));
  399.             }
  400.             $bias = $this->_adapt($idx - $old_idx, $deco_len + 1, $is_first);
  401.             $is_first = false;
  402.             $char += (int) ($idx / ($deco_len + 1));
  403.             $idx %= ($deco_len + 1);
  404.             if ($deco_len > 0) {
  405.                 // Make room for the decoded char
  406.                 for ($i = $deco_len; $i > $idx; $i--) {
  407.                     $decoded[$i] = $decoded[($i - 1)];
  408.                 }
  409.             }
  410.             $decoded[$idx++] = $char;
  411.         }
  412.         return $this->_ucs4_to_utf8($decoded);
  413.     }
  414.  
  415.     /**
  416.     * The actual encoding algorithm
  417.     * @access   private
  418.     */
  419.     function _encode($decoded)
  420.     {
  421.         // We cannot encode a domain name containing the Punycode prefix
  422.         $extract = strlen($this->_punycode_prefix);
  423.         $check_pref = $this->_utf8_to_ucs4($this->_punycode_prefix);
  424.         $check_deco = array_slice($decoded, 0, $extract);
  425.  
  426.         if ($check_pref == $check_deco) {
  427.             $this->_error('This is already a punycode string');
  428.             return false;
  429.         }
  430.         // We will not try to encode strings consisting of basic code points only
  431.         $encodable = false;
  432.         foreach ($decoded as $k => $v) {
  433.             if ($v > 0x7a) {
  434.                 $encodable = true;
  435.                 break;
  436.             }
  437.         }
  438.         if (!$encodable) {
  439.             $this->_error('The given string does not contain encodable chars');
  440.             return false;
  441.         }
  442.  
  443.         // Do NAMEPREP
  444.         $decoded = $this->_nameprep($decoded);
  445.         if (!$decoded || !is_array($decoded)) return false; // NAMEPREP failed
  446.  
  447.         $deco_len  = count($decoded);
  448.         if (!$deco_len) return false; // Empty array
  449.  
  450.         $codecount = 0; // How many chars have been consumed
  451.  
  452.         $encoded = '';
  453.         // Copy all basic code points to output
  454.         for ($i = 0; $i < $deco_len; ++$i) {
  455.             $test = $decoded[$i];
  456.             // Will match [0-9a-zA-Z-]
  457.             if ((0x2F < $test && $test < 0x40)
  458.                     || (0x40 < $test && $test < 0x5B)
  459.                     || (0x60 < $test && $test <= 0x7B)
  460.                     || (0x2D == $test)) {
  461.                 $encoded .= chr($decoded[$i]);
  462.                 $codecount++;
  463.             }
  464.         }
  465.         if ($codecount == $deco_len) return $encoded; // All codepoints were basic ones
  466.  
  467.         // Start with the prefix; copy it to output
  468.         $encoded = $this->_punycode_prefix.$encoded;
  469.  
  470.         // If we have basic code points in output, add an hyphen to the end
  471.         if ($codecount) $encoded .= '-';
  472.  
  473.         // Now find and encode all non-basic code points
  474.         $is_first  = true;
  475.         $cur_code  = $this->_initial_n;
  476.         $bias      = $this->_initial_bias;
  477.         $delta     = 0;
  478.         while ($codecount < $deco_len) {
  479.             // Find the smallest code point >= the current code point and
  480.             // remember the last ouccrence of it in the input
  481.             for ($i = 0, $next_code = $this->_max_ucs; $i < $deco_len; $i++) {
  482.                 if ($decoded[$i] >= $cur_code && $decoded[$i] <= $next_code) {
  483.                     $next_code = $decoded[$i];
  484.                 }
  485.             }
  486.  
  487.             $delta += ($next_code - $cur_code) * ($codecount + 1);
  488.             $cur_code = $next_code;
  489.  
  490.             // Scan input again and encode all characters whose code point is $cur_code
  491.             for ($i = 0; $i < $deco_len; $i++) {
  492.                 if ($decoded[$i] < $cur_code) {
  493.                     $delta++;
  494.                 } elseif ($decoded[$i] == $cur_code) {
  495.                     for ($q = $delta, $k = $this->_base; 1; $k += $this->_base) {
  496.                         $t = ($k <= $bias) ? $this->_tmin :
  497.                                 (($k >= $bias + $this->_tmax) ? $this->_tmax : $k - $bias);
  498.                         if ($q < $t) break;
  499.                         $encoded .= $this->_encode_digit(ceil($t + (($q - $t) % ($this->_base - $t))));
  500.                         $q = ($q - $t) / ($this->_base - $t);
  501.                     }
  502.                     $encoded .= $this->_encode_digit($q);
  503.                     $bias = $this->_adapt($delta, $codecount+1, $is_first);
  504.                     $codecount++;
  505.                     $delta = 0;
  506.                     $is_first = false;
  507.                 }
  508.             }
  509.             $delta++;
  510.             $cur_code++;
  511.         }
  512.         return $encoded;
  513.     }
  514.  
  515.     /**
  516.     * Adapt the bias according to the current code point and position
  517.     * @access   private
  518.     */
  519.     function _adapt($delta, $npoints, $is_first)
  520.     {
  521.         $delta = (int) ($is_first ? ($delta / $this->_damp) : ($delta / 2));
  522.         $delta += (int) ($delta / $npoints);
  523.         for ($k = 0; $delta > (($this->_base - $this->_tmin) * $this->_tmax) / 2; $k += $this->_base) {
  524.             $delta = (int) ($delta / ($this->_base - $this->_tmin));
  525.         }
  526.         return (int) ($k + ($this->_base - $this->_tmin + 1) * $delta / ($delta + $this->_skew));
  527.     }
  528.  
  529.     /**
  530.     * Encoding a certain digit
  531.     * @access   private
  532.     */
  533.     function _encode_digit($d)
  534.     {
  535.         return chr($d + 22 + 75 * ($d < 26));
  536.     }
  537.  
  538.     /**
  539.     * Decode a certain digit
  540.     * @access   private
  541.     */
  542.     function _decode_digit($cp)
  543.     {
  544.         $cp = ord($cp);
  545.         return ($cp - 48 < 10) ? $cp - 22 : (($cp - 65 < 26) ? $cp - 65 : (($cp - 97 < 26) ? $cp - 97 : $this->_base));
  546.     }
  547.  
  548.     /**
  549.     * Internal error handling method
  550.     * @access   private
  551.     */
  552.     function _error($error = '')
  553.     {
  554.         $this->_error = $error;
  555.     }
  556.  
  557.     /**
  558.     * Do Nameprep according to RFC3491 and RFC3454
  559.     * @param    array    Unicode Characters
  560.     * @return   string   Unicode Characters, Nameprep'd
  561.     * @access   private
  562.     */
  563.     function _nameprep($input)
  564.     {
  565.         $output = array();
  566.         $error = false;
  567.         //
  568.         // Mapping
  569.         // Walking through the input array, performing the required steps on each of
  570.         // the input chars and putting the result into the output array
  571.         // While mapping required chars we apply the cannonical ordering
  572.  
  573.         // $this->_show_hex($input);
  574.         foreach ($input as $v) {
  575.             // Map to nothing == skip that code point
  576.             if (in_array($v, $this->_np_['map_nothing'])) continue;
  577.  
  578.             // Try to find prohibited input
  579.             if (in_array($v, $this->_np_['prohibit']) || in_array($v, $this->_np_['general_prohibited'])) {
  580.                 $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
  581.                 return false;
  582.             }
  583.             foreach ($this->_np_['prohibit_ranges'] as $range) {
  584.                 if ($range[0] <= $v && $v <= $range[1]) {
  585.                     $this->_error('NAMEPREP: Prohibited input U+'.sprintf('%08X', $v));
  586.                     return false;
  587.                 }
  588.             }
  589.             //
  590.             // Hangul syllable decomposition
  591.             if (0xAC00 <= $v && $v <= 0xD7AF) {
  592.                 foreach ($this->_hangul_decompose($v) as $out) {
  593.                     $output[] = $out;
  594.                 }
  595.             // There's a decomposition mapping for that code point
  596.             } elseif (isset($this->_np_['replacemaps'][$v])) {
  597.                 foreach ($this->_apply_cannonical_ordering($this->_np_['replacemaps'][$v]) as $out) {
  598.                     $output[] = $out;
  599.                 }
  600.             } else {
  601.                 $output[] = $v;
  602.             }
  603.         }
  604.         //
  605.         // Combine code points
  606.         //
  607.         $last_class   = 0;
  608.         $last_starter = 0;
  609.         $out_len      = count($output);
  610.         for ($i = 0; $i < $out_len; ++$i) {
  611.             $class = $this->_get_combining_class($output[$i]);
  612.             if ((!$last_class || $last_class != $class) && $class) {
  613.                 // Try to match
  614.                 $seq_len = $i - $last_starter;
  615.                 $out = $this->_combine(array_slice($output, $last_starter, $seq_len));
  616.                 // On match: Replace the last starter with the composed character and remove
  617.                 // the now redundant non-starter(s)
  618.                 if ($out) {
  619.                     $output[$last_starter] = $out;
  620.                     if (count($out) != $seq_len) {
  621.                         for ($j = $i+1; $j < $out_len; ++$j) {
  622.                             $output[$j-1] = $output[$j];
  623.                         }
  624.                         unset($output[$out_len]);
  625.                     }
  626.                     // Rewind the for loop by one, since there can be more possible compositions
  627.                     $i--;
  628.                     $out_len--;
  629.                     $last_class = ($i == $last_starter) ? 0 : $this->_get_combining_class($output[$i-1]);
  630.                     continue;
  631.                 }
  632.             }
  633.             if (!$class) { // The current class is 0
  634.                 $last_starter = $i;
  635.             }
  636.             $last_class = $class;
  637.         }
  638.         return $output;
  639.     }
  640.  
  641.     /**
  642.     * Decomposes a Hangul syllable
  643.     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
  644.     * @param    integer  32bit UCS4 code point
  645.     * @return   array    Either Hangul Syllable decomposed or original 32bit value as one value array
  646.     * @access   private
  647.     */
  648.     function _hangul_decompose($char)
  649.     {
  650.         $sindex = $char - $this->_sbase;
  651.         if ($sindex < 0 || $sindex >= $this->_scount) {
  652.             return array($char);
  653.         }
  654.         $result = array();
  655.         $T = $this->_tbase + $sindex % $this->_tcount;
  656.         $result[] = (int) ($this->_lbase + $sindex / $this->_ncount);
  657.         $result[] = (int) ($this->_vbase + ($sindex % $this->_ncount) / $this->_tcount);
  658.         if ($T != $this->_tbase) $result[] = $T;
  659.         return $result;
  660.     }
  661.  
  662.     /**
  663.     * Ccomposes a Hangul syllable
  664.     * (see http://www.unicode.org/unicode/reports/tr15/#Hangul
  665.     * @param    array    Decomposed UCS4 sequence
  666.     * @return   array    UCS4 sequence with syllables composed
  667.     * @access   private
  668.     */
  669.     function _hangul_compose($input)
  670.     {
  671.         $inp_len = count($input);
  672.         if (!$inp_len) return array();
  673.         $result = array();
  674.         $last = $input[0];
  675.         $result[] = $last; // copy first char from input to output
  676.  
  677.         for ($i = 1; $i < $inp_len; ++$i) {
  678.             $char = $input[$i];
  679.  
  680.             // Find out, wether two current characters from L and V
  681.             $lindex = $last - $this->_lbase;
  682.             if (0 <= $lindex && $lindex < $this->_lcount) {
  683.                 $vindex = $char - $this->_vbase;
  684.                 if (0 <= $vindex && $vindex < $this->_vcount) {
  685.                     // create syllable of form LV
  686.                     $last = ($this->_sbase + ($lindex * $this->_vcount + $vindex) * $this->_tcount);
  687.                     $out_off = count($result) - 1;
  688.                     $result[$out_off] = $last; // reset last
  689.                     continue; // discard char
  690.                 }
  691.             }
  692.  
  693.             // Find out, wether two current characters are LV and T
  694.             $sindex = $last - $this->_sbase;
  695.             if (0 <= $sindex && $sindex < $this->_scount && ($sindex % $this->_tcount) == 0) {
  696.                 $tindex = $char - $this->_tbase;
  697.                 if (0 <= $tindex && $tindex <= $this->_tcount) {
  698.                     // create syllable of form LVT
  699.                     $last += $tindex;
  700.                     $out_off = count($result) - 1;
  701.                     $result[$out_off] = $last; // reset last
  702.                     continue; // discard char
  703.                 }
  704.             }
  705.             // if neither case was true, just add the character
  706.             $last = $char;
  707.             $result[] = $char;
  708.         }
  709.         return $result;
  710.     }
  711.  
  712.     /**
  713.     * Returns the combining class of a certain wide char
  714.     * @param    integer    Wide char to check (32bit integer)
  715.     * @return   integer    Combining class if found, else 0
  716.     * @access   private
  717.     */
  718.     function _get_combining_class($char)
  719.     {
  720.         return isset($this->_np_['norm_combcls'][$char]) ? $this->_np_['norm_combcls'][$char] : 0;
  721.     }
  722.  
  723.     /**
  724.     * Apllies the cannonical ordering of a decomposed UCS4 sequence
  725.     * @param    array      Decomposed UCS4 sequence
  726.     * @return   array      Ordered USC4 sequence
  727.     * @access   private
  728.     */
  729.     function _apply_cannonical_ordering($input)
  730.     {
  731.         $swap = true;
  732.         $size = count($input);
  733.         while ($swap) {
  734.             $swap = false;
  735.             $last = $this->_get_combining_class($input[0]);
  736.             for ($i = 0; $i < $size - 1; ++$i) {
  737.                 $next = $this->_get_combining_class($input[$i+1]);
  738.                 if ($next != 0 && $last > $next) {
  739.                     // Move item leftward until it fits
  740.                     for ($j = $i + 1; $j > 0; --$j) {
  741.                         if ($this->_get_combining_class($input[$j - 1]) <= $next) break;
  742.                         $t = $input[$j];
  743.                         $input[$j] = $input[$j - 1];
  744.                         $input[$j - 1] = $t;
  745.                         $swap = 1;
  746.                     }
  747.                     // Reentering the loop looking at the old character again
  748.                     $next = $last;
  749.                 }
  750.                 $last = $next;
  751.             }
  752.         }
  753.         return $input;
  754.     }
  755.  
  756.     /**
  757.     * Do composition of a sequence of starter and non-starter
  758.     * @param    array      UCS4 Decomposed sequence
  759.     * @return   array      Ordered USC4 sequence
  760.     * @access   private
  761.     */
  762.     function _combine($input)
  763.     {
  764.         $inp_len = count($input);
  765.         // Is it a Hangul syllable?
  766.         if (1 != $inp_len) {
  767.             $hangul = $this->_hangul_compose($input);
  768.             if (count($hangul) != $inp_len) return $hangul; // This place is probably wrong
  769.         }
  770.         foreach ($this->_np_['replacemaps'] as $np_src => $np_target) {
  771.             if ($np_target[0] != $input[0]) continue;
  772.             if (count($np_target) != $inp_len) continue;
  773.             $hit = false;
  774.             foreach ($input as $k2 => $v2) {
  775.                 if ($v2 == $np_target[$k2]) {
  776.                     $hit = true;
  777.                 } else {
  778.                     $hit = false;
  779.                     break;
  780.                 }
  781.             }
  782.             if ($hit) return $np_src;
  783.         }
  784.         return false;
  785.     }
  786.  
  787.     /**
  788.     * This converts an UTF-8 encoded string to its UCS-4 representation
  789.     * By talking about UCS-4 "strings" we mean arrays of 32bit integers representing
  790.     * each of the "chars". This is due to PHP not being able to handle strings with
  791.     * bit depth different from 8. This apllies to the reverse method _ucs4_to_utf8(), too.
  792.     * The following UTF-8 encodings are supported:
  793.     * bytes bits  representation
  794.     * 1        7  0xxxxxxx
  795.     * 2       11  110xxxxx 10xxxxxx
  796.     * 3       16  1110xxxx 10xxxxxx 10xxxxxx
  797.     * 4       21  11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  798.     * 5       26  111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  799.     * 6       31  1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  800.     * Each x represents a bit that can be used to store character data.
  801.     * The five and six byte sequences are part of Annex D of ISO/IEC 10646-1:2000
  802.     * @access   private
  803.     */
  804.     function _utf8_to_ucs4($input)
  805.     {
  806.         $output = array();
  807.         $out_len = 0;
  808.         $inp_len = strlen($input);
  809.         $mode = 'next';
  810.         $test = 'none';
  811.         for ($k = 0; $k < $inp_len; ++$k) {
  812.             $v = ord($input{$k}); // Extract byte from input string
  813.  
  814.             if ($v < 128) { // We found an ASCII char - put into stirng as is
  815.                 $output[$out_len] = $v;
  816.                 ++$out_len;
  817.                 if ('add' == $mode) {
  818.                     $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
  819.                     return false;
  820.                 }
  821.                 continue;
  822.             }
  823.             if ('next' == $mode) { // Try to find the next start byte; determine the width of the Unicode char
  824.                 $start_byte = $v;
  825.                 $mode = 'add';
  826.                 $test = 'range';
  827.                 if ($v >> 5 == 6) { // &110xxxxx 10xxxxx
  828.                     $next_byte = 0; // Tells, how many times subsequent bitmasks must rotate 6bits to the left
  829.                     $v = ($v - 192) << 6;
  830.                 } elseif ($v >> 4 == 14) { // &1110xxxx 10xxxxxx 10xxxxxx
  831.                     $next_byte = 1;
  832.                     $v = ($v - 224) << 12;
  833.                 } elseif ($v >> 3 == 30) { // &11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  834.                     $next_byte = 2;
  835.                     $v = ($v - 240) << 18;
  836.                 } elseif ($v >> 2 == 62) { // &111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  837.                     $next_byte = 3;
  838.                     $v = ($v - 248) << 24;
  839.                 } elseif ($v >> 1 == 126) { // &1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
  840.                     $next_byte = 4;
  841.                     $v = ($v - 252) << 30;
  842.                 } else {
  843.                     $this->_error('This might be UTF-8, but I don\'t understand it at byte '.$k);
  844.                     return false;
  845.                 }
  846.                 if ('add' == $mode) {
  847.                     $output[$out_len] = (int) $v;
  848.                     ++$out_len;
  849.                     continue;
  850.                 }
  851.             }
  852.             if ('add' == $mode) {
  853.                 if (!$this->_allow_overlong && $test == 'range') {
  854.                     $test = 'none';
  855.                     if (($v < 0xA0 && $start_byte == 0xE0) || ($v < 0x90 && $start_byte == 0xF0) || ($v > 0x8F && $start_byte == 0xF4)) {
  856.                         $this->_error('Bogus UTF-8 character detected (out of legal range) at byte '.$k);
  857.                         return false;
  858.                     }
  859.                 }
  860.                 if ($v >> 6 == 2) { // Bit mask must be 10xxxxxx
  861.                     $v = ($v - 128) << ($next_byte * 6);
  862.                     $output[($out_len - 1)] += $v;
  863.                     --$next_byte;
  864.                 } else {
  865.                     $this->_error('Conversion from UTF-8 to UCS-4 failed: malformed input at byte '.$k);
  866.                     return false;
  867.                 }
  868.                 if ($next_byte < 0) {
  869.                     $mode = 'next';
  870.                 }
  871.             }
  872.         } // for
  873.         return $output;
  874.     }
  875.  
  876.     /**
  877.     * Convert UCS-4 string into UTF-8 string
  878.     * See _utf8_to_ucs4() for details
  879.     * @access   private
  880.     */
  881.     function _ucs4_to_utf8($input)
  882.     {
  883.         $output = '';
  884.         foreach ($input as $v) {
  885.             // $v = ord($v);
  886.             if ($v < 128) { // 7bit are transferred literally
  887.                 $output .= chr($v);
  888.             } elseif ($v < (1 << 11)) { // 2 bytes
  889.                 $output .= chr(192 + ($v >> 6)) . chr(128 + ($v & 63));
  890.             } elseif ($v < (1 << 16)) { // 3 bytes
  891.                 $output .= chr(224 + ($v >> 12)) . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
  892.             } elseif ($v < (1 << 21)) { // 4 bytes
  893.                 $output .= chr(240 + ($v >> 18)) . chr(128 + (($v >> 12) & 63))
  894.                          . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
  895.             } elseif ($v < (1 << 26)) { // 5 bytes
  896.                 $output .= chr(248 + ($v >> 24)) . chr(128 + (($v >> 18) & 63))
  897.                          . chr(128 + (($v >> 12) & 63)) . chr(128 + (($v >> 6) & 63))
  898.                          . chr(128 + ($v & 63));
  899.             } elseif ($v < (1 << 31)) { // 6 bytes
  900.                 $output .= chr(252 + ($v >> 30)) . chr(128 + (($v >> 24) & 63))
  901.                          . chr(128 + (($v >> 18) & 63)) . chr(128 + (($v >> 12) & 63))
  902.                          . chr(128 + (($v >> 6) & 63)) . chr(128 + ($v & 63));
  903.             } else {
  904.                 $this->_error('Conversion from UCS-4 to UTF-8 failed: malformed input at byte '.$k);
  905.                 return false;
  906.             }
  907.         }
  908.         return $output;
  909.     }
  910.  
  911.     /**
  912.      * Convert UCS-4 array into UCS-4 string
  913.      *
  914.      * @access   private
  915.      */
  916.     function _ucs4_to_ucs4_string($input)
  917.     {
  918.         $output = '';
  919.         // Take array values and split output to 4 bytes per value
  920.         // The bit mask is 255, which reads &11111111
  921.         foreach ($input as $v) {
  922.             $output .= chr(($v >> 24) & 255)
  923.                      . chr(($v >> 16) & 255)
  924.                      . chr(($v >> 8) & 255)
  925.                      . chr($v & 255);
  926.         }
  927.         return $output;
  928.     }
  929.  
  930.     /**
  931.      * Convert UCS-4 strin into UCS-4 garray
  932.      *
  933.      * @access   private
  934.      */
  935.     function _ucs4_string_to_ucs4($input)
  936.     {
  937.         $output = array();
  938.  
  939.         $inp_len = strlen($input);
  940.         // Input length must be dividable by 4
  941.         if ($inp_len % 4) {
  942.             $this->_error('Input UCS4 string is broken');
  943.             return false;
  944.         }
  945.  
  946.         // Empty input - return empty output
  947.         if (!$inp_len) return $output;
  948.  
  949.         for ($i = 0, $out_len = -1; $i < $inp_len; ++$i) {
  950.             // Increment output position every 4 input bytes
  951.             if (!($i % 4)) {
  952.                 $out_len++;
  953.                 $output[$out_len] = 0;
  954.             }
  955.             $output[$out_len] += ord($input{$i}) << (8 * (3 - ($i % 4) ) );
  956.         }
  957.         return $output;
  958.     }
  959. }
  960.  
  961. /**
  962. * Adapter class for aligning the API of idna_convert with that of
  963. * Net_IDNA
  964. * @author  Matthias Sommerfeld <[email protected]>
  965. */
  966. class Net_IDNA_php4 extends idna_convert
  967. {
  968.     /**
  969.     * Sets a new option value. Available options and values:
  970.     * [encoding - Use either UTF-8, UCS4 as array or UCS4 as string as input ('utf8' for UTF-8,
  971.     *         'ucs4_string' and 'ucs4_array' respectively for UCS4); The output is always UTF-8]
  972.     * [overlong - Unicode does not allow unnecessarily long encodings of chars,
  973.     *             to allow this, set this parameter to true, else to false;
  974.     *             default is false.]
  975.     * [strict - true: strict mode, good for registration purposes - Causes errors
  976.     *           on failures; false: loose mode, ideal for "wildlife" applications
  977.     *           by silently ignoring errors and returning the original input instead
  978.     *
  979.     * @param    mixed     Parameter to set (string: single parameter; array of Parameter => Value pairs)
  980.     * @param    string    Value to use (if parameter 1 is a string)
  981.     * @return   boolean   true on success, false otherwise
  982.     * @access   public
  983.     */
  984.     function setParams($option, $param = false)
  985.     {
  986.         return $this->IC->set_parameters($option, $param);
  987.     }
  988. }
  989.  
  990. ?>

Documentation generated on Mon, 05 Mar 2007 21:07:30 +0000 by phpDocumentor 1.3.1