前言

域名的编码解码

什么是PunyCode

英文域名在IIS或Apache中的虚拟主机设置,可以直接使用英文域名,如webmasterhome.cn。 中文域名在IIS或Apache中应如何设置主机头呢
在进行设置之前要先把中文域名转化成为PunyCode编码,然后在主机头设置里面填上中文域名所对应的PunyCode编码(xn–fiqsC146Ah4ex80Cro7A.com)便可。 新的国际化域名的标准在网域名称编码上,为了保留向下兼容性及不影响现有的应用程序协议,因此将多国语言域名转成ASCII编码

目标

前置条件

正式内容

<?php

class Punycode
{
    const TMIN=1;
    const TMAX=26;
    const BASE = 36;
    const INITIAL_N = 128;
    const INITIAL_BIAS = 72;
    const DAMP = 700;
    const SKEW = 38;
    const DELIMITER = '-';
    const MAXINT = 2147483647;

    //encode编码时候的错误返回值
    const ERROR_BAD_INPUT  = -1;
    const ERROR_BIG_OUTPUT = -2;
    const ERROR_OVERFLOW   = -3;

    /**
     * 配置
     * 1: 使用iconv
     * 2: iconv Unicode-1-1
     * 3: 使用mb_convert_encoding(较通用,需mb_string支持)
     */
    const CHARSET_MODE = 3;


    /**
     * punycode编码
     * @param string $input
     *      输入的字符传不能有空格.
     *      以.分割几段来进行处理
     *      对中文全角字母不区分大小写
     *      中文字符不能包含@#$%之类的字符
     * @param string $code 字符编码
     */
    public static function encode($input, $code="UTF-8")
    {
        $input=trim($input);
        //由于包括CNNIC在内的中国技术人员的倡导和推动,此次发布的国际标准在英文句号"."的基础上,另外增加了中文句号作为多语种域名各级之间的分隔符。
        //这主要是为了满足中文域名的独特需求,省去输入中文域名时中英文之间切换的麻烦。
        $input=str_replace("。",".",$input);
        if (!self::hasChinese($input)) return $input;
        //$input = stripslashes($input);

        $strarr=array();
        $strarr=explode(".",$input);
        $output="";
        for ($i=0;$i<count($strarr);$i++){
            $tmp_output = self::encodeHandle($strarr[$i], $code);
            if(!$tmp_output || $tmp_output<0)	return;

            if ($i!=count($strarr)-1) $tmp_output.=".";
            $output.=$tmp_output;
        }
        return $output;
    }

    /**
     * 判断域名是否包含中文字符
     * @param string $domain
     * @return bool
     */
    public static function hasChinese($domain)
    {
        $len=strlen($domain);
        for($i=0;$i<$len;$i++){
            if(ord(substr($domain,$i,1))>128)
                return true;
        }
        return false;
    }

    /**
     * unicode 编码
     * @param string $str
     * @return string $newstr
     */
    public static function unicodeEncode($str)
    {
        //$newstr = chr(255).chr(254);
        $newstr = '';
        for ($i=0; $i<strlen($str); $i+=2) {
            $newstr .= chr(ord(substr($str, $i+1, 1))).chr(ord(substr($str, $i, 1)));
        }
        return $newstr;
    }

    /**
     * 判断数字是否是ansi字符,禁止 @#$%^ 之类的符号
     * @param string $char
     * @return bool
     */
    public static function isBasic($char)
    {
        return $char<128;
        //return $aa=(($c==45) || ($c>=48 && $c<=57) || ($c>=65 && $c<=90) || ($c>=97 && $c<=122));
    }

    /**
     * 处理数字,大小写字母
     * @param string $bcp
     * @param string $flag
     */
    public static function basicEncode($bcp, $flag)
    {
        //if ($bcp-97<26) $bcp-=32;
        $bcp-=($bcp-97<26)<<5;
        return $bcp+((!$flag && ($bcp-65<26))<<5);
    }

    /**
     * 处理数字,大小写字母
     * @param char $d
     * @param char $flag
     * @return char
     */
    public static function digitEncode($d,$flag)
    {
        $s1=(int)($d<26);//0或1
        $s2=(int)($flag!=0);
        return $d+22+75*$s1-($s2<<5);
    }


    /**
     * 编码的参数
     */
    public static function adapt($delta,$numpoints,$firsttime)
    {
        $delta= $firsttime ? (int)($delta/self::DAMP) : (int)($delta/2);

        $delta+=(int)($delta/$numpoints);

        for ($k=0; $delta>(int)(((self::BASE-self::TMIN)*self::TMAX)/2); $k+=self::BASE){
            $delta = (int)($delta/(self::BASE-self::TMIN));
        }
        return $k+(int)(((self::BASE-self::TMIN+1)*$delta)/($delta+self::SKEW));
    }


    //主要的编码转换工作
    public static function encodeHandle($input, $code="UTF-8")
    {
        $oldinput=$input;
        //将输入字符转换为Unicode.cnnic用GBK,GBK是GB2312的扩展.取代GBK的是GB18030
        if (self::CHARSET_MODE == 1) {
            $input = iconv($code, "Unicode", $input);
        } elseif (self::CHARSET_MODE == 2) {
            $input = iconv($code, "Unicode-1-1", $input);
            $input = self::unicodeEncode($input);
        } elseif (self::CHARSET_MODE == 3) {
            $input = mb_convert_encoding($input, "Unicode", $code);
            $input = self::unicodeEncode($input);
        }

        //去掉前面的255和254
        if (ord(substr($input, 0)) == 255 && ord(substr($input, 1)) == 254)
            $input = substr($input, 2);

        $n        = self::INITIAL_N;
        $delta    = 0;
        $out      = 0;
        $max_out  = 256;
        $bias     = self::INITIAL_BIAS;
        $output   = "";
        $inputlen = strlen($input);
        $ar       = array();  //用一个数组来保存字的编码
        for ($i=0; $i<$inputlen; $i+=2){
            $tmpar = ord($input{$i+1}) * 256 + ord($input{$i});
            //32为空格的Unicode,空格后不作处理(参考cnnic).输入是最好限制输入不能包含空格
            if ($tmpar == 32)
                break;
            $ar[]=$tmpar;
        }
        $inputlen = count($ar);
        //将大写全角字符转为小写
        for ($i=0; $i<$inputlen; $i++) {
            if ($ar[$i] >= 65313 && $ar[$i] <= 65338)
                $ar[$i] = $ar[$i]+32;
        }
        //$arr=array();
        $case_flags = '';
        for ($j=0; $j<$inputlen; $j++){
            if (self::isBasic($ar[$j])){
                if ($max_out-$out<2)
                    return self::ERROR_BIG_OUTPUT;
                //$arr[]=$case_flags ? punycode_encode_basic($input{$j}, $case_flags{$j}) : ord($input{$j});
                $output.=$case_flags ? chr(self::basicEncode($ar[$j], $case_flags{$j})) : chr($ar[$j]);//不考虑大小写
                $out++;
            }
        }

        $h=$b=$out;
        if ($b>0) {
            $output .= self::DELIMITER;
            $out++;
        }

        while ($h<$inputlen){
            for ($m=self::MAXINT,$j=0;$j<$inputlen;$j++){
                if ($ar[$j]>=$n && $ar[$j]<$m) $m=$ar[$j];
            }

            if ($m-$n>(self::MAXINT-$delta)/($h+1)) return self::ERROR_OVERFLOW;
            $delta+=($m-$n)*($h+1);
            $n=$m;

            for ($j=0;$j<$inputlen;$j++){
                if ($ar[$j]<$n){
                    if (++$delta==0) return self::ERROR_OVERFLOW;
                }

                if ($ar[$j]==$n){
                    $q=$delta;
                    for ($k=self::BASE; ;$k+=self::BASE){
                        if ($out>=$max_out) return self::ERROR_BIG_OUTPUT;
                        $t=$k<=$bias ? self::TMIN:($k>=($bias+self::TMAX) ? self::TMAX:($k-$bias));
                        if ($q<$t) break;
                        //$arr[]=punycode_encode_digit($t+($q-$t)%(punycode_BASE-$t),0);
                        $output.=chr(self::digitEncode($t+($q-$t)%(self::BASE-$t),0));
                        $out++;
                        $q=(int)(($q-$t)/(self::BASE-$t));
                    }

                    //$arr[]=punycode_encode_digit($q,$case_flags && $case_flags{$j});
                    //$output.=chr(punycode_encode_digit($q,$case_flags && $case_flags{$j}));
                    $output.=chr(self::digitEncode($q,0));//忽略大小写(0输出小写字母,1输出大写字母)
                    $out++;
                    $bias=self::adapt($delta, $h+1, $h==$b);
                    $delta=0;
                    $h++;
                }
            }
            $delta++;
            $n++;
        }
        if (substr($output,-1,1)==self::DELIMITER) $output=substr($output,0,-1);
        if ($output!=$oldinput) $output="xn--".$output;
        return $output;
    }


//=============================================

    /**
     * punycode 解码
     * @param string $input
     * @param string $code
     * @return string
     */
    public static function decode($input, $code="UTF-8")
    {
        $input=trim($input);
        if (self::hasChinese($input))
            return $input;

        $strarr=array();
        $strarr=explode(".",$input);
        $output="";
        for ($i=0;$i<count($strarr);$i++){
            if (substr($strarr[$i],0,4)=="xn--"){
                $input=substr($strarr[$i],4);
                $outtmp=self::decodeHandle($input, $code);
                if (!$outtmp || $outtmp<0)  return;
                $output.=$outtmp;
            }else{
                $output.=$strarr[$i];
            }
            if ($i!=count($strarr)-1) $output.=".";
        }//for
        return $output;
    }

    /**
     * 主要的解码转换工作
     * @param string $input
     * @param string $code
     * @return string
     */
    public static function decodeHandle($input, $code="UTF-8")
    {
        $n        = self::INITIAL_N;
        $out      = 0;
        $i        = 0;
        $max_out  = 256;
        $bias     = self::INITIAL_BIAS;
        $inputlen = strlen($input);
        $outputa  = array();

        $b = 0;
        for ($j=0;$j<$inputlen;$j++)
            if ($input{$j}=="-")
                $b=$j;

        for ($j=0; $j<$b; $j++) {
            /*不考虑大小写
            if (ord($input{$j})-65<26)
                $case_flags[$out]="1";
            else
                $case_flags[$out]="0";
            */
            if (ord($input{$j})>128) return -1;
            //$output.=$input{$j};
            $outputa[]=ord($input{$j});
            $out++;
        }

        for ($in=$b>0?$b+1:0;$in<$inputlen;$out++) {
            $oldi=$i;
            $w=1;
            for ($k=self::BASE; ;$k+=self::BASE){
                if ($in>=$inputlen)
                    return -2;
                $digit = self::digitDecode(ord($input{$in++}));
                if ($digit>=self::BASE) return -3;
                if ($digit>(self::MAXINT-$i)/$w) return -4;
                $i=$i+$digit*$w;
                $t=$k<=$bias ? self::TMIN : ($k>=($bias+self::TMAX) ? self::TMAX:($k-$bias));
                if ($digit<$t) break;
                if ($w>self::MAXINT/(self::BASE-$t)) return -5;
                $w=$w*(self::BASE-$t);
            }

            $bias = self::adapt($i-$oldi,$out+1,$oldi==0);
            if ($i/($out+1)>self::MAXINT-$n)
                return -6;
            $n+=(int)($i/($out+1));
            $i=$i%($out+1);

            if ($out>=$max_out) return -7;

            /*不考虑大小写
                    for ($q=0;$q<$out-$i;$i++) $case_flags[$i+1+$q]= $case_flags[$i+$q];

                    if ($input[$in-1]-65<26)
                        $case_flags[$i]='1';
                    else
                        $case_flags[$i]='0';
            */
            for ($qq=0;$qq<($out-$i);$qq++) $outputa[($i+$out)-$i-$qq]=$outputa[($i+$out)-$i-$qq-1];

            $outputa[$i++]=$n;
        }

        $outputstr="";
        for ($i=0;$i<count($outputa);$i++){
            if ($outputa[$i]<128)
                $outputstr.=chr($outputa[$i]).chr(0);
            else{
                $hx=dechex($outputa[$i]);
                $gaowei=substr($hx,2,2);
                $diwei=substr($hx,0,2);
                $tmp_output=chr(hexdec($gaowei)).chr(hexdec($diwei));
                $outputstr.=$tmp_output;
            }
        }
        //echo $outputstr." <br>";
        //for ($i=0; $i<strlen($outputstr); $i++) echo ord(substr($outputstr,$i,1))." ";
        if (self::CHARSET_MODE == 1) {
            $outputstr = iconv("Unicode", $code, $outputstr);
        } elseif (self::CHARSET_MODE == 2) {
            $outputstr = self::unicodeEncode($outputstr);
            $outputstr = iconv("Unicode-1-1", $code, $outputstr);
        } elseif (self::CHARSET_MODE == 3) {
            $outputstr = self::unicodeEncode($outputstr);
            $outputstr = mb_convert_encoding($outputstr, $code, "Unicode");
        }
        return $outputstr;
    }

    /**
     * 处理数字,大小写字母
     * @param char $char
     * @return char
     */
    public static function digitDecode($char)
    {
        if (($char-48)<10) return ($char-22);
        if (($char-65)<26) return ($char-65);
        if (($char-97)<26) return ($char-97);
        return self::BASE;
    }
}