我在我的PHP应用程序中是否正确支持UTF-8?
我在我的PHP应用程序中是否正确支持UTF-8?
我想要确保我关于UTF-8的所有了解都是正确的。我一直在尝试使用UTF-8,但是我不断遇到更多的错误和其他奇怪的问题,使得似乎几乎不可能有一个100%的UTF-8网站。总是有一个藏在某个地方让我忽略的问题。也许在这里有人可以纠正我的列表或者确认它,这样我就不会漏掉任何重要的东西。
数据库
每个网站都必须将数据存储在某个地方。无论你的PHP设置如何,你也必须配置数据库。如果你无法访问配置文件,那么在连接后立即确保“SET NAMES 'utf8'”。此外,确保在所有表格上使用utf8_unicode_ci。这假设数据库是MySQL,你需要为其他数据库进行更改。
正则表达式
我使用的正则表达式比普通的搜索替换更复杂。我必须记住使用“/u”修饰符,以便PCRE不会损坏我的字符串。然而,即使如此还是会有问题。
字符串函数
所有默认的字符串函数(strlen(),strpos()等)都应该用查看字符而不是字节的多字节字符串函数替代。
头部信息
你应该确保你的服务器返回正确的头部信息,以便浏览器知道你尝试使用的字符集(就像你必须告诉MySQL一样)。
在页面头部放入正确的标签也是一个好主意。尽管实际的头部信息将覆盖这个标签,但如果它们不同,这个标签仍然会起作用。
问题
当页面加载时,我是否需要将从用户代理(HTML表单和URI)接收到的所有内容转换为UTF-8,还是可以将字符串/值保持不变并且通过这些函数运行而不会出现问题?
如果我确实需要将所有内容转换为UTF-8 - 那么我应该采取哪些步骤?mb_detect_encoding似乎是为此而构建的,但我不断看到人们抱怨它并不总是有效。mb_check_encoding似乎也有一个问题,它无法将好的UTF-8字符串与格式错误的字符串区分开来。
如果我给mb_*函数传递一个非UTF-8字符串,会导致问题吗?
如果UTF字符串编码不正确,会出现什么问题(例如在正则表达式中出现解析错误)?还是它只是将实体标记为错误(html)?不正确编码的字符串是否有可能导致函数返回FALSE,因为字符串是错误的?
我听说你还应该将表单标记为UTF-8(accept-charset="UTF-8"),但我不确定好处是什么..?
UTF-16是为了解决UTF-8的限制而编写的吗?例如,UTF-8的字符空间是否耗尽了?(Y2(UTF)k?)
函数
这里有一些我找到的自定义PHP函数,但我没有办法验证它们是否有效。也许有人可以给我一个可以使用的示例。首先是convertToUTF8(),然后是来自WordPress的seems_utf8函数。
如果有人感兴趣,我找到了一个很好的示例页面可以用来测试UTF-8。
在PHP应用程序中正确支持UTF-8的问题是什么?
问题的原因:
- MySQL中的utf_*排序规则可以正确存储UTF-8数据,只是在排序时应用了不同的排序规则。
- Apache和PHP可以通过设置httpd.conf/.htaccess中的“AddDefaultCharset utf-8”和php.ini中的“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的大哥,它只是有不同的用途。
在PHP应用程序中正确支持UTF-8的问题,是由以下原因引起的:
1. 如果使用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配置参数。
2. 客户端通常不会指示其发送的参数的编码。如果期望utf-8编码的数据,并将其视为utf-8编码,可能会出现编码错误(在utf-8中无效的字节序列)。因此,数据可能无法按预期显示,解析器可能会中止解析。但至少用户输入不能“逃脱”并对内联SQL语句或HTML输出造成更多伤害。
解决方法是确保所有函数都使用正确的编码(在这种情况下为utf-8)。
3. accept-charset不能保证只接收到具有指定编码的数据。客户端可能甚至没有“使用”/解析包含表单元素的HTML文档。设置accept-charset属性可能有所帮助,但不是“可靠”的。
解决方法是确保在服务器端对接收到的数据进行正确的编码处理。
关于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())不会考虑通过查询设置的字符集。解决方法是使用[mysqli]->set_charset() / 在PDO连接字符串中使用"charset=utf8"。
为了正确支持UTF-8,需要使用适当的设置和函数来处理字符编码,并确保服务器和客户端都使用相同的编码。
我是否正确支持我的PHP应用中的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直到IE6 Service Pack 1才将字节序列“\xC0\xBC”解释为“<”。Opera在大约版本7之前也弄错了。幸运的是,这些较旧的浏览器正在消失,但是为了防止这些浏览器仍然存在(或者新的愚蠢浏览器将来犯同样的错误),过滤过长序列仍然是值得的。你可以使用正则表达式来做到这一点,并修复其他错误的序列,只允许正确的UTF-8通过,例如W3的这个。
如果你在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”的文件。
seems_utf8
与使用正则表达式相比,这种方法非常低效!
此外,确保在所有表上使用utf8_unicode_ci。
实际上,你可以在MySQL中不使用这个,将MySQL视为存储字节的地方,只在脚本中将其解释为UTF-8。使用utf8_unicode_ci的好处是它可以根据非ASCII字符的知识进行排序和不区分大小写的比较,因此例如,“ŕ”和“Ŕ”是相同的字符。如果你使用非UTF8字符集,你应该坚持使用二进制(区分大小写)匹配。
无论你选择哪种方式,都要保持一致:在表和连接中使用相同的字符集。你要避免的是在脚本和数据库之间进行丢失的字符集转换。
谢谢提供W3函数的链接。我在文档中找到了一个PHP版本:us3.php.net/manual/en/function.mb-detect-encoding.php#68607
你说“不要在表单上使用accept-charset”,因为它在非UTF-8表单中在IE上无法正常工作。如果页面已经是UTF-8,添加accept-charset="UTF-8"
(我没有听说这个有问题)有什么好处吗?
没有,如果页面已经是UTF-8,添加accept-charset="UTF-8"
没有任何效果(无论是遵循标准的浏览器还是IE)。
好的,谢谢。我将这个作为一个具体问题提问,也许你想回答,在这里:stackoverflow.com/questions/3719974/…,还有一个相关的问题在这里:stackoverflow.com/questions/3715264/…。