我的 PHP 应用程序是不是正确支持 UTF-8?
Posted
技术标签:
【中文标题】我的 PHP 应用程序是不是正确支持 UTF-8?【英文标题】:Am I correctly supporting UTF-8 in my PHP apps?我的 PHP 应用程序是否正确支持 UTF-8? 【发布时间】:2010-11-21 23:24:43 【问题描述】:我想确保我所知道的关于 UTF-8 的一切都是正确的。我一直在尝试使用 UTF-8 一段时间,但我不断发现越来越多的错误和其他奇怪的事情,这使得拥有 100% UTF-8 的网站似乎几乎是不可能的。总有一个我似乎想念的地方。也许这里有人可以更正我的列表或确定它,这样我就不会错过任何重要的事情。
数据库
每个站点都必须在某处存储数据。无论您的 php 设置是什么,您还必须配置数据库。如果您无法访问配置文件,请确保在连接后立即“SET NAMES 'utf8'”。此外,请确保在所有表格上使用utf8_ unicode_ ci。这假设 mysql 用于数据库,您将不得不为其他数据库进行更改。
正则表达式
我做了很多 more complex 的正则表达式,而不是您的平均搜索替换。我必须记住使用“/u”修饰符,以便PCRE doesn't corrupt my strings。然而,即便如此,仍有still problems apparently。
字符串函数
所有默认的字符串函数(strlen()、strpos() 等)都应该替换为Multibyte String Functions,它查看的是字符而不是字节。
标题 您应该确保您的服务器为浏览器返回正确的标头,以了解您尝试使用的字符集(就像您必须告诉 MySQL 一样)。
header('内容类型: text/html; 字符集=utf-8');
将正确的标签放在页头也是一个好主意。虽然如果它们不同,实际的标题会覆盖它。
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
问题
我是否需要在页面加载时将从用户代理(HTML 表单和 URI)收到的所有内容转换为 UTF-8,或者我是否可以保留字符串/值原样并仍然通过这些函数运行它们没问题?
如果我确实需要将所有内容都转换为 UTF-8 - 那么我应该采取哪些步骤? mb_detect_encoding 似乎是为此而建的,但我一直看到人们抱怨它并不总是有效。 mb_check_encoding 似乎也无法从格式错误的字符串中区分出好的 UTF-8 字符串。
PHP 是否根据使用的编码方式(如文件类型)以不同的方式将字符串存储在内存中,或者它是否仍像常规字符串一样存储,其中一些字符的解释方式不同(如 & amp; vs & in HTML)。 chazomaticus 回答这个问题:
在 PHP(最高到 PHP5,无论如何)中,字符串 只是字节序列。有 没有隐含或显式的字符集 与他们相关联;那是东西 程序员必须跟踪。
如果将非 UTF-8 字符串提供给 mb_* 函数,它会导致问题吗?
如果 UTF 字符串编码不正确,会出现问题(比如正则表达式中的解析错误?)还是只是将实体标记为错误(html)?编码不当的字符串是否有可能因为字符串错误而导致函数返回 FALSE?
我听说您也应该将表单标记为 UTF-8 (accept-charset="UTF-8"),但我不确定有什么好处..?
编写 UTF-16 是为了解决 UTF-8 的限制吗?就像 UTF-8 的字符空间用完了一样吗? (Y2(UTF)k?)
函数
以下是我发现的几个自定义 PHP 函数,但我没有任何方法可以验证它们是否确实有效。也许有人有一个我可以使用的例子。首先是 convertToUTF8(),然后是 wordpress 中的似乎_utf8。
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;
如果有人感兴趣,我找到了一个很好的示例页面来使用when testing UTf-8。
【问题讨论】:
实际上,你有它倒退。编写 UTF-8 是为了解决 UTF-16 的问题。具体来说,UTF-16 要求每个字符占用 2 个字节(16 位),而我们美国程序员不喜欢这样,因为这意味着我们所有的文件都会翻倍,所以他们创建了 UTF-8,这是倒退的- 与 ASCII 兼容,因此所有纯 ASCII 文件都将在 UTF-8 中有效,从而省去了很多人将所有源代码文件从 ASCII 转换为 UTF-16 的麻烦。 我没有看到任何提及使用 mb_internal_encoding。您可能想调查一下,看看它是否与您相关。 对于 MySQL,不要手动调用set names
,因为它不会更新用于 real_escape_string 的字符集。请改用mysql_set_character_set
。见dev.mysql.com/doc/refman/5.0/en/mysql-set-character-set.html 和***.com/a/1317239/632951
@Pacerier,从大约 5 年前开始,没有人应该使用 mysql_real_escape_string()。如果您仍在使用它,请尽快升级到 PDO。不建议手动引用字符串而不是使用准备好的语句。
@Xeoncross,见***.com/q/26596294/632951
【参考方案1】:
当页面加载时,我是否需要将从用户代理收到的所有内容(HTML 表单和 URI)转换为 UTF-8
没有。用户代理应该以 UTF-8 格式提交数据;否则,您将失去 Unicode 的优势。
确保用户代理以 UTF-8 格式提交的方法是提供包含它以 UTF-8 编码提交的表单的页面。使用 Content-Type 标头(如果您打算保存表单并独立工作,也可以使用 meta http-equiv)。
我听说您也应该将表单标记为 UTF-8 (accept-charset="UTF-8")
不要。这在 HTML 标准中是个好主意,但 IE 从来没有做对。它应该声明一个允许的字符集的排他列表,但 IE 将它视为一个附加字符集的列表,以每个字段为基础进行尝试。所以如果你有一个 ISO-8859-1 页面和一个“accept-charset="UTF-8"” 表单,IE 会首先尝试将一个字段编码为 ISO-8859-1,如果有一个非 8859-1字符,然后它将诉诸UTF-8。
但是由于 IE 不会告诉您它使用的是 ISO-8859-1 还是 UTF-8,所以这对您绝对没有用。您必须分别猜测每个字段使用的是哪种编码!没有用。省略该属性并将您的页面作为 UTF-8 提供;这是你目前能做的最好的事情。
如果 UTF 字符串编码不正确会出错
如果您让这样的序列通过浏览器,您可能会遇到麻烦。有“超长序列”将低编号代码点编码为比必要的更长的字节序列。这意味着如果您通过在字节序列中查找该 ASCII 字符来过滤“
在 Unicode 的早期,超长序列被禁止,但微软花了很长时间才把它们搞定:IE 将字节序列 '\xC0\xBC' 解释为 'this one)修复其他错误序列。
如果您在 PHP 中使用 mb_ 函数,您可能不会遇到这些问题。我不能肯定地说,当我还在编写 PHP 时,mb_* 是不可用的脆弱的。
无论如何,这也是删除控制字符的好时机,控制字符是一大且通常不被重视的错误来源。除了 W3 正则表达式取出的其他字符外,我还会从提交的字符串中删除字符 9 和 13;对于您知道不应该是多行文本框的字符串,删除普通换行符也是值得的。
编写 UTF-16 是为了解决 UTF-8 的限制问题吗?
不,UTF-16 是每个代码点两个字节的编码,用于在内存中更轻松地索引 Unicode 字符串(从所有 Unicode 都适合两个字节的日子开始;Windows 和 Java 等系统仍然这样做那样)。与 UTF-8 不同,它与 ASCII 不兼容,并且在 Web 上几乎没有用处。但是你偶尔会在保存的文件中遇到它,通常是那些被 Windows 在另存为菜单中将 UTF-16LE 描述为“Unicode”的 Windows 用户保存的文件。
似乎_utf8
与正则表达式相比,这非常低效!
另外,请确保在所有表格上使用 utf8_unicode_ci。
实际上,您可以在没有这个的情况下摆脱困境,将 MySQL 视为只存储字节的存储,并且仅在脚本中将它们解释为 UTF-8。使用 utf8_unicode_ci 的优点是它会根据关于非 ASCII 字符的知识进行整理(排序和进行不区分大小写的比较),例如。 “ŕ”和“Ŕ”是同一个字符。如果您使用非 UTF8 排序规则,则应坚持二进制(区分大小写)匹配。
无论您选择哪种方式,请始终如一地执行:为您的表格使用与您的连接相同的字符集。您要避免的是脚本和数据库之间的有损字符集转换。
【讨论】:
感谢 W3 函数的链接。我在文档us3.php.net/manual/en/function.mb-detect-encoding.php#68607 中找到了一个 PHP 版本 您说“不要在表单上使用接受字符集”,因为它在 IE 中对于非 UTF8 表单无法正常工作。如果您的页面已经是 UTF-8,添加accept-charset="UTF-8"
(我没听说有问题)有什么好处吗?
@philfreo: 不,在已经是 UTF-8 的页面上添加accept-charset="UTF-8"
将无效(无论是在遵循标准的浏览器中还是在 IE 中)。
好的,谢谢。我将此作为您可能想要回答的具体问题提出,此处:***.com/questions/3719974/…,以及相关问题:***.com/questions/3715264/…【参考方案2】:
你现在所做的大部分应该是正确的。
一些注意事项:MySQL 中的任何 utf_*
排序规则都会将您的数据正确存储为 UTF-8,它们之间的唯一区别是排序时应用的排序规则(字母顺序)。
您可以告诉 Apache 和 PHP 分别在 httpd.conf/.htaccess 和 php.ini 中发出正确的字符集标头设置 AddDefaultCharset utf-8
和 default_charset = "utf-8"
。
您可以告诉 mbstring 扩展来处理字符串函数。这对我有用:
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 Purifier 在处理之前过滤 GET/POST 数据。
Accept-charset
从那时起就一直在规范中,但它在浏览器中的实际支持或多或少为零。浏览器通常会使用包含表单的页面的编码。
UTF-16 不是 UTF-8 的老大哥,它只是用于不同的目的。
【讨论】:
【参考方案3】: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,无所谓)
<?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'));
打印
string(6) "abcxyz"
string(0) ""
E4F6FC 不是一个有效的 utf-8 字节序列,因此 htmlspecialchars 返回一个空字符串。其他功能可能会返回?或另一个“特殊”字符。但至少他们不会将一个字符“误认为”为恶意控制字符——只要他们都坚持“正确”的编码(在这种情况下为 utf-8)。
accept-charset 不保证您将只收到具有该编码的数据。据您所知,客户端甚至可能没有“使用”/解析包含表单元素的 html 文档。这可能会有所帮助,并且没有理由不设置该属性。但它并不“可靠”。
【讨论】:
关于 SET NAMES:所以基本上,在 PHP 5.2.3 之前,如果您无法更改服务器配置并且它不符合您的需要,那么 mysql_real_escape_string 是无用的?这听起来确实像是需要在 PHP 文档中明确编写的东西——而且听起来我应该开始更新我的数据库代码,只是为了安全起见...... 虽然php.net/mysql_set_charset 没有解释为什么 SET NAMES 可能不好,但至少它说“不推荐使用 mysql_query() 执行 SET NAMES ..”。 在查询中不使用 SET NAMES 的原因是旧的甚至“现代” MySQLi 和 PDO 函数,例如用于转义 (mysqli_real_escape_string() / PDO::quote() ) 不采用通过查询设置的字符集。您需要在 PDO 连接字符串中使用 [mysqli]->set_charset() / "charset=utf8"。【参考方案4】:UTF-8 很好,并且没有 UTF-16 解决的任何限制。 PHP 不会改变它在内存中存储字符串的方式(与 Python 不同)。如果整个数据流使用 UTF-8(Web 表单接收 UTF-8 数据,表使用 utf8 编码并且您使用的是SET NAMES utf8
,并且数据存储没有被更改(没有字符集转换),那应该没问题.
【讨论】:
顺便说一下,你应该在你的数据库中使用 utf8_general_ci。使用 utf8_unicode_ci 不会有任何问题【参考方案5】:对于来自表单的用户输入,我将此属性添加到我的form
s 标签:accept-charset="utf-8"
。这样您收到的数据应该始终采用 utf-8 编码。
【讨论】:
恐怕这不可靠,正如 bobince 正确提到的那样。您应该设置标题或元标记以强制浏览器进入 utf-8。这将自动强制页面上的表单以 utf-8 格式提交数据。以上是关于我的 PHP 应用程序是不是正确支持 UTF-8?的主要内容,如果未能解决你的问题,请参考以下文章
C# UTF-8 base64 编码在 PHP 中无法正确解码
错误:“输入不是正确的 UTF-8,表示编码!”使用 PHP 的 simplexml_load_string
linux php5 安装discuz提示 mysql_connect() 不支持 请检查 mysql 模块是不是正确加载