关于unicode:我在PHP应用程序中正确支持UTF-8吗?

Am I correctly supporting UTF-8 in my PHP apps?

我想确保我所知道的关于UTF-8的一切都是正确的。我已经尝试使用UTF-8有一段时间了,但是我不断遇到越来越多的bug和其他奇怪的东西,这使得拥有一个100%的UTF-8站点几乎是不可能的。总有一个我似乎怀念的地方。也许这里的人可以更正我的列表,或者确定它,这样我就不会错过任何重要的事情。

数据库

每个站点都必须将数据存储在某个地方。无论您的PHP设置是什么,您还必须配置数据库。如果无法访问配置文件,请确保在连接后立即"设置名称"为"utf8"。另外,确保在所有表上使用utf8 uuunicode uci。这假设MySQL是一个数据库,您必须为其他数据库进行更改。

正则表达式

我做了很多regex,比一般的搜索替换更复杂。我必须记住使用"/u"修饰符,这样PCRE就不会损坏我的字符串。然而,即便如此,显然仍然存在问题。

字符串函数

所有默认的字符串函数(strlen()、strpos()等)都应替换为查看字符而不是字节的多字节字符串函数。

报头您应该确保您的服务器返回了正确的头部,以便浏览器知道您要使用什么字符集(就像您必须告诉MySQL一样)。

header('Content-Type: text/html;
charset=utf-8');

将正确的标记放在页头也是一个好主意。尽管实际头文件将覆盖这一点,但如果它们不同。

1
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

问题

当页面加载时,我是否需要将从用户代理(HTML表单的&uri)接收到的所有内容转换为UTF-8,或者如果我可以保留字符串/值的原样,并且仍然在这些函数中运行它们而不会出现问题?

如果我确实需要将所有内容转换为UTF-8,那么应该采取什么步骤?mb-detect-u编码似乎是为此而构建的,但我一直看到人们抱怨它并不总是有效。mb_check_编码似乎也有一个问题,告诉一个良好的utf-8字符串从一个畸形的。

根据使用的编码(如文件类型),PHP在内存中存储字符串的方式是否不同,或者它是否仍然像常规的sting一样存储,其中一些字符的解释方式不同(如&;amp;vs&;in html)。chazomaticus回答此问题:

In PHP (up to PHP5, anyway), strings
are just sequences of bytes. There is
no implied or explicit character set
associated with them; that's something
the programmer must keep track of.

如果给一个mb_*函数一个非utf-8字符串,它会不会导致问题?

如果utf字符串编码不正确,会出现问题(比如regex中的解析错误?)还是只将一个实体标记为坏(HTML)?是否有可能由于字符串不正确而导致函数返回false?

我听说您也应该将表单标记为utf-8(接受charset="utf-8"),但我不确定它的好处是什么……

用UTF-8写UTF-16是为了解决一个限制吗?就像utf-8没有足够的字符空间?(Y2(UTF)K?)

功能

下面是一些我已经找到的自定义PHP函数,但我没有任何方法来验证它们是否真正有效。也许有人有一个我可以用的例子。首先是converttoutf8(),然后看起来像是WordPress的utf8。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function seems_utf8($str) {
    $length = strlen($str);
    for ($i=0; $i < $length; $i++) {
        $c = ord($str[$i]);
        if ($c < 0x80) $n = 0; # 0bbbbbbb
       elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
       elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
       elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
       elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
       elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
       else return false; # Does not match any model
       for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
           if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
                return false;
        }
    }
    return true;
}

function is_utf8($str) {
    $c=0; $b=0;
    $bits=0;
    $len=strlen($str);
    for($i=0; $i<$len; $i++){
        $c=ord($str[$i]);
        if($c > 128){
            if(($c >= 254)) return false;
            elseif($c >= 252) $bits=6;
            elseif($c >= 248) $bits=5;
            elseif($c >= 240) $bits=4;
            elseif($c >= 224) $bits=3;
            elseif($c >= 192) $bits=2;
            else return false;
            if(($i+$bits) > $len) return false;
            while($bits > 1){
                $i++;
                $b=ord($str[$i]);
                if($b < 128 || $b > 191) return false;
                $bits--;
            }
        }
    }
    return true;
}

如果有人感兴趣,我发现了一个很好的示例页面,可以在测试UTF-8时使用。


Do I need to convert everything that I receive from the user agent (HTML form's & URI) to UTF-8 when the page loads

号的用户代理的数据应该在submitting UTF-8格式;如果不是你失去效益(Unicode。

的方式确保一个用户代理submits在UTF-8格式的冰的含两个发球页的形式在它的submitting UTF-8编码。使用"内容类型标头(HTTP等价和元。如果你intend的形式保存了两个独立的和工作)。

I have heard that you should mark you forms as UTF-8 also (accept-charset="UTF-8")

不,不。这是一个很好的想法,在IE的HTML标准,但没有得到它的权利。这两个国家应该"容许字符集的列表,但它作为一个单位treats战略两个额外的字符集是一个尝试,每一场的基础上。所以,如果你有一个ISO 8859-1"页和接受字符="UTF-8"的形式,即试图将第一编码一场为ISO-8859-1,如果有一非8859-1字符在那儿,然后它会两个utf -度假村8。

但由于IE不告诉你它是否使用ISO 8859-1或UTF-8的,那绝对没有用的你。我想你会有两个,分别为每个字段的编码,这是在使用!不是有用的。《omit属性和服务你的页面为UTF-8;那真的可以做最好的时刻。

If a UTF string is improperly encoded will something go wrong

如果你很容易通过一个这样的序列的两个浏览器,你可能是在麻烦。的意思是"overlong序列的编码方式,低碳的概念,在一个代码点冰的字节序列(不是必要的。如果你在这均值滤波的"<"城市,寻找在一个ASCII字符(字节序列,你可能错过一A,和易脚本元素到你的思想是安全的文本。

overlong序列是banned回来的早一天的Unicode,但它把微软的一个很长的时间让他们两人在一起也会interpret的字节序列的xc0 XBC。"作为一个群集的IP服务包1直到IE6。歌剧也得到了它错误的IP(约两个版本,我认为)7。luckily这些年长的browsers是死了,但它还是值得的overlong滤波序列在这些案例browsers是(现在仍然是browsers化妆或新的白痴一样的错误在未来)。你可以这样做,对方的要求和固定的序列,和一个正则表达式,只允许适当的UTF - 8通,如这一个虔诚的.

如果你在使用_ MB的PHP函数,你可能会从这些问题是绝缘的。我不能说_酸性AA MB *是脆弱unusable写作当我是静止的PHP。

在任何情况下,这是一个很好的时间也除去控制人物,这是一个大的和普遍unappreciated来源的错误。我chars remove 9和13从submitted加两个字符串的正则表达式的W3的人需要时间;它也为平原和newlines砍掉。你知道不应该是多行文本框。

Was UTF-16 written to address a limit in UTF-8?

utf - 16号,是一个双字节编码,每个代码点的使用Unicode字符串,使密封更容易吗?在存储器(从天,当所有的Unicode将安装在双字节;Windows系统和Java类,仍然做它的方式)。unlike UTF-8格式,它以ASCII兼容的冰槽,小鸭子是个没有用的网站。但你偶尔需要它在档案救一救市,通常Windows用户谁已经misled市Windows的UTF - 16le描述为"Unicode"Save As菜单猫的时间。

seems_utf8

这是非常低效的两个相对的正则表达式。

Also, make sure to use utf8_unicode_ci on all of your tables.

你可以逃跑的实际上是没有这个处理MySQL作为一个大的什么,但他们只字节解释为UTF-8在你的脚本。大学的优势utf8 _使用Unicode _ CI的冰,这将collate(黑鸭子做案例将用于钝感)与知识(非ASCII字符,所以橡树。"?’吗?是相同的字符。如果你使用一个非utf8 collation应该坚持两个二进制(案例匹配敏感)。

你选择whichever,做consistently:使用相同的字符集为你的桌子为你做你的连接。你想避免的是一个有损之间的字符集转换你的脚本和数据库。


你现在所做的大部分都应该是正确的。

一些注意事项:MySQL中的任何utf_*排序规则都会正确地将数据存储为utf-8,它们之间唯一的区别是排序时应用的排序规则(字母顺序)。

您可以告诉apache和php分别在php.ini的httpd.conf/.htaccess和default_charset ="utf-8"中发出正确的字符集头设置AddDefaultCharset utf-8

您可以告诉mbstring扩展来处理字符串函数。这对我很有用:

1
2
3
4
mbstring.internal_encoding=utf-8
mbstring.http_output=UTF-8
mbstring.encoding_translation=On
mbstring.func_overload=6

(这使mail(功能保持原样-我发现将其设置为7会破坏我的邮件头)

对于字符集转换,请查看https://sourceforge.net/projects/phputf8/。

PHP根本不关心变量中的内容,它只是盲目地存储和检索其内容。

如果声明一个mbstring.internal_encoding,并以另一种编码提供给一个mb_*函数字符串,则会得到意外的结果。您无论如何都可以安全地将ASCII发送到UTF-8函数。

如果你担心有人故意发布错误编码的东西,我相信你应该考虑使用HTML净化器在处理之前过滤获取/发布数据。

Accept-charset从一开始就一直在规范中,但它在浏览器中的实际支持或多或少是零。浏览器将使用包含表单的页面的编码。

UTF-16并不是UTF-8的大哥,它只是起到了不同的作用。


database/mysql:如果你使用的是SET NAMES,比如php/mysql,那么你会把mysql的real-escape-string()隐藏在字符编码变化的黑暗中。这可能导致错误的结果。因此,如果您依赖一个像mysql-real-escape-string这样的转义函数(因为您没有使用准备好的语句),那么SET NAMES是一个次优的解决方案。这就是为什么引入了mysql_set_charset()或者gentoo应用了一个补丁,为php/mysql和php/mysqli添加了配置参数mysql.connect_charset。

客户机通常不指示它发送的参数的编码。如果您希望使用UTF-8编码的数据并将其视为编码错误(在UTF-8中无效的字节序列)。因此,数据可能无法按预期显示,或者解析器可能会中止解析。但至少用户输入不能"转义"并造成更多危害,例如在内联SQL语句或HTML输出中。例如,使用脚本(另存为ISO-8859-1或UTF-8,无所谓)

1
2
3
4
5
6
<?php
$s = 'abcxyz';
var_dump(htmlspecialchars($s, ENT_QUOTES, 'utf-8'));
// adding the byte sequence for ??ü in iso-8859-1
$s = 'abc'. chr(0xE4) . chr(0xF6) . chr(0xFC). 'xyz';
var_dump(htmlspecialchars($s, ENT_QUOTES, 'utf-8'));

印刷品

1
2
string(6)"abcxyz"
string(0)""

e4f6fc不是有效的utf-8字节序列,因此htmlspecialchars返回空字符串。其他功能可能会返回?或者另一个"特殊"的角色。但至少他们不会"错误"地将一个字符作为恶意控制字符-只要他们都坚持"正确"的编码(在本例中是UTF-8)。

接受字符集并不能保证只接收使用该编码的数据。据您所知,客户端甚至可能没有"使用"/解析包含表单元素的HTML文档。这可能会有所帮助,而且没有理由不设置该属性。但它不"可靠"。


对于来自表单的用户输入,我将此属性添加到我的form的标记:accept-charset="utf-8"。这样,您接收的数据应该始终是UTF-8编码的。


UTF-8很好,而且没有UTF-16解决的任何限制。PHP不会改变在内存中存储字符串的方式(与Python不同)。如果整个数据流使用UTF-8(web表单接收UTF-8数据,表使用UTF 8编码,而您使用的是SET NAMES utf8,并且数据存储时没有被更改(没有字符集转换),那就没问题了。